From 16d8ad8115c373bc0975173621a7310b1a88090a Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 7 Nov 2024 09:12:04 +0100 Subject: [PATCH 01/30] Fixes https://github.com/umbraco/Umbraco-CMS/issues/17430 (#17439) Fixed issue where block based types did not use their StorageValueType but fallback to the short string --- .../PropertyEditors/BlockEditorPropertyValueEditor.cs | 10 +++++++--- .../PropertyEditors/BlockGridPropertyEditorBase.cs | 9 ++++++--- .../PropertyEditors/BlockListPropertyEditorBase.cs | 10 +++++++--- .../BlockValuePropertyValueEditorBase.cs | 11 ++++++++--- .../PropertyEditors/RichTextPropertyEditor.cs | 8 +++++--- .../PropertyEditors/DataValueEditorReuseTests.cs | 11 +++++++---- 6 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs index 798cdf1d7d..a43921156a 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyValueEditor.cs @@ -35,7 +35,9 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal IIOHelper ioHelper) : this(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + ioHelper, + attribute) { } @@ -46,8 +48,10 @@ public abstract class BlockEditorPropertyValueEditor : BlockVal IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, BlockEditorVarianceHandler blockEditorVarianceHandler, - ILanguageService languageService) - : base(propertyEditors, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, dataValueReferenceFactories, blockEditorVarianceHandler, languageService) => + ILanguageService languageService, + IIOHelper ioHelper, + DataEditorAttribute attribute) + : base(propertyEditors, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, dataValueReferenceFactories, blockEditorVarianceHandler, languageService, ioHelper, attribute) => _jsonSerializer = jsonSerializer; /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs index 4e410841a1..1975faabc2 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockGridPropertyEditorBase.cs @@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.Validation; @@ -37,11 +38,12 @@ public abstract class BlockGridPropertyEditorBase : DataEditor #region Value Editor protected override IDataValueEditor CreateValueEditor() => - DataValueEditorFactory.Create(); + DataValueEditorFactory.Create(Attribute!); internal class BlockGridEditorPropertyValueEditor : BlockEditorPropertyValueEditor { public BlockGridEditorPropertyValueEditor( + DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, IDataTypeConfigurationCache dataTypeConfigurationCache, @@ -52,8 +54,9 @@ public abstract class BlockGridPropertyEditorBase : DataEditor IBlockEditorElementTypeCache elementTypeCache, IPropertyValidationService propertyValidationService, BlockEditorVarianceHandler blockEditorVarianceHandler, - ILanguageService languageService) - : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService) + ILanguageService languageService, + IIOHelper ioHelper) + : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService, ioHelper, attribute) { BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, elementTypeCache)); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs index 749937bd4d..93277438ce 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockListPropertyEditorBase.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Cache.PropertyEditors; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Models.Validation; @@ -47,11 +48,12 @@ public abstract class BlockListPropertyEditorBase : DataEditor protected virtual BlockEditorDataConverter CreateBlockEditorDataConverter() => new BlockListEditorDataConverter(_jsonSerializer); protected override IDataValueEditor CreateValueEditor() => - DataValueEditorFactory.Create(CreateBlockEditorDataConverter()); + DataValueEditorFactory.Create(Attribute!, CreateBlockEditorDataConverter()); internal class BlockListEditorPropertyValueEditor : BlockEditorPropertyValueEditor { public BlockListEditorPropertyValueEditor( + DataEditorAttribute attribute, BlockEditorDataConverter blockEditorDataConverter, PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, @@ -63,8 +65,10 @@ public abstract class BlockListPropertyEditorBase : DataEditor IJsonSerializer jsonSerializer, IPropertyValidationService propertyValidationService, BlockEditorVarianceHandler blockEditorVarianceHandler, - ILanguageService languageService) - : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService) + ILanguageService languageService, + IIOHelper ioHelper + ) + : base(propertyEditors, dataValueReferenceFactories, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, blockEditorVarianceHandler, languageService, ioHelper, attribute) { BlockEditorValues = new BlockEditorValues(blockEditorDataConverter, elementTypeCache, logger); Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, elementTypeCache)); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs index 8a774a5cfd..1c2624ffc5 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs @@ -42,7 +42,10 @@ public abstract class BlockValuePropertyValueEditorBase : DataV jsonSerializer, dataValueReferenceFactoryCollection, StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService()) + StaticServiceProvider.Instance.GetRequiredService(), + ioHelper, + attribute + ) { } @@ -53,8 +56,10 @@ public abstract class BlockValuePropertyValueEditorBase : DataV IJsonSerializer jsonSerializer, DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection, BlockEditorVarianceHandler blockEditorVarianceHandler, - ILanguageService languageService) - : base(shortStringHelper, jsonSerializer) + ILanguageService languageService, + IIOHelper ioHelper, + DataEditorAttribute attribute) + : base(shortStringHelper, jsonSerializer, ioHelper, attribute) { _propertyEditors = propertyEditors; _dataTypeConfigurationCache = dataTypeConfigurationCache; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index f1281747c2..25b028f1e1 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -65,7 +65,7 @@ public class RichTextPropertyEditor : DataEditor /// /// protected override IDataValueEditor CreateValueEditor() => - DataValueEditorFactory.Create(); + DataValueEditorFactory.Create(Attribute!); protected override IConfigurationEditor CreateConfigurationEditor() => new RichTextConfigurationEditor(_ioHelper); @@ -87,6 +87,7 @@ public class RichTextPropertyEditor : DataEditor private readonly ILogger _logger; public RichTextPropertyValueEditor( + DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeConfigurationCache dataTypeReadCache, ILogger logger, @@ -102,8 +103,9 @@ public class RichTextPropertyEditor : DataEditor DataValueReferenceFactoryCollection dataValueReferenceFactoryCollection, IRichTextRequiredValidator richTextRequiredValidator, BlockEditorVarianceHandler blockEditorVarianceHandler, - ILanguageService languageService) - : base(propertyEditors, dataTypeReadCache, shortStringHelper, jsonSerializer, dataValueReferenceFactoryCollection, blockEditorVarianceHandler, languageService) + ILanguageService languageService, + IIOHelper ioHelper) + : base(propertyEditors, dataTypeReadCache, shortStringHelper, jsonSerializer, dataValueReferenceFactoryCollection, blockEditorVarianceHandler, languageService, ioHelper, attribute) { _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _imageSourceParser = imageSourceParser; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index 4fe4c34b6e..39132ee7a8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -39,8 +39,9 @@ public class DataValueEditorReuseTests var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of()); _dataValueEditorFactoryMock .Setup(m => - m.Create(It.IsAny>())) + m.Create(It.IsAny(), It.IsAny>())) .Returns(() => new BlockListPropertyEditorBase.BlockListEditorPropertyValueEditor( + new DataEditorAttribute("a"), new BlockListEditorDataConverter(Mock.Of()), _propertyEditorCollection, _dataValueReferenceFactories, @@ -52,7 +53,9 @@ public class DataValueEditorReuseTests Mock.Of(), Mock.Of(), blockVarianceHandler, - Mock.Of())); + Mock.Of(), + Mock.Of() + )); } [Test] @@ -109,7 +112,7 @@ public class DataValueEditorReuseTests Assert.NotNull(dataValueEditor2); Assert.AreNotSame(dataValueEditor1, dataValueEditor2); _dataValueEditorFactoryMock.Verify( - m => m.Create(It.IsAny>()), + m => m.Create(It.IsAny(), It.IsAny>()), Times.Exactly(2)); } @@ -131,7 +134,7 @@ public class DataValueEditorReuseTests Assert.AreEqual("config", ((DataValueEditor)dataValueEditor2).ConfigurationObject); Assert.AreNotSame(dataValueEditor1, dataValueEditor2); _dataValueEditorFactoryMock.Verify( - m => m.Create(It.IsAny>()), + m => m.Create(It.IsAny(), It.IsAny>()), Times.Exactly(2)); } } From 669c585ac4c7a9d244000037cdbb6078f35f740a Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 7 Nov 2024 10:42:40 +0100 Subject: [PATCH 02/30] Validate client IDs before applying them (#17426) * Validate client IDs before applying them * Add operation result to return * Update src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Zeegaan Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ClientCredentialsUserControllerBase.cs | 4 ++ .../UserClientCredentialsOperationStatus.cs | 3 +- src/Umbraco.Core/Services/UserService.cs | 11 ++- .../BackOfficeUserClientCredentialsManager.cs | 1 + ...iceUserClientCredentialsOperationStatus.cs | 3 +- .../Services/UserServiceTests.cs | 68 +++++++++++++++++++ 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs index 9a30da3811..1751564b37 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/User/ClientCredentials/ClientCredentialsUserControllerBase.cs @@ -18,6 +18,10 @@ public abstract class ClientCredentialsUserControllerBase : UserControllerBase .WithTitle("Duplicate client ID") .WithDetail("The specified client ID is already in use. Choose another client ID.") .Build()), + BackOfficeUserClientCredentialsOperationStatus.InvalidClientId => BadRequest(problemDetailsBuilder + .WithTitle("Invalid client ID") + .WithDetail("The specified client ID is invalid. A valid client ID can only contain [a-z], [A-Z], [0-9], and [-._~].") + .Build()), _ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder .WithTitle("Unknown client credentials operation status.") .Build()), diff --git a/src/Umbraco.Core/Services/OperationStatus/UserClientCredentialsOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/UserClientCredentialsOperationStatus.cs index c33fd829b4..585d87c47a 100644 --- a/src/Umbraco.Core/Services/OperationStatus/UserClientCredentialsOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/UserClientCredentialsOperationStatus.cs @@ -4,5 +4,6 @@ public enum UserClientCredentialsOperationStatus { Success, DuplicateClientId, - InvalidUser + InvalidUser, + InvalidClientId } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 07da9f631a..0aa900c840 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using Microsoft.Extensions.DependencyInjection; using System.Security.Claims; using System.Security.Cryptography; +using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Cache; @@ -35,7 +36,7 @@ namespace Umbraco.Cms.Core.Services; /// Represents the UserService, which is an easy access to operations involving , /// and eventually Backoffice Users. /// -internal class UserService : RepositoryService, IUserService +internal partial class UserService : RepositoryService, IUserService { private readonly GlobalSettings _globalSettings; private readonly SecuritySettings _securitySettings; @@ -2483,6 +2484,11 @@ internal class UserService : RepositoryService, IUserService public async Task AddClientIdAsync(Guid userKey, string clientId) { + if (ValidClientId().IsMatch(clientId) is false) + { + return UserClientCredentialsOperationStatus.InvalidClientId; + } + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); IEnumerable currentClientIds = _userRepository.GetAllClientIds(); @@ -2670,5 +2676,8 @@ internal class UserService : RepositoryService, IUserService } } + [GeneratedRegex(@"^[\w\d\-\._~]*$")] + private static partial Regex ValidClientId(); + #endregion } diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserClientCredentialsManager.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserClientCredentialsManager.cs index 7036fa0e88..21afdd0165 100644 --- a/src/Umbraco.Infrastructure/Security/BackOfficeUserClientCredentialsManager.cs +++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserClientCredentialsManager.cs @@ -35,6 +35,7 @@ public sealed class BackOfficeUserClientCredentialsManager : ClientCredentialsMa { UserClientCredentialsOperationStatus.InvalidUser => Attempt.Fail(BackOfficeUserClientCredentialsOperationStatus.InvalidUser), UserClientCredentialsOperationStatus.DuplicateClientId => Attempt.Fail(BackOfficeUserClientCredentialsOperationStatus.DuplicateClientId), + UserClientCredentialsOperationStatus.InvalidClientId => Attempt.Fail(BackOfficeUserClientCredentialsOperationStatus.InvalidClientId), _ => throw new ArgumentOutOfRangeException($"Unsupported client ID operation status: {result}") }; } diff --git a/src/Umbraco.Infrastructure/Security/OperationStatus/BackOfficeUserClientCredentialsOperationStatus.cs b/src/Umbraco.Infrastructure/Security/OperationStatus/BackOfficeUserClientCredentialsOperationStatus.cs index fd86ce68cb..f0370a13a3 100644 --- a/src/Umbraco.Infrastructure/Security/OperationStatus/BackOfficeUserClientCredentialsOperationStatus.cs +++ b/src/Umbraco.Infrastructure/Security/OperationStatus/BackOfficeUserClientCredentialsOperationStatus.cs @@ -4,5 +4,6 @@ public enum BackOfficeUserClientCredentialsOperationStatus { Success, DuplicateClientId, - InvalidUser + InvalidUser, + InvalidClientId } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs index 67048e2900..e20fcb5310 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/UserServiceTests.cs @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -967,6 +968,54 @@ public class UserServiceTests : UmbracoIntegrationTest } } + [TestCase(UserKind.Default, UserClientCredentialsOperationStatus.InvalidUser)] + [TestCase(UserKind.Api, UserClientCredentialsOperationStatus.Success)] + public async Task Can_Assign_ClientId_To_Api_User(UserKind userKind, UserClientCredentialsOperationStatus expectedResult) + { + // Arrange + var user = await CreateTestUser(userKind); + + // Act + var result = await UserService.AddClientIdAsync(user.Key, "client-one"); + + // Assert + Assert.AreEqual(expectedResult, result); + } + + [TestCase("abcdefghijklmnopqrstuvwxyz", UserClientCredentialsOperationStatus.Success)] + [TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZ", UserClientCredentialsOperationStatus.Success)] + [TestCase("1234567890", UserClientCredentialsOperationStatus.Success)] + [TestCase(".-_~", UserClientCredentialsOperationStatus.Success)] + [TestCase("!", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("#", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("$", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("&", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("'", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("(", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase(")", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("*", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("+", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase(",", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("/", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase(":", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase(";", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("=", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("?", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("@", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("[", UserClientCredentialsOperationStatus.InvalidClientId)] + [TestCase("]", UserClientCredentialsOperationStatus.InvalidClientId)] + public async Task Can_Use_Only_Unreserved_Characters_For_ClientId(string clientId, UserClientCredentialsOperationStatus expectedResult) + { + // Arrange + var user = await CreateTestUser(UserKind.Api); + + // Act + var result = await UserService.AddClientIdAsync(user.Key, clientId); + + // Assert + Assert.AreEqual(expectedResult, result); + } + private Content[] BuildContentItems(int numberToCreate) { var template = TemplateBuilder.CreateTextPageTemplate(); @@ -999,6 +1048,25 @@ public class UserServiceTests : UmbracoIntegrationTest return user; } + private async Task CreateTestUser(UserKind userKind) + { + var userGroup = CreateTestUserGroup(); + + var result = await UserService.CreateAsync( + Constants.Security.SuperUserKey, + new UserCreateModel + { + Email = "test1@test.com", + Id = Guid.NewGuid(), + Kind = userKind, + Name = "test1@test.com", + UserName = "test1@test.com", + UserGroupKeys = new HashSet { userGroup.Key } + }); + Assert.IsTrue(result.Success); + return result.Result.CreatedUser!; + } + private List CreateTestUsers(int[] startContentIds, IUserGroup userGroup, int numberToCreate) { var users = new List(); From e7eb14d3107c06931c7b9605ba40eb1539b03fa1 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Thu, 7 Nov 2024 12:20:22 +0100 Subject: [PATCH 03/30] V15/task/cleanup obsolete (#17433) * Replace obsolete UserGroup Alias consts to key equivalent in tests * Update use of usergroup alias consts to key equivalent in IsSystemgroup extension method * Obsolete (internally) unused helper function which purpose doesn't even seem true * Prepped EmbedProviders for proper removal of non async methods and unneeded proxy methods * Remove obsoleted UmbracoPath and updated internal references * Corrected mistake and updated unittets * Update usergroup tests that use aliases for "system" groups * Replace more uses of globalsettings.UmbracoPath * Remove GetDateType by key non async * Cleanup some usages of hostingEnvironment.MapPathContentRoot * More easy obsoletion cleanup * Small Typeload cleanup * More obsolete removal * Deploy obsoletion cleanup * Remove obsolete methods from OEmbedProviderBase.cs --------- Co-authored-by: Zeegaan --- .../Controllers/BackOfficeLoginController.cs | 2 +- ...ckOfficeAuthBuilderOpenIddictExtensions.cs | 20 --- ...mbracoEFCoreServiceCollectionExtensions.cs | 61 -------- src/Umbraco.Core/Actions/ActionToPublish.cs | 35 ----- .../Cache/DataTypeConfigurationCache.cs | 2 +- .../Implement/ContentCacheRefresher.cs | 10 -- src/Umbraco.Core/Composing/TypeLoader.cs | 73 ---------- .../Configuration/GlobalSettingsExtensions.cs | 6 +- .../Configuration/Models/GlobalSettings.cs | 10 -- .../Configuration/Models/NuCacheSettings.cs | 2 +- .../Configuration/Models/SecuritySettings.cs | 7 - .../Models/WebRoutingSettings.cs | 4 - .../ModelsBuilderConfigExtensions.cs | 20 ++- src/Umbraco.Core/Constants-Security.cs | 18 ++- .../DeliveryApi/IApiRichTextElementParser.cs | 5 +- .../UmbracoBuilder.Configuration.cs | 14 -- .../DependencyInjection/UmbracoBuilder.cs | 23 +++ .../Deploy/IDataTypeConfigurationConnector.cs | 42 ++---- src/Umbraco.Core/Deploy/IFileSource.cs | 45 ------ src/Umbraco.Core/Deploy/IFileType.cs | 18 --- src/Umbraco.Core/Deploy/IImageSourceParser.cs | 39 +----- src/Umbraco.Core/Deploy/ILocalLinkParser.cs | 39 +----- src/Umbraco.Core/Deploy/IServiceConnector.cs | 131 +----------------- src/Umbraco.Core/Deploy/IValueConnector.cs | 45 ++---- .../Events/UserNotificationsHandler.cs | 5 - .../Media/EmbedProviders/DailyMotion.cs | 9 +- .../Media/EmbedProviders/Flickr.cs | 10 +- .../Media/EmbedProviders/GettyImages.cs | 8 +- .../Media/EmbedProviders/Giphy.cs | 8 +- src/Umbraco.Core/Media/EmbedProviders/Hulu.cs | 8 +- .../Media/EmbedProviders/Issuu.cs | 8 +- .../Media/EmbedProviders/Kickstarter.cs | 8 +- .../Media/EmbedProviders/LottieFiles.cs | 18 ++- .../EmbedProviders/OEmbedProviderBase.cs | 62 ++++----- .../Media/EmbedProviders/Slideshare.cs | 8 +- .../Media/EmbedProviders/SoundCloud.cs | 8 +- src/Umbraco.Core/Media/EmbedProviders/Ted.cs | 8 +- .../Media/EmbedProviders/Twitter.cs | 8 +- .../Media/EmbedProviders/Vimeo.cs | 8 +- src/Umbraco.Core/Media/EmbedProviders/X.cs | 9 +- .../Media/EmbedProviders/Youtube.cs | 8 +- src/Umbraco.Core/Media/IEmbedProvider.cs | 1 + .../Models/Membership/UserGroupExtensions.cs | 12 +- .../Routing/ContentFinderByIdPath.cs | 2 +- src/Umbraco.Core/Services/DataTypeService.cs | 9 -- .../Services/DateTypeServiceExtensions.cs | 2 +- src/Umbraco.Core/Services/IDataTypeService.cs | 10 -- .../Services/NotificationService.cs | 4 +- src/Umbraco.Core/Services/UserService.cs | 1 + .../UmbracoBuilder.CoreServices.cs | 1 - .../Migrations/Install/DatabaseDataCreator.cs | 1 - .../ModelsBuilder/Building/ModelsGenerator.cs | 35 ++++- .../ModelsBuilder/ModelsGenerationError.cs | 33 ++++- .../ModelsBuilder/OutOfDateModelsStatus.cs | 28 +++- .../Packaging/PackageDataInstallation.cs | 4 +- .../AspNetCore/AspNetCoreBackOfficeInfo.cs | 2 +- .../InMemoryAuto/InMemoryModelFactory.cs | 64 ++++++++- .../Mvc/UmbracoMvcConfigureOptions.cs | 2 +- .../Views/UmbracoViewPage.cs | 3 +- .../Controllers/RenderNoContentController.cs | 3 +- .../Extensions/HtmlHelperRenderExtensions.cs | 3 +- tests/Umbraco.Tests.Common/TestHelperBase.cs | 7 +- .../UmbracoBuilderExtensions.cs | 3 +- .../Services/ContentServiceTests.cs | 8 +- .../UserGroupServiceValidationTests.cs | 16 +-- .../Services/UserServiceCrudTests.Filter.cs | 26 ++-- .../Services/UserServiceCrudTests.Get.cs | 16 +-- .../UserServiceCrudTests.PartialUpdates.cs | 12 +- .../DbContext/CustomDbContextTests.cs | 31 ----- .../Umbraco.Core/Components/ComponentTests.cs | 90 +----------- .../Composing/ComposingTestBase.cs | 5 - .../Umbraco.Core/Composing/TypeLoaderTests.cs | 6 - .../Models/GlobalSettingsTests.cs | 2 +- .../Umbraco.Core/CoreThings/UdiTests.cs | 22 ++- .../Services/UserGroupServiceTests.cs | 16 ++- 75 files changed, 436 insertions(+), 916 deletions(-) delete mode 100644 src/Umbraco.Cms.Persistence.EFCore/Extensions/BackOfficeAuthBuilderOpenIddictExtensions.cs delete mode 100644 src/Umbraco.Core/Actions/ActionToPublish.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs b/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs index aa8b711257..c6ff576b93 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/BackOfficeLoginController.cs @@ -51,7 +51,7 @@ public class BackOfficeLoginController : Controller if (string.IsNullOrEmpty(model.UmbracoUrl)) { - model.UmbracoUrl = _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoPath); + model.UmbracoUrl = _hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath); } if (string.IsNullOrEmpty(model.ReturnUrl)) diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/BackOfficeAuthBuilderOpenIddictExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/BackOfficeAuthBuilderOpenIddictExtensions.cs deleted file mode 100644 index 3764537988..0000000000 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/BackOfficeAuthBuilderOpenIddictExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Persistence.EFCore; - -namespace Umbraco.Extensions; - -public static class BackOfficeAuthBuilderOpenIddictExtensions -{ - [Obsolete("This is no longer used and will be removed in V15. Instead use the overload that specifies the DbContext type.")] - public static IUmbracoBuilder AddUmbracoEFCoreDbContext(this IUmbracoBuilder builder) - { - builder.Services.AddUmbracoEFCoreContext((options, connectionString, providerName) => - { - // Register the entity sets needed by OpenIddict. - options.UseOpenIddict(); - }); - - return builder; - } -} diff --git a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs index 7694f83dd2..d7da8a65fe 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/Extensions/UmbracoEFCoreServiceCollectionExtensions.cs @@ -14,45 +14,6 @@ public static class UmbracoEFCoreServiceCollectionExtensions { public delegate void DefaultEFCoreOptionsAction(DbContextOptionsBuilder options, string? providerName, string? connectionString); - [Obsolete("Use AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) instead.")] - public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) - where T : DbContext - { - services.AddPooledDbContextFactory((provider, builder) => SetupDbContext(defaultEFCoreOptionsAction, provider, builder)); - services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); - - services.AddUnique, AmbientEFCoreScopeStack>(); - services.AddUnique, EFCoreScopeAccessor>(); - services.AddUnique, EFCoreScopeProvider>(); - services.AddSingleton>(); - services.AddSingleton>(); - - return services; - } - - [Obsolete("Use AddUmbracoDbContext(this IServiceCollection services, Action? optionsAction = null) instead.")] - public static IServiceCollection AddUmbracoEFCoreContext(this IServiceCollection services, string connectionString, string providerName, DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction = null) - where T : DbContext - { - // Replace data directory - string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); - if (string.IsNullOrEmpty(dataDirectory) is false) - { - connectionString = connectionString.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); - } - - services.AddPooledDbContextFactory(options => defaultEFCoreOptionsAction?.Invoke(options, providerName, connectionString)); - services.AddTransient(services => services.GetRequiredService>().CreateDbContext()); - - services.AddUnique, AmbientEFCoreScopeStack>(); - services.AddUnique, EFCoreScopeAccessor>(); - services.AddUnique, EFCoreScopeProvider>(); - services.AddSingleton>(); - services.AddSingleton>(); - - return services; - } - /// /// Adds a EFCore DbContext with all the services needed to integrate with Umbraco scopes. /// @@ -149,26 +110,4 @@ public static class UmbracoEFCoreServiceCollectionExtensions builder.UseDatabaseProvider(connectionStrings.ProviderName, connectionStrings.ConnectionString); } - - [Obsolete] - private static void SetupDbContext(DefaultEFCoreOptionsAction? defaultEFCoreOptionsAction, IServiceProvider provider, DbContextOptionsBuilder builder) - { - ConnectionStrings connectionStrings = GetConnectionStringAndProviderName(provider); - defaultEFCoreOptionsAction?.Invoke(builder, connectionStrings.ConnectionString, connectionStrings.ProviderName); - } - - [Obsolete] - private static ConnectionStrings GetConnectionStringAndProviderName(IServiceProvider serviceProvider) - { - ConnectionStrings connectionStrings = serviceProvider.GetRequiredService>().CurrentValue; - - // Replace data directory - string? dataDirectory = AppDomain.CurrentDomain.GetData(Constants.System.DataDirectoryName)?.ToString(); - if (string.IsNullOrEmpty(dataDirectory) is false) - { - connectionStrings.ConnectionString = connectionStrings.ConnectionString?.Replace(Constants.System.DataDirectoryPlaceholder, dataDirectory); - } - - return connectionStrings; - } } diff --git a/src/Umbraco.Core/Actions/ActionToPublish.cs b/src/Umbraco.Core/Actions/ActionToPublish.cs deleted file mode 100644 index a980d819aa..0000000000 --- a/src/Umbraco.Core/Actions/ActionToPublish.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -namespace Umbraco.Cms.Core.Actions; - -/// -/// This action is invoked when children to a document is being sent to published (by an editor without publishrights). -/// -[Obsolete("Scheduled for removal in v13")] -public class ActionToPublish : IAction -{ - /// - public const string ActionLetter = "Umb.Document.SendForApproval"; - - /// - public const string ActionAlias = "sendtopublish"; - - /// - public string Letter => ActionLetter; - - /// - public string Alias => ActionAlias; - - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - - /// - public string Icon => "icon-outbox"; - - /// - public bool ShowInNotifier => true; - - /// - public bool CanBePermissionAssigned => true; -} diff --git a/src/Umbraco.Core/Cache/DataTypeConfigurationCache.cs b/src/Umbraco.Core/Cache/DataTypeConfigurationCache.cs index 78ee17d2f8..70c9b4161b 100644 --- a/src/Umbraco.Core/Cache/DataTypeConfigurationCache.cs +++ b/src/Umbraco.Core/Cache/DataTypeConfigurationCache.cs @@ -26,7 +26,7 @@ internal sealed class DataTypeConfigurationCache : IDataTypeConfigurationCache var cacheKey = GetCacheKey(key); if (_memoryCache.TryGetValue(cacheKey, out T? configuration) is false) { - IDataType? dataType = _dataTypeService.GetDataType(key); + IDataType? dataType = _dataTypeService.GetAsync(key).GetAwaiter().GetResult(); configuration = dataType?.ConfigurationAs(); // Only cache if data type was found (but still cache null configurations) diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs index cc224186aa..759e724c88 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs @@ -367,16 +367,6 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase? _assemblies; - /// - /// Initializes a new instance of the class. - /// - [Obsolete("Please use an alternative constructor.")] - public TypeLoader( - ITypeFinder typeFinder, - IRuntimeHash runtimeHash, - IAppPolicyCache runtimeCache, - DirectoryInfo localTempPath, - ILogger logger, - IProfiler profiler, - IEnumerable? assembliesToScan = null) - : this(typeFinder, logger, assembliesToScan) - { - } - - /// - /// Initializes a new instance of the class. - /// - [Obsolete("Please use an alternative constructor.")] - public TypeLoader( - ITypeFinder typeFinder, - IRuntimeHash runtimeHash, - IAppPolicyCache runtimeCache, - DirectoryInfo localTempPath, - ILogger logger, - IProfiler profiler, - bool detectChanges, - IEnumerable? assembliesToScan = null) - : this(typeFinder, logger, assembliesToScan) - { - } - public TypeLoader( ITypeFinder typeFinder, ILogger logger, @@ -100,18 +67,6 @@ public sealed class TypeLoader [Obsolete("This will be removed in a future version.")] public IEnumerable TypeLists => _types.Values; - /// - /// Sets a type list. - /// - /// For unit tests. - // internal for tests - [Obsolete("This will be removed in a future version.")] - public void AddTypeList(TypeList typeList) - { - Type tobject = typeof(object); // CompositeTypeTypeKey does not support null values - _types[new CompositeTypeTypeKey(typeList.BaseType ?? tobject, typeList.AttributeType ?? tobject)] = typeList; - } - #region Get Assembly Attributes /// @@ -136,34 +91,6 @@ public sealed class TypeLoader #region Cache - // internal for tests - [Obsolete("This will be removed in a future version.")] - public Attempt> TryGetCached(Type baseType, Type attributeType) => - Attempt>.Fail(); - - // internal for tests - [Obsolete("This will be removed in a future version.")] - public Dictionary<(string, string), IEnumerable>? ReadCache() => null; - - // internal for tests - [Obsolete("This will be removed in a future version.")] - public string? GetTypesListFilePath() => null; - - // internal for tests - [Obsolete("This will be removed in a future version.")] - public void WriteCache() - { - } - - /// - /// Clears cache. - /// - /// Generally only used for resetting cache, for example during the install process. - [Obsolete("This will be removed in a future version.")] - public void ClearTypesCache() - { - } - #endregion #region Get Types diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index 2f49bfd146..67966c9849 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -22,7 +22,7 @@ public static class GlobalSettingsExtensions return _backOfficePath; } - _backOfficePath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); + _backOfficePath = hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath); return _backOfficePath; } @@ -54,9 +54,9 @@ public static class GlobalSettingsExtensions this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { - var path = string.IsNullOrEmpty(globalSettings.UmbracoPath) + var path = string.IsNullOrEmpty(Constants.System.DefaultUmbracoPath) ? string.Empty - : hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); + : hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath); if (path.IsNullOrWhiteSpace()) { diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 58d1bb7134..7d2556ea60 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -76,16 +76,6 @@ public class GlobalSettings [DefaultValue(StaticVersionCheckPeriod)] public int VersionCheckPeriod { get; set; } = StaticVersionCheckPeriod; - /// - /// Gets or sets a value for the Umbraco back-office path. - /// - [Obsolete($"UmbracoPath is no longer configurable, use Constants.System.DefaultUmbracoPath instead. This property is scheduled for removal in a future version.")] - public string UmbracoPath - { - get => Constants.System.DefaultUmbracoPath; - set { } - } - /// /// Gets or sets a value for the Umbraco icons path. /// diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs index 9b1fde9826..0095a8f165 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs @@ -19,7 +19,7 @@ public class NuCacheSettings /// /// Gets or sets a value defining the BTree block size. /// - [Obsolete("This property is no longer used")] + [Obsolete("This property is no longer used. Scheduled for removal in v16")] public int? BTreeBlockSize { get; set; } /// diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 2a57a0a74c..b5d81c1ab3 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -95,13 +95,6 @@ public class SecuritySettings [DefaultValue(StaticUserDefaultLockoutTimeInMinutes)] public int UserDefaultLockoutTimeInMinutes { get; set; } = StaticUserDefaultLockoutTimeInMinutes; - /// - /// Gets or sets a value indicating whether to allow editing invariant properties from a non-default language variation. - /// - [Obsolete("Use ContentSettings.AllowEditFromInvariant instead")] - [DefaultValue(StaticAllowEditInvariantFromNonDefault)] - public bool AllowEditInvariantFromNonDefault { get; set; } = StaticAllowEditInvariantFromNonDefault; - /// /// Gets or sets a value indicating whether to allow concurrent logins. /// diff --git a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs index 37a39a6c92..9c96f87c31 100644 --- a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs @@ -60,10 +60,6 @@ public class WebRoutingSettings [DefaultValue(StaticValidateAlternativeTemplates)] public bool ValidateAlternativeTemplates { get; set; } = StaticValidateAlternativeTemplates; - [Obsolete("Use DisableFindContentByIdentifierPath instead. This will be removed in Umbraco 15." )] - [DefaultValue(StaticDisableFindContentByIdPath)] - public bool DisableFindContentByIdPath { get; set; } = StaticDisableFindContentByIdPath; - [DefaultValue(StaticDisableFindContentByIdentifierPath)] public bool DisableFindContentByIdentifierPath { get; set; } = StaticDisableFindContentByIdentifierPath; /// diff --git a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs index 294040f414..0ade116444 100644 --- a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs +++ b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs @@ -1,6 +1,8 @@ +using Microsoft.Extensions.Hosting; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Exceptions; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Extensions; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Extensions; @@ -8,6 +10,22 @@ public static class ModelsBuilderConfigExtensions { private static string? _modelsDirectoryAbsolute; + public static string ModelsDirectoryAbsolute( + this ModelsBuilderSettings modelsBuilderConfig, + IHostEnvironment hostEnvironment) + { + if (_modelsDirectoryAbsolute is null) + { + var modelsDirectory = modelsBuilderConfig.ModelsDirectory; + var root = hostEnvironment.MapPathContentRoot("~/"); + + _modelsDirectoryAbsolute = GetModelsDirectory(root, modelsDirectory, modelsBuilderConfig.AcceptUnsafeModelsDirectory); + } + + return _modelsDirectoryAbsolute; + } + + [Obsolete("Use the non obsoleted equivalent instead. Scheduled for removal in v16")] public static string ModelsDirectoryAbsolute( this ModelsBuilderSettings modelsBuilderConfig, IHostingEnvironment hostingEnvironment) diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 2f24fa182d..df6f26ca1a 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -49,27 +49,35 @@ public static partial class Constants /// /// The key of the admin group /// - public static readonly Guid AdminGroupKey = new("E5E7F6C8-7F9C-4B5B-8D5D-9E1E5A4F7E4D"); + public static readonly Guid AdminGroupKey = new(AdminGroupKeyString); + internal const string AdminGroupKeyString = "E5E7F6C8-7F9C-4B5B-8D5D-9E1E5A4F7E4D"; + /// /// The key of the editor group /// - public static readonly Guid EditorGroupKey = new("44DC260E-B4D4-4DD9-9081-EEC5598F1641"); + public static readonly Guid EditorGroupKey = new(EditorGroupKeyString); + internal const string EditorGroupKeyString = "44DC260E-B4D4-4DD9-9081-EEC5598F1641"; + /// /// The key of the sensitive data group /// - public static readonly Guid SensitiveDataGroupKey = new("8C6AD70F-D307-4E4A-AF58-72C2E4E9439D"); + public static readonly Guid SensitiveDataGroupKey = new(SensitiveDataGroupKeyString); + internal const string SensitiveDataGroupKeyString = "8C6AD70F-D307-4E4A-AF58-72C2E4E9439D"; /// /// The key of the translator group /// - public static readonly Guid TranslatorGroupKey = new("F2012E4C-D232-4BD1-8EAE-4384032D97D8"); + public static readonly Guid TranslatorGroupKey = new(TranslatorGroupString); + internal const string TranslatorGroupString = "F2012E4C-D232-4BD1-8EAE-4384032D97D8"; /// /// The key of the writer group /// - public static readonly Guid WriterGroupKey = new("9FC2A16F-528C-46D6-A014-75BF4EC2480C"); + public static readonly Guid WriterGroupKey = new(WriterGroupKeyString); + internal const string WriterGroupKeyString = "9FC2A16F-528C-46D6-A014-75BF4EC2480C"; + public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; diff --git a/src/Umbraco.Core/DeliveryApi/IApiRichTextElementParser.cs b/src/Umbraco.Core/DeliveryApi/IApiRichTextElementParser.cs index 50b9b5d581..4b1b1e92d3 100644 --- a/src/Umbraco.Core/DeliveryApi/IApiRichTextElementParser.cs +++ b/src/Umbraco.Core/DeliveryApi/IApiRichTextElementParser.cs @@ -5,9 +5,6 @@ namespace Umbraco.Cms.Core.DeliveryApi; public interface IApiRichTextElementParser { - // NOTE: remember to also remove the default implementation of the method overload when this one is removed. - [Obsolete($"Please use the overload that accepts {nameof(RichTextBlockModel)}. Will be removed in V15.")] - IRichTextElement? Parse(string html); - IRichTextElement? Parse(string html, RichTextBlockModel? richTextBlockModel) => null; + IRichTextElement? Parse(string html, RichTextBlockModel? richTextBlockModel); } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index a3b3ed7631..f393a9cedb 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -109,20 +109,6 @@ public static partial class UmbracoBuilderExtensions builder.Services.Configure(Constants.Configuration.NamedOptions.CacheEntry.Document, builder.Config.GetSection($"{Constants.Configuration.ConfigCacheEntry}:{Constants.Configuration.NamedOptions.CacheEntry.Document}")); - // TODO: Remove this in V12 - // This is to make the move of the AllowEditInvariantFromNonDefault setting from SecuritySettings to ContentSettings backwards compatible - // If there is a value in security settings, but no value in content setting we'll use that value, otherwise content settings always wins. - builder.Services.Configure(settings => - { - var securitySettingsValue = builder.Config.GetSection($"{Constants.Configuration.ConfigSecurity}").GetValue(nameof(SecuritySettings.AllowEditInvariantFromNonDefault)); - var contentSettingsValue = builder.Config.GetSection($"{Constants.Configuration.ConfigContent}").GetValue(nameof(ContentSettings.AllowEditInvariantFromNonDefault)); - - if (securitySettingsValue is not null && contentSettingsValue is null) - { - settings.AllowEditInvariantFromNonDefault = securitySettingsValue.Value; - } - }); - return builder; } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 9fb195481f..5e06ea5042 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -63,6 +63,7 @@ namespace Umbraco.Cms.Core.DependencyInjection public ILoggerFactory BuilderLoggerFactory { get; } /// + [Obsolete("Only here to comply with obsolete implementation. Scheduled for removal in v16")] public IHostingEnvironment? BuilderHostingEnvironment { get; } public IProfiler Profiler { get; } @@ -79,6 +80,7 @@ namespace Umbraco.Cms.Core.DependencyInjection /// /// Initializes a new instance of the class. /// + [Obsolete("Use a non obsolete constructor instead. Scheduled for removal in v16")] public UmbracoBuilder( IServiceCollection services, IConfiguration config, @@ -99,6 +101,27 @@ namespace Umbraco.Cms.Core.DependencyInjection AddCoreServices(); } + /// + /// Initializes a new instance of the class. + /// + public UmbracoBuilder( + IServiceCollection services, + IConfiguration config, + TypeLoader typeLoader, + ILoggerFactory loggerFactory, + IProfiler profiler, + AppCaches appCaches) + { + Services = services; + Config = config; + BuilderLoggerFactory = loggerFactory; + Profiler = profiler; + AppCaches = appCaches; + TypeLoader = typeLoader; + + AddCoreServices(); + } + /// /// Gets a collection builder (and registers the collection). /// diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs index 052d3df388..bfaf32196e 100644 --- a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs +++ b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs @@ -19,18 +19,6 @@ public interface IDataTypeConfigurationConnector /// IEnumerable PropertyEditorAliases { get; } - /// - /// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies. - /// - /// The data type. - /// The dependencies. - /// The context cache. - /// - /// The artifact configuration value. - /// - [Obsolete("Use ToArtifactAsync() instead. This method will be removed in a future version.")] - string? ToArtifact(IDataType dataType, ICollection dependencies, IContextCache contextCache); - /// /// Gets the artifact configuration value corresponding to a data type configuration and gather dependencies. /// @@ -41,22 +29,11 @@ public interface IDataTypeConfigurationConnector /// /// A task that represents the asynchronous operation. The task result contains the artifact configuration value. /// - Task ToArtifactAsync(IDataType dataType, ICollection dependencies, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(ToArtifact(dataType, dependencies, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Gets the data type configuration corresponding to an artifact configuration value. - /// - /// The data type. - /// The artifact configuration value. - /// The context cache. - /// - /// The data type configuration. - /// - [Obsolete("Use FromArtifactAsync() instead. This method will be removed in a future version.")] - IDictionary FromArtifact(IDataType dataType, string? configuration, IContextCache contextCache); + Task ToArtifactAsync( + IDataType dataType, + ICollection dependencies, + IContextCache contextCache, + CancellationToken cancellationToken = default); /// /// Gets the data type configuration corresponding to an artifact configuration value. @@ -68,8 +45,9 @@ public interface IDataTypeConfigurationConnector /// /// A task that represents the asynchronous operation. The task result contains the data type configuration. /// - Task> FromArtifactAsync(IDataType dataType, string? configuration, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(FromArtifact(dataType, configuration, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete + Task> FromArtifactAsync( + IDataType dataType, + string? configuration, + IContextCache contextCache, + CancellationToken cancellationToken = default); } diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs index 53db3e26e6..230140c9bf 100644 --- a/src/Umbraco.Core/Deploy/IFileSource.cs +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -6,19 +6,6 @@ namespace Umbraco.Cms.Core.Deploy; /// public interface IFileSource { - /// - /// Gets the content of a file as a stream. - /// - /// A file entity identifier. - /// - /// A stream with read access to the file content. - /// - /// - /// Returns null if no content could be read. - /// The caller should ensure that the stream is properly closed/disposed. - /// - [Obsolete("Use GetFileStreamAsync() instead. This method will be removed in a future version.")] - Stream GetFileStream(StringUdi udi); /// /// Gets the content of a file as a stream. @@ -34,19 +21,6 @@ public interface IFileSource /// Task GetFileStreamAsync(StringUdi udi, CancellationToken token); - /// - /// Gets the content of a file as a string. - /// - /// A file entity identifier. - /// - /// A string containing the file content. - /// - /// - /// Returns null if no content could be read. - /// - [Obsolete("Use GetFileContentAsync() instead. This method will be removed in a future version.")] - string GetFileContent(StringUdi udi); - /// /// Gets the content of a file as a string. /// @@ -60,16 +34,6 @@ public interface IFileSource /// Task GetFileContentAsync(StringUdi udi, CancellationToken token); - /// - /// Gets the length of a file. - /// - /// A file entity identifier. - /// - /// The length of the file, or -1 if the file does not exist. - /// - [Obsolete("Use GetFileLengthAsync() instead. This method will be removed in a future version.")] - long GetFileLength(StringUdi udi); - /// /// Gets the length of a file. /// @@ -80,15 +44,6 @@ public interface IFileSource /// Task GetFileLengthAsync(StringUdi udi, CancellationToken token); - /// - /// Gets files and store them using a file store. - /// - /// The UDIs of the files to get. - /// A flag indicating whether to continue if a file isn't found or to stop and throw a FileNotFoundException. - /// A collection of file types which can store the files. - [Obsolete("Use GetFilesAsync() instead. This method will be removed in a future version.")] - void GetFiles(IEnumerable udis, bool continueOnFileNotFound, IFileTypeCollection fileTypes); - /// /// Gets files and store them using a file store. /// diff --git a/src/Umbraco.Core/Deploy/IFileType.cs b/src/Umbraco.Core/Deploy/IFileType.cs index a135481eb4..f8fa5ba92d 100644 --- a/src/Umbraco.Core/Deploy/IFileType.cs +++ b/src/Umbraco.Core/Deploy/IFileType.cs @@ -13,16 +13,6 @@ public interface IFileType /// bool CanSetPhysical { get; } - /// - /// Gets the stream. - /// - /// The UDI. - /// - /// The stream. - /// - [Obsolete("Use GetStreamAsync() instead. This method will be removed in a future version.")] - Stream GetStream(StringUdi udi); - /// /// Gets the stream as an asynchronous operation. /// @@ -51,14 +41,6 @@ public interface IFileType /// long GetLength(StringUdi udi); - /// - /// Sets the stream. - /// - /// The UDI. - /// The stream. - [Obsolete("Use SetStreamAsync() instead. This method will be removed in a future version.")] - void SetStream(StringUdi udi, Stream stream); - /// /// Sets the stream as an asynchronous operation. /// diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs index 3817dc6fb8..dd4124e889 100644 --- a/src/Umbraco.Core/Deploy/IImageSourceParser.cs +++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs @@ -5,21 +5,6 @@ namespace Umbraco.Cms.Core.Deploy; /// public interface IImageSourceParser { - /// - /// Parses an Umbraco property value and produces an artifact property value. - /// - /// The property value. - /// A list of dependencies. - /// The context cache. - /// - /// The parsed value. - /// - /// - /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. - /// - [Obsolete("Use ToArtifactAsync() instead. This method will be removed in a future version.")] - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache); - /// /// Parses an Umbraco property value and produces an artifact property value. /// @@ -33,24 +18,7 @@ public interface IImageSourceParser /// /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. /// - Task ToArtifactAsync(string value, ICollection dependencies, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(ToArtifact(value, dependencies, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Parses an artifact property value and produces an Umbraco property value. - /// - /// The artifact property value. - /// The context cache. - /// - /// The parsed value. - /// - /// - /// Turns umb://media/... into /media/.... - /// - [Obsolete("Use FromArtifactAsync() instead. This method will be removed in a future version.")] - string FromArtifact(string value, IContextCache contextCache); + Task ToArtifactAsync(string value, ICollection dependencies, IContextCache contextCache, CancellationToken cancellationToken = default); /// /// Parses an artifact property value and produces an Umbraco property value. @@ -64,8 +32,5 @@ public interface IImageSourceParser /// /// Turns umb://media/... into /media/.... /// - Task FromArtifactAsync(string value, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(FromArtifact(value, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete + Task FromArtifactAsync(string value, IContextCache contextCache, CancellationToken cancellationToken = default); } diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs index fcd3405a3c..357b0ecda6 100644 --- a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs +++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs @@ -5,21 +5,6 @@ namespace Umbraco.Cms.Core.Deploy; /// public interface ILocalLinkParser { - /// - /// Parses an Umbraco property value and produces an artifact property value. - /// - /// The property value. - /// A list of dependencies. - /// The context cache. - /// - /// The parsed value. - /// - /// - /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the dependencies. - /// - [Obsolete("Use ToArtifactAsync() instead. This method will be removed in a future version.")] - string ToArtifact(string value, ICollection dependencies, IContextCache contextCache); - /// /// Parses an Umbraco property value and produces an artifact property value. /// @@ -33,24 +18,7 @@ public interface ILocalLinkParser /// /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the dependencies. /// - Task ToArtifactAsync(string value, ICollection dependencies, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(ToArtifact(value, dependencies, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Parses an artifact property value and produces an Umbraco property value. - /// - /// The artifact property value. - /// The context cache. - /// - /// The parsed value. - /// - /// - /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. - /// - [Obsolete("Use FromArtifactAsync() instead. This method will be removed in a future version.")] - string FromArtifact(string value, IContextCache contextCache); + Task ToArtifactAsync(string value, ICollection dependencies, IContextCache contextCache, CancellationToken cancellationToken = default); /// /// Parses an artifact property value and produces an Umbraco property value. @@ -64,8 +32,5 @@ public interface ILocalLinkParser /// /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. /// - Task FromArtifactAsync(string value, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(FromArtifact(value, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete + Task FromArtifactAsync(string value, IContextCache contextCache, CancellationToken cancellationToken = default); } diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index 79666f2fa7..4f8b2bfe5d 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -8,17 +8,6 @@ namespace Umbraco.Cms.Core.Deploy; /// public interface IServiceConnector : IDiscoverable { - /// - /// Gets an artifact. - /// - /// The entity identifier of the artifact. - /// The context cache. - /// - /// The corresponding artifact or null. - /// - [Obsolete("Use GetArtifactAsync() instead. This method will be removed in a future version.")] - IArtifact? GetArtifact(Udi udi, IContextCache contextCache); - /// /// Gets an artifact. /// @@ -28,21 +17,7 @@ public interface IServiceConnector : IDiscoverable /// /// A task that represents the asynchronous operation. The task result contains the corresponding artifact or null. /// - Task GetArtifactAsync(Udi udi, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(GetArtifact(udi, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Gets an artifact. - /// - /// The entity. - /// The context cache. - /// - /// The corresponding artifact. - /// - [Obsolete("Use GetArtifactAsync() instead. This method will be removed in a future version.")] - IArtifact GetArtifact(object entity, IContextCache contextCache); + Task GetArtifactAsync(Udi udi, IContextCache contextCache, CancellationToken cancellationToken = default); /// /// Gets an artifact. @@ -53,21 +28,7 @@ public interface IServiceConnector : IDiscoverable /// /// A task that represents the asynchronous operation. The task result contains the corresponding artifact. /// - Task GetArtifactAsync(object entity, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(GetArtifact(entity, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Initializes processing for an artifact. - /// - /// The artifact. - /// The deploy context. - /// - /// The state of an artifact being deployed. - /// - [Obsolete("Use ProcessInitAsync() instead. This method will be removed in a future version.")] - ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context); + Task GetArtifactAsync(object entity, IContextCache contextCache, CancellationToken cancellationToken = default); /// /// Initializes processing for an artifact. @@ -78,19 +39,7 @@ public interface IServiceConnector : IDiscoverable /// /// A task that represents the asynchronous operation. The task result contains the state of an artifact being deployed. /// - Task ProcessInitAsync(IArtifact artifact, IDeployContext context, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(ProcessInit(artifact, context)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Processes an artifact. - /// - /// The state of the artifact being deployed. - /// The deploy context. - /// The processing pass number. - [Obsolete("Use ProcessAsync() instead. This method will be removed in a future version.")] - void Process(ArtifactDeployState dart, IDeployContext context, int pass); + Task ProcessInitAsync(IArtifact artifact, IDeployContext context, CancellationToken cancellationToken = default); /// /// Processes an artifact. @@ -102,23 +51,7 @@ public interface IServiceConnector : IDiscoverable /// /// A task that represents the asynchronous operation. /// - Task ProcessAsync(ArtifactDeployState state, IDeployContext context, int pass, CancellationToken cancellationToken = default) - { - // TODO: Remove default implementation in v15 -#pragma warning disable CS0618 // Type or member is obsolete - Process(state, context, pass); -#pragma warning restore CS0618 // Type or member is obsolete - - return Task.CompletedTask; - } - - /// - /// Explodes/expands an UDI range into UDIs. - /// - /// The UDI range. - /// The list of UDIs where to add the exploded/expanded UDIs. - [Obsolete("Use ExpandRangeAsync() instead. This method will be removed in a future version.")] - void Explode(UdiRange range, List udis); + Task ProcessAsync(ArtifactDeployState state, IDeployContext context, int pass, CancellationToken cancellationToken = default); /// /// Expands an UDI range into UDIs. @@ -128,30 +61,7 @@ public interface IServiceConnector : IDiscoverable /// /// Returns an which when enumerated will asynchronously expand the UDI range into UDIs. /// - async IAsyncEnumerable ExpandRangeAsync(UdiRange range, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - // TODO: Remove default implementation in v15 - var udis = new List(); -#pragma warning disable CS0618 // Type or member is obsolete - Explode(range, udis); -#pragma warning restore CS0618 // Type or member is obsolete - - foreach (Udi udi in udis) - { - yield return await ValueTask.FromResult(udi); - } - } - - /// - /// Gets a named range for a specified UDI and selector. - /// - /// The UDI. - /// The selector. - /// - /// The named range for the specified UDI and selector. - /// - [Obsolete("Use GetRangeAsync() instead. This method will be removed in a future version.")] - NamedUdiRange GetRange(Udi udi, string selector); + IAsyncEnumerable ExpandRangeAsync(UdiRange range, CancellationToken cancellationToken = default); /// /// Gets a named range for a specified UDI and selector. @@ -162,31 +72,7 @@ public interface IServiceConnector : IDiscoverable /// /// A task that represents the asynchronous operation. The task result contains the named range for the specified UDI and selector. /// - Task GetRangeAsync(Udi udi, string selector, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(GetRange(udi, selector)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Gets a named range for specified entity type, identifier and selector. - /// - /// The entity type. - /// The identifier. - /// The selector. - /// - /// The named range for the specified entity type, identifier and selector. - /// - /// - /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now? - /// - /// At the moment our UI has a hard time returning proper UDIs, mainly because Core's tree do - /// not manage GUIDs but only integers... so we have to provide a way to support it. The string id here - /// can be either a real string (for string UDIs) or an "integer as a string", using the value "-1" to - /// indicate the "root" i.e. an open UDI. - /// - /// - [Obsolete("Use GetRangeAsync() instead. This method will be removed in a future version.")] - NamedUdiRange GetRange(string entityType, string sid, string selector); + Task GetRangeAsync(Udi udi, string selector, CancellationToken cancellationToken = default); /// /// Gets a named range for specified entity type, identifier and selector. @@ -207,10 +93,7 @@ public interface IServiceConnector : IDiscoverable /// indicate the "root" i.e. an open UDI. /// /// - Task GetRangeAsync(string entityType, string sid, string selector, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(GetRange(entityType, sid, selector)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete + Task GetRangeAsync(string entityType, string sid, string selector, CancellationToken cancellationToken = default); /// /// Compares two artifacts. diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs index 06d37e792e..663d3bd471 100644 --- a/src/Umbraco.Core/Deploy/IValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -20,19 +20,6 @@ public interface IValueConnector /// IEnumerable PropertyEditorAliases { get; } - /// - /// Gets the deploy property value corresponding to a content property value, and gather dependencies. - /// - /// The content property value. - /// The value property type - /// The content dependencies. - /// The context cache. - /// - /// The deploy property value. - /// - [Obsolete("Use ToArtifactAsync() instead. This method will be removed in a future version.")] - string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache); - /// /// Gets the deploy property value corresponding to a content property value, and gather dependencies. /// @@ -44,23 +31,11 @@ public interface IValueConnector /// /// A task that represents the asynchronous operation. The task result contains the deploy property value. /// - Task ToArtifactAsync(object? value, IPropertyType propertyType, ICollection dependencies, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(ToArtifact(value, propertyType, dependencies, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Gets the content property value corresponding to a deploy property value. - /// - /// The deploy property value. - /// The value property type - /// The current content property value. - /// The context cache. - /// - /// The content property value. - /// - [Obsolete("Use FromArtifactAsync() instead. This method will be removed in a future version.")] - object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache); + Task ToArtifactAsync(object? value, + IPropertyType propertyType, + ICollection dependencies, + IContextCache contextCache, + CancellationToken cancellationToken = default); /// /// Gets the content property value corresponding to a deploy property value. @@ -73,8 +48,10 @@ public interface IValueConnector /// /// A task that represents the asynchronous operation. The task result contains the content property value. /// - Task FromArtifactAsync(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache, CancellationToken cancellationToken = default) -#pragma warning disable CS0618 // Type or member is obsolete - => Task.FromResult(FromArtifact(value, propertyType, currentValue, contextCache)); // TODO: Remove default implementation in v15 -#pragma warning restore CS0618 // Type or member is obsolete + Task FromArtifactAsync( + string? value, + IPropertyType propertyType, + object? currentValue, + IContextCache contextCache, + CancellationToken cancellationToken = default); } diff --git a/src/Umbraco.Core/Events/UserNotificationsHandler.cs b/src/Umbraco.Core/Events/UserNotificationsHandler.cs index ebc7840fa1..6356046963 100644 --- a/src/Umbraco.Core/Events/UserNotificationsHandler.cs +++ b/src/Umbraco.Core/Events/UserNotificationsHandler.cs @@ -25,7 +25,6 @@ public sealed class UserNotificationsHandler : INotificationHandler, INotificationHandler, INotificationHandler, - INotificationHandler, INotificationHandler, INotificationHandler, INotificationHandler @@ -107,10 +106,6 @@ public sealed class UserNotificationsHandler : _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); } - [Obsolete("Scheduled for removal in v13")] - public void Handle(ContentSentToPublishNotification notification) => - _notifier.Notify(_actions.GetAction(), notification.Entity); - public void Handle(ContentSortedNotification notification) { var parentId = notification.SortedEntities.Select(x => x.ParentId).Distinct().ToList(); diff --git a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs index 9c75db64e2..0f2abd3b8e 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs @@ -23,6 +23,11 @@ public class DailyMotion : OEmbedProviderBase { "format", "xml" }, }; + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetXmlBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); @@ -33,7 +38,5 @@ public class DailyMotion : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs index 6838c0f73f..0d7d8b3a0f 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs @@ -22,11 +22,9 @@ public class Flickr : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); XmlDocument xmlDocument = await base.GetXmlResponseAsync(requestUrl, cancellationToken); @@ -38,4 +36,8 @@ public class Flickr : OEmbedProviderBase return string.Format("\"{3}\"", imageUrl, imageWidth, imageHeight, WebUtility.HtmlEncode(imageTitle)); } + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetMarkupAsync(url, maxWidth, maxHeight, cancellationToken); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs index 02631542d0..41364111af 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs @@ -22,10 +22,12 @@ public class GettyImages : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetJsonBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs index e9938f3164..1e8b2c96c8 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs @@ -20,10 +20,12 @@ public class Giphy : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetJsonBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs index 5fdd22c667..4e4d53e6ad 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs @@ -20,10 +20,12 @@ public class Hulu : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetJsonBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs index adeb6b45f8..42120b70c5 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs @@ -28,10 +28,12 @@ public class Issuu : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetXmlBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs index 78c50253ec..a54c03455f 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs @@ -20,10 +20,12 @@ public class Kickstarter : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetJsonBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs index 56c2c8a1fd..8ce00feea7 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs @@ -21,14 +21,12 @@ public class LottieFiles : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = await this.GetJsonResponseAsync(requestUrl, cancellationToken); + var requestUrl = GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = await GetJsonResponseAsync(requestUrl, cancellationToken); var html = oembed?.GetHtml(); // LottieFiles doesn't seem to support maxwidth and maxheight via oembed @@ -53,4 +51,12 @@ public class LottieFiles : OEmbedProviderBase return html; } + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] + public override async Task GeOEmbedDataAsync( + string url, + int? maxWidth, + int? maxHeight, + CancellationToken cancellationToken) + => await GetMarkupAsync(url, maxWidth, maxHeight, cancellationToken); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs index f0b0f3a217..369b6d7249 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs @@ -21,9 +21,14 @@ public abstract class OEmbedProviderBase : IEmbedProvider [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public abstract string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); - public virtual Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) => Task.FromResult(GetMarkup(url, maxWidth ?? 0, maxHeight ?? 0)); + public abstract Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken); + + [Obsolete("Cleanup, only proxied to by GetMarkupAsync implementations. Planned for removal in v16")] + public virtual Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => Task.FromResult(GetMarkup(url, maxWidth ?? 0, maxHeight ?? 0)); public virtual string GetEmbedProviderUrl(string url, int? maxWidth, int? maxHeight) => GetEmbedProviderUrl(url, maxWidth ?? 0, maxHeight ?? 0); + public virtual string GetEmbedProviderUrl(string url, int maxWidth, int maxHeight) { if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) @@ -54,22 +59,6 @@ public abstract class OEmbedProviderBase : IEmbedProvider return fullUrl.ToString(); } - [Obsolete("Use DownloadResponseAsync instead. This will be removed in Umbraco 15.")] - public virtual string DownloadResponse(string url) - { - if (_httpClient == null) - { - _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(Constants.HttpClients.Headers.UserAgentProductName); - } - - using (var request = new HttpRequestMessage(HttpMethod.Get, url)) - { - using HttpResponseMessage response = _httpClient.SendAsync(request).GetAwaiter().GetResult(); - return response.Content.ReadAsStringAsync().Result; - } - } - public virtual async Task DownloadResponseAsync(string url, CancellationToken cancellationToken) { if (_httpClient == null) @@ -85,14 +74,6 @@ public abstract class OEmbedProviderBase : IEmbedProvider } } - [Obsolete("Use GetJsonResponseAsync instead. This will be removed in Umbraco 15.")] - public virtual T? GetJsonResponse(string url) - where T : class - { - var response = DownloadResponse(url); - return _jsonSerializer.Deserialize(response); - } - public virtual async Task GetJsonResponseAsync(string url, CancellationToken cancellationToken) where T : class { @@ -109,19 +90,30 @@ public abstract class OEmbedProviderBase : IEmbedProvider return doc; } - [Obsolete("Use GetXmlResponseAsync instead. This will be removed in Umbraco 15.")] - public virtual XmlDocument GetXmlResponse(string url) - { - var response = DownloadResponse(url); - var doc = new XmlDocument(); - doc.LoadXml(response); - - return doc; - } - public virtual string GetXmlProperty(XmlDocument doc, string property) { XmlNode? selectSingleNode = doc.SelectSingleNode(property); return selectSingleNode != null ? selectSingleNode.InnerText : string.Empty; } + + public virtual async Task GetJsonBasedMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + { + var requestUrl = GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = await GetJsonResponseAsync(requestUrl, cancellationToken); + + return oembed?.GetHtml(); + } + + public virtual async Task GetXmlBasedMarkupAsync( + string url, + int? maxWidth, + int? maxHeight, + CancellationToken cancellationToken, + string property = "/oembed/html") + { + var requestUrl = GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = await GetXmlResponseAsync(requestUrl, cancellationToken); + + return GetXmlProperty(xmlDocument, property); + } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs index c1b4731bfb..f7b5a0830c 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs @@ -21,10 +21,12 @@ public class Slideshare : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetXmlBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs index 43092dddaa..ab8e7ab0a5 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs @@ -22,10 +22,12 @@ public class Soundcloud : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetXmlBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs index 8816382cf8..a68df4f6fb 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs @@ -21,10 +21,12 @@ public class Ted : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetXmlBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs index ac5cab8604..3b57f0c44d 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs @@ -21,10 +21,12 @@ public class Twitter : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetJsonBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs index f4db5b17c9..2f6816f4c1 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs @@ -21,10 +21,12 @@ public class Vimeo : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetXmlBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/EmbedProviders/X.cs b/src/Umbraco.Core/Media/EmbedProviders/X.cs index c4d102e941..391796dc51 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/X.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/X.cs @@ -18,11 +18,10 @@ public class X : OEmbedProviderBase public override Dictionary RequestParams => new(); + [Obsolete("Use GetMarkupAsync instead. This will be removed in v16.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - return oembed?.GetHtml(); - } + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetJsonBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs index edaf37c0b0..4392e6a4dd 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs @@ -24,10 +24,12 @@ public class YouTube : OEmbedProviderBase [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); - } + => GetMarkupAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + public override async Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + => await GetJsonBasedMarkupAsync(url, maxWidth, maxHeight, cancellationToken); + + [Obsolete("Use GetMarkupAsync instead. Planned for removal in v16")] public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) { var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index fb27b4bb9b..64e0947abd 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -20,5 +20,6 @@ public interface IEmbedProvider [Obsolete("Use GetMarkupAsync instead. This will be removed in Umbraco 15.")] string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); + Task GetMarkupAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) => Task.FromResult(GetMarkup(url, maxWidth ?? 0, maxHeight ?? 0)); } diff --git a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs index 8c5940ac75..ac6c8ce765 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs @@ -30,13 +30,13 @@ public static class UserGroupExtensions } public static bool IsSystemUserGroup(this IUserGroup group) => - IsSystemUserGroup(group.Alias); + IsSystemUserGroup(group.Key); public static bool IsSystemUserGroup(this IReadOnlyUserGroup group) => - IsSystemUserGroup(group.Alias); + IsSystemUserGroup(group.Key); - private static bool IsSystemUserGroup(this string? groupAlias) => - groupAlias == Constants.Security.AdminGroupAlias - || groupAlias == Constants.Security.SensitiveDataGroupAlias - || groupAlias == Constants.Security.TranslatorGroupAlias; + private static bool IsSystemUserGroup(this Guid? groupKey) => + groupKey == Constants.Security.AdminGroupKey + || groupKey == Constants.Security.SensitiveDataGroupKey + || groupKey == Constants.Security.TranslatorGroupKey; } diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index 86a559b5db..32bca4aa45 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -53,7 +53,7 @@ public class ContentFinderByIdPath : ContentFinderByIdentifierPathBase, IContent return Task.FromResult(false); } - if (umbracoContext.InPreviewMode == false && (_webRoutingSettings.DisableFindContentByIdPath || _webRoutingSettings.DisableFindContentByIdentifierPath)) + if (umbracoContext.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdentifierPath) { return Task.FromResult(false); } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 7797a30015..f22948968a 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -292,15 +292,6 @@ namespace Umbraco.Cms.Core.Services.Implement return dataType; } - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - [Obsolete("Please use GetAsync. Will be removed in V15.")] - public IDataType? GetDataType(Guid id) - => GetAsync(id).GetAwaiter().GetResult(); - /// public Task GetAsync(Guid id) { diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs index c330a03772..ac7e8550b2 100644 --- a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -13,7 +13,7 @@ public static class DateTypeServiceExtensions return false; // built in ones can never be ignoring start nodes } - IDataType? dataType = dataTypeService.GetDataType(key); + IDataType? dataType = dataTypeService.GetAsync(key).GetAwaiter().GetResult(); if (dataType != null && dataType.ConfigurationObject is IIgnoreUserStartNodesConfig ignoreStartNodesConfig) { diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index e2f808cc7e..3a4576552c 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -76,16 +76,6 @@ public interface IDataTypeService : IService [Obsolete("Please use GetAsync. Will be removed in V15.")] IDataType? GetDataType(int id); - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - /// - /// - [Obsolete("Please use GetAsync. Will be removed in V15.")] - IDataType? GetDataType(Guid id); - /// /// Gets an by its Name /// diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 142738d49f..e12183a1a6 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -461,7 +461,7 @@ public class NotificationService : INotificationService var protocol = _globalSettings.UseHttps ? "https" : "http"; var subjectVars = new NotificationEmailSubjectParams( - string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(_globalSettings.UmbracoPath)), + string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(Constants.System.DefaultUmbracoPath)), actionName, content.Name); @@ -479,7 +479,7 @@ public class NotificationService : INotificationService string.Concat(content.Id, ".aspx"), protocol), performingUser.Name, - string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(_globalSettings.UmbracoPath)), + string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(Constants.System.DefaultUmbracoPath)), summary.ToString()); var fromMail = _contentSettings.Notifications.Email ?? _globalSettings.Smtp?.From; diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 0aa900c840..3463516cc2 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -478,6 +478,7 @@ internal partial class UserService : RepositoryService, IUserService /// This is just the default user group that the membership provider will use /// /// + [Obsolete("No (backend) code path is using this anymore, so it can not be considered the default. Planned for removal in V16.")] public string GetDefaultMemberType() => Constants.Security.WriterGroupAlias; /// diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index a7a71d3300..1f8647c410 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -321,7 +321,6 @@ public static partial class UmbracoBuilderExtensions .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() - .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler(); diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index d9eefbe76f..eac8aca1b5 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -196,7 +196,6 @@ internal class DatabaseDataCreator var userGroupKeyToPermissions = new Dictionary>() { [Constants.Security.AdminGroupKey] = new[] { ActionNew.ActionLetter, ActionUpdate.ActionLetter, ActionDelete.ActionLetter, ActionMove.ActionLetter, ActionCopy.ActionLetter, ActionSort.ActionLetter, ActionRollback.ActionLetter, ActionProtect.ActionLetter, ActionAssignDomain.ActionLetter, ActionPublish.ActionLetter, ActionRights.ActionLetter, ActionUnpublish.ActionLetter, ActionBrowse.ActionLetter, ActionCreateBlueprintFromContent.ActionLetter, ActionNotify.ActionLetter, ":", "5", "7", "T" }, - [Constants.Security.WriterGroupKey] = new[] { ActionNew.ActionLetter, ActionUpdate.ActionLetter, ActionToPublish.ActionLetter, ActionBrowse.ActionLetter, ActionNotify.ActionLetter, ":" }, [Constants.Security.EditorGroupKey] = new[] { ActionNew.ActionLetter, ActionUpdate.ActionLetter, ActionDelete.ActionLetter, ActionMove.ActionLetter, ActionCopy.ActionLetter, ActionSort.ActionLetter, ActionRollback.ActionLetter, ActionProtect.ActionLetter, ActionPublish.ActionLetter, ActionUnpublish.ActionLetter, ActionBrowse.ActionLetter, ActionCreateBlueprintFromContent.ActionLetter, ActionNotify.ActionLetter, ":", "5", "T" }, [Constants.Security.TranslatorGroupKey] = new[] { ActionUpdate.ActionLetter, ActionBrowse.ActionLetter }, }; diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/ModelsGenerator.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/ModelsGenerator.cs index 0a668f2d45..408d89d143 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/ModelsGenerator.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/ModelsGenerator.cs @@ -1,31 +1,58 @@ using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Extensions; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Infrastructure.ModelsBuilder.Building; public class ModelsGenerator : IModelsGenerator { - private readonly IHostingEnvironment _hostingEnvironment; private readonly OutOfDateModelsStatus _outOfDateModels; + private readonly IHostEnvironment _hostEnvironment; private readonly UmbracoServices _umbracoService; private ModelsBuilderSettings _config; + [Obsolete("Use a not obsoleted constructor instead. Scheduled for removal in v16")] public ModelsGenerator(UmbracoServices umbracoService, IOptionsMonitor config, OutOfDateModelsStatus outOfDateModels, IHostingEnvironment hostingEnvironment) { _umbracoService = umbracoService; _config = config.CurrentValue; _outOfDateModels = outOfDateModels; - _hostingEnvironment = hostingEnvironment; + config.OnChange(x => _config = x); + + _hostEnvironment = StaticServiceProvider.Instance.GetRequiredService(); + } + + [Obsolete("Use a not obsoleted constructor instead. Scheduled for removal in v16")] + public ModelsGenerator(UmbracoServices umbracoService, IOptionsMonitor config, + OutOfDateModelsStatus outOfDateModels, IHostingEnvironment hostingEnvironment, IHostEnvironment hostEnvironment) + { + _umbracoService = umbracoService; + _config = config.CurrentValue; + _outOfDateModels = outOfDateModels; + config.OnChange(x => _config = x); + + _hostEnvironment = hostEnvironment; + } + + public ModelsGenerator(UmbracoServices umbracoService, IOptionsMonitor config, + OutOfDateModelsStatus outOfDateModels, IHostEnvironment hostEnvironment) + { + _umbracoService = umbracoService; + _config = config.CurrentValue; + _outOfDateModels = outOfDateModels; + _hostEnvironment = hostEnvironment; config.OnChange(x => _config = x); } public void GenerateModels() { - var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostingEnvironment); + var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostEnvironment); if (!Directory.Exists(modelsDirectory)) { Directory.CreateDirectory(modelsDirectory); diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/ModelsGenerationError.cs b/src/Umbraco.Infrastructure/ModelsBuilder/ModelsGenerationError.cs index 02db02afda..e7ee821875 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/ModelsGenerationError.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/ModelsGenerationError.cs @@ -1,23 +1,48 @@ using System.Text; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Extensions; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Infrastructure.ModelsBuilder; public sealed class ModelsGenerationError { - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IHostEnvironment _hostEnvironment; private ModelsBuilderSettings _config; /// /// Initializes a new instance of the class. /// + [Obsolete("Use a not obsoleted constructor instead. Scheduled for removal in v16")] public ModelsGenerationError(IOptionsMonitor config, IHostingEnvironment hostingEnvironment) { _config = config.CurrentValue; - _hostingEnvironment = hostingEnvironment; + config.OnChange(x => _config = x); + _hostEnvironment = StaticServiceProvider.Instance.GetRequiredService(); + } + + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Use a not obsoleted constructor instead. Scheduled for removal in v16")] + public ModelsGenerationError( + IOptionsMonitor config, + IHostingEnvironment hostingEnvironment, + IHostEnvironment hostEnvironment) + { + _config = config.CurrentValue; + config.OnChange(x => _config = x); + _hostEnvironment = hostEnvironment; + } + + public ModelsGenerationError(IOptionsMonitor config, IHostEnvironment hostEnvironment) + { + _config = config.CurrentValue; + _hostEnvironment = hostEnvironment; config.OnChange(x => _config = x); } @@ -73,7 +98,7 @@ public sealed class ModelsGenerationError private string? GetErrFile() { - var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostingEnvironment); + var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostEnvironment); if (!Directory.Exists(modelsDirectory)) { return null; diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs b/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs index 4336f8ec71..e0f4ce5ba0 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs @@ -1,9 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Notifications; using Umbraco.Extensions; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Cms.Infrastructure.ModelsBuilder; @@ -13,8 +16,8 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder; public sealed class OutOfDateModelsStatus : INotificationHandler, INotificationHandler { - private readonly IHostingEnvironment _hostingEnvironment; private ModelsBuilderSettings _config; + private readonly IHostEnvironment _hostEnvironment; /// /// Initializes a new instance of the class. @@ -22,7 +25,24 @@ public sealed class OutOfDateModelsStatus : INotificationHandler config, IHostingEnvironment hostingEnvironment) { _config = config.CurrentValue; - _hostingEnvironment = hostingEnvironment; + _hostEnvironment = StaticServiceProvider.Instance.GetRequiredService(); + config.OnChange(x => _config = x); + } + + public OutOfDateModelsStatus( + IOptionsMonitor config, + IHostingEnvironment hostingEnvironment, + IHostEnvironment hostEnvironment) + { + _config = config.CurrentValue; + _hostEnvironment = hostEnvironment; + config.OnChange(x => _config = x); + } + + public OutOfDateModelsStatus(IOptionsMonitor config, IHostEnvironment hostEnvironment) + { + _config = config.CurrentValue; + _hostEnvironment = hostEnvironment; config.OnChange(x => _config = x); } @@ -70,7 +90,7 @@ public sealed class OutOfDateModelsStatus : INotificationHandler _logger; private readonly FileSystemWatcher? _watcher; private readonly Lazy _umbracoServices; // TODO: this is because of circular refs :( - private readonly IHostingEnvironment _hostingEnvironment; + private readonly IHostEnvironment _hostEnvironment; private readonly IApplicationShutdownRegistry _hostingLifetime; private readonly ModelsGenerationError _errors; private readonly IPublishedValueFallback _publishedValueFallback; @@ -48,6 +53,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto private ModelsBuilderSettings _config; private bool _disposedValue; + [Obsolete("Use a not obsoleted constructor instead. Scheduled for removal in v16")] public InMemoryModelFactory( Lazy umbracoServices, IProfilingLogger profilingLogger, @@ -63,12 +69,62 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto _profilingLogger = profilingLogger; _logger = logger; _config = config.CurrentValue; - _hostingEnvironment = hostingEnvironment; + _hostEnvironment = StaticServiceProvider.Instance.GetRequiredService(); _hostingLifetime = hostingLifetime; _publishedValueFallback = publishedValueFallback; _loadContextManager = loadContextManager; _runtimeCompilationCacheBuster = runtimeCompilationCacheBuster; - _errors = new ModelsGenerationError(config, _hostingEnvironment); + _errors = new ModelsGenerationError(config, _hostEnvironment); + _ver = 1; // zero is for when we had no version + _skipver = -1; // nothing to skip + + if (!hostingEnvironment.IsHosted) + { + return; + } + + config.OnChange(x => _config = x); + _pureLiveDirectory = new Lazy(PureLiveDirectoryAbsolute); + + if (!Directory.Exists(_pureLiveDirectory.Value)) + { + Directory.CreateDirectory(_pureLiveDirectory.Value); + } + + // BEWARE! if the watcher is not properly released then for some reason the + // BuildManager will start confusing types - using a 'registered object' here + // though we should probably plug into Umbraco's MainDom - which is internal + _hostingLifetime.RegisterObject(this); + _watcher = new FileSystemWatcher(_pureLiveDirectory.Value); + _watcher.Changed += WatcherOnChanged; + _watcher.EnableRaisingEvents = true; + + // get it here, this need to be fast + _debugLevel = _config.DebugLevel; + } + + public InMemoryModelFactory( + Lazy umbracoServices, + IProfilingLogger profilingLogger, + ILogger logger, + IOptionsMonitor config, + IHostingEnvironment hostingEnvironment, + IHostEnvironment hostEnvironment, + IApplicationShutdownRegistry hostingLifetime, + IPublishedValueFallback publishedValueFallback, + InMemoryAssemblyLoadContextManager loadContextManager, + RuntimeCompilationCacheBuster runtimeCompilationCacheBuster) + { + _umbracoServices = umbracoServices; + _profilingLogger = profilingLogger; + _logger = logger; + _config = config.CurrentValue; + _hostEnvironment = hostEnvironment; + _hostingLifetime = hostingLifetime; + _publishedValueFallback = publishedValueFallback; + _loadContextManager = loadContextManager; + _runtimeCompilationCacheBuster = runtimeCompilationCacheBuster; + _errors = new ModelsGenerationError(config, _hostEnvironment); _ver = 1; // zero is for when we had no version _skipver = -1; // nothing to skip @@ -359,7 +415,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto } } - public string PureLiveDirectoryAbsolute() => _hostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.TempData + "/InMemoryAuto"); + public string PureLiveDirectoryAbsolute() => _hostEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.TempData + "/InMemoryAuto"); // This is NOT thread safe but it is only called from within a lock private Assembly ReloadAssembly(string pathToAssembly) diff --git a/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs index 401e620d4c..8131a03160 100644 --- a/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs +++ b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs @@ -32,7 +32,7 @@ public class UmbracoMvcConfigureOptions : IConfigureOptions if (options.Conventions.Any(convention => convention is UmbracoBackofficeToken) is false) { // Replace the BackOfficeToken in routes. - var backofficePath = _globalSettings.UmbracoPath.TrimStart(Core.Constants.CharArrays.TildeForwardSlash); + var backofficePath = Core.Constants.System.DefaultUmbracoPath.TrimStart(Core.Constants.CharArrays.TildeForwardSlash); options.Conventions.Add(new UmbracoBackofficeToken(Core.Constants.Web.AttributeRouting.BackOfficeToken, backofficePath)); } } diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index f851acab3b..4fbf5ccde8 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models; @@ -140,7 +141,7 @@ public abstract class UmbracoViewPage : RazorPage markupToInject = string.Format( ContentSettings.PreviewBadge, - HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoPath), + HostingEnvironment.ToAbsolute(Core.Constants.System.DefaultUmbracoPath), Context.Request.GetEncodedUrl(), UmbracoContext.PublishedRequest?.PublishedContent?.Key); } diff --git a/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs b/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs index 06b6d4a818..92d1c67ba2 100644 --- a/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs +++ b/src/Umbraco.Web.Website/Controllers/RenderNoContentController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; @@ -49,7 +50,7 @@ public class RenderNoContentController : Controller return Redirect("~/"); } - var model = new NoNodesViewModel { UmbracoPath = _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoPath) }; + var model = new NoNodesViewModel { UmbracoPath = _hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath) }; return View(_globalSettings.NoNodesViewPath, model); } diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs index 3b3a95df2d..6546203229 100644 --- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; @@ -85,7 +86,7 @@ public static class HtmlHelperRenderExtensions var htmlBadge = string.Format( contentSettings.PreviewBadge, - hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath), + hostingEnvironment.ToAbsolute(Constants.System.DefaultUmbracoPath), WebUtility.UrlEncode(httpContextAccessor.GetRequiredHttpContext().Request.Path), umbracoContext.PublishedRequest?.PublishedContent?.Key); return new HtmlString(htmlBadge); diff --git a/tests/Umbraco.Tests.Common/TestHelperBase.cs b/tests/Umbraco.Tests.Common/TestHelperBase.cs index 072e20635a..905d86042f 100644 --- a/tests/Umbraco.Tests.Common/TestHelperBase.cs +++ b/tests/Umbraco.Tests.Common/TestHelperBase.cs @@ -197,12 +197,7 @@ public abstract class TestHelperBase public TypeLoader GetMockedTypeLoader() => new( Mock.Of(), - new VaryingRuntimeHash(), - Mock.Of(), - new DirectoryInfo(GetHostingEnvironment() - .MapPathContentRoot(Constants.SystemDirectories.TempData)), - Mock.Of>(), - Mock.Of()); + Mock.Of>()); /// /// Some test files are copied to the /bin (/bin/debug) on build, this is a utility to return their physical path based diff --git a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index 732fc0a385..e08a72a8a6 100644 --- a/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/tests/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration.Models; @@ -129,7 +130,7 @@ public static class UmbracoBuilderExtensions uiProject.Create(); } - var mainLangFolder = new DirectoryInfo(Path.Combine(uiProject.FullName, globalSettings.Value.UmbracoPath.TrimStartExact("~/"), "config", "lang")); + var mainLangFolder = new DirectoryInfo(Path.Combine(uiProject.FullName, Constants.System.DefaultUmbracoPath.TrimStartExact("~/"), "config", "lang")); return new LocalizedTextServiceFileSources( loggerFactory.CreateLogger(), diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs index 67612b5756..7767dc8f2e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs @@ -55,6 +55,8 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent private IUserService UserService => GetRequiredService(); + private IUserGroupService UserGroupService => GetRequiredService(); + private IRelationService RelationService => GetRequiredService(); private ILocalizedTextService TextService => GetRequiredService(); @@ -2055,11 +2057,11 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent ContentService.Save(content2); Assert.IsTrue(ContentService.Publish(content2, content2.AvailableCultures.ToArray(), userId: -1).Success); - var editorGroup = UserService.GetUserGroupByAlias(Constants.Security.EditorGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); editorGroup.StartContentId = content1.Id; UserService.Save(editorGroup); - var admin = UserService.GetUserById(Constants.Security.SuperUserId); + var admin = await UserService.GetAsync(Constants.Security.SuperUserKey); admin.StartContentIds = new[] { content1.Id }; UserService.Save(admin); @@ -2071,7 +2073,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent new List { new() { RuleType = "test", RuleValue = "test" } })); Assert.IsTrue(PublicAccessService.AddRule(content1, "test2", "test2").Success); - var user = UserService.GetUserById(Constants.Security.SuperUserId); + var user = await UserService.GetAsync(Constants.Security.SuperUserKey); var userGroup = UserService.GetUserGroupByAlias(user.Groups.First().Alias); Assert.IsNotNull(NotificationService.CreateNotification(user, content1, "X")); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserGroupServiceValidationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserGroupServiceValidationTests.cs index 9d3d8aeee5..d14b887b57 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserGroupServiceValidationTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserGroupServiceValidationTests.cs @@ -169,11 +169,9 @@ public class UserGroupServiceValidationTests : UmbracoIntegrationTest Assert.AreEqual(updateName, updatedGroup.Name); } - // these keys are not defined as "const" in Constants.Security but as "static readonly", so we have to hardcode - // them here ("static readonly" can't be used as testcase inputs as they are not constants) - [TestCase("E5E7F6C8-7F9C-4B5B-8D5D-9E1E5A4F7E4D", "admin")] - [TestCase("8C6AD70F-D307-4E4A-AF58-72C2E4E9439D", "sensitiveData")] - [TestCase("F2012E4C-D232-4BD1-8EAE-4384032D97D8", "translator")] + [TestCase(Constants.Security.AdminGroupKeyString, "admin")] + [TestCase(Constants.Security.SensitiveDataGroupKeyString, "sensitiveData")] + [TestCase(Constants.Security.TranslatorGroupString, "translator")] public async Task Cannot_Delete_System_UserGroups(string userGroupKeyAsString, string expectedGroupAlias) { // since we can't use the constants as input, let's make sure we don't get false positives by double checking the group alias @@ -188,10 +186,8 @@ public class UserGroupServiceValidationTests : UmbracoIntegrationTest Assert.AreEqual(UserGroupOperationStatus.CanNotDeleteIsSystemUserGroup, result.Result); } - // these keys are not defined as "const" in Constants.Security but as "static readonly", so we have to hardcode - // them here ("static readonly" can't be used as testcase inputs as they are not constants) - [TestCase("44DC260E-B4D4-4DD9-9081-EEC5598F1641", "editor")] - [TestCase("9FC2A16F-528C-46D6-A014-75BF4EC2480C", "writer")] + [TestCase( Constants.Security.EditorGroupKeyString, "editor")] + [TestCase(Constants.Security.WriterGroupKeyString, "writer")] public async Task Can_Delete_Non_System_UserGroups(string userGroupKeyAsString, string expectedGroupAlias) { // since we can't use the constants as input, let's make sure we don't get false positives by double checking the group alias @@ -203,6 +199,6 @@ public class UserGroupServiceValidationTests : UmbracoIntegrationTest var result = await UserGroupService.DeleteAsync(key); Assert.IsTrue(result.Success); - Assert.AreEqual(UserGroupOperationStatus.Success, result.Result); + Assert.AreEqual(result.Result,UserGroupOperationStatus.Success); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs index 82d28c5441..71b557d4d2 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Filter.cs @@ -16,7 +16,7 @@ public partial class UserServiceCrudTests public async Task Cannot_Request_Disabled_If_Hidden(UserState includeState) { var userService = CreateUserService(new SecuritySettings {HideDisabledUsersInBackOffice = true}); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var createModel = new UserCreateModel { @@ -44,8 +44,8 @@ public partial class UserServiceCrudTests public async Task Only_Super_User_Can_Filter_Super_user() { var userService = CreateUserService(); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); - var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); + var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); var nonSuperCreateModel = new UserCreateModel { @@ -78,7 +78,7 @@ public partial class UserServiceCrudTests public async Task Super_User_Can_Filter_Super_User() { var userService = CreateUserService(); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var nonSuperCreateModel = new UserCreateModel { @@ -107,8 +107,8 @@ public partial class UserServiceCrudTests public async Task Non_Admins_Cannot_Filter_Admins() { var userService = CreateUserService(); - var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var editorCreateModel = new UserCreateModel { @@ -146,8 +146,8 @@ public partial class UserServiceCrudTests public async Task Admins_Can_Filter_Admins() { var userService = CreateUserService(); - var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var editorCreateModel = new UserCreateModel { @@ -185,10 +185,10 @@ public partial class UserServiceCrudTests private async Task CreateTestUsers(IUserService userService) { - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); - var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); - var writerGroup = await UserGroupService.GetAsync(Constants.Security.WriterGroupAlias); - var translatorGroup = await UserGroupService.GetAsync(Constants.Security.TranslatorGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); + var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); + var writerGroup = await UserGroupService.GetAsync(Constants.Security.WriterGroupKey); + var translatorGroup = await UserGroupService.GetAsync(Constants.Security.TranslatorGroupKey); var createModels = new List { @@ -262,7 +262,7 @@ public partial class UserServiceCrudTests var userService = CreateUserService(); await CreateTestUsers(userService); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var filter = new UserFilter { ExcludeUserGroups = new HashSet { editorGroup!.Key } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Get.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Get.cs index 69263159e6..6d33c92a8a 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Get.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.Get.cs @@ -13,8 +13,8 @@ public partial class UserServiceCrudTests public async Task Only_Super_User_Can_Get_Super_user() { var userService = CreateUserService(); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); - var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); + var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); var nonSuperCreateModel = new UserCreateModel { @@ -43,7 +43,7 @@ public partial class UserServiceCrudTests public async Task Super_User_Can_See_Super_User() { var userService = CreateUserService(); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var nonSuperCreateModel = new UserCreateModel { @@ -71,8 +71,8 @@ public partial class UserServiceCrudTests public async Task Non_Admins_Cannot_Get_admins() { var userService = CreateUserService(); - var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var editorCreateModel = new UserCreateModel { @@ -107,8 +107,8 @@ public partial class UserServiceCrudTests public async Task Admins_Can_See_Admins() { var userService = CreateUserService(); - var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var adminGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var editorCreateModel = new UserCreateModel { @@ -144,7 +144,7 @@ public partial class UserServiceCrudTests public async Task Cannot_See_Disabled_When_HideDisabled_Is_True() { var userService = CreateUserService(securitySettings: new SecuritySettings { HideDisabledUsersInBackOffice = true }); - var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var firstEditorCreateModel = new UserCreateModel { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.PartialUpdates.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.PartialUpdates.cs index 013e53057e..5eaf3b07ae 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.PartialUpdates.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/UserServiceCrudTests.PartialUpdates.cs @@ -11,7 +11,7 @@ public partial class UserServiceCrudTests [Test] public async Task Can_Enable_User() { - var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var userCreateModel = new UserCreateModel { @@ -39,7 +39,7 @@ public partial class UserServiceCrudTests [Test] public async Task Can_Disable_User() { - var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var userCreateModel = new UserCreateModel { @@ -63,7 +63,7 @@ public partial class UserServiceCrudTests [Test] public async Task User_Cannot_Disable_Self() { - var adminUserGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupAlias); + var adminUserGroup = await UserGroupService.GetAsync(Constants.Security.AdminGroupKey); var userCreateModel = new UserCreateModel { @@ -85,7 +85,7 @@ public partial class UserServiceCrudTests [Test] public async Task Cannot_Disable_Invited_User() { - var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var userInviteModel = new UserInviteModel { @@ -108,7 +108,7 @@ public partial class UserServiceCrudTests [Test] public async Task Enable_Missing_User_Fails_With_Not_Found() { - var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var userCreateModel = new UserCreateModel { @@ -132,7 +132,7 @@ public partial class UserServiceCrudTests [Test] public async Task Disable_Missing_User_Fails_With_Not_Found() { - var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupAlias); + var editorUserGroup = await UserGroupService.GetAsync(Constants.Security.EditorGroupKey); var userCreateModel = new UserCreateModel { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs index bfa3adb92b..9a17df69ad 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Persistence.EFCore/DbContext/CustomDbContextTests.cs @@ -68,34 +68,3 @@ public class CustomDbContextCustomSqliteProviderTests : UmbracoIntegrationTest } } -[Obsolete] -[TestFixture] -[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] -public class CustomDbContextLegacyExtensionProviderTests : UmbracoIntegrationTest -{ - [Test] - public void Can_Register_Custom_DbContext_And_Resolve() - { - var dbContext = Services.GetRequiredService(); - - Assert.IsNotNull(dbContext); - Assert.IsNotEmpty(dbContext.Database.GetConnectionString()); - } - - protected override void CustomTestSetup(IUmbracoBuilder builder) - { - builder.Services.AddUmbracoEFCoreContext("Data Source=:memory:;Version=3;New=True;", "Microsoft.Data.Sqlite", (options, connectionString, providerName) => - { - options.UseSqlite(connectionString); - }); - } - - internal class CustomDbContext : Microsoft.EntityFrameworkCore.DbContext - { - public CustomDbContext(DbContextOptions options) - : base(options) - { - } - } -} - diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 94bff4a0c9..bc8b8f8a5b 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -108,11 +108,7 @@ public class ComponentTests private static TypeLoader MockTypeLoader() => new( Mock.Of(), - new VaryingRuntimeHash(), - Mock.Of(), - new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), - Mock.Of>(), - Mock.Of()); + Mock.Of>()); [Test] public async Task Boot1A() @@ -140,16 +136,6 @@ public class ComponentTests return new Composer1(); } - if (type == typeof(Composer5)) - { - return new Composer5(); - } - - if (type == typeof(Component5)) - { - return new Component5(new SomeResource()); - } - if (type == typeof(IProfilingLogger)) { return new ProfilingLogger(Mock.Of>(), Mock.Of()); @@ -297,26 +283,6 @@ public class ComponentTests return new Composer1(); } - if (type == typeof(Composer5)) - { - return new Composer5(); - } - - if (type == typeof(Composer5a)) - { - return new Composer5a(); - } - - if (type == typeof(Component5)) - { - return new Component5(new SomeResource()); - } - - if (type == typeof(Component5a)) - { - return new Component5a(); - } - if (type == typeof(IProfilingLogger)) { return new ProfilingLogger(Mock.Of>(), Mock.Of()); @@ -337,24 +303,15 @@ public class ComponentTests }); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - Type[] types = { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; + Type[] types = { typeof(Composer1) }; var composers = new ComposerGraph(composition, types, Enumerable.Empty(), Mock.Of>()); Assert.IsEmpty(Composed); composers.Compose(); - AssertTypeArray(TypeArray(), Composed); var builder = composition.WithCollectionBuilder(); builder.RegisterWith(register); var components = builder.CreateCollection(factory); - - Assert.IsEmpty(Initialized); - await components.InitializeAsync(false, default); - AssertTypeArray(TypeArray(), Initialized); - - Assert.IsEmpty(Terminated); - await components.TerminateAsync(false, default); - AssertTypeArray(TypeArray(), Terminated); } [Test] @@ -491,11 +448,7 @@ public class ComponentTests var typeFinder = TestHelper.GetTypeFinder(); var typeLoader = new TypeLoader( typeFinder, - new VaryingRuntimeHash(), - AppCaches.Disabled.RuntimeCache, - new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), - Mock.Of>(), - Mock.Of()); + Mock.Of>()); var register = MockRegister(); var builder = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); @@ -537,43 +490,6 @@ public class ComponentTests { } - public class Composer5 : TestComposerBase - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - builder.Components().Append(); - } - } - - [ComposeAfter(typeof(Composer5))] - public class Composer5a : TestComposerBase - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - builder.Components().Append(); - } - } - - public class TestComponentBase : IComponent - { - public virtual void Initialize() => Initialized.Add(GetType()); - - public virtual void Terminate() => Terminated.Add(GetType()); - } - - public class Component5 : TestComponentBase - { - private readonly ISomeResource _resource; - - public Component5(ISomeResource resource) => _resource = resource; - } - - public class Component5a : TestComponentBase - { - } - [Disable] public class Composer6 : TestComposerBase { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs index f3c615e98e..aa3ac2a3b8 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs @@ -31,12 +31,7 @@ public abstract class ComposingTestBase var typeFinder = TestHelper.GetTypeFinder(); TypeLoader = new TypeLoader( typeFinder, - new VaryingRuntimeHash(), - NoAppCache.Instance, - new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), - Mock.Of(), - false, AssembliesToScan); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs index d02da0dda9..068bf44db6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs @@ -43,13 +43,7 @@ public class TypeLoaderTests }; _typeLoader = new TypeLoader( typeFinder, - new VaryingRuntimeHash(), - NoAppCache.Instance, - new DirectoryInfo(TestHelper.GetHostingEnvironment() - .MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), - Mock.Of(), - false, assemblies); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs index ca38c928f8..fbf1ea75ca 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs @@ -28,7 +28,7 @@ public class GlobalSettingsTests { hostingSettings.CurrentValue.ApplicationVirtualPath = rootPath; - var globalSettings = new GlobalSettings { UmbracoPath = path }; + var globalSettings = new GlobalSettings(); Assert.AreEqual(outcome, globalSettings.GetUmbracoMvcAreaNoCache(hostingEnvironment)); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs index b7f9e4c4cd..03d086b4a6 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs @@ -313,24 +313,22 @@ public class UdiTests [UdiDefinition("foo", UdiType.GuidUdi)] public class FooConnector : IServiceConnector { - public IArtifact GetArtifact(Udi udi, IContextCache contextCache) => throw new NotImplementedException(); + public Task GetArtifactAsync(Udi udi, IContextCache contextCache, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public IArtifact GetArtifact(object entity, IContextCache contextCache) => throw new NotImplementedException(); + public Task GetArtifactAsync(object entity, IContextCache contextCache, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context) => + public Task ProcessInitAsync(IArtifact artifact, IDeployContext context, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Task ProcessAsync(ArtifactDeployState state, IDeployContext context, int pass, + CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public void Process(ArtifactDeployState dart, IDeployContext context, int pass) => - throw new NotImplementedException(); + public IAsyncEnumerable ExpandRangeAsync(UdiRange range, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public void Explode(UdiRange range, List udis) => throw new NotImplementedException(); + public Task GetRangeAsync(Udi udi, string selector, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public NamedUdiRange GetRange(Udi udi, string selector) => throw new NotImplementedException(); + public Task GetRangeAsync(string entityType, string sid, string selector, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public NamedUdiRange GetRange(string entityType, string sid, string selector) => - throw new NotImplementedException(); - - public bool Compare(IArtifact art1, IArtifact art2, ICollection differences = null) => - throw new NotImplementedException(); + public bool Compare(IArtifact? art1, IArtifact? art2, ICollection? differences = null) => throw new NotImplementedException(); } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserGroupServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserGroupServiceTests.cs index 268b3f0659..314c38d9bb 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserGroupServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserGroupServiceTests.cs @@ -90,21 +90,27 @@ public class UserGroupServiceTests }); } - [TestCase(false,UserGroupOperationStatus.Success)] - [TestCase(true,UserGroupOperationStatus.CanNotUpdateAliasIsSystemUserGroup)] - public async Task Can_Not_Update_SystemGroup_Alias(bool isSystemGroup, UserGroupOperationStatus status) + // Obsoletion will be resolved when they are converted to internal consts. + [TestCase(null, null, UserGroupOperationStatus.Success)] + [TestCase(Constants.Security.AdminGroupKeyString, Constants.Security.AdminGroupAlias, UserGroupOperationStatus.CanNotUpdateAliasIsSystemUserGroup)] + [TestCase(Constants.Security.SensitiveDataGroupKeyString, Constants.Security.SensitiveDataGroupAlias, UserGroupOperationStatus.CanNotUpdateAliasIsSystemUserGroup)] + [TestCase(Constants.Security.TranslatorGroupString, Constants.Security.TranslatorGroupAlias, UserGroupOperationStatus.CanNotUpdateAliasIsSystemUserGroup)] + public async Task Can_Not_Update_SystemGroup_Alias(string? systemGroupKey, string? systemGroupAlias, UserGroupOperationStatus status) { + // prep + var userGroupAlias = systemGroupAlias ?? "someNonSystemAlias"; + Guid userGroupKey = systemGroupKey is not null ? new Guid(systemGroupKey) : Guid.NewGuid(); + // Arrange var actingUserKey = Guid.NewGuid(); var mockUser = SetupUserWithGroupAccess(actingUserKey, [Constants.Security.AdminGroupAlias]); var userService = SetupUserServiceWithGetUserByKey(actingUserKey, mockUser); var userGroupRepository = new Mock(); - var userGroupKey = Guid.NewGuid(); var persistedUserGroup = new UserGroup( Mock.Of(), 0, - isSystemGroup ? Constants.Security.AdminGroupAlias : "someNonSystemAlias", + userGroupAlias, "Administrators", null) { From d101829a9097924f6a93a8eec2e9a7682cd910a9 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:17:36 +0100 Subject: [PATCH 04/30] V15: Fix reload memory cache endpoint (#17446) * Check for id later. * Return 501 status codes from obsoleted endpoints * Use contants * Ensure ordering of cache refresher is not changes --------- Co-authored-by: Bjarke Berg --- .../CollectPublishedCacheController.cs | 5 ++--- .../StatusPublishedCacheController.cs | 7 ++++--- .../Implement/ContentCacheRefresher.cs | 20 +++++++++++-------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs index 4cbac68446..ef759ca5a4 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/CollectPublishedCacheController.cs @@ -1,7 +1,6 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.PublishedCache; namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache; @@ -11,9 +10,9 @@ public class CollectPublishedCacheController : PublishedCacheControllerBase { [HttpPost("collect")] [MapToApiVersion("1.0")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status501NotImplemented)] public async Task Collect(CancellationToken cancellationToken) { - return Ok(); + return StatusCode(StatusCodes.Status501NotImplemented); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs index aad76e7dbf..3a9d72c12c 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PublishedCache/StatusPublishedCacheController.cs @@ -1,7 +1,6 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.PublishedCache; namespace Umbraco.Cms.Api.Management.Controllers.PublishedCache; @@ -11,7 +10,9 @@ public class StatusPublishedCacheController : PublishedCacheControllerBase { [HttpGet("status")] [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status501NotImplemented)] public async Task> Status(CancellationToken cancellationToken) - => await Task.FromResult(Ok("Obsoleted")); + { + return StatusCode(StatusCodes.Status501NotImplemented); + } } diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs index 759e724c88..1f81c35912 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs @@ -86,15 +86,16 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase(); IAppPolicyCache isolatedCache = AppCaches.IsolatedCaches.GetOrCreate(); - foreach (JsonPayload payload in payloads.Where(x => x.Id != default)) + foreach (JsonPayload payload in payloads) { - // By INT Id - isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Id)); - - // By GUID Key - isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Key)); - + if (payload.Id != default) + { + // By INT Id + isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + // By GUID Key + isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Key)); + } // remove those that are in the branch if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) @@ -115,7 +116,10 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase Date: Thu, 7 Nov 2024 14:13:25 +0100 Subject: [PATCH 05/30] Fix wrong urls returned from Url() extension and do not show unavailable paths on info tab (#17445) * Fixed issue with urls shown to info tap, even if the url was not available because the document assigned a domain was not published in the culture * Fix issue where left to right and right to left ordering of urlsegments was not handled when using the legacy urls --- src/Umbraco.Core/Services/DocumentUrlService.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/DocumentUrlService.cs b/src/Umbraco.Core/Services/DocumentUrlService.cs index 5148720a56..9f01fa63e9 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -488,6 +488,12 @@ public class DocumentUrlService : IDocumentUrlService } } + if (_globalSettings.ForceCombineUrlPathLeftToRight + || CultureInfo.GetCultureInfo(cultureOrDefault).TextInfo.IsRightToLeft is false) + { + urlSegments.Reverse(); + } + if (foundDomain is not null) { //we found a domain, and not to construct the route in the funny legacy way @@ -548,8 +554,15 @@ public class DocumentUrlService : IDocumentUrlService Dictionary domainDictionary = await domainDictionaryTask; if (domainDictionary.TryGetValue(culture, out Domain? domain)) { - foundDomain = domain; - break; + Attempt domainKeyAttempt = _idKeyMap.GetKeyForId(domain.ContentId, UmbracoObjectTypes.Document); + if (domainKeyAttempt.Success) + { + if (_publishStatusQueryService.IsDocumentPublished(domainKeyAttempt.Result, culture)) + { + foundDomain = domain; + break; + } + } } } From 60393b76763d46c60fb78bd2f17222de37224746 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 8 Nov 2024 06:55:43 +0000 Subject: [PATCH 06/30] [V15] Updated dotnet template for Umbraco Packages with Bellisima (#17108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [WIP] Create Umbraco/Bellissima Package * Removes existing 'UmbracoPackage' This is because the RCL based one will be the only one going forward * Rename existing UmbracoPackageRCL to UmbracoPackage * Drop the mentions of RCL in the identifiers * CodeQL GitHub Action is complaining due to V15 wanting v9 .NET * Rename UmbracoPackage template to UmbracoExtension As this will only scaffold an extension and not other bits for a package such as Nuget, Github Actions & other things needed to be done to ship out a package * Remove package lock as npm install by the OS should generate this and can differ between Windows, Linux/OSX * Move JS clientside stuff into a folder called Client Will allow us to ignore the folder if or when doing a dotnet pack with a rule in CSProj * Add in .VSCode recommened extensions file to get the useful Lit Extension for completions in VSCode * For now remove the example dashboard & prop editor * Add a simple entrypoint * Fix path for primary output after rename * Use link suggested from Lotte * Use backofficeEntryPoint as entryPoint is deprecated * Update the umbraco-package.json to opt into telemetry as per PR suggestion * Improve commented code to include a link to docs * Improve readme from suggestions * Updates package.json to use latest Vite & TS Copies the tsconfig from the default scaffolding of vite lit-ts CLI * Adds the base property suggestion from Jacob & puts in a comment as to what its used for * Work in progress from hackathon day/afternoon * Hey-API generating a HTTP Client had changed and was a PITA to figure out what had changed Things to do for next time: Include these files if they include --include-samples flag * constants.ts * Controllers/ * Composers/ * client/src/api * client/src/dashboards/ * client/src/entrypoints Change file contents * client/src/bundle.manifests.ts * client/src/package.json (extra dependencies) * Adds in new property/flag/switch for dotnet new template * Warren cant spell Whether 🙈 * Update template.json to exclude the sample files if flag is not set/false * Make SLN happy/build * Conditional content in files for IncludeExample flag/switch * Need to include the content otherwise it doesnt get packed by nuget * Fix the path for the openapi-ts-config.ts file to be included/excluded * Use the project name from the dotnet new to help name manifests * Update namespaces so they get updated when dotnet new templatge is run with the --name * Updated example * Fix up VS Code recommended extension for Lit VSCode Should be .json not a .ts * Fix up build - as we dont use the imported UmbCurrentUserContext * Remove the relative path to the JSON schema as unable to know path to the web project * Typo fix as spooted by Rich * Update templates/UmbracoExtension/.template.config/template.json Co-authored-by: Lotte Pitcher * Adds a --site-domain flag/switch to use for setting the domain prefix Sets a default value of https://localhost:5000 We have no way of knowing what URL/domain the site is running at for the Umbraco website project * Rename stuff so its not 'example' & only have ping if include-examples is not set * As agreed with Lotte makes sense we always generate OpenAPI & TS client * Update umbraco-extension description * Generic node script to generate the TS client Checks if it can connect to it first and prompts user to ensure Umbraco site running or perhaps they need to change the URL in package.json for the node script * Generated API has conditional stuff in now to have just Ping or the more examples based on switch/flag * Adds symbols safeNamespace and safeName safeNamespace uses the one built in and then safeName, depends on the cleaned namespace to then use a custom transform (forms) to then use a regex replace on . and _ to ensure we have a nicer name still for namespaces, class names, URL/routes for the swagger etc... * change to use Umbraco.Extension as sourcename - check \.template.config\readme.md for 'placeholder' guidance * use '-sd' as shortname for site-domain as otherwise shows up as '-p:d' * fix typescript build error when not including examples * use provided name for API description as always being added * Missing renames of Contrioller stuff with Lotte @ hackathon * We missed the ctor * Titlecase the API URLs for Swagger/API Controller * dashboard tweaks * Missing [Server] on Whats the Time Modal/UUI-box --------- Co-authored-by: leekelleher Co-authored-by: Lotte Pitcher Co-authored-by: Lotte Pitcher (cherry picked from commit 2f4b198ad36082700c24f5f2fe81663a603675a0) --- templates/Umbraco.Templates.csproj | 12 +- .../.template.config/dotnetcli.host.json | 8 + .../.template.config/ide.host.json | 35 ++ .../.template.config/readme.md | 25 ++ .../.template.config/template.json | 170 ++++++++ .../Client/.vscode/extensions.json | 5 + .../UmbracoExtension/Client/package.json | 21 + .../Client/public/umbraco-package.json | 14 + .../Client/scripts/generate-openapi.js | 48 +++ .../UmbracoExtension/Client/src/api/index.ts | 6 + .../Client/src/api/schemas.gen.ts | 391 ++++++++++++++++++ .../Client/src/api/services.gen.ts | 41 ++ .../Client/src/api/types.gen.ts | 107 +++++ .../Client/src/bundle.manifests.ts | 13 + .../src/dashboards/dashboard.element.ts | 176 ++++++++ .../Client/src/dashboards/manifest.ts | 18 + .../Client/src/entrypoints/entrypoint.ts | 38 ++ .../Client/src/entrypoints/manifest.ts | 8 + .../UmbracoExtension/Client/tsconfig.json | 26 ++ .../UmbracoExtension/Client/vite.config.ts | 17 + .../Composers/UmbracoExtensionApiComposer.cs | 73 ++++ templates/UmbracoExtension/Constants.cs | 7 + .../UmbracoExtensionApiController.cs | 49 +++ .../UmbracoExtensionApiControllerBase.cs | 16 + templates/UmbracoExtension/README.txt | 38 ++ .../Umbraco.Extension.csproj} | 23 +- .../.template.config/dotnetcli.host.json | 18 - .../.template.config/ide.host.json | 15 - .../.template.config/template.json | 108 ----- .../UmbracoPackage/umbraco-package.json | 6 - .../UmbracoPackage/UmbracoPackage.csproj | 27 -- .../buildTransitive/UmbracoPackage.targets | 21 - .../.template.config/ide.host.json | 20 - .../.template.config/template.json | 82 ---- .../wwwroot/umbraco-package.json | 6 - 35 files changed, 1375 insertions(+), 313 deletions(-) rename templates/{UmbracoPackageRcl => UmbracoExtension}/.template.config/dotnetcli.host.json (72%) create mode 100644 templates/UmbracoExtension/.template.config/ide.host.json create mode 100644 templates/UmbracoExtension/.template.config/readme.md create mode 100644 templates/UmbracoExtension/.template.config/template.json create mode 100644 templates/UmbracoExtension/Client/.vscode/extensions.json create mode 100644 templates/UmbracoExtension/Client/package.json create mode 100644 templates/UmbracoExtension/Client/public/umbraco-package.json create mode 100644 templates/UmbracoExtension/Client/scripts/generate-openapi.js create mode 100644 templates/UmbracoExtension/Client/src/api/index.ts create mode 100644 templates/UmbracoExtension/Client/src/api/schemas.gen.ts create mode 100644 templates/UmbracoExtension/Client/src/api/services.gen.ts create mode 100644 templates/UmbracoExtension/Client/src/api/types.gen.ts create mode 100644 templates/UmbracoExtension/Client/src/bundle.manifests.ts create mode 100644 templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts create mode 100644 templates/UmbracoExtension/Client/src/dashboards/manifest.ts create mode 100644 templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts create mode 100644 templates/UmbracoExtension/Client/src/entrypoints/manifest.ts create mode 100644 templates/UmbracoExtension/Client/tsconfig.json create mode 100644 templates/UmbracoExtension/Client/vite.config.ts create mode 100644 templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs create mode 100644 templates/UmbracoExtension/Constants.cs create mode 100644 templates/UmbracoExtension/Controllers/UmbracoExtensionApiController.cs create mode 100644 templates/UmbracoExtension/Controllers/UmbracoExtensionApiControllerBase.cs create mode 100644 templates/UmbracoExtension/README.txt rename templates/{UmbracoPackageRcl/UmbracoPackage.csproj => UmbracoExtension/Umbraco.Extension.csproj} (50%) delete mode 100644 templates/UmbracoPackage/.template.config/dotnetcli.host.json delete mode 100644 templates/UmbracoPackage/.template.config/ide.host.json delete mode 100644 templates/UmbracoPackage/.template.config/template.json delete mode 100644 templates/UmbracoPackage/App_Plugins/UmbracoPackage/umbraco-package.json delete mode 100644 templates/UmbracoPackage/UmbracoPackage.csproj delete mode 100644 templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets delete mode 100644 templates/UmbracoPackageRcl/.template.config/ide.host.json delete mode 100644 templates/UmbracoPackageRcl/.template.config/template.json delete mode 100644 templates/UmbracoPackageRcl/wwwroot/umbraco-package.json diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index b75df9f9a5..44d073a109 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -16,10 +16,9 @@ UmbracoProject\Program.cs UmbracoProject - - - + + UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension) UmbracoProject\Views\Partials\blocklist @@ -40,6 +39,13 @@ + + + + + + + <_TemplateJsonFiles Include="**\.template.config\template.json" Exclude="bin\**;obj\**" /> diff --git a/templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json b/templates/UmbracoExtension/.template.config/dotnetcli.host.json similarity index 72% rename from templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json rename to templates/UmbracoExtension/.template.config/dotnetcli.host.json index 9a960b348e..a3eea8aab7 100644 --- a/templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json +++ b/templates/UmbracoExtension/.template.config/dotnetcli.host.json @@ -17,6 +17,14 @@ "SupportPagesAndViews": { "longName": "support-pages-and-views", "shortName": "s" + }, + "IncludeExample": { + "longName": "include-example", + "shortName": "ex" + }, + "SiteDomain": { + "longName": "site-domain", + "shortName": "sd" } } } diff --git a/templates/UmbracoExtension/.template.config/ide.host.json b/templates/UmbracoExtension/.template.config/ide.host.json new file mode 100644 index 0000000000..324a4045ae --- /dev/null +++ b/templates/UmbracoExtension/.template.config/ide.host.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json.schemastore.org/ide.host.json", + "order": 0, + "icon": "../../icon.png", + "description": { + "id": "umbraco-extension", + "text": "Umbraco Extension - A Razor Class Library project for building Umbraco extensions." + }, + "symbolInfo": [ + { + "id": "UmbracoVersion", + "isVisible": true + }, + { + "id": "SupportPagesAndViews", + "isVisible": true, + "persistenceScope": "templateGroup" + }, + { + "id": "IncludeExample", + "isVisible": true, + "description": { + "text": "Whether to include an example dashboard and supporting code" + } + }, + { + "id": "SiteDomain", + "isVisible": true, + "defaultValue": "https://localhost:5000", + "description": { + "text": "If using the --include-example then you can supply the domain prefix such as 'https://localhost:5000' to communicate with the Umbraco website for generating the TypeScript OpenAPI client" + } + } + ] +} diff --git a/templates/UmbracoExtension/.template.config/readme.md b/templates/UmbracoExtension/.template.config/readme.md new file mode 100644 index 0000000000..3dea89fff0 --- /dev/null +++ b/templates/UmbracoExtension/.template.config/readme.md @@ -0,0 +1,25 @@ +# Customising the umbraco-extension template + +## Source Name + +The source name is set to `Umbraco.Extension` + +The templating engine will rename any folder or file whose name contains `Umbraco.Extension` replacing it with the provided name. + +The templating engine will replace the text in any file as follows: + +- `Umbraco.Extension` with the safe namespace for the provided name +- `Umbraco_Extension` with the safe default class name for the provided name +- `umbraco.extension` with the safe namespace for the provided name, in lower case +- `umbraco_extension` with the safe default class name for the provided name, in lower case + +## Custom Replacements + +The following custom placeholders have been configured in `template.json`: + +- `UmbracoExtension` will be replaced with the safe namespace but without . or _ +- `umbracoextension` will be replaced with the safe namespace but without . or _ , in lower case +- `umbraco-extension` will be replaced with the kebab case transform of the provided name +- `Umbraco Extension` will be replaced with a 'friendly' version of the provided name, e.g. MyProject > My Project. NB it will render a trailing space so you don't need to add one. + +The first three custom placeholders have been configured to replace the text in both files and filenames. \ No newline at end of file diff --git a/templates/UmbracoExtension/.template.config/template.json b/templates/UmbracoExtension/.template.config/template.json new file mode 100644 index 0000000000..5ada4ae33f --- /dev/null +++ b/templates/UmbracoExtension/.template.config/template.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://json.schemastore.org/template.json", + "author": "Umbraco HQ", + "classifications": [ + "Web", + "CMS", + "Umbraco", + "Extension", + "Plugin", + "Razor Class Library" + ], + "name": "Umbraco Extension", + "description": "A Razor Class Library project for building Umbraco extensions.", + "groupIdentity": "Umbraco.Templates.UmbracoExtension", + "identity": "Umbraco.Templates.UmbracoExtension", + "shortName": "umbraco-extension", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "Umbraco.Extension", + "defaultName": "Umbraco.Extension", + "preferNameDirectory": true, + "symbols": { + "Framework": { + "displayName": "Framework", + "description": "The target framework for the project.", + "type": "parameter", + "datatype": "choice", + "choices": [ + { + "displayName": ".NET 9.0", + "description": "Target net9.0", + "choice": "net9.0" + } + ], + "defaultValue": "net9.0", + "replaces": "net9.0" + }, + "UmbracoVersion": { + "displayName": "Umbraco version", + "description": "The version of Umbraco.Cms to add as PackageReference. By default it installs the latest non pre-release version", + "type": "parameter", + "datatype": "string", + "defaultValue": "*", + "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" + }, + "SkipRestore": { + "displayName": "Skip restore", + "description": "If specified, skips the automatic restore of the project on create.", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" + }, + "SupportPagesAndViews": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Support pages and views", + "description": "Whether to support adding traditional Razor pages and Views to this library." + }, + "KebabCasedName": { + "type": "derived", + "valueSource": "name", + "replaces": "umbraco-extension", + "fileRename": "umbraco-extension", + "valueTransform": "kebabCase" + }, + "SafeName": { + "type": "derived", + "valueSource": "name", + "valueTransform": "safe_namespace" + }, + "SafeCleanName": { + "type": "derived", + "valueSource": "SafeName", + "replaces": "UmbracoExtension", + "fileRename": "UmbracoExtension", + "valueTransform": "removePunctuation" + }, + "SafeCleanNameLower": { + "type": "derived", + "valueSource": "SafeCleanName", + "replaces": "umbracoextension", + "fileRename": "umbracoextension", + "valueTransform": "lowerCase" + }, + "SafeCleanNameFriendly": { + "type": "derived", + "valueSource": "SafeCleanName", + "replaces": "Umbraco Extension", + "valueTransform": "pascalCaseToSpaces" + }, + "IncludeExample": { + "displayName": "Include Example Code", + "description": "Whether to include an example dashboard and other code to get started with.", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" + }, + "SiteDomain": { + "displayName": "Site Domain", + "description": "If using the --include-example then you can supply the domain prefix such as 'https://localhost:5000' to communicate with the Umbraco website for generating the TypeScript OpenAPI client", + "type": "parameter", + "datatype": "string", + "defaultValue": "https://localhost:5000", + "replaces": "https://localhost:44339" + } + }, + "forms": { + "removePunctuation": { + "identifier": "replace", + "pattern": "[\\._]", + "replacement": "" + }, + "pascalCaseToSpaces": { + "identifier": "replace", + "pattern": "([A-Z][a-z]+)", + "replacement": "$1 " + } + }, + "primaryOutputs": [ + { + "path": "Umbraco.Extension.csproj" + } + ], + "postActions": [ + { + "id": "restore", + "condition": "(!SkipRestore)", + "description": "Restore NuGet packages required by this project.", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + }, + { + "actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2", + "args": { + "executable": "powershell", + "args": "cd Client;npm install;npm run build;", + "redirectStandardError": false, + "redirectStandardOutput": false + }, + "manualInstructions": [ + { + "text": "From the 'Client' folder run 'npm install' and then 'npm run build'" + } + ], + "continueOnError": true, + "description ": "Installs node modules" + } + ], + "sources": [ + { + "modifiers": [ + { + "condition": "(!IncludeExample)", + "exclude": [ + "[Cc]lient/src/dashboards/**", + "[Cc]lient/src/api/schemas.gen.ts" + ] + } + ] + } + ] +} diff --git a/templates/UmbracoExtension/Client/.vscode/extensions.json b/templates/UmbracoExtension/Client/.vscode/extensions.json new file mode 100644 index 0000000000..c152378477 --- /dev/null +++ b/templates/UmbracoExtension/Client/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "runem.lit-plugin" + ] +} diff --git a/templates/UmbracoExtension/Client/package.json b/templates/UmbracoExtension/Client/package.json new file mode 100644 index 0000000000..c4b1ae9b6b --- /dev/null +++ b/templates/UmbracoExtension/Client/package.json @@ -0,0 +1,21 @@ +{ + "name": "umbraco-extension", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "watch": "tsc && vite build --watch", + "build": "tsc && vite build", + "generate-client": "node scripts/generate-openapi.js https://localhost:44339/umbraco/swagger/umbracoextension/swagger.json" + }, + "devDependencies": { + "@hey-api/client-fetch": "^0.4.2", + "@hey-api/openapi-ts": "^0.53.11", + "@umbraco-cms/backoffice": "^UMBRACO_VERSION_FROM_TEMPLATE", + "chalk": "^5.3.0", + "cross-env": "^7.0.3", + "node-fetch": "^3.3.2", + "typescript": "^5.6.3", + "vite": "^5.4.9" + } +} diff --git a/templates/UmbracoExtension/Client/public/umbraco-package.json b/templates/UmbracoExtension/Client/public/umbraco-package.json new file mode 100644 index 0000000000..2ca2f83317 --- /dev/null +++ b/templates/UmbracoExtension/Client/public/umbraco-package.json @@ -0,0 +1,14 @@ +{ + "id": "Umbraco.Extension", + "name": "Umbraco.Extension", + "version": "0.0.0", + "allowPackageTelemetry": true, + "extensions": [ + { + "name": "Umbraco ExtensionBundle", + "alias": "Umbraco.Extension.Bundle", + "type": "bundle", + "js": "/App_Plugins/UmbracoExtension/umbraco-extension.js" + } + ] +} diff --git a/templates/UmbracoExtension/Client/scripts/generate-openapi.js b/templates/UmbracoExtension/Client/scripts/generate-openapi.js new file mode 100644 index 0000000000..93bb481289 --- /dev/null +++ b/templates/UmbracoExtension/Client/scripts/generate-openapi.js @@ -0,0 +1,48 @@ +import fetch from 'node-fetch'; +import chalk from 'chalk'; +import { createClient } from '@hey-api/openapi-ts'; + +// Start notifying user we are generating the TypeScript client +console.log(chalk.green("Generating OpenAPI client...")); + +const swaggerUrl = process.argv[2]; +if (swaggerUrl === undefined) { + console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`)); + console.error(`Please provide the URL to the OpenAPI spec as the first argument found in ${chalk.yellow('package.json')}`); + console.error(`Example: node generate-openapi.js ${chalk.yellow('https://localhost:44331/umbraco/swagger/REPLACE_ME/swagger.json')}`); + process.exit(); +} + +// Needed to ignore self-signed certificates from running Umbraco on https on localhost +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + +// Start checking to see if we can connect to the OpenAPI spec +console.log("Ensure your Umbraco instance is running"); +console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`); + +fetch(swaggerUrl).then(response => { + if (!response.ok) { + console.error(chalk.red(`ERROR: OpenAPI spec returned with a non OK (200) response: ${response.status} ${response.statusText}`)); + console.error(`The URL to your Umbraco instance may be wrong or the instance is not running`); + console.error(`Please verify or change the URL in the ${chalk.yellow('package.json')} for the script ${chalk.yellow('generate-openapi')}`); + return; + } + + console.log(`OpenAPI spec fetched successfully`); + console.log(`Calling ${chalk.yellow('hey-api')} to generate TypeScript client`); + + createClient({ + client: '@hey-api/client-fetch', + input: swaggerUrl, + output: 'src/api', + services: { + asClass: true, + } + }); + +}) + .catch(error => { + console.error(`ERROR: Failed to connect to the OpenAPI spec: ${chalk.red(error.message)}`); + console.error(`The URL to your Umbraco instance may be wrong or the instance is not running`); + console.error(`Please verify or change the URL in the ${chalk.yellow('package.json')} for the script ${chalk.yellow('generate-openapi')}`); + }); diff --git a/templates/UmbracoExtension/Client/src/api/index.ts b/templates/UmbracoExtension/Client/src/api/index.ts new file mode 100644 index 0000000000..7661d65371 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/index.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by @hey-api/openapi-ts +//#if(IncludeExample) +export * from './schemas.gen'; +//#endif +export * from './services.gen'; +export * from './types.gen'; diff --git a/templates/UmbracoExtension/Client/src/api/schemas.gen.ts b/templates/UmbracoExtension/Client/src/api/schemas.gen.ts new file mode 100644 index 0000000000..1a1885f5d5 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/schemas.gen.ts @@ -0,0 +1,391 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export const DocumentGranularPermissionModelSchema = { + required: ['context', 'key', 'permission'], + type: 'object', + properties: { + key: { + type: 'string', + format: 'uuid' + }, + context: { + type: 'string', + readOnly: true + }, + permission: { + type: 'string' + } + }, + additionalProperties: false +} as const; + +export const ReadOnlyUserGroupModelSchema = { + required: ['alias', 'allowedLanguages', 'allowedSections', 'granularPermissions', 'hasAccessToAllLanguages', 'id', 'key', 'name', 'permissions'], + type: 'object', + properties: { + id: { + type: 'integer', + format: 'int32' + }, + key: { + type: 'string', + format: 'uuid' + }, + name: { + type: 'string' + }, + icon: { + type: 'string', + nullable: true + }, + startContentId: { + type: 'integer', + format: 'int32', + nullable: true + }, + startMediaId: { + type: 'integer', + format: 'int32', + nullable: true + }, + alias: { + type: 'string' + }, + hasAccessToAllLanguages: { + type: 'boolean' + }, + allowedLanguages: { + type: 'array', + items: { + type: 'integer', + format: 'int32' + } + }, + permissions: { + uniqueItems: true, + type: 'array', + items: { + type: 'string' + } + }, + granularPermissions: { + uniqueItems: true, + type: 'array', + items: { + oneOf: [ + { + '$ref': '#/components/schemas/DocumentGranularPermissionModel' + }, + { + '$ref': '#/components/schemas/UnknownTypeGranularPermissionModel' + } + ] + } + }, + allowedSections: { + type: 'array', + items: { + type: 'string' + } + } + }, + additionalProperties: false +} as const; + +export const UnknownTypeGranularPermissionModelSchema = { + required: ['context', 'permission'], + type: 'object', + properties: { + context: { + type: 'string' + }, + permission: { + type: 'string' + } + }, + additionalProperties: false +} as const; + +export const UserGroupModelSchema = { + required: ['alias', 'allowedLanguages', 'allowedSections', 'createDate', 'granularPermissions', 'hasAccessToAllLanguages', 'hasIdentity', 'id', 'key', 'permissions', 'updateDate', 'userCount'], + type: 'object', + properties: { + id: { + type: 'integer', + format: 'int32' + }, + key: { + type: 'string', + format: 'uuid' + }, + createDate: { + type: 'string', + format: 'date-time' + }, + updateDate: { + type: 'string', + format: 'date-time' + }, + deleteDate: { + type: 'string', + format: 'date-time', + nullable: true + }, + hasIdentity: { + type: 'boolean', + readOnly: true + }, + startMediaId: { + type: 'integer', + format: 'int32', + nullable: true + }, + startContentId: { + type: 'integer', + format: 'int32', + nullable: true + }, + icon: { + type: 'string', + nullable: true + }, + alias: { + type: 'string' + }, + name: { + type: 'string', + nullable: true + }, + hasAccessToAllLanguages: { + type: 'boolean' + }, + permissions: { + uniqueItems: true, + type: 'array', + items: { + type: 'string' + } + }, + granularPermissions: { + uniqueItems: true, + type: 'array', + items: { + oneOf: [ + { + '$ref': '#/components/schemas/DocumentGranularPermissionModel' + }, + { + '$ref': '#/components/schemas/UnknownTypeGranularPermissionModel' + } + ] + } + }, + allowedSections: { + type: 'array', + items: { + type: 'string' + }, + readOnly: true + }, + userCount: { + type: 'integer', + format: 'int32', + readOnly: true + }, + allowedLanguages: { + type: 'array', + items: { + type: 'integer', + format: 'int32' + }, + readOnly: true + } + }, + additionalProperties: false +} as const; + +export const UserKindModelSchema = { + enum: ['Default', 'Api'], + type: 'string' +} as const; + +export const UserModelSchema = { + required: ['allowedSections', 'createDate', 'email', 'failedPasswordAttempts', 'groups', 'hasIdentity', 'id', 'isApproved', 'isLockedOut', 'key', 'kind', 'profileData', 'sessionTimeout', 'updateDate', 'username', 'userState'], + type: 'object', + properties: { + id: { + type: 'integer', + format: 'int32' + }, + key: { + type: 'string', + format: 'uuid' + }, + createDate: { + type: 'string', + format: 'date-time' + }, + updateDate: { + type: 'string', + format: 'date-time' + }, + deleteDate: { + type: 'string', + format: 'date-time', + nullable: true + }, + hasIdentity: { + type: 'boolean', + readOnly: true + }, + emailConfirmedDate: { + type: 'string', + format: 'date-time', + nullable: true + }, + invitedDate: { + type: 'string', + format: 'date-time', + nullable: true + }, + username: { + type: 'string' + }, + email: { + type: 'string' + }, + rawPasswordValue: { + type: 'string', + nullable: true + }, + passwordConfiguration: { + type: 'string', + nullable: true + }, + isApproved: { + type: 'boolean' + }, + isLockedOut: { + type: 'boolean' + }, + lastLoginDate: { + type: 'string', + format: 'date-time', + nullable: true + }, + lastPasswordChangeDate: { + type: 'string', + format: 'date-time', + nullable: true + }, + lastLockoutDate: { + type: 'string', + format: 'date-time', + nullable: true + }, + failedPasswordAttempts: { + type: 'integer', + format: 'int32' + }, + comments: { + type: 'string', + nullable: true + }, + userState: { + '$ref': '#/components/schemas/UserStateModel' + }, + name: { + type: 'string', + nullable: true + }, + allowedSections: { + type: 'array', + items: { + type: 'string' + }, + readOnly: true + }, + profileData: { + oneOf: [ + { + '$ref': '#/components/schemas/UserModel' + }, + { + '$ref': '#/components/schemas/UserProfileModel' + } + ], + readOnly: true + }, + securityStamp: { + type: 'string', + nullable: true + }, + avatar: { + type: 'string', + nullable: true + }, + sessionTimeout: { + type: 'integer', + format: 'int32' + }, + startContentIds: { + type: 'array', + items: { + type: 'integer', + format: 'int32' + }, + nullable: true + }, + startMediaIds: { + type: 'array', + items: { + type: 'integer', + format: 'int32' + }, + nullable: true + }, + language: { + type: 'string', + nullable: true + }, + kind: { + '$ref': '#/components/schemas/UserKindModel' + }, + groups: { + type: 'array', + items: { + oneOf: [ + { + '$ref': '#/components/schemas/ReadOnlyUserGroupModel' + }, + { + '$ref': '#/components/schemas/UserGroupModel' + } + ] + }, + readOnly: true + } + }, + additionalProperties: false +} as const; + +export const UserProfileModelSchema = { + required: ['id'], + type: 'object', + properties: { + id: { + type: 'integer', + format: 'int32' + }, + name: { + type: 'string', + nullable: true + } + }, + additionalProperties: false +} as const; + +export const UserStateModelSchema = { + enum: ['Active', 'Disabled', 'LockedOut', 'Invited', 'Inactive', 'All'], + type: 'string' +} as const; \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/services.gen.ts b/templates/UmbracoExtension/Client/src/api/services.gen.ts new file mode 100644 index 0000000000..c9d1c0b91b --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/services.gen.ts @@ -0,0 +1,41 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; +//#if(IncludeExample) +import type { PingError, PingResponse, WhatsMyNameError, WhatsMyNameResponse, WhatsTheTimeMrWolfError, WhatsTheTimeMrWolfResponse, WhoAmIError, WhoAmIResponse } from './types.gen'; +//#else +import type { PingError, PingResponse } from './types.gen'; +//#endif + +export const client = createClient(createConfig()); + +export class UmbracoExtensionService { + public static ping(options?: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/umbraco/umbracoextension/api/v1/ping' + }); + } +//#if(IncludeExample) + public static whatsMyName(options?: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/umbraco/umbracoextension/api/v1/whatsMyName' + }); + } + + public static whatsTheTimeMrWolf(options?: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf' + }); + } + + public static whoAmI(options?: Options) { + return (options?.client ?? client).get({ + ...options, + url: '/umbraco/umbracoextension/api/v1/whoAmI' + }); + } +//#endif +} diff --git a/templates/UmbracoExtension/Client/src/api/types.gen.ts b/templates/UmbracoExtension/Client/src/api/types.gen.ts new file mode 100644 index 0000000000..e666792803 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/types.gen.ts @@ -0,0 +1,107 @@ +// This file is auto-generated by @hey-api/openapi-ts +//#if(IncludeExample) +export type DocumentGranularPermissionModel = { + key: string; + readonly context: string; + permission: string; +}; + +export type ReadOnlyUserGroupModel = { + id: number; + key: string; + name: string; + icon?: (string) | null; + startContentId?: (number) | null; + startMediaId?: (number) | null; + alias: string; + hasAccessToAllLanguages: boolean; + allowedLanguages: Array<(number)>; + permissions: Array<(string)>; + granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>; + allowedSections: Array<(string)>; +}; + +export type UnknownTypeGranularPermissionModel = { + context: string; + permission: string; +}; + +export type UserGroupModel = { + id: number; + key: string; + createDate: string; + updateDate: string; + deleteDate?: (string) | null; + readonly hasIdentity: boolean; + startMediaId?: (number) | null; + startContentId?: (number) | null; + icon?: (string) | null; + alias: string; + name?: (string) | null; + hasAccessToAllLanguages: boolean; + permissions: Array<(string)>; + granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>; + readonly allowedSections: Array<(string)>; + readonly userCount: number; + readonly allowedLanguages: Array<(number)>; +}; + +export type UserKindModel = 'Default' | 'Api'; + +export type UserModel = { + id: number; + key: string; + createDate: string; + updateDate: string; + deleteDate?: (string) | null; + readonly hasIdentity: boolean; + emailConfirmedDate?: (string) | null; + invitedDate?: (string) | null; + username: string; + email: string; + rawPasswordValue?: (string) | null; + passwordConfiguration?: (string) | null; + isApproved: boolean; + isLockedOut: boolean; + lastLoginDate?: (string) | null; + lastPasswordChangeDate?: (string) | null; + lastLockoutDate?: (string) | null; + failedPasswordAttempts: number; + comments?: (string) | null; + userState: UserStateModel; + name?: (string) | null; + readonly allowedSections: Array<(string)>; + readonly profileData: (UserModel | UserProfileModel); + securityStamp?: (string) | null; + avatar?: (string) | null; + sessionTimeout: number; + startContentIds?: Array<(number)> | null; + startMediaIds?: Array<(number)> | null; + language?: (string) | null; + kind: UserKindModel; + readonly groups: Array<(ReadOnlyUserGroupModel | UserGroupModel)>; +}; + +export type UserProfileModel = { + id: number; + name?: (string) | null; +}; + +export type UserStateModel = 'Active' | 'Disabled' | 'LockedOut' | 'Invited' | 'Inactive' | 'All'; +//#endif +export type PingResponse = (string); + +export type PingError = (unknown); +//#if(IncludeExample) +export type WhatsMyNameResponse = (string); + +export type WhatsMyNameError = (unknown); + +export type WhatsTheTimeMrWolfResponse = (string); + +export type WhatsTheTimeMrWolfError = (unknown); + +export type WhoAmIResponse = ((UserModel)); + +export type WhoAmIError = (unknown); +//#endif diff --git a/templates/UmbracoExtension/Client/src/bundle.manifests.ts b/templates/UmbracoExtension/Client/src/bundle.manifests.ts new file mode 100644 index 0000000000..fb2d2bfa09 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/bundle.manifests.ts @@ -0,0 +1,13 @@ +import { manifests as entrypoints } from './entrypoints/manifest'; +//#if IncludeExample +import { manifests as dashboards } from './dashboards/manifest'; +//#endif + +// Job of the bundle is to collate all the manifests from different parts of the extension and load other manifests +// We load this bundle from umbraco-package.json +export const manifests: Array = [ + ...entrypoints, + //#if IncludeExample + ...dashboards, + //#endif +]; diff --git a/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts new file mode 100644 index 0000000000..287464086e --- /dev/null +++ b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts @@ -0,0 +1,176 @@ +import { LitElement, css, html, customElement, state } from "@umbraco-cms/backoffice/external/lit"; +import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api"; +import { UmbracoExtensionService, UserModel } from "../api"; +import { UUIButtonElement } from "@umbraco-cms/backoffice/external/uui"; +import { UMB_NOTIFICATION_CONTEXT, UmbNotificationContext } from "@umbraco-cms/backoffice/notification"; +import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserModel } from "@umbraco-cms/backoffice/current-user"; + +@customElement('example-dashboard') +export class ExampleDashboardElement extends UmbElementMixin(LitElement) { + + @state() + private _yourName: string | undefined = "Press the button!"; + + @state() + private _timeFromMrWolf: Date | undefined; + + @state() + private _serverUserData: UserModel | undefined = undefined; + + @state() + private _contextCurrentUser: UmbCurrentUserModel | undefined = undefined; + + constructor() { + super(); + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (notificationContext) => { + this.#notificationContext = notificationContext; + }); + + this.consumeContext(UMB_CURRENT_USER_CONTEXT, (currentUserContext) => { + + // When we have the current user context + // We can observe properties from it, such as the current user or perhaps just individual properties + // When the currentUser object changes we will get notified and can reset the @state properrty + this.observe(currentUserContext.currentUser, (currentUser) => { + this._contextCurrentUser = currentUser; + }); + }); + } + + #notificationContext: UmbNotificationContext | undefined = undefined; + + #onClickWhoAmI = async (ev: Event) => { + const buttonElement = ev.target as UUIButtonElement; + buttonElement.state = "waiting"; + + const { data, error } = await UmbracoExtensionService.whoAmI(); + + if (error) { + buttonElement.state = "failed"; + console.error(error); + return; + } + + if (data !== undefined) { + this._serverUserData = data; + buttonElement.state = "success"; + } + + if (this.#notificationContext) { + this.#notificationContext.peek("warning", { + data: { + headline: `You are ${this._serverUserData?.name}`, + message: `Your email is ${this._serverUserData?.email}`, + } + }) + } + } + + #onClickWhatsTheTimeMrWolf = async (ev: Event) => { + const buttonElement = ev.target as UUIButtonElement; + buttonElement.state = "waiting"; + + // Getting a string - should I expect a datetime?! + const { data, error } = await UmbracoExtensionService.whatsTheTimeMrWolf(); + + if (error) { + buttonElement.state = "failed"; + console.error(error); + return; + } + + if (data !== undefined) { + this._timeFromMrWolf = new Date(data); + buttonElement.state = "success"; + } + } + + #onClickWhatsMyName = async (ev: Event) => { + const buttonElement = ev.target as UUIButtonElement; + buttonElement.state = "waiting"; + + const { data, error } = await UmbracoExtensionService.whatsMyName(); + + if (error) { + buttonElement.state = "failed"; + console.error(error); + return; + } + + this._yourName = data; + buttonElement.state = "success"; + } + + render() { + return html` + +
[Server]
+

${this._serverUserData?.email ? this._serverUserData.email : 'Press the button!'}

+
    + ${this._serverUserData?.groups.map(group => html`
  • ${group.name}
  • `)} +
+ + Who am I? + +

This endpoint gets your current user from the server and displays your email and list of user groups. + It also displays a Notification with your details.

+
+ + +
[Server]
+

${this._yourName }

+ + Whats my name? + +

This endpoint has a forced delay to show the button 'waiting' state for a few seconds before completing the request.

+
+ + +
[Server]
+

${this._timeFromMrWolf ? this._timeFromMrWolf.toLocaleString() : 'Press the button!'}

+ + Whats the time Mr Wolf? + +

This endpoint gets the current date and time from the server.

+
+ + +
[Context]
+

Current user email: ${this._contextCurrentUser?.email}

+

This is the JSON object available by consuming the 'UMB_CURRENT_USER_CONTEXT' context:

+ ${JSON.stringify(this._contextCurrentUser, null, 2)} +
+ `; + } + + static styles = [ + css` + :host { + display: grid; + gap: var(--uui-size-layout-1); + padding: var(--uui-size-layout-1); + grid-template-columns: 1fr 1fr 1fr; + } + + uui-box { + margin-bottom: var(--uui-size-layout-1); + } + + h2 { + margin-top:0; + } + + .wide { + grid-column: span 3; + } + `]; +} + +export default ExampleDashboardElement; + +declare global { + interface HTMLElementTagNameMap { + 'example-dashboard': ExampleDashboardElement; + } +} diff --git a/templates/UmbracoExtension/Client/src/dashboards/manifest.ts b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts new file mode 100644 index 0000000000..a020e3ab2c --- /dev/null +++ b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts @@ -0,0 +1,18 @@ +export const manifests: Array = [ + { + name: "Umbraco ExtensionDashboard", + alias: "Umbraco.Extension.Dashboard", + type: 'dashboard', + js: () => import("./dashboard.element"), + meta: { + label: "Example Dashboard", + pathname: "example-dashboard" + }, + conditions: [ + { + alias: 'Umb.Condition.SectionAlias', + match: 'Umb.Section.Content', + } + ], + } +]; diff --git a/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts b/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts new file mode 100644 index 0000000000..65796c3202 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts @@ -0,0 +1,38 @@ +import { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api'; +//#if IncludeExample +import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; +import { client } from '../api'; +//#endif + +// load up the manifests here +export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { + + console.log('Hello from my extension 🎉'); + //#if IncludeExample + // Will use only to add in Open API config with generated TS OpenAPI HTTPS Client + // Do the OAuth token handshake stuff + _host.consumeContext(UMB_AUTH_CONTEXT, async (authContext) => { + + // Get the token info from Umbraco + const config = authContext.getOpenApiConfiguration(); + + client.setConfig({ + baseUrl: config.base, + credentials: config.credentials + }); + + // For every request being made, add the token to the headers + // Can't use the setConfig approach above as its set only once and + // tokens expire and get refreshed + client.interceptors.request.use(async (request, _options) => { + const token = await config.token(); + request.headers.set('Authorization', `Bearer ${token}`); + return request; + }); + }); + //#endif +}; + +export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => { + console.log('Goodbye from my extension 👋'); +}; diff --git a/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts new file mode 100644 index 0000000000..5dcda9de89 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts @@ -0,0 +1,8 @@ +export const manifests: Array = [ + { + name: "Umbraco ExtensionEntrypoint", + alias: "Umbraco.Extension.Entrypoint", + type: "backofficeEntryPoint", + js: () => import("./entrypoint"), + } +]; diff --git a/templates/UmbracoExtension/Client/tsconfig.json b/templates/UmbracoExtension/Client/tsconfig.json new file mode 100644 index 0000000000..1b3364784e --- /dev/null +++ b/templates/UmbracoExtension/Client/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ "ES2020", "DOM", "DOM.Iterable" ], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "types": [ "@umbraco-cms/backoffice/extension-types" ] + }, + "include": [ "src" ] +} diff --git a/templates/UmbracoExtension/Client/vite.config.ts b/templates/UmbracoExtension/Client/vite.config.ts new file mode 100644 index 0000000000..5232076b8c --- /dev/null +++ b/templates/UmbracoExtension/Client/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + lib: { + entry: "src/bundle.manifests.ts", // Bundle registers one or more manifests + formats: ["es"], + fileName: "umbraco-extension", + }, + outDir: "../wwwroot/App_Plugins/UmbracoExtension", // your web component will be saved in this location + emptyOutDir: true, + sourcemap: true, + rollupOptions: { + external: [/^@umbraco/], + }, + } +}); diff --git a/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs b/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs new file mode 100644 index 0000000000..481e1d19bb --- /dev/null +++ b/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs @@ -0,0 +1,73 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Api.Management.OpenApi; +using Umbraco.Cms.Api.Common.OpenApi; + +namespace Umbraco.Extension.Composers +{ + public class UmbracoExtensionApiComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + + builder.Services.AddSingleton(); + + builder.Services.Configure(opt => + { + // Related documentation: + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/versioning-your-api + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/access-policies + + // Configure the Swagger generation options + // Add in a new Swagger API document solely for our own package that can be browsed via Swagger UI + // Along with having a generated swagger JSON file that we can use to auto generate a TypeScript client + opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo + { + Title = "Umbraco ExtensionBackoffice API", + Version = "1.0", + // Contact = new OpenApiContact + // { + // Name = "Some Developer", + // Email = "you@company.com", + // Url = new Uri("https://company.com") + // } + }); + + // Enable Umbraco authentication for the "Example" Swagger document + // PR: https://github.com/umbraco/Umbraco-CMS/pull/15699 + opt.OperationFilter(); + }); + } + + public class UmbracoExtensionOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase + { + protected override string ApiName => Constants.ApiName; + } + + // This is used to generate nice operation IDs in our swagger json file + // So that the gnerated TypeScript client has nice method names and not too verbose + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids#operation-ids + public class CustomOperationHandler : OperationIdHandler + { + public CustomOperationHandler(IOptions apiVersioningOptions) : base(apiVersioningOptions) + { + } + + protected override bool CanHandle(ApiDescription apiDescription, ControllerActionDescriptor controllerActionDescriptor) + { + return controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith("Umbraco.Extension.Controllers", comparisonType: StringComparison.InvariantCultureIgnoreCase) is true; + } + + public override string Handle(ApiDescription apiDescription) => $"{apiDescription.ActionDescriptor.RouteValues["action"]}"; + } + } +} diff --git a/templates/UmbracoExtension/Constants.cs b/templates/UmbracoExtension/Constants.cs new file mode 100644 index 0000000000..9fc6796da1 --- /dev/null +++ b/templates/UmbracoExtension/Constants.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Extension +{ + public class Constants + { + public const string ApiName = "umbracoextension"; + } +} diff --git a/templates/UmbracoExtension/Controllers/UmbracoExtensionApiController.cs b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiController.cs new file mode 100644 index 0000000000..2f62ec2785 --- /dev/null +++ b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiController.cs @@ -0,0 +1,49 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +#if IncludeExample +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +#endif + +namespace Umbraco.Extension.Controllers +{ + [ApiVersion("1.0")] + [ApiExplorerSettings(GroupName = "Umbraco.Extension")] + public class UmbracoExtensionApiController : UmbracoExtensionApiControllerBase + { +#if IncludeExample + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public UmbracoExtensionApiController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } +#endif + + [HttpGet("ping")] + [ProducesResponseType(StatusCodes.Status200OK)] + public string Ping() => "Pong"; +#if IncludeExample + + [HttpGet("whatsTheTimeMrWolf")] + [ProducesResponseType(typeof(DateTime), 200)] + public DateTime WhatsTheTimeMrWolf() => DateTime.Now; + + [HttpGet("whatsMyName")] + [ProducesResponseType(StatusCodes.Status200OK)] + public string WhatsMyName() + { + // So we can see a long request in the dashboard with a spinning progress wheel + Thread.Sleep(2000); + + var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + return currentUser?.Name ?? "I have no idea who you are"; + } + + [HttpGet("whoAmI")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IUser? WhoAmI() => _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; +#endif + } +} diff --git a/templates/UmbracoExtension/Controllers/UmbracoExtensionApiControllerBase.cs b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiControllerBase.cs new file mode 100644 index 0000000000..3e5a5e14a0 --- /dev/null +++ b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiControllerBase.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Cms.Web.Common.Routing; + +namespace Umbraco.Extension.Controllers +{ + [ApiController] + [BackOfficeRoute("umbracoextension/api/v{version:apiVersion}")] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] + [MapToApi(Constants.ApiName)] + public class UmbracoExtensionApiControllerBase : ControllerBase + { + } +} diff --git a/templates/UmbracoExtension/README.txt b/templates/UmbracoExtension/README.txt new file mode 100644 index 0000000000..7beee9137e --- /dev/null +++ b/templates/UmbracoExtension/README.txt @@ -0,0 +1,38 @@ + _ _ _ + | | | | | | + __| | ___ | |_ _ __ ___| |_ _ __ _____ __ + / _` |/ _ \| __| '_ \ / _ \ __| | '_ \ / _ \ \ /\ / / + | (_| | (_) | |_| | | | __/ |_ | | | | __/\ V V / + \__,_|\___/ \__|_| |_|\___|\__| |_| |_|\___| \_/\_/ _ _ + | | | | (_) + _ _ _ __ ___ | |__ _ __ __ _ ___ ___ _____ _| |_ ___ _ __ ___ _ ___ _ __ + | | | | '_ ` _ \| '_ \| '__/ _` |/ __/ _ \ / _ \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \ + | |_| | | | | | | |_) | | | (_| | (_| (_) | | __/> <| || __/ | | \__ \ | (_) | | | | + \__,_|_| |_| |_|_.__/|_| \__,_|\___\___/ \___/_/\_\\__\___|_| |_|___/_|\___/|_| |_| + + +== Requirements == +* Node LTS Version 20.17.0+ +* Use a tool such as NVM (Node Version Manager) for your OS to help manage multiple versions of Node + +== Node Version Manager tools == +* https://github.com/coreybutler/nvm-windows +* https://github.com/nvm-sh/nvm +* https://docs.volta.sh/guide/getting-started + +== Steps == +* Open a terminal inside the `\Client` folder +* Run `npm install` to install all the dependencies +* Run `npm run build` to build the project +* The build output is copied to `wwwroot\App_Plugins\UmbracoExtension\umbraco-extension.js` + +== File Watching == +* Add this Razor Class Library Project as a project reference to an Umbraco Website project +* From the `\Client` folder run the command `npm run watch` this will monitor the changes to the *.ts files and rebuild the project +* With the Umbraco website project running the Razor Class Library Project will refresh the browser when the build is complete + +== Suggestion == +* Use VSCode as the editor of choice as it has good tooling support for TypeScript and it will recommend a VSCode Extension for good Lit WebComponent completions + +== Other Resources == +* Umbraco Docs - https://docs.umbraco.com/umbraco-cms/customizing/extend-and-customize-editing-experience diff --git a/templates/UmbracoPackageRcl/UmbracoPackage.csproj b/templates/UmbracoExtension/Umbraco.Extension.csproj similarity index 50% rename from templates/UmbracoPackageRcl/UmbracoPackage.csproj rename to templates/UmbracoExtension/Umbraco.Extension.csproj index b959751c87..3b70140f35 100644 --- a/templates/UmbracoPackageRcl/UmbracoPackage.csproj +++ b/templates/UmbracoExtension/Umbraco.Extension.csproj @@ -4,16 +4,14 @@ enable enable true - UmbracoPackage - App_Plugins/UmbracoPackage + Umbraco.Extension + / - UmbracoPackage - UmbracoPackage - UmbracoPackage - ... - umbraco plugin package + Umbraco.Extension + Umbraco.Extension + Umbraco.Extension @@ -23,5 +21,16 @@ + + + + + + + + + + + diff --git a/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json deleted file mode 100644 index 6473c5c643..0000000000 --- a/templates/UmbracoPackage/.template.config/dotnetcli.host.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/dotnetcli.host.json", - "symbolInfo": { - "Framework": { - "longName": "Framework", - "shortName": "F", - "isHidden": true - }, - "UmbracoVersion": { - "longName": "version", - "shortName": "v" - }, - "SkipRestore": { - "longName": "no-restore", - "shortName": "" - } - } -} diff --git a/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json deleted file mode 100644 index 0464cfeb1f..0000000000 --- a/templates/UmbracoPackage/.template.config/ide.host.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/ide.host.json", - "order": 0, - "icon": "../../icon.png", - "description": { - "id": "UmbracoPackage", - "text": "Umbraco Package - An empty Umbraco CMS package/plugin." - }, - "symbolInfo": [ - { - "id": "UmbracoVersion", - "isVisible": true - } - ] -} diff --git a/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json deleted file mode 100644 index 5c93b1d68d..0000000000 --- a/templates/UmbracoPackage/.template.config/template.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/template.json", - "author": "Umbraco HQ", - "classifications": [ - "Web", - "CMS", - "Umbraco", - "Package", - "Plugin" - ], - "name": "Umbraco Package", - "description": "An empty Umbraco package/plugin project ready to get started.", - "groupIdentity": "Umbraco.Templates.UmbracoPackage", - "identity": "Umbraco.Templates.UmbracoPackage.CSharp", - "shortName": "umbracopackage", - "tags": { - "language": "C#", - "type": "project" - }, - "sourceName": "UmbracoPackage", - "defaultName": "UmbracoPackage1", - "preferNameDirectory": true, - "symbols": { - "Framework": { - "displayName": "Framework", - "description": "The target framework for the project.", - "type": "parameter", - "datatype": "choice", - "choices": [ - { - "displayName": ".NET 9.0", - "description": "Target net9.0", - "choice": "net9.0" - } - ], - "defaultValue": "net9.0", - "replaces": "net9.0" - }, - "UmbracoVersion": { - "displayName": "Umbraco version", - "description": "The version of Umbraco.Cms to add as PackageReference.", - "type": "parameter", - "datatype": "string", - "defaultValue": "*", - "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" - }, - "SkipRestore": { - "displayName": "Skip restore", - "description": "If specified, skips the automatic restore of the project on create.", - "type": "parameter", - "datatype": "bool", - "defaultValue": "false" - }, - "Namespace": { - "type": "derived", - "valueSource": "name", - "valueTransform": "safe_namespace", - "fileRename": "UmbracoPackage", - "replaces": "UmbracoPackage" - }, - "MsBuildName": { - "type": "generated", - "generator": "regex", - "dataType": "string", - "parameters": { - "source": "name", - "steps": [ - { - "regex": "\\s", - "replacement": "" - }, - { - "regex": "\\.", - "replacement": "" - }, - { - "regex": "-", - "replacement": "" - }, - { - "regex": "^[^a-zA-Z_]+", - "replacement": "" - } - ] - }, - "replaces": "UmbracoPackageMsBuild" - } - }, - "primaryOutputs": [ - { - "path": "UmbracoPackage.csproj" - } - ], - "postActions": [ - { - "id": "restore", - "condition": "(!SkipRestore)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ - { - "text": "Run 'dotnet restore'" - } - ], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - ] -} diff --git a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/umbraco-package.json b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/umbraco-package.json deleted file mode 100644 index 153f0b0576..0000000000 --- a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/umbraco-package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "UmbracoPackage", - "name": "UmbracoPackage", - "allowPackageTelemetry": true, - "extensions": [] -} diff --git a/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj deleted file mode 100644 index 9790da947c..0000000000 --- a/templates/UmbracoPackage/UmbracoPackage.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - net9.0 - enable - enable - . - UmbracoPackage - - - - UmbracoPackage - UmbracoPackage - UmbracoPackage - ... - umbraco plugin package - - - - - - - - - - - - diff --git a/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets b/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets deleted file mode 100644 index 4c376ac97b..0000000000 --- a/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets +++ /dev/null @@ -1,21 +0,0 @@ - - - $(MSBuildThisFileDirectory)..\App_Plugins\UmbracoPackage\**\*.* - - - - - - - - - - - - - - - - - - diff --git a/templates/UmbracoPackageRcl/.template.config/ide.host.json b/templates/UmbracoPackageRcl/.template.config/ide.host.json deleted file mode 100644 index 8e630f1e99..0000000000 --- a/templates/UmbracoPackageRcl/.template.config/ide.host.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/ide.host.json", - "order": 0, - "icon": "../../icon.png", - "description": { - "id": "UmbracoPackageRcl", - "text": "Umbraco Package RCL - An empty Umbraco package/plugin (Razor Class Library)." - }, - "symbolInfo": [ - { - "id": "UmbracoVersion", - "isVisible": true - }, - { - "id": "SupportPagesAndViews", - "isVisible": true, - "persistenceScope": "templateGroup" - } - ] -} diff --git a/templates/UmbracoPackageRcl/.template.config/template.json b/templates/UmbracoPackageRcl/.template.config/template.json deleted file mode 100644 index c03c860141..0000000000 --- a/templates/UmbracoPackageRcl/.template.config/template.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/template.json", - "author": "Umbraco HQ", - "classifications": [ - "Web", - "CMS", - "Umbraco", - "Package", - "Plugin", - "Razor Class Library" - ], - "name": "Umbraco Package RCL", - "description": "An empty Umbraco package/plugin (Razor Class Library).", - "groupIdentity": "Umbraco.Templates.UmbracoPackageRcl", - "identity": "Umbraco.Templates.UmbracoPackageRcl.CSharp", - "shortName": "umbracopackage-rcl", - "tags": { - "language": "C#", - "type": "project" - }, - "sourceName": "UmbracoPackage", - "defaultName": "UmbracoPackage1", - "preferNameDirectory": true, - "symbols": { - "Framework": { - "displayName": "Framework", - "description": "The target framework for the project.", - "type": "parameter", - "datatype": "choice", - "choices": [ - { - "displayName": ".NET 9.0", - "description": "Target net9.0", - "choice": "net9.0" - } - ], - "defaultValue": "net9.0", - "replaces": "net9.0" - }, - "UmbracoVersion": { - "displayName": "Umbraco version", - "description": "The version of Umbraco.Cms to add as PackageReference.", - "type": "parameter", - "datatype": "string", - "defaultValue": "*", - "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" - }, - "SkipRestore": { - "displayName": "Skip restore", - "description": "If specified, skips the automatic restore of the project on create.", - "type": "parameter", - "datatype": "bool", - "defaultValue": "false" - }, - "SupportPagesAndViews": { - "type": "parameter", - "datatype": "bool", - "defaultValue": "false", - "displayName": "Support pages and views", - "description": "Whether to support adding traditional Razor pages and Views to this library." - } - }, - "primaryOutputs": [ - { - "path": "UmbracoPackage.csproj" - } - ], - "postActions": [ - { - "id": "restore", - "condition": "(!SkipRestore)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ - { - "text": "Run 'dotnet restore'" - } - ], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - ] -} diff --git a/templates/UmbracoPackageRcl/wwwroot/umbraco-package.json b/templates/UmbracoPackageRcl/wwwroot/umbraco-package.json deleted file mode 100644 index 153f0b0576..0000000000 --- a/templates/UmbracoPackageRcl/wwwroot/umbraco-package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "UmbracoPackage", - "name": "UmbracoPackage", - "allowPackageTelemetry": true, - "extensions": [] -} From 316ce5d4fe722542613a642da416fff69c9b7fe2 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 8 Nov 2024 11:09:21 +0100 Subject: [PATCH 07/30] Add endpoint for retrieving the published (if any) version of a document (#17278) --- .../ByKeyPublishedDocumentController.cs | 58 ++++++++ .../Factories/DocumentPresentationFactory.cs | 17 +++ .../Factories/IDocumentPresentationFactory.cs | 2 + .../Mapping/Content/ContentMapDefinition.cs | 11 +- .../Mapping/Document/DocumentMapDefinition.cs | 23 ++++ src/Umbraco.Cms.Api.Management/OpenApi.json | 126 ++++++++++++++++++ .../PublishedDocumentResponseModel.cs | 5 + 7 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyPublishedDocumentController.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishedDocumentResponseModel.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyPublishedDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyPublishedDocumentController.cs new file mode 100644 index 0000000000..2368afc732 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyPublishedDocumentController.cs @@ -0,0 +1,58 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Core.Actions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security.Authorization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.Document; + +[ApiVersion("1.0")] +public class ByKeyPublishedDocumentController : DocumentControllerBase +{ + private readonly IAuthorizationService _authorizationService; + private readonly IContentEditingService _contentEditingService; + private readonly IDocumentPresentationFactory _documentPresentationFactory; + + public ByKeyPublishedDocumentController( + IAuthorizationService authorizationService, + IContentEditingService contentEditingService, + IDocumentPresentationFactory documentPresentationFactory) + { + _authorizationService = authorizationService; + _contentEditingService = contentEditingService; + _documentPresentationFactory = documentPresentationFactory; + } + + [HttpGet("{id:guid}/published")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PublishedDocumentResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task ByKeyPublished(CancellationToken cancellationToken, Guid id) + { + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + User, + ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id), + AuthorizationPolicies.ContentPermissionByResource); + + if (!authorizationResult.Succeeded) + { + return Forbidden(); + } + + IContent? content = await _contentEditingService.GetAsync(id); + if (content == null || content.Published is false) + { + return DocumentNotFound(); + } + + PublishedDocumentResponseModel model = await _documentPresentationFactory.CreatePublishedResponseModelAsync(content); + return Ok(model); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs index c6291f4db0..817cd9fa2e 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs @@ -54,6 +54,23 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory return responseModel; } + public async Task CreatePublishedResponseModelAsync(IContent content) + { + PublishedDocumentResponseModel responseModel = _umbracoMapper.Map(content)!; + + responseModel.Urls = await _documentUrlFactory.CreateUrlsAsync(content); + + Guid? templateKey = content.PublishTemplateId.HasValue + ? _templateService.GetAsync(content.PublishTemplateId.Value).Result?.Key + : null; + + responseModel.Template = templateKey.HasValue + ? new ReferenceByIdModel { Id = templateKey.Value } + : null; + + return responseModel; + } + public DocumentItemResponseModel CreateItemResponseModel(IDocumentEntitySlim entity) { var responseModel = new DocumentItemResponseModel diff --git a/src/Umbraco.Cms.Api.Management/Factories/IDocumentPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IDocumentPresentationFactory.cs index 23b11b0e4f..40a537e01a 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IDocumentPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IDocumentPresentationFactory.cs @@ -14,6 +14,8 @@ public interface IDocumentPresentationFactory { Task CreateResponseModelAsync(IContent content); + Task CreatePublishedResponseModelAsync(IContent content); + DocumentItemResponseModel CreateItemResponseModel(IDocumentEntitySlim entity); DocumentBlueprintItemResponseModel CreateBlueprintItemResponseModel(IDocumentEntitySlim entity); diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Content/ContentMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Content/ContentMapDefinition.cs index 038e3fbf8b..6970e7d251 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Content/ContentMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Content/ContentMapDefinition.cs @@ -19,7 +19,7 @@ public abstract class ContentMapDefinition MapValueViewModels(IEnumerable properties, ValueViewModelMapping? additionalPropertyMapping = null) => + protected IEnumerable MapValueViewModels(IEnumerable properties, ValueViewModelMapping? additionalPropertyMapping = null, bool published = false) => properties .SelectMany(property => property .Values @@ -31,12 +31,19 @@ public abstract class ContentMapDefinition((_, _) => new DocumentResponseModel(), Map); + mapper.Define((_, _) => new PublishedDocumentResponseModel(), Map); mapper.Define((_, _) => new DocumentCollectionResponseModel(), Map); mapper.Define((_, _) => new DocumentBlueprintResponseModel(), Map); } @@ -44,6 +45,28 @@ public class DocumentMapDefinition : ContentMapDefinition(source.ContentType)!; + target.Values = MapValueViewModels(source.Properties, published: true); + target.Variants = MapVariantViewModels( + source, + (culture, _, documentVariantViewModel) => + { + documentVariantViewModel.Name = source.GetPublishName(culture) ?? documentVariantViewModel.Name; + DocumentVariantState variantState = DocumentVariantStateHelper.GetState(source, culture); + documentVariantViewModel.State = variantState == DocumentVariantState.PublishedPendingChanges + ? DocumentVariantState.Published + : variantState; + documentVariantViewModel.PublishDate = culture == null + ? source.PublishDate + : source.GetPublishDate(culture); + }); + target.IsTrashed = source.Trashed; + } + // Umbraco.Code.MapAll private void Map(IContent source, DocumentCollectionResponseModel target, MapperContext context) { diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index eb912bf856..6240675b2c 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -8967,6 +8967,66 @@ ] } }, + "/umbraco/management/api/v1/document/{id}/published": { + "get": { + "tags": [ + "Document" + ], + "operationId": "GetDocumentByIdPublished", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PublishedDocumentResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ProblemDetails" + } + ] + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + }, + "403": { + "description": "The authenticated user do not have access to this resource" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/document/{id}/referenced-by": { "get": { "tags": [ @@ -42471,6 +42531,72 @@ }, "additionalProperties": false }, + "PublishedDocumentResponseModel": { + "required": [ + "documentType", + "id", + "isTrashed", + "urls", + "values", + "variants" + ], + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentValueResponseModel" + } + ] + } + }, + "variants": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentVariantResponseModel" + } + ] + } + }, + "id": { + "type": "string", + "format": "uuid" + }, + "documentType": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentTypeReferenceResponseModel" + } + ] + }, + "urls": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentUrlInfoModel" + } + ] + } + }, + "template": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReferenceByIdModel" + } + ], + "nullable": true + }, + "isTrashed": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "RedirectStatusModel": { "enum": [ "Enabled", diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishedDocumentResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishedDocumentResponseModel.cs new file mode 100644 index 0000000000..a228a3954b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/PublishedDocumentResponseModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Document; + +public class PublishedDocumentResponseModel : DocumentResponseModel +{ +} From 6e689a56c4475fad27985c4d21deabc86ac9f17b Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Sat, 9 Nov 2024 12:51:54 +0100 Subject: [PATCH 08/30] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 21926435d6..71091820ed 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 21926435d6deac0c8e1bf2537577e9a7002a7ba2 +Subproject commit 71091820edbca0ecf669f520c804d8fedf730d17 From a78d0aad97d93a0a28a7dd0105b949b21295c91e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Sat, 9 Nov 2024 12:52:29 +0100 Subject: [PATCH 09/30] Remove submodule src/Umbraco.Web.UI.Client --- .gitmodules | 3 --- src/Umbraco.Web.UI.Client | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 src/Umbraco.Web.UI.Client diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d1e6ead0dd..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "src/Umbraco.Web.UI.Client"] - path = src/Umbraco.Web.UI.Client - url = https://github.com/umbraco/Umbraco.CMS.Backoffice.git diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client deleted file mode 160000 index 71091820ed..0000000000 --- a/src/Umbraco.Web.UI.Client +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 71091820edbca0ecf669f520c804d8fedf730d17 From b4ad4bec97b3bd943626d958b2f52c15a4e75222 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Sat, 9 Nov 2024 11:16:01 +0100 Subject: [PATCH 10/30] rename to avoid clash with keyword 'source' --- .../config/source-content/index.ts | 1 + .../input-content-picker-source.element.ts | 140 ++++++++++++++++++ .../config/source-content/manifests.ts | 13 ++ ...editor-ui-content-picker-source.element.ts | 52 +++++++ ...editor-ui-content-picker-source.stories.ts | 15 ++ ...ty-editor-ui-content-picker-source.test.ts | 23 +++ .../property-editors/content-picker/index.ts | 2 +- .../content-picker/manifests.ts | 2 +- 8 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/input-content-picker-source.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.stories.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/property-editor-ui-content-picker-source.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/index.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/index.ts new file mode 100644 index 0000000000..f45438cbdc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/index.ts @@ -0,0 +1 @@ +export * from './input-content-picker-source.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/input-content-picker-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/input-content-picker-source.element.ts new file mode 100644 index 0000000000..9ae2b22d53 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/content-picker/config/source-content/input-content-picker-source.element.ts @@ -0,0 +1,140 @@ +import type { UmbContentPickerDynamicRoot, UmbContentPickerSourceType } from '../../types.js'; +import type { UmbInputContentPickerDocumentRootElement } from '../../dynamic-root/components/input-content-picker-document-root.element.js'; +import { html, customElement, property, css, state, nothing } from '@umbraco-cms/backoffice/external/lit'; +import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; + +import '../../dynamic-root/components/input-content-picker-document-root.element.js'; + +@customElement('umb-input-content-picker-source') +export class UmbInputContentPickerSourceElement extends UUIFormControlMixin(UmbLitElement, '') { + protected override getFormElement() { + return undefined; + } + + #type: UmbContentPickerSourceType = 'content'; + + @property() + public set type(value: UmbContentPickerSourceType) { + if (value === undefined) { + value = this.#type; + } + + const oldValue = this.#type; + + this._options = this._options.map((option) => + option.value === value ? { ...option, selected: true } : { ...option, selected: false }, + ); + + this.#type = value; + + this.requestUpdate('type', oldValue); + } + public get type(): UmbContentPickerSourceType { + return this.#type; + } + + @property({ attribute: 'node-id' }) + nodeId?: string; + + @property({ attribute: false }) + dynamicRoot?: UmbContentPickerDynamicRoot; + + @state() + _options: Array