diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs deleted file mode 100644 index 09a3e410fde5..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Security; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentAppFactoryCollection : BuilderCollectionBase -{ - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly ILogger _logger; - - public ContentAppFactoryCollection( - Func> items, - ILogger logger, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor) - : base(items) - { - _logger = logger; - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - } - - public IEnumerable GetContentAppsFor(object o, IEnumerable? userGroups = null) - { - IEnumerable roles = GetCurrentUserGroups(); - - var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); - - var aliases = new HashSet(); - List? dups = null; - - foreach (ContentApp app in apps) - { - if (app.Alias is not null) - { - if (aliases.Contains(app.Alias)) - { - (dups ??= new List()).Add(app.Alias); - } - else - { - aliases.Add(app.Alias); - } - } - } - - if (dups != null) - { - // dying is not user-friendly, so let's write to log instead, and wish people read logs... - - // throw new InvalidOperationException($"Duplicate content app aliases found: {string.Join(",", dups)}"); - _logger.LogWarning("Duplicate content app aliases found: {DuplicateAliases}", string.Join(",", dups)); - } - - return apps; - } - - private IEnumerable GetCurrentUserGroups() - { - IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - return currentUser == null - ? Enumerable.Empty() - : currentUser.Groups; - } -} diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs deleted file mode 100644 index 002fe156281b..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Security; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentAppFactoryCollectionBuilder : OrderedCollectionBuilderBase -{ - protected override ContentAppFactoryCollectionBuilder This => this; - - // need to inject dependencies in the collection, so override creation - public override ContentAppFactoryCollection CreateCollection(IServiceProvider factory) - { - // get the logger factory just-in-time - see note below for manifest parser - ILoggerFactory loggerFactory = factory.GetRequiredService(); - IBackOfficeSecurityAccessor backOfficeSecurityAccessor = - factory.GetRequiredService(); - return new ContentAppFactoryCollection(() => CreateItems(factory), loggerFactory.CreateLogger(), backOfficeSecurityAccessor); - } - - protected override IEnumerable CreateItems(IServiceProvider factory) - { - // get the manifest parser just-in-time - injecting it in the ctor would mean that - // simply getting the builder in order to configure the collection, would require - // its dependencies too, and that can create cycles or other oddities - ILegacyManifestParser legacyManifestParser = factory.GetRequiredService(); - IIOHelper ioHelper = factory.GetRequiredService(); - return base.CreateItems(factory) - .Concat(legacyManifestParser.CombinedManifest.ContentApps.Select(x => - new LegacyManifestContentAppFactory(x, ioHelper))); - } -} diff --git a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs deleted file mode 100644 index 2ee435300588..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentEditorContentAppFactory : IContentAppFactory -{ - // see note on ContentApp - internal const int Weight = -100; - - private ContentApp? _contentApp; - private ContentApp? _mediaApp; - private ContentApp? _memberApp; - - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) - { - switch (o) - { - case IContent content when content.Properties.Count > 0: - return _contentApp ??= new ContentApp - { - Alias = "umbContent", - Name = "Content", - Icon = Constants.Icons.Content, - View = "views/content/apps/content/content.html", - Weight = Weight, - }; - - case IMedia media when media.ContentType.ListView is null || media.Properties.Count > 0: - return _mediaApp ??= new ContentApp - { - Alias = "umbContent", - Name = "Content", - Icon = Constants.Icons.Content, - View = "views/media/apps/content/content.html", - Weight = Weight, - }; - - case IMember _: - return _memberApp ??= new ContentApp - { - Alias = "umbContent", - Name = "Content", - Icon = Constants.Icons.Content, - View = "views/member/apps/content/content.html", - Weight = Weight, - }; - - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs deleted file mode 100644 index 1e318e380ee6..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentInfoContentAppFactory : IContentAppFactory -{ - // see note on ContentApp - private const int Weight = +100; - - private ContentApp? _contentApp; - private ContentApp? _mediaApp; - private ContentApp? _memberApp; - - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) - { - switch (o) - { - case IContent _: - return _contentApp ??= new ContentApp - { - Alias = "umbInfo", - Name = "Info", - Icon = "icon-info", - View = "views/content/apps/info/info.html", - Weight = Weight, - }; - - case IMedia _: - return _mediaApp ??= new ContentApp - { - Alias = "umbInfo", - Name = "Info", - Icon = "icon-info", - View = "views/media/apps/info/info.html", - Weight = Weight, - }; - case IMember _: - return _memberApp ??= new ContentApp - { - Alias = "umbInfo", - Name = "Info", - Icon = "icon-info", - View = "views/member/apps/info/info.html", - Weight = Weight, - }; - - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs deleted file mode 100644 index 5e4f6a7a888a..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentTypeDesignContentAppFactory : IContentAppFactory -{ - private const int Weight = -200; - - private ContentApp? _contentTypeApp; - - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) - { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp - { - Alias = "design", - Name = "Design", - Icon = "icon-document-dashed-line", - View = "views/documentTypes/views/design/design.html", - Weight = Weight, - }; - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs deleted file mode 100644 index 8aed04050f6f..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentTypeListViewContentAppFactory : IContentAppFactory -{ - private const int Weight = -180; - - private ContentApp? _contentTypeApp; - - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) - { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp - { - Alias = "listView", - Name = "List view", - Icon = "icon-list", - View = "views/documentTypes/views/listview/listview.html", - Weight = Weight, - }; - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs deleted file mode 100644 index b585a7db4d08..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentTypePermissionsContentAppFactory : IContentAppFactory -{ - private const int Weight = -160; - - private ContentApp? _contentTypeApp; - - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) - { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp - { - Alias = "permissions", - Name = "Permissions", - Icon = "icon-keychain", - View = "views/documentTypes/views/permissions/permissions.html", - Weight = Weight, - }; - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs deleted file mode 100644 index 712e1e7c1e0f..000000000000 --- a/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ContentTypeTemplatesContentAppFactory : IContentAppFactory -{ - private const int Weight = -140; - - private ContentApp? _contentTypeApp; - - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) - { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp - { - Alias = "templates", - Name = "Templates", - Icon = "icon-layout", - View = "views/documentTypes/views/templates/templates.html", - Weight = Weight, - }; - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs deleted file mode 100644 index 21bfcfcef024..000000000000 --- a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -internal class DictionaryContentAppFactory : IContentAppFactory -{ - private const int Weight = -100; - - private ContentApp? _dictionaryApp; - - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) - { - switch (source) - { - case IDictionaryItem _: - return _dictionaryApp ??= new ContentApp - { - Alias = "dictionaryContent", - Name = "Content", - Icon = "icon-document", - View = "views/dictionary/views/content/content.html", - Weight = Weight, - }; - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs deleted file mode 100644 index 8a92660ee50b..000000000000 --- a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs +++ /dev/null @@ -1,158 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.ContentApps; - -public class ListViewContentAppFactory : IContentAppFactory -{ - // see note on ContentApp - private const int Weight = -666; - - private readonly IDataTypeService _dataTypeService; - private readonly PropertyEditorCollection _propertyEditors; - - public ListViewContentAppFactory(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) - { - _dataTypeService = dataTypeService; - _propertyEditors = propertyEditors; - } - - public static ContentApp CreateContentApp( - IDataTypeService dataTypeService, - PropertyEditorCollection propertyEditors, - string entityType, - string contentTypeAlias, - int defaultListViewDataType) - { - if (dataTypeService == null) - { - throw new ArgumentNullException(nameof(dataTypeService)); - } - - if (propertyEditors == null) - { - throw new ArgumentNullException(nameof(propertyEditors)); - } - - if (string.IsNullOrWhiteSpace(entityType)) - { - throw new ArgumentException("message", nameof(entityType)); - } - - if (string.IsNullOrWhiteSpace(contentTypeAlias)) - { - throw new ArgumentException("message", nameof(contentTypeAlias)); - } - - if (defaultListViewDataType == default) - { - throw new ArgumentException("defaultListViewDataType", nameof(defaultListViewDataType)); - } - - var contentApp = new ContentApp - { - Alias = "umbListView", - Name = "Child items", - Icon = "icon-list", - View = "views/content/apps/listview/listview.html", - Weight = Weight, - }; - - var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; - - // first try to get the custom one if there is one - IDataType? dt = dataTypeService.GetDataType(customDtdName) - ?? dataTypeService.GetDataType(defaultListViewDataType); - - if (dt == null) - { - throw new InvalidOperationException( - "No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); - } - - IDataEditor? editor = propertyEditors[dt.EditorAlias]; - if (editor == null) - { - throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); - } - - IDictionary listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.ConfigurationData); - - // add the entity type to the config - listViewConfig["entityType"] = entityType; - - // Override Tab Label if tabName is provided - if (listViewConfig.ContainsKey("tabName")) - { - var configTabName = listViewConfig["tabName"]; - if (string.IsNullOrWhiteSpace(configTabName?.ToString()) == false) - { - contentApp.Name = configTabName.ToString(); - } - } - - // Override Icon if icon is provided - if (listViewConfig.ContainsKey("icon")) - { - var configIcon = listViewConfig["icon"]; - if (string.IsNullOrWhiteSpace(configIcon?.ToString()) == false) - { - contentApp.Icon = configIcon.ToString(); - } - } - - // if the list view is configured to show umbContent first, update the list view content app weight accordingly - if (listViewConfig.ContainsKey("showContentFirst") && - listViewConfig["showContentFirst"]?.ToString().TryConvertTo().Result == true) - { - contentApp.Weight = ContentEditorContentAppFactory.Weight + 1; - } - - // This is the view model used for the list view app - contentApp.ViewModel = new List - { - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", - Label = string.Empty, - Value = null, - Config = listViewConfig, - }, - }; - - return contentApp; - } - - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) - { - string contentTypeAlias, entityType; - int dtdId; - - switch (o) - { - case IContent content when content.ContentType.ListView is null: - return null; - case IContent content: - contentTypeAlias = content.ContentType.Alias; - entityType = "content"; - dtdId = Constants.DataTypes.DefaultContentListView; - break; - case IMedia media when media.ContentType.ListView is null && - media.ContentType.Alias != Constants.Conventions.MediaTypes.Folder: - return null; - case IMedia media: - contentTypeAlias = media.ContentType.Alias; - entityType = "media"; - dtdId = Constants.DataTypes.DefaultMediaListView; - break; - default: - return null; - } - - return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); - } -} diff --git a/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs b/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs deleted file mode 100644 index 5ba19cabb042..000000000000 --- a/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.ContentApps; - -internal class MemberEditorContentAppFactory : IContentAppFactory -{ - // see note on ContentApp - internal const int Weight = +50; - - private ContentApp? _memberApp; - - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) - { - switch (source) - { - case IMember _: - return _memberApp ??= new ContentApp - { - Alias = "umbMembership", - Name = "Member", - Icon = "icon-user", - View = "views/member/apps/membership/membership.html", - Weight = Weight, - }; - - default: - return null; - } - } -} diff --git a/src/Umbraco.Core/Dashboards/AccessRule.cs b/src/Umbraco.Core/Dashboards/AccessRule.cs deleted file mode 100644 index eb7383f60169..000000000000 --- a/src/Umbraco.Core/Dashboards/AccessRule.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Dashboards; - -/// -/// Implements . -/// -public class AccessRule : IAccessRule -{ - /// - public AccessRuleType Type { get; set; } = AccessRuleType.Unknown; - - /// - public string? Value { get; set; } -} diff --git a/src/Umbraco.Core/Dashboards/AccessRuleType.cs b/src/Umbraco.Core/Dashboards/AccessRuleType.cs deleted file mode 100644 index 63d92fc38ac8..000000000000 --- a/src/Umbraco.Core/Dashboards/AccessRuleType.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Umbraco.Cms.Core.Dashboards; - -/// -/// Defines dashboard access rules type. -/// -public enum AccessRuleType -{ - /// - /// Unknown (default value). - /// - Unknown = 0, - - /// - /// Grant access to the dashboard if user belongs to the specified user group. - /// - Grant, - - /// - /// Deny access to the dashboard if user belongs to the specified user group. - /// - Deny, - - /// - /// Grant access to the dashboard if user has access to the specified section. - /// - GrantBySection, -} diff --git a/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs b/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs deleted file mode 100644 index ca56204f5c20..000000000000 --- a/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Umbraco.Cms.Core.Dashboards; - -public class AnalyticsDashboard : IDashboard -{ - public string Alias => "settingsAnalytics"; - - public string[] Sections => new[] { Constants.Applications.Settings }; - - public string View => "views/dashboard/settings/analytics.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/ContentDashboard.cs b/src/Umbraco.Core/Dashboards/ContentDashboard.cs deleted file mode 100644 index fecb23aabac0..000000000000 --- a/src/Umbraco.Core/Dashboards/ContentDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(10)] -public class ContentDashboard : IDashboard -{ - public string Alias => "contentIntro"; - - public string[] Sections => new[] { Constants.Applications.Content }; - - public string View => "views/dashboard/default/startupdashboardintro.html"; - - public IAccessRule[] AccessRules { get; } = Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/DashboardCollection.cs b/src/Umbraco.Core/Dashboards/DashboardCollection.cs deleted file mode 100644 index ebcf79fc7f1b..000000000000 --- a/src/Umbraco.Core/Dashboards/DashboardCollection.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -public class DashboardCollection : BuilderCollectionBase -{ - public DashboardCollection(Func> items) - : base(items) - { - } -} diff --git a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs deleted file mode 100644 index 3e3fed260f49..000000000000 --- a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Manifest; - -namespace Umbraco.Cms.Core.Dashboards; - -public class DashboardCollectionBuilder : WeightedCollectionBuilderBase -{ - protected override DashboardCollectionBuilder This => this; - - protected override IEnumerable CreateItems(IServiceProvider factory) - { - // get the manifest parser just-in-time - injecting it in the ctor would mean that - // simply getting the builder in order to configure the collection, would require - // its dependencies too, and that can create cycles or other oddities - ILegacyManifestParser legacyManifestParser = factory.GetRequiredService(); - - IEnumerable dashboardSections = - Merge(base.CreateItems(factory), legacyManifestParser.CombinedManifest.Dashboards); - - return dashboardSections; - } - - private IEnumerable Merge( - IEnumerable dashboardsFromCode, - IReadOnlyList dashboardFromManifest) => - dashboardsFromCode.Concat(dashboardFromManifest) - .Where(x => !string.IsNullOrEmpty(x.Alias)) - .OrderBy(GetWeight); - - private int GetWeight(IDashboard dashboard) - { - switch (dashboard) - { - case LegacyManifestDashboard manifestDashboardDefinition: - return manifestDashboardDefinition.Weight; - - default: - return GetWeight(dashboard.GetType()); - } - } -} diff --git a/src/Umbraco.Core/Dashboards/DashboardSlim.cs b/src/Umbraco.Core/Dashboards/DashboardSlim.cs deleted file mode 100644 index a79392c0d05d..000000000000 --- a/src/Umbraco.Core/Dashboards/DashboardSlim.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Dashboards; - -[DataContract(IsReference = true)] -public class DashboardSlim : IDashboardSlim -{ - public string? Alias { get; set; } - - public string? View { get; set; } -} diff --git a/src/Umbraco.Core/Dashboards/ExamineDashboard.cs b/src/Umbraco.Core/Dashboards/ExamineDashboard.cs deleted file mode 100644 index c4a68982a92c..000000000000 --- a/src/Umbraco.Core/Dashboards/ExamineDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(20)] -public class ExamineDashboard : IDashboard -{ - public string Alias => "settingsExamine"; - - public string[] Sections => new[] { Constants.Applications.Settings }; - - public string View => "views/dashboard/settings/examinemanagement.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/FormsDashboard.cs b/src/Umbraco.Core/Dashboards/FormsDashboard.cs deleted file mode 100644 index 414655348468..000000000000 --- a/src/Umbraco.Core/Dashboards/FormsDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(10)] -public class FormsDashboard : IDashboard -{ - public string Alias => "formsInstall"; - - public string[] Sections => new[] { Constants.Applications.Forms }; - - public string View => "views/dashboard/forms/formsdashboardintro.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs b/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs deleted file mode 100644 index 2804372d7f98..000000000000 --- a/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(50)] -public class HealthCheckDashboard : IDashboard -{ - public string Alias => "settingsHealthCheck"; - - public string[] Sections => new[] { Constants.Applications.Settings }; - - public string View => "views/dashboard/settings/healthcheck.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/IAccessRule.cs b/src/Umbraco.Core/Dashboards/IAccessRule.cs deleted file mode 100644 index fcd78ebc9b9a..000000000000 --- a/src/Umbraco.Core/Dashboards/IAccessRule.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Umbraco.Cms.Core.Dashboards; - -/// -/// Represents an access rule. -/// -public interface IAccessRule -{ - /// - /// Gets or sets the rule type. - /// - AccessRuleType Type { get; set; } - - /// - /// Gets or sets the value for the rule. - /// - string? Value { get; set; } -} diff --git a/src/Umbraco.Core/Dashboards/IDashboard.cs b/src/Umbraco.Core/Dashboards/IDashboard.cs deleted file mode 100644 index 96e29d05393a..000000000000 --- a/src/Umbraco.Core/Dashboards/IDashboard.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Dashboards; - -/// -/// Represents a dashboard. -/// -public interface IDashboard : IDashboardSlim -{ - /// - /// Gets the aliases of sections/applications where this dashboard appears. - /// - /// - /// - /// This field is *not* needed by the UI and therefore we want to exclude - /// it from serialization, but it is deserialized as part of the manifest, - /// therefore we cannot plainly ignore it. - /// - /// - /// So, it has to remain a data member, plus we use our special - /// JsonDontSerialize attribute (see attribute for more details). - /// - /// - [DataMember(Name = "sections")] - string[] Sections { get; } - - /// - /// Gets the access rule determining the visibility of the dashboard. - /// - /// - /// - /// This field is *not* needed by the UI and therefore we want to exclude - /// it from serialization, but it is deserialized as part of the manifest, - /// therefore we cannot plainly ignore it. - /// - /// - /// So, it has to remain a data member, plus we use our special - /// JsonDontSerialize attribute (see attribute for more details). - /// - /// - [DataMember(Name = "access")] - IAccessRule[] AccessRules { get; } -} diff --git a/src/Umbraco.Core/Dashboards/IDashboardSlim.cs b/src/Umbraco.Core/Dashboards/IDashboardSlim.cs deleted file mode 100644 index c3907b1af4f5..000000000000 --- a/src/Umbraco.Core/Dashboards/IDashboardSlim.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Dashboards; - -/// -/// Represents a dashboard with only minimal data. -/// -public interface IDashboardSlim -{ - /// - /// Gets the alias of the dashboard. - /// - [DataMember(Name = "alias")] - string? Alias { get; } - - /// - /// Gets the view used to render the dashboard. - /// - [DataMember(Name = "view")] - string? View { get; } -} diff --git a/src/Umbraco.Core/Dashboards/MediaDashboard.cs b/src/Umbraco.Core/Dashboards/MediaDashboard.cs deleted file mode 100644 index 122b886b5057..000000000000 --- a/src/Umbraco.Core/Dashboards/MediaDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(10)] -public class MediaDashboard : IDashboard -{ - public string Alias => "mediaFolderBrowser"; - - public string[] Sections => new[] { Constants.Applications.Media }; - - public string View => "views/dashboard/media/mediafolderbrowser.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/MembersDashboard.cs b/src/Umbraco.Core/Dashboards/MembersDashboard.cs deleted file mode 100644 index 0394fda4a09a..000000000000 --- a/src/Umbraco.Core/Dashboards/MembersDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(10)] -public class MembersDashboard : IDashboard -{ - public string Alias => "memberIntro"; - - public string[] Sections => new[] { Constants.Applications.Members }; - - public string View => "views/dashboard/members/membersdashboardvideos.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs b/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs deleted file mode 100644 index 30bcd202c248..000000000000 --- a/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(40)] -public class ModelsBuilderDashboard : IDashboard -{ - public string Alias => "settingsModelsBuilder"; - - public string[] Sections => new[] {Constants.Applications.Settings }; - - public string View => "views/dashboard/settings/modelsbuildermanagement.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs b/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs deleted file mode 100644 index ef6edd51e740..000000000000 --- a/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(60)] -public class ProfilerDashboard : IDashboard -{ - public string Alias => "settingsProfiler"; - - public string[] Sections => new[] { Constants.Applications.Settings }; - - public string View => "views/dashboard/settings/profiler.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs b/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs deleted file mode 100644 index c852d79adbc4..000000000000 --- a/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(30)] -public class PublishedStatusDashboard : IDashboard -{ - public string Alias => "settingsPublishedStatus"; - - public string[] Sections => new[] { Constants.Applications.Settings }; - - public string View => "views/dashboard/settings/publishedstatus.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs b/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs deleted file mode 100644 index 839494c5d9ba..000000000000 --- a/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(20)] -public class RedirectUrlDashboard : IDashboard -{ - public string Alias => "contentRedirectManager"; - - public string[] Sections => new[] { Constants.Applications.Content }; - - public string View => "views/dashboard/content/redirecturls.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/Dashboards/SettingsDashboards.cs b/src/Umbraco.Core/Dashboards/SettingsDashboards.cs deleted file mode 100644 index f46c52ba9083..000000000000 --- a/src/Umbraco.Core/Dashboards/SettingsDashboards.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Dashboards; - -[Weight(10)] -public class SettingsDashboard : IDashboard -{ - public string Alias => "settingsWelcome"; - - public string[] Sections => new[] { Constants.Applications.Settings }; - - public string View => "views/dashboard/settings/settingsdashboardintro.html"; - - public IAccessRule[] AccessRules => Array.Empty(); -} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs index bec4c3929862..af42e0993630 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs @@ -1,12 +1,8 @@ using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.DynamicRoot.QuerySteps; -using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Media; -using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Sections; using Umbraco.Cms.Core.Webhooks; namespace Umbraco.Cms.Core.DependencyInjection; @@ -32,22 +28,6 @@ public static IUmbracoBuilder AddComponent(this IUmbracoBuilder builder) return builder; } - /// - /// Register a content app. - /// - /// The type of the content app. - /// The builder. - /// - /// The builder. - /// - public static IUmbracoBuilder AddContentApp(this IUmbracoBuilder builder) - where T : IContentAppFactory - { - builder.ContentApps().Append(); - - return builder; - } - /// /// Register a content finder. /// @@ -64,38 +44,6 @@ public static IUmbracoBuilder AddContentFinder(this IUmbracoBuilder builder) return builder; } - /// - /// Register a dashboard. - /// - /// The type of the dashboard. - /// The builder. - /// - /// The builder. - /// - public static IUmbracoBuilder AddDashboard(this IUmbracoBuilder builder) - where T : IDashboard - { - builder.Dashboards().Add(); - - return builder; - } - - /// - /// Register a manifest filter. - /// - /// The type of the manifest filter. - /// The Builder. - /// - /// The builder. - /// - public static IUmbracoBuilder AddManifestFilter(this IUmbracoBuilder builder) - where T : class, ILegacyManifestFilter - { - builder.ManifestFilters().Append(); - - return builder; - } - /// /// Register a media URL provider. /// @@ -128,21 +76,6 @@ public static IUmbracoBuilder AddEmbedProvider(this IUmbracoBuilder builder) return builder; } - /// - /// Register a section. - /// - /// The type of the section. - /// The builder. - /// - /// The builder. - /// - public static IUmbracoBuilder AddSection(this IUmbracoBuilder builder) - where T : ISection - { - builder.Sections().Append(); - - return builder; - } /// /// Register a URL provider. diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 66415f88eee9..3b00039812a6 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -1,21 +1,17 @@ using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.ContentApps; -using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.DeliveryApi; using Umbraco.Cms.Core.DynamicRoot.Origin; using Umbraco.Cms.Core.DynamicRoot.QuerySteps; using Umbraco.Cms.Core.Editors; using Umbraco.Cms.Core.HealthChecks; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; -using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Media.EmbedProviders; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Sections; using Umbraco.Cms.Core.Snippets; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Tour; @@ -40,18 +36,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) builder.DataEditors().Add(builder.TypeLoader.GetDataEditors); builder.Actions().Add(builder.TypeLoader.GetActions); - // register known content apps - builder.ContentApps() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - // all built-in finders in the correct order, // devs can then modify this list on application startup builder.ContentFinders() @@ -70,16 +54,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Append(); builder.MediaUrlProviders() .Append(); - // register back office sections in the order we want them rendered - builder.Sections() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); builder.DynamicRootOriginFinders() .Append() @@ -95,20 +69,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Append(); builder.Components(); - // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards - builder.Dashboards() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(builder.TypeLoader.GetTypes()); builder.PartialViewSnippets(); builder.DataValueReferenceFactories(); builder.PropertyValueConverters().Append(builder.TypeLoader.GetTypes()); @@ -120,7 +80,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Add() .Add() .Add(); - builder.ManifestFilters(); builder.MediaUrlGenerators(); // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable builder.EmbedProviders() @@ -154,13 +113,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - /// - /// Gets the content apps collection builder. - /// - /// The builder. - public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - /// /// Gets the content finders collection builder. /// @@ -205,13 +157,6 @@ public static UrlProviderCollectionBuilder UrlProviders(this IUmbracoBuilder bui public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - /// - /// Gets the backoffice sections/applications collection builder. - /// - /// The builder. - public static SectionCollectionBuilder Sections(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - public static DynamicRootOriginFinderCollectionBuilder DynamicRootOriginFinders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); @@ -230,13 +175,6 @@ public static DynamicRootQueryStepCollectionBuilder DynamicRootSteps(this IUmbra public static ComponentCollectionBuilder Components(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - /// - /// Gets the backoffice dashboards collection builder. - /// - /// The builder. - public static DashboardCollectionBuilder Dashboards(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - /// /// Gets the partial view snippets collection builder. /// @@ -293,13 +231,6 @@ public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbr internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - /// - /// Gets the manifest filter collection builder. - /// - /// The builder. - public static LegacyManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - /// /// Gets the content finders collection builder. /// diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 39e412d6435a..08586120c079 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -194,7 +194,6 @@ private void AddCoreServices() Services.AddSingleton(); - Services.AddUnique(); Services.AddSingleton(); // will be injected in controllers when needed to invoke rest endpoints on Our @@ -219,8 +218,6 @@ private void AddCoreServices() Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/IO/ILegacyPackageManifestFileProviderFactory.cs b/src/Umbraco.Core/IO/ILegacyPackageManifestFileProviderFactory.cs deleted file mode 100644 index 39414a87d5b2..000000000000 --- a/src/Umbraco.Core/IO/ILegacyPackageManifestFileProviderFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.Extensions.FileProviders; - -namespace Umbraco.Cms.Core.IO; - -/// -/// Factory for creating instances for providing the package.manifest file. -/// -public interface ILegacyPackageManifestFileProviderFactory : IFileProviderFactory -{ -} diff --git a/src/Umbraco.Core/Manifest/CompositeLegacyPackageManifest.cs b/src/Umbraco.Core/Manifest/CompositeLegacyPackageManifest.cs deleted file mode 100644 index 2692b0b9530a..000000000000 --- a/src/Umbraco.Core/Manifest/CompositeLegacyPackageManifest.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Umbraco.Cms.Core.PropertyEditors; - -namespace Umbraco.Cms.Core.Manifest; - -/// -/// A package manifest made up of all combined manifests -/// -public class CompositeLegacyPackageManifest -{ - public CompositeLegacyPackageManifest( - IReadOnlyList propertyEditors, - IReadOnlyList parameterEditors, - IReadOnlyList contentApps, - IReadOnlyList dashboards, - IReadOnlyList sections, - IReadOnlyDictionary> scripts, - IReadOnlyDictionary> stylesheets) - { - PropertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); - ParameterEditors = parameterEditors ?? throw new ArgumentNullException(nameof(parameterEditors)); - ContentApps = contentApps ?? throw new ArgumentNullException(nameof(contentApps)); - Dashboards = dashboards ?? throw new ArgumentNullException(nameof(dashboards)); - Sections = sections ?? throw new ArgumentNullException(nameof(sections)); - Scripts = scripts ?? throw new ArgumentNullException(nameof(scripts)); - Stylesheets = stylesheets ?? throw new ArgumentNullException(nameof(stylesheets)); - } - - /// - /// Gets or sets the property editors listed in the manifest. - /// - public IReadOnlyList PropertyEditors { get; } - - /// - /// Gets or sets the parameter editors listed in the manifest. - /// - public IReadOnlyList ParameterEditors { get; } - - /// - /// Gets or sets the content apps listed in the manifest. - /// - public IReadOnlyList ContentApps { get; } - - /// - /// Gets or sets the dashboards listed in the manifest. - /// - public IReadOnlyList Dashboards { get; } - - /// - /// Gets or sets the sections listed in the manifest. - /// - public IReadOnlyCollection Sections { get; } - - public IReadOnlyDictionary> Scripts { get; } - - public IReadOnlyDictionary> Stylesheets { get; } -} diff --git a/src/Umbraco.Core/Manifest/ILegacyManifestFilter.cs b/src/Umbraco.Core/Manifest/ILegacyManifestFilter.cs deleted file mode 100644 index fd72466cbc70..000000000000 --- a/src/Umbraco.Core/Manifest/ILegacyManifestFilter.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Umbraco.Cms.Core.Manifest; - -/// -/// Provides filtering for package manifests. -/// -public interface ILegacyManifestFilter -{ - /// - /// Filters package manifests. - /// - /// The package manifests. - /// - /// It is possible to remove, change, or add manifests. - /// - void Filter(List manifests); -} diff --git a/src/Umbraco.Core/Manifest/ILegacyManifestParser.cs b/src/Umbraco.Core/Manifest/ILegacyManifestParser.cs deleted file mode 100644 index 0a854774b314..000000000000 --- a/src/Umbraco.Core/Manifest/ILegacyManifestParser.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Umbraco.Cms.Core.Manifest; - -public interface ILegacyManifestParser -{ - string AppPluginsPath { get; set; } - - /// - /// Gets all manifests, merged into a single manifest object. - /// - /// - CompositeLegacyPackageManifest CombinedManifest { get; } - - /// - /// Parses a manifest. - /// - LegacyPackageManifest ParseManifest(string text); - - /// - /// Returns all package individual manifests - /// - /// - IEnumerable GetManifests(); -} diff --git a/src/Umbraco.Core/Manifest/ILegacyPackageManifest.cs b/src/Umbraco.Core/Manifest/ILegacyPackageManifest.cs deleted file mode 100644 index 073290d40587..000000000000 --- a/src/Umbraco.Core/Manifest/ILegacyPackageManifest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Runtime.Serialization; -using Umbraco.Cms.Core.PropertyEditors; - -namespace Umbraco.Cms.Core.Manifest; - -public interface ILegacyPackageManifest -{ - /// - /// Gets the source path of the manifest. - /// - /// - /// - /// Gets the full absolute file path of the manifest, - /// using system directory separators. - /// - /// - string Source { get; set; } - - /// - /// Gets or sets the scripts listed in the manifest. - /// - [DataMember(Name = "javascript")] - string[] Scripts { get; set; } - - /// - /// Gets or sets the stylesheets listed in the manifest. - /// - [DataMember(Name = "css")] - string[] Stylesheets { get; set; } - - /// - /// Gets or sets the property editors listed in the manifest. - /// - [DataMember(Name = "propertyEditors")] - IDataEditor[] PropertyEditors { get; set; } - - /// - /// Gets or sets the parameter editors listed in the manifest. - /// - [DataMember(Name = "parameterEditors")] - IDataEditor[] ParameterEditors { get; set; } - - /// - /// Gets or sets the content apps listed in the manifest. - /// - [DataMember(Name = "contentApps")] - LegacyManifestContentAppDefinition[] ContentApps { get; set; } - - /// - /// Gets or sets the dashboards listed in the manifest. - /// - [DataMember(Name = "dashboards")] - LegacyManifestDashboard[] Dashboards { get; set; } - - /// - /// Gets or sets the sections listed in the manifest. - /// - [DataMember(Name = "sections")] - LegacyManifestSection[] Sections { get; set; } -} diff --git a/src/Umbraco.Core/Manifest/LegacyManifestAssets.cs b/src/Umbraco.Core/Manifest/LegacyManifestAssets.cs deleted file mode 100644 index 05b0123a0d17..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyManifestAssets.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Umbraco.Cms.Core.Manifest; - -public class LegacyManifestAssets -{ - public LegacyManifestAssets(string? packageName, IReadOnlyList assets) - { - PackageName = packageName ?? throw new ArgumentNullException(nameof(packageName)); - Assets = assets ?? throw new ArgumentNullException(nameof(assets)); - } - - public string PackageName { get; } - - public IReadOnlyList Assets { get; } -} diff --git a/src/Umbraco.Core/Manifest/LegacyManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/LegacyManifestContentAppDefinition.cs deleted file mode 100644 index 914923ec31a3..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyManifestContentAppDefinition.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Manifest; - -// contentApps: [ -// { -// name: 'App Name', // required -// alias: 'appAlias', // required -// weight: 0, // optional, default is 0, use values between -99 and +99 -// icon: 'icon.app', // required -// view: 'path/view.htm', // required -// show: [ // optional, default is always show -// '-content/foo', // hide for content type 'foo' -// '+content/*', // show for all other content types -// '+media/*', // show for all media types -// '+role/admin' // show for admin users. Role based permissions will override others. -// ] -// }, -// ... -// ] - -/// -/// Represents a content app definition, parsed from a manifest. -/// -/// Is used to create an actual . -[DataContract(Name = "appdef", Namespace = "")] -public class LegacyManifestContentAppDefinition -{ - private readonly string? _view; - - /// - /// Gets or sets the name of the content app. - /// - [DataMember(Name = "name")] - public string? Name { get; set; } - - /// - /// Gets or sets the unique alias of the content app. - /// - /// - /// Must be a valid javascript identifier, ie no spaces etc. - /// - [DataMember(Name = "alias")] - public string? Alias { get; set; } - - /// - /// Gets or sets the weight of the content app. - /// - [DataMember(Name = "weight")] - public int Weight { get; set; } - - /// - /// Gets or sets the icon of the content app. - /// - /// - /// Must be a valid helveticons class name (see http://hlvticons.ch/). - /// - [DataMember(Name = "icon")] - public string? Icon { get; set; } - - /// - /// Gets or sets the view for rendering the content app. - /// - [DataMember(Name = "view")] - public string? View { get; set; } - - /// - /// Gets or sets the list of 'show' conditions for the content app. - /// - [DataMember(Name = "show")] - public string[] Show { get; set; } = Array.Empty(); -} diff --git a/src/Umbraco.Core/Manifest/LegacyManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/LegacyManifestContentAppFactory.cs deleted file mode 100644 index 56795f7e5983..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyManifestContentAppFactory.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System.Text.RegularExpressions; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Manifest; - -// contentApps: [ -// { -// name: 'App Name', // required -// alias: 'appAlias', // required -// weight: 0, // optional, default is 0, use values between -99 and +99 -// icon: 'icon.app', // required -// view: 'path/view.htm', // required -// show: [ // optional, default is always show -// '-content/foo', // hide for content type 'foo' -// '+content/*', // show for all other content types -// '+media/*', // show for all media types -// '-member/foo' // hide for member type 'foo' -// '+member/*' // show for all member types -// '+role/admin' // show for admin users. Role based permissions will override others. -// ] -// }, -// ... -// ] - -/// -/// Represents a content app factory, for content apps parsed from the manifest. -/// -public class LegacyManifestContentAppFactory : IContentAppFactory -{ - private readonly LegacyManifestContentAppDefinition _definition; - private readonly IIOHelper _ioHelper; - - private ContentApp? _app; - private ShowRule[]? _showRules; - - public LegacyManifestContentAppFactory(LegacyManifestContentAppDefinition definition, IIOHelper ioHelper) - { - _definition = definition; - _ioHelper = ioHelper; - } - - /// - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) - { - string? partA, partB; - - switch (o) - { - case IContent content: - partA = "content"; - partB = content.ContentType.Alias; - break; - - case IMedia media: - partA = "media"; - partB = media.ContentType.Alias; - break; - case IMember member: - partA = "member"; - partB = member.ContentType.Alias; - break; - case IContentType contentType: - partA = "contentType"; - partB = contentType?.Alias; - break; - case IDictionaryItem _: - partA = "dictionary"; - partB = "*"; // Not really a different type for dictionary items - break; - - default: - return null; - } - - ShowRule[] rules = _showRules ??= ShowRule.Parse(_definition.Show).ToArray(); - var userGroupsList = userGroups.ToList(); - - var okRole = false; - var hasRole = false; - var okType = false; - var hasType = false; - - foreach (ShowRule rule in rules) - { - if (rule.PartA?.InvariantEquals("role") ?? false) - { - // if roles have been ok-ed already, skip the rule - if (okRole) - { - continue; - } - - // remember we have role rules - hasRole = true; - - foreach (IReadOnlyUserGroup group in userGroupsList) - { - // if the entry does not apply, skip - if (!rule.Matches("role", group.Alias)) - { - continue; - } - - // if the entry applies, - // if it's an exclude entry, exit, do not display the content app - if (!rule.Show) - { - return null; - } - - // else ok to display, remember roles are ok, break from userGroupsList - okRole = rule.Show; - break; - } - } - - // it is a type rule - else - { - // if type has been ok-ed already, skip the rule - if (okType) - { - continue; - } - - // remember we have type rules - hasType = true; - - // if the entry does not apply, skip it - if (!rule.Matches(partA, partB)) - { - continue; - } - - // if the entry applies, - // if it's an exclude entry, exit, do not display the content app - if (!rule.Show) - { - return null; - } - - // else ok to display, remember type rules are ok - okType = true; - } - } - - // if roles rules are specified but not ok, - // or if type roles are specified but not ok, - // cannot display the content app - if ((hasRole && !okRole) || (hasType && !okType)) - { - return null; - } - - // else - // content app can be displayed - return _app ??= new ContentApp - { - Alias = _definition.Alias, - Name = _definition.Name, - Icon = _definition.Icon, - View = _ioHelper.ResolveRelativeOrVirtualUrl(_definition.View), - Weight = _definition.Weight, - }; - } - - private class ShowRule - { - private static readonly Regex ShowRegex = new( - "^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public bool Show { get; private set; } - - public string? PartA { get; private set; } - - public string? PartB { get; private set; } - - public static IEnumerable Parse(string[] rules) - { - foreach (var rule in rules) - { - Match match = ShowRegex.Match(rule); - if (!match.Success) - { - throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest."); - } - - yield return new ShowRule - { - Show = match.Groups[1].Value != "-", - PartA = match.Groups[2].Value, - PartB = match.Groups[3].Value, - }; - } - } - - public bool Matches(string? partA, string? partB) => - (PartA == "*" || (PartA?.InvariantEquals(partA) ?? false)) && - (PartB == "*" || (PartB?.InvariantEquals(partB) ?? false)); - } -} diff --git a/src/Umbraco.Core/Manifest/LegacyManifestDashboard.cs b/src/Umbraco.Core/Manifest/LegacyManifestDashboard.cs deleted file mode 100644 index 9cc0ad9091bf..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyManifestDashboard.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Runtime.Serialization; -using Umbraco.Cms.Core.Dashboards; - -namespace Umbraco.Cms.Core.Manifest; - -[DataContract] -public class LegacyManifestDashboard : IDashboard -{ - [DataMember(Name = "weight")] - public int Weight { get; set; } = 100; - - [DataMember(Name = "alias", IsRequired = true)] - public string Alias { get; set; } = null!; - - [DataMember(Name = "view", IsRequired = true)] - public string View { get; set; } = null!; - - [DataMember(Name = "sections")] - public string[] Sections { get; set; } = Array.Empty(); - - [DataMember(Name = "access")] - public IAccessRule[] AccessRules { get; set; } = Array.Empty(); -} diff --git a/src/Umbraco.Core/Manifest/LegacyManifestFilterCollection.cs b/src/Umbraco.Core/Manifest/LegacyManifestFilterCollection.cs deleted file mode 100644 index a2af5ee44071..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyManifestFilterCollection.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Manifest; - -/// -/// Contains the manifest filters. -/// -public class LegacyManifestFilterCollection : BuilderCollectionBase -{ - public LegacyManifestFilterCollection(Func> items) - : base(items) - { - } - - /// - /// Filters package manifests. - /// - /// The package manifests. - public void Filter(List manifests) - { - foreach (ILegacyManifestFilter filter in this) - { - filter.Filter(manifests); - } - } -} diff --git a/src/Umbraco.Core/Manifest/LegacyManifestFilterCollectionBuilder.cs b/src/Umbraco.Core/Manifest/LegacyManifestFilterCollectionBuilder.cs deleted file mode 100644 index ae8c41a9f00c..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyManifestFilterCollectionBuilder.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Manifest; - -public class LegacyManifestFilterCollectionBuilder : OrderedCollectionBuilderBase -{ - protected override LegacyManifestFilterCollectionBuilder This => this; - - // do NOT cache this, it's only used once - protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; -} diff --git a/src/Umbraco.Core/Manifest/LegacyManifestSection.cs b/src/Umbraco.Core/Manifest/LegacyManifestSection.cs deleted file mode 100644 index e0b04465235c..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyManifestSection.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Runtime.Serialization; -using Umbraco.Cms.Core.Sections; - -namespace Umbraco.Cms.Core.Manifest; - -[DataContract(Name = "section", Namespace = "")] -public class LegacyManifestSection : ISection -{ - [DataMember(Name = "alias")] - public string Alias { get; set; } = string.Empty; - - [DataMember(Name = "name")] - public string Name { get; set; } = string.Empty; -} diff --git a/src/Umbraco.Core/Manifest/LegacyPackageManifest.cs b/src/Umbraco.Core/Manifest/LegacyPackageManifest.cs deleted file mode 100644 index ca3028316bd1..000000000000 --- a/src/Umbraco.Core/Manifest/LegacyPackageManifest.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System.Runtime.Serialization; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Manifest; - -/// -/// Represents the content of a package manifest. -/// -[DataContract] -public class LegacyPackageManifest -{ - private string? _packageName; - - /// - /// Gets or sets the package identifier. - /// - /// - /// The package identifier. - /// - [DataMember(Name = "id")] - public string? PackageId { get; set; } - - /// - /// Gets or sets the name of the package. If not specified, uses the package identifier or directory name instead. - /// - /// - /// The name of the package. - /// - [DataMember(Name = "name")] - public string? PackageName - { - get - { - if (!_packageName.IsNullOrWhiteSpace()) - { - return _packageName; - } - - if (!PackageId.IsNullOrWhiteSpace()) - { - _packageName = PackageId; - } - - if (!Source.IsNullOrWhiteSpace()) - { - _packageName = Path.GetFileName(Path.GetDirectoryName(Source)); - } - - return _packageName; - } - set => _packageName = value; - } - - /// - /// Gets or sets the package view. - /// - /// - /// The package view. - /// - [DataMember(Name = "packageView")] - public string? PackageView { get; set; } - - /// - /// Gets or sets the source path of the manifest. - /// - /// - /// The source path. - /// - /// - /// Gets the full/absolute file path of the manifest, using system directory separators. - /// - [IgnoreDataMember] - public string Source { get; set; } = null!; - - /// - /// Gets or sets the version of the package. - /// - /// - /// The version of the package. - /// - [DataMember(Name = "version")] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets the assembly name to get the package version from. - /// - /// - /// The assembly name to get the package version from. - /// - [DataMember(Name = "versionAssemblyName")] - public string? VersionAssemblyName { get; set; } - - /// - /// Gets or sets a value indicating whether telemetry is allowed. - /// - /// - /// true if package telemetry is allowed; otherwise, false. - /// - [DataMember(Name = "allowPackageTelemetry")] - public bool AllowPackageTelemetry { get; set; } = true; - - /// - /// Gets or sets the bundle options. - /// - /// - /// The bundle options. - /// - [DataMember(Name = "bundleOptions")] - public BundleOptions BundleOptions { get; set; } - - /// - /// Gets or sets the scripts listed in the manifest. - /// - /// - /// The scripts. - /// - [DataMember(Name = "javascript")] - public string[] Scripts { get; set; } = Array.Empty(); - - /// - /// Gets or sets the stylesheets listed in the manifest. - /// - /// - /// The stylesheets. - /// - [DataMember(Name = "css")] - public string[] Stylesheets { get; set; } = Array.Empty(); - - /// - /// Gets or sets the property editors listed in the manifest. - /// - /// - /// The property editors. - /// - [DataMember(Name = "propertyEditors")] - public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the parameter editors listed in the manifest. - /// - /// - /// The parameter editors. - /// - [DataMember(Name = "parameterEditors")] - public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the content apps listed in the manifest. - /// - /// - /// The content apps. - /// - [DataMember(Name = "contentApps")] - public LegacyManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); - - /// - /// Gets or sets the dashboards listed in the manifest. - /// - /// - /// The dashboards. - /// - [DataMember(Name = "dashboards")] - public LegacyManifestDashboard[] Dashboards { get; set; } = Array.Empty(); - - /// - /// Gets or sets the sections listed in the manifest. - /// - /// - /// The sections. - /// - [DataMember(Name = "sections")] - public LegacyManifestSection[] Sections { get; set; } = Array.Empty(); -} diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs deleted file mode 100644 index 02c32adc540c..000000000000 --- a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Models.ContentEditing; - -/// -/// Represents a content app. -/// -/// -/// Content apps are editor extensions. -/// -[DataContract(Name = "app", Namespace = "")] -public class ContentApp -{ - /// - /// Gets the name of the content app. - /// - [DataMember(Name = "name")] - public string? Name { get; set; } - - /// - /// Gets the unique alias of the content app. - /// - /// - /// Must be a valid javascript identifier, ie no spaces etc. - /// - [DataMember(Name = "alias")] - public string? Alias { get; set; } - - /// - /// Gets or sets the weight of the content app. - /// - /// - /// Content apps are ordered by weight, from left (lowest values) to right (highest values). - /// Some built-in apps have special weights: listview is -666, content is -100 and infos is +100. - /// - /// The default weight is 0, meaning somewhere in-between content and infos, but weight could - /// be used for ordering between user-level apps, or anything really. - /// - /// - [DataMember(Name = "weight")] - public int Weight { get; set; } - - /// - /// Gets the icon of the content app. - /// - /// - /// Must be a valid helveticons class name (see http://hlvticons.ch/). - /// - [DataMember(Name = "icon")] - public string? Icon { get; set; } - - /// - /// Gets the view for rendering the content app. - /// - [DataMember(Name = "view")] - public string? View { get; set; } - - /// - /// The view model specific to this app - /// - [DataMember(Name = "viewModel")] - public object? ViewModel { get; set; } - - /// - /// Gets a value indicating whether the app is active. - /// - /// - /// Normally reserved for Angular to deal with but in some cases this can be set on the server side. - /// - [DataMember(Name = "active")] - public bool Active { get; set; } - - /// - /// Gets or sets the content app badge. - /// - [DataMember(Name = "badge")] - public ContentAppBadge? Badge { get; set; } -} diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs deleted file mode 100644 index 7a50073d1717..000000000000 --- a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Models.ContentEditing; - -/// -/// Represents a content app badge -/// -[DataContract(Name = "badge", Namespace = "")] -public class ContentAppBadge -{ - /// - /// Initializes a new instance of the class. - /// - public ContentAppBadge() => Type = ContentAppBadgeType.Default; - - /// - /// Gets or sets the number displayed in the badge - /// - [DataMember(Name = "count")] - public int Count { get; set; } - - /// - /// Gets or sets the type of badge to display - /// - /// - /// This controls the background color of the badge. - /// Warning will display a dark yellow badge - /// Alert will display a red badge - /// Default will display a turquoise badge - /// - [DataMember(Name = "type")] - public ContentAppBadgeType Type { get; set; } -} diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs deleted file mode 100644 index 718b36db3358..000000000000 --- a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Models.ContentEditing; - -// TODO: This was marked with `[StringEnumConverter]` to inform the serializer -// to serialize the values to string instead of INT (which is the default) -// so we need to either invent our own attribute and make the implementation aware of it -// or ... something else? - -/// -/// Represent the content app badge types -/// -[DataContract(Name = "contentAppBadgeType")] -public enum ContentAppBadgeType -{ - [EnumMember(Value = "default")] - Default = 0, - - [EnumMember(Value = "warning")] - Warning = 1, - - [EnumMember(Value = "alert")] - Alert = 2, -} diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs index ca4ea3eb94b2..045911d7f6df 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs @@ -29,7 +29,6 @@ public ContentItemDisplay() Notifications = new List(); Errors = new Dictionary(); Variants = new List(); - ContentApps = new List(); } [DataMember(Name = "id", IsRequired = true)] @@ -173,10 +172,6 @@ public ContentItemDisplay() [DataMember(Name = "isBlueprint")] public bool IsBlueprint { get; set; } - [DataMember(Name = "apps")] - [JsonPropertyName("apps")] - public IEnumerable ContentApps { get; set; } - /// /// The real persisted content object - used during inbound model binding /// diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs index 59e5bffb4b4d..91ff3afeb7c9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs @@ -15,7 +15,6 @@ public DictionaryDisplay() { Notifications = new List(); Translations = new List(); - ContentApps = new List(); } /// @@ -30,12 +29,6 @@ public DictionaryDisplay() [DataMember(Name = "translations")] public List Translations { get; private set; } - /// - /// Apps for the dictionary item - /// - [DataMember(Name = "apps")] - public List ContentApps { get; private set; } - /// /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs index 110ab98547d1..1029846b1c19 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs @@ -28,9 +28,6 @@ public DocumentTypeDisplay() => [DataMember(Name = "allowSegmentVariant")] public bool AllowSegmentVariant { get; set; } - [DataMember(Name = "apps")] - public IEnumerable? ContentApps { get; set; } - [DataMember(Name = "historyCleanup")] public HistoryCleanupViewModel? HistoryCleanup { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs deleted file mode 100644 index e0216f66f867..000000000000 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.Models.ContentEditing; - -/// -/// Represents a content app factory. -/// -public interface IContentAppFactory -{ - /// - /// Gets the content app for an object. - /// - /// The source object. - /// The user groups - /// The content app for the object, or null. - /// - /// - /// The definition must determine, based on , whether - /// the content app should be displayed or not, and return either a - /// instance, or null. - /// - /// - ContentApp? GetContentAppFor(object source, IEnumerable userGroups); -} diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs index 784e5510fb58..6ff449eec706 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs @@ -8,14 +8,9 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "content", Namespace = "")] public class MediaItemDisplay : ListViewAwareContentItemDisplayBase { - public MediaItemDisplay() => ContentApps = new List(); - [DataMember(Name = "contentType")] public ContentTypeBasic? ContentType { get; set; } [DataMember(Name = "mediaLink")] public string? MediaLink { get; set; } - - [DataMember(Name = "apps")] - public IEnumerable ContentApps { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs index 161c085d355e..d4e95c343848 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs @@ -8,10 +8,6 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "content", Namespace = "")] public class MemberDisplay : ListViewAwareContentItemDisplayBase { - public MemberDisplay() => - - // MemberProviderFieldMapping = new Dictionary(); - ContentApps = new List(); [DataMember(Name = "contentType")] public ContentTypeBasic? ContentType { get; set; } @@ -28,19 +24,6 @@ public MemberDisplay() => [DataMember(Name = "isApproved")] public bool IsApproved { get; set; } - // [DataMember(Name = "membershipScenario")] - // public MembershipScenario MembershipScenario { get; set; } - - // /// - // /// This is used to indicate how to map the membership provider properties to the save model, this mapping - // /// will change if a developer has opted to have custom member property aliases specified in their membership provider config, - // /// or if we are editing a member that is not an Umbraco member (custom provider) - // /// - // [DataMember(Name = "fieldConfig")] - // public IDictionary MemberProviderFieldMapping { get; set; } - [DataMember(Name = "apps")] - public IEnumerable ContentApps { get; set; } - [DataMember(Name = "membershipProperties")] public IEnumerable? MembershipProperties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs index cd89f46fc663..e5cdcc404605 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs @@ -8,6 +8,4 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "content", Namespace = "")] public class MemberListDisplay : ContentItemDisplayBase { - [DataMember(Name = "apps")] - public IEnumerable? ContentApps { get; set; } } diff --git a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs index 017ac1eb22fc..868b25f73ddb 100644 --- a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs @@ -1,7 +1,5 @@ -using Umbraco.Cms.Core.ContentApps; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -11,7 +9,6 @@ namespace Umbraco.Cms.Core.Models.Mapping; public class CommonMapper { - private readonly ContentAppFactoryCollection _contentAppDefinitions; private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; private readonly ILocalizedTextService _localizedTextService; private readonly IUserService _userService; @@ -19,12 +16,10 @@ public class CommonMapper public CommonMapper( IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService) { _userService = userService; _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _contentAppDefinitions = contentAppDefinitions; _localizedTextService = localizedTextService; } @@ -46,23 +41,4 @@ public CommonMapper( ContentTypeBasic? contentTypeBasic = context.Map(contentType); return contentTypeBasic; } - - public IEnumerable GetContentApps(IUmbracoEntity source) => GetContentAppsForEntity(source); - - public IEnumerable GetContentAppsForEntity(IEntity source) - { - ContentApp[] apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray(); - - // localize content app names - foreach (ContentApp app in apps) - { - var localizedAppName = _localizedTextService.Localize("apps", app.Alias); - if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false) - { - app.Name = localizedAppName; - } - } - - return apps; - } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs index d91a6e7ffb71..81341e7e9e2c 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs @@ -281,7 +281,6 @@ private void Map(IContentType source, DocumentTypeDisplay target, MapperContext target.AllowCultureVariant = source.VariesByCulture(); target.AllowSegmentVariant = source.VariesBySegment(); - target.ContentApps = _commonMapper.GetContentAppsForEntity(source); target.Variations = source.Variations; // sync templates diff --git a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs index 004b493935c9..cfc1b5d4f425 100644 --- a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs @@ -65,10 +65,6 @@ private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext target.Name = source.ItemKey; target.ParentId = source.ParentId ?? Guid.Empty; target.Udi = Udi.Create(Constants.UdiEntityType.DictionaryItem, source.Key); - if (_commonMapper != null) - { - target.ContentApps.AddRange(_commonMapper.GetContentAppsForEntity(source)); - } target.Path = _dictionaryService.CalculatePath(source.ParentId, source.Id); diff --git a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs deleted file mode 100644 index 7e943d1840ef..000000000000 --- a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Sections; -using Umbraco.Cms.Core.Services; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Models.Mapping; - -public class SectionMapDefinition : IMapDefinition -{ - private readonly ILocalizedTextService _textService; - - public SectionMapDefinition(ILocalizedTextService textService) => _textService = textService; - - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new Section(), Map); - - // this is for AutoMapper ReverseMap - but really? - mapper.Define(); - mapper.Define(); - mapper.Define(Map); - mapper.Define(); - mapper.Define(); - mapper.Define(); - mapper.Define(); - mapper.Define(); - mapper.Define(); - } - - // Umbraco.Code.MapAll - private static void Map(Section source, LegacyManifestSection target, MapperContext context) - { - target.Alias = source.Alias; - target.Name = source.Name; - } - - // Umbraco.Code.MapAll -RoutePath - private void Map(ISection source, Section target, MapperContext context) - { - target.Alias = source.Alias; - target.Name = _textService.Localize("sections", source.Alias); - } -} diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs index c82b166eedf6..edd25966e121 100644 --- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs @@ -11,7 +11,6 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Sections; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -27,7 +26,6 @@ public class UserMapDefinition : IMapDefinition private readonly GlobalSettings _globalSettings; private readonly IImageUrlGenerator _imageUrlGenerator; private readonly MediaFileManager _mediaFileManager; - private readonly ISectionService _sectionService; private readonly IShortStringHelper _shortStringHelper; private readonly ILocalizedTextService _textService; private readonly IUserService _userService; @@ -38,7 +36,6 @@ public UserMapDefinition( ILocalizedTextService textService, IUserService userService, IEntityService entityService, - ISectionService sectionService, AppCaches appCaches, ActionCollection actions, IOptions globalSettings, @@ -48,7 +45,6 @@ public UserMapDefinition( ILocalizationService localizationService, IUserGroupService userGroupService) { - _sectionService = sectionService; _entityService = entityService; _userService = userService; _textService = textService; @@ -67,7 +63,6 @@ public UserMapDefinition( ILocalizedTextService textService, IUserService userService, IEntityService entityService, - ISectionService sectionService, AppCaches appCaches, ActionCollection actions, IOptions globalSettings, @@ -79,7 +74,6 @@ public UserMapDefinition( textService, userService, entityService, - sectionService, appCaches, actions, globalSettings, @@ -475,9 +469,6 @@ private void MapUserGroupBasic(UserGroupBasic target, IEnumerable sourceAll target.Languages = context.MapEnumerable(applicableLanguages).WhereNotNull(); - var allSections = _sectionService.GetSections(); - target.Sections = context.MapEnumerable(allSections.Where(x => sourceAllowedSections.Contains(x.Alias))).WhereNotNull(); - if (sourceStartMediaId > 0) { target.MediaStartNode = diff --git a/src/Umbraco.Core/Notifications/SendingDashboardsNotification.cs b/src/Umbraco.Core/Notifications/SendingDashboardsNotification.cs deleted file mode 100644 index 886e25752940..000000000000 --- a/src/Umbraco.Core/Notifications/SendingDashboardsNotification.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Umbraco.Cms.Core.Dashboards; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Web; - -namespace Umbraco.Cms.Core.Notifications; - -public class SendingDashboardsNotification : INotification -{ - public SendingDashboardsNotification(IEnumerable> dashboards, IUmbracoContext umbracoContext) - { - Dashboards = dashboards; - UmbracoContext = umbracoContext; - } - - public IUmbracoContext UmbracoContext { get; } - - public IEnumerable> Dashboards { get; } -} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs index 91346aa9cc40..b224155ea1fe 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs @@ -6,12 +6,6 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class PropertyEditorCollection : BuilderCollectionBase { - public PropertyEditorCollection(DataEditorCollection dataEditors, ILegacyManifestParser legacyManifestParser) - : base(() => dataEditors - .Union(legacyManifestParser.CombinedManifest.PropertyEditors)) - { - } - public PropertyEditorCollection(DataEditorCollection dataEditors) : base(() => dataEditors) { diff --git a/src/Umbraco.Core/Sections/ContentSection.cs b/src/Umbraco.Core/Sections/ContentSection.cs deleted file mode 100644 index f8d46747b1ee..000000000000 --- a/src/Umbraco.Core/Sections/ContentSection.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office content section -/// -public class ContentSection : ISection -{ - /// - public string Alias => Constants.Applications.Content; - - /// - public string Name => "Content"; -} diff --git a/src/Umbraco.Core/Sections/FormsSection.cs b/src/Umbraco.Core/Sections/FormsSection.cs deleted file mode 100644 index 3ac36e47326e..000000000000 --- a/src/Umbraco.Core/Sections/FormsSection.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office media section -/// -public class FormsSection : ISection -{ - public string Alias => Constants.Applications.Forms; - - public string Name => "Forms"; -} diff --git a/src/Umbraco.Core/Sections/ISection.cs b/src/Umbraco.Core/Sections/ISection.cs deleted file mode 100644 index 565955dfe9ac..000000000000 --- a/src/Umbraco.Core/Sections/ISection.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines a back office section. -/// -public interface ISection -{ - /// - /// Gets the alias of the section. - /// - string Alias { get; } - - /// - /// Gets the name of the section. - /// - string Name { get; } -} diff --git a/src/Umbraco.Core/Sections/MediaSection.cs b/src/Umbraco.Core/Sections/MediaSection.cs deleted file mode 100644 index f5fd0a79b78e..000000000000 --- a/src/Umbraco.Core/Sections/MediaSection.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office media section -/// -public class MediaSection : ISection -{ - public string Alias => Constants.Applications.Media; - - public string Name => "Media"; -} diff --git a/src/Umbraco.Core/Sections/MembersSection.cs b/src/Umbraco.Core/Sections/MembersSection.cs deleted file mode 100644 index a2e98ac871e1..000000000000 --- a/src/Umbraco.Core/Sections/MembersSection.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office members section -/// -public class MembersSection : ISection -{ - /// - public string Alias => Constants.Applications.Members; - - /// - public string Name => "Members"; -} diff --git a/src/Umbraco.Core/Sections/PackagesSection.cs b/src/Umbraco.Core/Sections/PackagesSection.cs deleted file mode 100644 index d65acfccec8f..000000000000 --- a/src/Umbraco.Core/Sections/PackagesSection.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office packages section -/// -public class PackagesSection : ISection -{ - /// - public string Alias => Constants.Applications.Packages; - - /// - public string Name => "Packages"; -} diff --git a/src/Umbraco.Core/Sections/SectionCollection.cs b/src/Umbraco.Core/Sections/SectionCollection.cs deleted file mode 100644 index 83169a390dca..000000000000 --- a/src/Umbraco.Core/Sections/SectionCollection.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Sections; - -public class SectionCollection : BuilderCollectionBase -{ - public SectionCollection(Func> items) - : base(items) - { - } -} diff --git a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs deleted file mode 100644 index 0b8971d22307..000000000000 --- a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Manifest; - -namespace Umbraco.Cms.Core.Sections; - -public class - SectionCollectionBuilder : OrderedCollectionBuilderBase -{ - protected override SectionCollectionBuilder This => this; - - protected override IEnumerable CreateItems(IServiceProvider factory) - { - // get the manifest parser just-in-time - injecting it in the ctor would mean that - // simply getting the builder in order to configure the collection, would require - // its dependencies too, and that can create cycles or other oddities - ILegacyManifestParser legacyManifestParser = factory.GetRequiredService(); - - return base.CreateItems(factory).Concat(legacyManifestParser.CombinedManifest.Sections); - } -} diff --git a/src/Umbraco.Core/Sections/SettingsSection.cs b/src/Umbraco.Core/Sections/SettingsSection.cs deleted file mode 100644 index 3fe825c70df3..000000000000 --- a/src/Umbraco.Core/Sections/SettingsSection.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office settings section -/// -public class SettingsSection : ISection -{ - /// - public string Alias => Constants.Applications.Settings; - - /// - public string Name => "Settings"; -} diff --git a/src/Umbraco.Core/Sections/TranslationSection.cs b/src/Umbraco.Core/Sections/TranslationSection.cs deleted file mode 100644 index d11391c8111f..000000000000 --- a/src/Umbraco.Core/Sections/TranslationSection.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office translation section -/// -public class TranslationSection : ISection -{ - /// - public string Alias => Constants.Applications.Translation; - - /// - public string Name => "Translation"; -} diff --git a/src/Umbraco.Core/Sections/UsersSection.cs b/src/Umbraco.Core/Sections/UsersSection.cs deleted file mode 100644 index cea5047c8104..000000000000 --- a/src/Umbraco.Core/Sections/UsersSection.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Cms.Core.Sections; - -/// -/// Defines the back office users section -/// -public class UsersSection : ISection -{ - /// - public string Alias => Constants.Applications.Users; - - /// - public string Name => "Users"; -} diff --git a/src/Umbraco.Core/Services/DashboardService.cs b/src/Umbraco.Core/Services/DashboardService.cs deleted file mode 100644 index f5ddb30557bf..000000000000 --- a/src/Umbraco.Core/Services/DashboardService.cs +++ /dev/null @@ -1,161 +0,0 @@ -using Umbraco.Cms.Core.Dashboards; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Services; - -/// -/// A utility class for determine dashboard security -/// -public class DashboardService : IDashboardService -{ - private readonly DashboardCollection _dashboardCollection; - - private readonly ILocalizedTextService _localizedText; - - // TODO: Unit test all this!!! :/ - private readonly ISectionService _sectionService; - - public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection, ILocalizedTextService localizedText) - { - _sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService)); - _dashboardCollection = dashboardCollection ?? throw new ArgumentNullException(nameof(dashboardCollection)); - _localizedText = localizedText ?? throw new ArgumentNullException(nameof(localizedText)); - } - - /// - public IEnumerable> GetDashboards(string section, IUser? currentUser) - { - var tabs = new List>(); - var tabId = 0; - - foreach (IDashboard dashboard in _dashboardCollection.Where(x => x.Sections.InvariantContains(section))) - { - // validate access - if (currentUser is null || !CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules)) - { - continue; - } - - if (dashboard.View?.InvariantEndsWith(".ascx") ?? false) - { - throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported."); - } - - var dashboards = new List { dashboard }; - tabs.Add(new Tab - { - Id = tabId++, - Label = _localizedText.Localize("dashboardTabs", dashboard.Alias), - Alias = dashboard.Alias, - Properties = dashboards, - }); - } - - return tabs; - } - - /// - public IDictionary>> GetDashboards(IUser? currentUser) => _sectionService - .GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser)); - - private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules) - { - IAccessRule[]? denyRules = null, grantRules = null, grantBySectionRules = null; - - IEnumerable> groupedRules = rules.GroupBy(x => x.Type); - foreach (IGrouping group in groupedRules) - { - IAccessRule[] a = group.ToArray(); - switch (group.Key) - { - case AccessRuleType.Deny: - denyRules = a; - break; - case AccessRuleType.Grant: - grantRules = a; - break; - case AccessRuleType.GrantBySection: - grantBySectionRules = a; - break; - default: - throw new NotSupportedException($"The '{group.Key}'-AccessRuleType is not supported."); - } - } - - return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(), - grantBySectionRules ?? Array.Empty()); - } - - private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable rules) - { - if (user.Id == Constants.Security.SuperUserId) - { - return true; - } - - (IAccessRule[] denyRules, IAccessRule[] grantRules, IAccessRule[] grantBySectionRules) = GroupRules(rules); - - var hasAccess = true; - string[]? assignedUserGroups = null; - - // if there are no grant rules, then access is granted by default, unless denied - // otherwise, grant rules determine if access can be granted at all - if (grantBySectionRules.Length > 0 || grantRules.Length > 0) - { - hasAccess = false; - - // check if this item has any grant-by-section arguments. - // if so check if the user has access to any of the sections approved, if so they will be allowed to see it (so far) - if (grantBySectionRules.Length > 0) - { - var allowedSections = sectionService.GetAllowedSections(user.Id).Select(x => x.Alias).ToArray(); - var wantedSections = grantBySectionRules.SelectMany(g => - g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ?? - Array.Empty()).ToArray(); - - if (wantedSections.Intersect(allowedSections).Any()) - { - hasAccess = true; - } - } - - // if not already granted access, check if this item as any grant arguments. - // if so check if the user is in one of the user groups approved, if so they will be allowed to see it (so far) - if (hasAccess == false && grantRules.Any()) - { - assignedUserGroups = user.Groups.Select(x => x.Alias).ToArray(); - var wantedUserGroups = grantRules.SelectMany(g => - g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ?? - Array.Empty()).ToArray(); - - if (wantedUserGroups.Intersect(assignedUserGroups).Any()) - { - hasAccess = true; - } - } - } - - // No need to check denyRules if there aren't any, just return current state - if (denyRules.Length == 0) - { - return hasAccess; - } - - // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will - // be denied to see it no matter what - assignedUserGroups ??= user.Groups.Select(x => x.Alias).ToArray(); - var deniedUserGroups = denyRules.SelectMany(g => - g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ?? - Array.Empty()) - .ToArray(); - - if (deniedUserGroups.Intersect(assignedUserGroups).Any()) - { - hasAccess = false; - } - - return hasAccess; - } -} diff --git a/src/Umbraco.Core/Services/IDashboardService.cs b/src/Umbraco.Core/Services/IDashboardService.cs deleted file mode 100644 index 2792b142feea..000000000000 --- a/src/Umbraco.Core/Services/IDashboardService.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Cms.Core.Dashboards; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.Services; - -public interface IDashboardService -{ - /// - /// Gets dashboard for a specific section/application - /// For a specific backoffice user - /// - /// - /// - /// - IEnumerable> GetDashboards(string section, IUser? currentUser); - - /// - /// Gets all dashboards, organized by section, for a user. - /// - /// - /// - IDictionary>> GetDashboards(IUser? currentUser); -} diff --git a/src/Umbraco.Core/Services/ISectionService.cs b/src/Umbraco.Core/Services/ISectionService.cs deleted file mode 100644 index 515896cafceb..000000000000 --- a/src/Umbraco.Core/Services/ISectionService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Umbraco.Cms.Core.Sections; - -namespace Umbraco.Cms.Core.Services; - -public interface ISectionService -{ - /// - /// The cache storage for all applications - /// - IEnumerable GetSections(); - - /// - /// Get the user group's allowed sections - /// - /// - /// - IEnumerable GetAllowedSections(int userId); - - /// - /// Gets the application by its alias. - /// - /// The application alias. - /// - ISection? GetByAlias(string appAlias); -} diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs deleted file mode 100644 index 61ff97889483..000000000000 --- a/src/Umbraco.Core/Services/SectionService.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Sections; - -namespace Umbraco.Cms.Core.Services; - -public class SectionService : ISectionService -{ - private readonly SectionCollection _sectionCollection; - private readonly IUserService _userService; - - public SectionService( - IUserService userService, - SectionCollection sectionCollection) - { - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _sectionCollection = sectionCollection ?? throw new ArgumentNullException(nameof(sectionCollection)); - } - - /// - /// The cache storage for all applications - /// - public IEnumerable GetSections() - => _sectionCollection; - - /// - public IEnumerable GetAllowedSections(int userId) - { - IUser? user = _userService.GetUserById(userId); - if (user == null) - { - throw new InvalidOperationException("No user found with id " + userId); - } - - return GetSections().Where(x => user.AllowedSections.Contains(x.Alias)); - } - - /// - public ISection? GetByAlias(string appAlias) - => GetSections().FirstOrDefault(t => t.Alias.Equals(appAlias, StringComparison.OrdinalIgnoreCase)); -} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 5f0f86e2ad35..47022734fb9f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -134,14 +134,10 @@ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builde builder.Services.AddTransient(); // register manifest parser, will be injected in collection builders where needed - builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - // register the manifest filter collection builder (collection is empty by default) - builder.ManifestFilters(); - builder.MediaUrlGenerators() .Add() .Add(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs index 7f0efa7b96eb..3fb4bfbb315f 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs @@ -28,7 +28,6 @@ public static IUmbracoBuilder AddCoreMappingProfiles(this IUmbracoBuilder builde .Add() .Add() .Add() - .Add() .Add() .Add() .Add() diff --git a/src/Umbraco.Infrastructure/Manifest/DashboardAccessRuleConverter.cs b/src/Umbraco.Infrastructure/Manifest/DashboardAccessRuleConverter.cs deleted file mode 100644 index 7ef945bda8d2..000000000000 --- a/src/Umbraco.Infrastructure/Manifest/DashboardAccessRuleConverter.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core.Dashboards; -using Umbraco.Cms.Core.Exceptions; -using Umbraco.Cms.Infrastructure.Serialization; - -namespace Umbraco.Cms.Core.Manifest; - -/// -/// Implements a json read converter for . -/// -internal class DashboardAccessRuleConverter : JsonReadConverter -{ - /// - protected override IAccessRule Create(Type objectType, string path, JObject jObject) => new AccessRule(); - - /// - protected override void Deserialize(JObject jobject, IAccessRule target, JsonSerializer serializer) - { - // see Create above, target is either DataEditor (parameter) or ConfiguredDataEditor (property) - if (!(target is AccessRule accessRule)) - { - throw new PanicException("panic."); - } - - GetRule(accessRule, jobject, "grant", AccessRuleType.Grant); - GetRule(accessRule, jobject, "deny", AccessRuleType.Deny); - GetRule(accessRule, jobject, "grantBySection", AccessRuleType.GrantBySection); - - if (accessRule.Type == AccessRuleType.Unknown) - { - throw new InvalidOperationException("Rule is not defined."); - } - } - - private void GetRule(AccessRule rule, JObject jobject, string name, AccessRuleType type) - { - JToken? token = jobject[name]; - if (token == null) - { - return; - } - - if (rule.Type != AccessRuleType.Unknown) - { - throw new InvalidOperationException("Multiple definition of a rule."); - } - - if (token.Type != JTokenType.String) - { - throw new InvalidOperationException("Rule value is not a string."); - } - - rule.Type = type; - rule.Value = token.Value(); - } -} diff --git a/src/Umbraco.Infrastructure/Manifest/LegacyManifestParser.cs b/src/Umbraco.Infrastructure/Manifest/LegacyManifestParser.cs deleted file mode 100644 index 4b57ccbde97b..000000000000 --- a/src/Umbraco.Infrastructure/Manifest/LegacyManifestParser.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.Loader; -using System.Text; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Web.Common.DependencyInjection; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Manifest; - -/// -/// Parses the Main.js file and replaces all tokens accordingly. -/// -public class LegacyManifestParser : ILegacyManifestParser -{ - private static readonly string _utf8Preamble = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()); - - private readonly IAppPolicyCache _cache; - private readonly IDataValueEditorFactory _dataValueEditorFactory; - private readonly ILegacyPackageManifestFileProviderFactory _legacyPackageManifestFileProviderFactory; - private readonly LegacyManifestFilterCollection _filters; - private readonly IHostingEnvironment _hostingEnvironment; - - private readonly IIOHelper _ioHelper; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILocalizedTextService _localizedTextService; - private readonly ILogger _logger; - private readonly IShortStringHelper _shortStringHelper; - private readonly ManifestValueValidatorCollection _validators; - - private string _path = null!; - - /// - /// Initializes a new instance of the class. - /// - public LegacyManifestParser( - AppCaches appCaches, - ManifestValueValidatorCollection validators, - LegacyManifestFilterCollection filters, - ILogger logger, - IIOHelper ioHelper, - IHostingEnvironment hostingEnvironment, - IJsonSerializer jsonSerializer, - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IDataValueEditorFactory dataValueEditorFactory, - ILegacyPackageManifestFileProviderFactory legacyPackageManifestFileProviderFactory) - { - if (appCaches == null) - { - throw new ArgumentNullException(nameof(appCaches)); - } - - _cache = appCaches.RuntimeCache; - _validators = validators ?? throw new ArgumentNullException(nameof(validators)); - _filters = filters ?? throw new ArgumentNullException(nameof(filters)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _ioHelper = ioHelper; - _hostingEnvironment = hostingEnvironment; - AppPluginsPath = "~/App_Plugins"; - _jsonSerializer = jsonSerializer; - _localizedTextService = localizedTextService; - _shortStringHelper = shortStringHelper; - _dataValueEditorFactory = dataValueEditorFactory; - _legacyPackageManifestFileProviderFactory = legacyPackageManifestFileProviderFactory; - } - - [Obsolete("Use other ctor - Will be removed in Umbraco 13")] - public LegacyManifestParser( - AppCaches appCaches, - ManifestValueValidatorCollection validators, - LegacyManifestFilterCollection filters, - ILogger logger, - IIOHelper ioHelper, - IHostingEnvironment hostingEnvironment, - IJsonSerializer jsonSerializer, - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IDataValueEditorFactory dataValueEditorFactory) - : this( - appCaches, - validators, - filters, - logger, - ioHelper, - hostingEnvironment, - jsonSerializer, - localizedTextService, - shortStringHelper, - dataValueEditorFactory, - StaticServiceProvider.Instance.GetRequiredService()) - { - } - - public string AppPluginsPath - { - get => _path; - set => _path = value.StartsWith("~/") ? _hostingEnvironment.MapPathContentRoot(value) : value; - } - - /// - /// Gets all manifests, merged into a single manifest object. - /// - /// - public CompositeLegacyPackageManifest CombinedManifest - => _cache.GetCacheItem("Umbraco.Core.Manifest.ManifestParser::Manifests", () => - { - IEnumerable manifests = GetManifests(); - return MergeManifests(manifests); - }, new TimeSpan(0, 4, 0))!; - - /// - /// Gets all manifests. - /// - public IEnumerable GetManifests() - { - var manifests = new List(); - IFileProvider? manifestFileProvider = _legacyPackageManifestFileProviderFactory.Create(); - - if (manifestFileProvider is null) - { - throw new ArgumentNullException(nameof(manifestFileProvider)); - } - - foreach (IFileInfo file in GetManifestFiles(manifestFileProvider, Constants.SystemDirectories.AppPlugins)) - { - try - { - using Stream stream = file.CreateReadStream(); - using var reader = new StreamReader(stream, Encoding.UTF8); - var text = reader.ReadToEnd(); - text = TrimPreamble(text); - if (string.IsNullOrWhiteSpace(text)) - { - continue; - } - - LegacyPackageManifest manifest = ParseManifest(text); - manifest.Source = file.PhysicalPath!; // We assure that the PhysicalPath is not null in GetManifestFiles() - manifests.Add(manifest); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to parse manifest at '{Path}', ignoring.", file.PhysicalPath); - } - } - - _filters.Filter(manifests); - - return manifests; - } - - /// - /// Parses a manifest. - /// - public LegacyPackageManifest ParseManifest(string text) - { - ArgumentNullException.ThrowIfNull(text); - - if (string.IsNullOrWhiteSpace(text)) - { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text)); - } - - LegacyPackageManifest? manifest = JsonConvert.DeserializeObject( - text, - new DataEditorConverter(_dataValueEditorFactory, _ioHelper, _shortStringHelper, _jsonSerializer), - new ValueValidatorConverter(_validators), - new DashboardAccessRuleConverter())!; - - if (string.IsNullOrEmpty(manifest.Version)) - { - string? assemblyName = manifest.VersionAssemblyName; - if (string.IsNullOrEmpty(assemblyName)) - { - // Fallback to package ID - assemblyName = manifest.PackageId; - } - - if (!string.IsNullOrEmpty(assemblyName) && - TryGetAssemblyInformationalVersion(assemblyName, out string? version)) - { - manifest.Version = version; - } - } - - // scripts and stylesheets are raw string, must process here - for (var i = 0; i < manifest.Scripts.Length; i++) - { - manifest.Scripts[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Scripts[i]); - } - - for (var i = 0; i < manifest.Stylesheets.Length; i++) - { - manifest.Stylesheets[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Stylesheets[i]); - } - - foreach (LegacyManifestContentAppDefinition contentApp in manifest.ContentApps) - { - contentApp.View = _ioHelper.ResolveRelativeOrVirtualUrl(contentApp.View); - } - - foreach (LegacyManifestDashboard dashboard in manifest.Dashboards) - { - dashboard.View = _ioHelper.ResolveRelativeOrVirtualUrl(dashboard.View); - } - - return manifest; - } - - private bool TryGetAssemblyInformationalVersion(string name, [NotNullWhen(true)] out string? version) - { - foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies) - { - AssemblyName assemblyName = assembly.GetName(); - if (string.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase) && - assembly.TryGetInformationalVersion(out version)) - { - return true; - } - } - - version = null; - return false; - } - - /// - /// Merges all manifests into one. - /// - private static CompositeLegacyPackageManifest MergeManifests(IEnumerable manifests) - { - var scripts = new Dictionary>(); - var stylesheets = new Dictionary>(); - var propertyEditors = new List(); - var parameterEditors = new List(); - var contentApps = new List(); - var dashboards = new List(); - var sections = new List(); - - foreach (LegacyPackageManifest manifest in manifests) - { - if (!scripts.TryGetValue(manifest.BundleOptions, out List? scriptsPerBundleOption)) - { - scriptsPerBundleOption = new List(); - scripts[manifest.BundleOptions] = scriptsPerBundleOption; - } - - scriptsPerBundleOption.Add(new LegacyManifestAssets(manifest.PackageName, manifest.Scripts)); - - if (!stylesheets.TryGetValue(manifest.BundleOptions, out List? stylesPerBundleOption)) - { - stylesPerBundleOption = new List(); - stylesheets[manifest.BundleOptions] = stylesPerBundleOption; - } - - stylesPerBundleOption.Add(new LegacyManifestAssets(manifest.PackageName, manifest.Stylesheets)); - - propertyEditors.AddRange(manifest.PropertyEditors); - - parameterEditors.AddRange(manifest.ParameterEditors); - - contentApps.AddRange(manifest.ContentApps); - - dashboards.AddRange(manifest.Dashboards); - - sections.AddRange(manifest.Sections.DistinctBy(x => x.Alias, StringComparer.OrdinalIgnoreCase)); - } - - return new CompositeLegacyPackageManifest( - propertyEditors, - parameterEditors, - contentApps, - dashboards, - sections, - scripts.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value), - stylesheets.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value)); - } - - private static string TrimPreamble(string text) - { - // strangely StartsWith(preamble) would always return true - if (text.Substring(0, 1) == _utf8Preamble) - { - text = text.Remove(0, _utf8Preamble.Length); - } - - return text; - } - - // Gets all manifest files - private static IEnumerable GetManifestFiles(IFileProvider fileProvider, string path) - { - var manifestFiles = new List(); - IEnumerable pluginFolders = fileProvider.GetDirectoryContents(path); - - foreach (IFileInfo pluginFolder in pluginFolders) - { - if (!pluginFolder.IsDirectory) - { - continue; - } - - manifestFiles.AddRange(GetNestedManifestFiles(fileProvider, $"{path}/{pluginFolder.Name}")); - } - - return manifestFiles; - } - - // Helper method to get all nested package.manifest files (recursively) - private static IEnumerable GetNestedManifestFiles(IFileProvider fileProvider, string path) - { - foreach (IFileInfo file in fileProvider.GetDirectoryContents(path)) - { - if (file.IsDirectory) - { - var virtualPath = WebPath.Combine(path, file.Name); - - // Recursively find nested package.manifest files - foreach (IFileInfo nested in GetNestedManifestFiles(fileProvider, virtualPath)) - { - yield return nested; - } - } - else if (file.Name.InvariantEquals("package.manifest") && !string.IsNullOrEmpty(file.PhysicalPath)) - { - yield return file; - } - } - } -} diff --git a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs index 2cc0afa1cfb8..d22efd15296a 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PackagingService.cs @@ -28,7 +28,6 @@ public class PackagingService : IPackagingService private readonly ICreatedPackagesRepository _createdPackages; private readonly IEventAggregator _eventAggregator; private readonly IKeyValueService _keyValueService; - private readonly ILegacyManifestParser _legacyManifestParser; private readonly IPackageInstallation _packageInstallation; private readonly PackageMigrationPlanCollection _packageMigrationPlans; private readonly ICoreScopeProvider _coreScopeProvider; @@ -40,7 +39,6 @@ public PackagingService( ICreatedPackagesRepository createdPackages, IPackageInstallation packageInstallation, IEventAggregator eventAggregator, - ILegacyManifestParser legacyManifestParser, IKeyValueService keyValueService, ICoreScopeProvider coreScopeProvider, PackageMigrationPlanCollection packageMigrationPlans, @@ -51,7 +49,6 @@ public PackagingService( _createdPackages = createdPackages; _packageInstallation = packageInstallation; _eventAggregator = eventAggregator; - _legacyManifestParser = legacyManifestParser; _keyValueService = keyValueService; _packageMigrationPlans = packageMigrationPlans; _coreScopeProvider = coreScopeProvider; @@ -279,50 +276,6 @@ public IEnumerable GetAllInstalledPackages() installedPackage.PackageMigrationPlans = currentPlans; } - // Collect and merge the packages from the manifests - foreach (LegacyPackageManifest package in _legacyManifestParser.GetManifests()) - { - if (package.PackageId is null && package.PackageName is null) - { - continue; - } - - InstalledPackage installedPackage; - if (package.PackageId is not null && installedPackages.FirstOrDefault(x => x.PackageId == package.PackageId) is InstalledPackage installedPackageById) - { - installedPackage = installedPackageById; - - // Always use package name from manifest - installedPackage.PackageName = package.PackageName; - } - else if (installedPackages.FirstOrDefault(x => x.PackageName == package.PackageName) is InstalledPackage installedPackageByName) - { - installedPackage = installedPackageByName; - - // Ensure package ID is set - installedPackage.PackageId ??= package.PackageId; - } - else - { - installedPackage = new InstalledPackage - { - PackageId = package.PackageId, - PackageName = package.PackageName, - }; - - installedPackages.Add(installedPackage); - } - - // Set additional values - installedPackage.AllowPackageTelemetry = package.AllowPackageTelemetry; - installedPackage.PackageView = package.PackageView; - - if (!string.IsNullOrEmpty(package.Version)) - { - installedPackage.Version = package.Version; - } - } - // Return all packages with an ID or name in the package.manifest or package migrations return installedPackages; } diff --git a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs index 3bb987290171..8780ebb359ce 100644 --- a/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs +++ b/src/Umbraco.Infrastructure/WebAssets/BackOfficeWebAssets.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json.Linq; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.WebAssets; using Umbraco.Extensions; @@ -18,13 +17,10 @@ public class BackOfficeWebAssets public const string UmbracoInitCssBundleName = "umbraco-backoffice-init-css"; public const string UmbracoCoreJsBundleName = "umbraco-backoffice-js"; public const string UmbracoExtensionsJsBundleName = "umbraco-backoffice-extensions-js"; - public const string UmbracoNonOptimizedPackageJsBundleName = "umbraco-backoffice-non-optimized-js"; - public const string UmbracoNonOptimizedPackageCssBundleName = "umbraco-backoffice-non-optimized-css"; public const string UmbracoTinyMceJsBundleName = "umbraco-tinymce-js"; public const string UmbracoUpgradeCssBundleName = "umbraco-authorize-upgrade-css"; private readonly CustomBackOfficeAssetsCollection _customBackOfficeAssetsCollection; private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILegacyManifestParser _parser; private readonly PropertyEditorCollection _propertyEditorCollection; private readonly IRuntimeMinifier _runtimeMinifier; @@ -32,14 +28,12 @@ public class BackOfficeWebAssets public BackOfficeWebAssets( IRuntimeMinifier runtimeMinifier, - ILegacyManifestParser parser, PropertyEditorCollection propertyEditorCollection, IHostingEnvironment hostingEnvironment, IOptionsMonitor globalSettings, CustomBackOfficeAssetsCollection customBackOfficeAssetsCollection) { _runtimeMinifier = runtimeMinifier; - _parser = parser; _propertyEditorCollection = propertyEditorCollection; _hostingEnvironment = hostingEnvironment; _globalSettings = globalSettings.CurrentValue; @@ -48,9 +42,6 @@ public BackOfficeWebAssets( globalSettings.OnChange(x => _globalSettings = x); } - public static string GetIndependentPackageBundleName(LegacyManifestAssets legacyManifestAssets, AssetType assetType) - => $"{legacyManifestAssets.PackageName.ToLowerInvariant()}-{(assetType == AssetType.Css ? "css" : "js")}"; - public void CreateBundles() { // Create bundles @@ -118,12 +109,6 @@ public void CreateBundles() FormatPaths( GetScriptsForBackOfficeExtensions(jsAssets))); - // Create a bundle per package manifest that is declaring an Independent bundle type - RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest.Scripts, AssetType.Javascript); - - // Create a single non-optimized (no file processing) bundle for all manifests declaring None as a bundle option - RegisterPackageBundlesForNoneOption(_parser.CombinedManifest.Scripts, UmbracoNonOptimizedPackageJsBundleName); - // This bundle includes all CSS from property editor assets, // custom back office assets, and any CSS found in package manifests // that have the default bundle options. @@ -140,68 +125,6 @@ public void CreateBundles() BundlingOptions.OptimizedAndComposite, FormatPaths( GetStylesheetsForBackOffice(cssAssets))); - - // Create a bundle per package manifest that is declaring an Independent bundle type - RegisterPackageBundlesForIndependentOptions(_parser.CombinedManifest?.Stylesheets, AssetType.Css); - - // Create a single non-optimized (no file processing) bundle for all manifests declaring None as a bundle option - RegisterPackageBundlesForNoneOption( - _parser.CombinedManifest?.Stylesheets, - UmbracoNonOptimizedPackageCssBundleName); - } - - private void RegisterPackageBundlesForNoneOption( - IReadOnlyDictionary>? combinedPackageManifestAssets, - string bundleName) - { - var assets = new HashSet(StringComparer.InvariantCultureIgnoreCase); - - // Create a bundle per package manifest that is declaring the matching BundleOptions - if (combinedPackageManifestAssets?.TryGetValue( - BundleOptions.None, - out IReadOnlyList? manifestAssetList) ?? false) - { - foreach (var asset in manifestAssetList.SelectMany(x => x.Assets)) - { - assets.Add(asset); - } - } - - _runtimeMinifier.CreateJsBundle( - bundleName, - - // no optimization, no composite files, just render individual files - BundlingOptions.NotOptimizedNotComposite, - FormatPaths(assets.ToArray())); - } - - private void RegisterPackageBundlesForIndependentOptions( - IReadOnlyDictionary>? combinedPackageManifestAssets, - AssetType assetType) - { - // Create a bundle per package manifest that is declaring the matching BundleOptions - if (combinedPackageManifestAssets?.TryGetValue( - BundleOptions.Independent, - out IReadOnlyList? manifestAssetList) ?? false) - { - foreach (LegacyManifestAssets manifestAssets in manifestAssetList) - { - var bundleName = GetIndependentPackageBundleName(manifestAssets, assetType); - var filePaths = FormatPaths(manifestAssets.Assets.ToArray()); - - switch (assetType) - { - case AssetType.Javascript: - _runtimeMinifier.CreateJsBundle(bundleName, BundlingOptions.OptimizedAndComposite, filePaths); - break; - case AssetType.Css: - _runtimeMinifier.CreateCssBundle(bundleName, BundlingOptions.OptimizedAndComposite, filePaths); - break; - default: - throw new IndexOutOfRangeException(); - } - } - } } /// @@ -212,17 +135,6 @@ private string[] GetScriptsForBackOfficeExtensions(IEnumerable property { var scripts = new HashSet(StringComparer.InvariantCultureIgnoreCase); - // only include scripts with the default bundle options here - if (_parser.CombinedManifest.Scripts.TryGetValue( - BundleOptions.Default, - out IReadOnlyList? manifestAssets)) - { - foreach (var script in manifestAssets.SelectMany(x => x.Assets)) - { - scripts.Add(script); - } - } - foreach (var script in propertyEditorScripts) { if (script is not null) @@ -252,17 +164,6 @@ private string[] GetStylesheetsForBackOffice(IEnumerable propertyEditor { var stylesheets = new HashSet(StringComparer.InvariantCultureIgnoreCase); - // only include css with the default bundle options here - if (_parser.CombinedManifest.Stylesheets.TryGetValue( - BundleOptions.Default, - out IReadOnlyList? manifestAssets)) - { - foreach (var script in manifestAssets.SelectMany(x => x.Assets)) - { - stylesheets.Add(script); - } - } - foreach (var stylesheet in propertyEditorStyles) { if (stylesheet is not null) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index a32be8e30ca1..979691acf230 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -50,7 +50,6 @@ public class BackOfficeController : UmbracoController private readonly IHttpContextAccessor _httpContextAccessor; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; - private readonly ILegacyManifestParser _legacyManifestParser; private readonly IRuntimeMinifier _runtimeMinifier; private readonly IRuntimeState _runtimeState; private readonly IOptions _securitySettings; @@ -85,7 +84,6 @@ public BackOfficeController( IBackOfficeExternalLoginProviders externalLogins, IHttpContextAccessor httpContextAccessor, IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, - ILegacyManifestParser legacyManifestParser, ServerVariablesParser serverVariables, IOptions securitySettings) { @@ -104,7 +102,6 @@ public BackOfficeController( _externalLogins = externalLogins; _httpContextAccessor = httpContextAccessor; _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; - _legacyManifestParser = legacyManifestParser; _serverVariables = serverVariables; _securitySettings = securitySettings; } @@ -263,12 +260,8 @@ public async Task AuthorizeUpgrade() [AllowAnonymous] public async Task Application() { - var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync( - _globalSettings, - _hostingEnvironment, - _legacyManifestParser); - - return new JavaScriptResult(result); + // Removed in separate PR + return Ok(); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index fe1ee521c488..0ca3c2ccbbb3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -13,7 +13,6 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Mail; using Umbraco.Cms.Core.Media; -using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.TemplateQuery; using Umbraco.Cms.Core.Services; @@ -382,10 +381,6 @@ internal async Task> GetServerVariablesAsync() "publicAccessApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetPublicAccess(0)) }, - { - "mediaApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( - controller => controller.GetRootMedia()) - }, { "iconApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetIcon(string.Empty)) @@ -394,10 +389,6 @@ internal async Task> GetServerVariablesAsync() "imagesApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetBigThumbnail(string.Empty)) }, - { - "sectionApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( - controller => controller.GetSections()) - }, { "treeApplicationApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetApplicationTrees(string.Empty, string.Empty, FormCollection.Empty, TreeUse.None)) @@ -434,18 +425,10 @@ internal async Task> GetServerVariablesAsync() "dataTypeApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetById(0)) }, - { - "dashboardApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( - controller => controller.GetDashboard(string.Empty)) - }, { "logApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetPagedEntityLog(0, 0, 0, Direction.Ascending, null)) }, - { - "memberApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( - controller => controller.GetByKey(Guid.Empty)) - }, { "packageApiBaseUrl", _linkGenerator.GetUmbracoApiServiceBaseUrl( controller => controller.GetCreatedPackages()) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index d6d024325d5f..36316ffd18bd 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -9,14 +9,12 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.ContentApps; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.Notifications; @@ -342,11 +340,6 @@ public IEnumerable GetByIds([FromQuery] int[] ids) /// public ActionResult GetRecycleBin() { - var apps = new List - { - ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, "recycleBin", "content", Constants.DataTypes.DefaultMembersListView) - }; - apps[0].Active = true; var display = new ContentItemDisplay { Id = Constants.System.RecycleBinContent, @@ -361,8 +354,7 @@ public ActionResult GetRecycleBin() CreateDate = DateTime.Now, Name = _localizedTextService.Localize("general", "recycleBin") } - }, - ContentApps = apps + } }; return display; @@ -545,9 +537,6 @@ private ContentItemDisplay CleanContentItemDisplay(ContentItemDisplay display) _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, display.DocumentType.Description); } - //remove the listview app if it exists - display.ContentApps = display.ContentApps.Where(x => x.Alias != "umbListView").ToList(); - return display; } @@ -664,12 +653,6 @@ public ActionResult> GetEmptyByKeys(Conten ContentItemDisplay? mapped = _umbracoMapper.Map(scaffold); - if (mapped is not null) - { - //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); - } - return mapped; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs b/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs deleted file mode 100644 index be71a7544a61..000000000000 --- a/src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System.Net; -using System.Text; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration; -using Umbraco.Cms.Core.Dashboards; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Core.Telemetry; -using Umbraco.Cms.Web.BackOffice.Filters; -using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Cms.Web.Common.Authorization; -using Umbraco.Cms.Web.Common.Controllers; -using Umbraco.Cms.Web.Common.Filters; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Web.BackOffice.Controllers; - -//we need to fire up the controller like this to enable loading of remote css directly from this controller -[PluginController(Constants.Web.Mvc.BackOfficeApiArea)] -[ValidationFilter] -[AngularJsonOnlyConfiguration] // TODO: This could be applied with our Application Model conventions -[IsBackOffice] -[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] -public class DashboardController : UmbracoApiController -{ - //we have just one instance of HttpClient shared for the entire application - private static readonly HttpClient HttpClient = new(); - private readonly AppCaches _appCaches; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IDashboardService _dashboardService; - private readonly ContentDashboardSettings _dashboardSettings; - private readonly ILogger _logger; - private readonly IShortStringHelper _shortStringHelper; - private readonly ISiteIdentifierService _siteIdentifierService; - private readonly IUmbracoVersion _umbracoVersion; - - /// - /// Initializes a new instance of the with all its dependencies. - /// - [ActivatorUtilitiesConstructor] - public DashboardController( - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - AppCaches appCaches, - ILogger logger, - IDashboardService dashboardService, - IUmbracoVersion umbracoVersion, - IShortStringHelper shortStringHelper, - IOptionsSnapshot dashboardSettings, - ISiteIdentifierService siteIdentifierService) - - { - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _appCaches = appCaches; - _logger = logger; - _dashboardService = dashboardService; - _umbracoVersion = umbracoVersion; - _shortStringHelper = shortStringHelper; - _siteIdentifierService = siteIdentifierService; - _dashboardSettings = dashboardSettings.Value; - } - - // TODO(V10) : change return type to Task> and consider removing baseUrl as parameter - //we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side - [ValidateAngularAntiForgeryToken] - public async Task GetRemoteDashboardContent(string section, string? baseUrl) - { - if (baseUrl is null) - { - baseUrl = "https://dashboard.umbraco.com/"; - } - - IUser? user = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - var allowedSections = string.Join(",", user?.AllowedSections ?? Array.Empty()); - var language = user?.Language; - var version = _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); - var isAdmin = user?.IsAdmin() ?? false; - _siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid siteIdentifier); - - if (!IsAllowedUrl(baseUrl)) - { - _logger.LogError( - $"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {baseUrl}"); - HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - - // Hacking the response - can't set the HttpContext.Response.Body, so instead returning the error as JSON - var errorJson = JsonConvert.SerializeObject(new { Error = "Dashboard source not permitted" }); - return JObject.Parse(errorJson); - } - - var url = string.Format( - "{0}{1}?section={2}&allowed={3}&lang={4}&version={5}&admin={6}&siteid={7}", - baseUrl, - _dashboardSettings.ContentDashboardPath, - section, - allowedSections, - language, - version, - isAdmin, - siteIdentifier); - var key = "umbraco-dynamic-dashboard-" + language + allowedSections.Replace(",", "-") + section; - - JObject? content = _appCaches.RuntimeCache.GetCacheItem(key); - var result = new JObject(); - if (content != null) - { - result = content; - } - else - { - //content is null, go get it - try - { - //fetch dashboard json and parse to JObject - var json = await HttpClient.GetStringAsync(url); - content = JObject.Parse(json); - result = content; - - _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); - } - catch (HttpRequestException ex) - { - _logger.LogError(ex.InnerException ?? ex, "Error getting dashboard content from {Url}", url); - - //it's still new JObject() - we return it like this to avoid error codes which triggers UI warnings - _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); - } - } - - return result; - } - - // TODO(V10) : consider removing baseUrl as parameter - public async Task GetRemoteDashboardCss(string section, string? baseUrl) - { - if (baseUrl is null) - { - baseUrl = "https://dashboard.umbraco.org/"; - } - - if (!IsAllowedUrl(baseUrl)) - { - _logger.LogError( - $"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {baseUrl}"); - return BadRequest("Dashboard source not permitted"); - } - - var url = string.Format(baseUrl + "css/dashboard.css?section={0}", section); - var key = "umbraco-dynamic-dashboard-css-" + section; - - var content = _appCaches.RuntimeCache.GetCacheItem(key); - var result = string.Empty; - - if (content != null) - { - result = content; - } - else - { - //content is null, go get it - try - { - //fetch remote css - content = await HttpClient.GetStringAsync(url); - - //can't use content directly, modified closure problem - result = content; - - //save server content for 30 mins - _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); - } - catch (HttpRequestException ex) - { - _logger.LogError(ex.InnerException ?? ex, "Error getting dashboard CSS from {Url}", url); - - //it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings - _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); - } - } - - - return Content(result, "text/css", Encoding.UTF8); - } - - public async Task GetRemoteXml(string site, string url) - { - if (!IsAllowedUrl(url)) - { - _logger.LogError( - $"The following URL is not listed in the setting 'Umbraco:CMS:ContentDashboard:ContentDashboardUrlAllowlist' in configuration: {url}"); - return BadRequest("Dashboard source not permitted"); - } - - // This is used in place of the old feedproxy.config - // Which was used to grab data from our.umbraco.com, umbraco.com or umbraco.tv - // for certain dashboards or the help drawer - var urlPrefix = string.Empty; - switch (site.ToUpper()) - { - case "TV": - urlPrefix = "https://umbraco.tv/"; - break; - - case "OUR": - urlPrefix = "https://our.umbraco.com/"; - break; - - case "COM": - urlPrefix = "https://umbraco.com/"; - break; - - default: - return NotFound(); - } - - - //Make remote call to fetch videos or remote dashboard feed data - var key = $"umbraco-XML-feed-{site}-{url.ToCleanString(_shortStringHelper, CleanStringType.UrlSegment)}"; - - var content = _appCaches.RuntimeCache.GetCacheItem(key); - var result = string.Empty; - - if (content != null) - { - result = content; - } - else - { - //content is null, go get it - try - { - //fetch remote css - content = await HttpClient.GetStringAsync($"{urlPrefix}{url}"); - - //can't use content directly, modified closure problem - result = content; - - //save server content for 30 mins - _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 30, 0)); - } - catch (HttpRequestException ex) - { - _logger.LogError(ex.InnerException ?? ex, "Error getting remote dashboard data from {UrlPrefix}{Url}", urlPrefix, url); - - //it's still string.Empty - we return it like this to avoid error codes which triggers UI warnings - _appCaches.RuntimeCache.InsertCacheItem(key, () => result, new TimeSpan(0, 5, 0)); - } - } - - return Content(result, "text/xml", Encoding.UTF8); - } - - // return IDashboardSlim - we don't need sections nor access rules - [ValidateAngularAntiForgeryToken] - [OutgoingEditorModelEvent] - public IEnumerable> GetDashboard(string section) - { - IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - return _dashboardService.GetDashboards(section, currentUser).Select(x => new Tab - { - Id = x.Id, - Key = x.Key, - Label = x.Label, - Alias = x.Alias, - Type = x.Type, - Expanded = x.Expanded, - IsActive = x.IsActive, - Properties = x.Properties?.Select(y => new DashboardSlim { Alias = y.Alias, View = y.View }) - }).ToList(); - } - - // Checks if the passed URL is part of the configured allowlist of addresses - private bool IsAllowedUrl(string url) - { - // No addresses specified indicates that any URL is allowed - if (_dashboardSettings.ContentDashboardUrlAllowlist is null || - _dashboardSettings.ContentDashboardUrlAllowlist.Contains(url, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - - return false; - } -} diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs deleted file mode 100644 index e2b2f36c536b..000000000000 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ /dev/null @@ -1,948 +0,0 @@ -using System.Globalization; -using System.Net.Mime; -using System.Text; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.ContentApps; -using Umbraco.Cms.Core.Dictionary; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Media; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Editors; -using Umbraco.Cms.Core.Models.Entities; -using Umbraco.Cms.Core.Models.Validation; -using Umbraco.Cms.Core.Persistence.Querying; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Web.BackOffice.Authorization; -using Umbraco.Cms.Web.BackOffice.Filters; -using Umbraco.Cms.Web.BackOffice.ModelBinders; -using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Cms.Web.Common.Authorization; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Web.BackOffice.Controllers; - -/// -/// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting -/// access to ALL of the methods on this controller will need access to the media application. -/// -[PluginController(Constants.Web.Mvc.BackOfficeApiArea)] -[Authorize(Policy = AuthorizationPolicies.SectionAccessMedia)] -[ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))] -[ParameterSwapControllerActionSelector(nameof(GetChildren), "id", typeof(int), typeof(Guid), typeof(Udi))] -public class MediaController : ContentControllerBase -{ - private readonly AppCaches _appCaches; - private readonly IFileStreamSecurityValidator? _fileStreamSecurityValidator; // make non nullable in v14 - private readonly IAuthorizationService _authorizationService; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; - private readonly ContentSettings _contentSettings; - private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - private readonly IDataTypeService _dataTypeService; - private readonly IEntityService _entityService; - private readonly IImageUrlGenerator _imageUrlGenerator; - private readonly ILocalizedTextService _localizedTextService; - private readonly ILogger _logger; - private readonly IMediaService _mediaService; - private readonly IMediaTypeService _mediaTypeService; - private readonly IRelationService _relationService; - private readonly IShortStringHelper _shortStringHelper; - private readonly ISqlContext _sqlContext; - private readonly IUmbracoMapper _umbracoMapper; - - [ActivatorUtilitiesConstructor] - public MediaController( - ICultureDictionary cultureDictionary, - ILoggerFactory loggerFactory, - IShortStringHelper shortStringHelper, - IEventMessagesFactory eventMessages, - ILocalizedTextService localizedTextService, - IOptionsSnapshot contentSettings, - IMediaTypeService mediaTypeService, - IMediaService mediaService, - IEntityService entityService, - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - IUmbracoMapper umbracoMapper, - IDataTypeService dataTypeService, - ISqlContext sqlContext, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - IRelationService relationService, - PropertyEditorCollection propertyEditors, - MediaFileManager mediaFileManager, - MediaUrlGeneratorCollection mediaUrlGenerators, - IHostingEnvironment hostingEnvironment, - IImageUrlGenerator imageUrlGenerator, - IJsonSerializer serializer, - IAuthorizationService authorizationService, - AppCaches appCaches, - IFileStreamSecurityValidator streamSecurityValidator) - : base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, serializer) - { - _shortStringHelper = shortStringHelper; - _contentSettings = contentSettings.Value; - _mediaTypeService = mediaTypeService; - _mediaService = mediaService; - _entityService = entityService; - _backofficeSecurityAccessor = backofficeSecurityAccessor; - _umbracoMapper = umbracoMapper; - _dataTypeService = dataTypeService; - _localizedTextService = localizedTextService; - _sqlContext = sqlContext; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _relationService = relationService; - _propertyEditors = propertyEditors; - _mediaFileManager = mediaFileManager; - _mediaUrlGenerators = mediaUrlGenerators; - _hostingEnvironment = hostingEnvironment; - _logger = loggerFactory.CreateLogger(); - _imageUrlGenerator = imageUrlGenerator; - _authorizationService = authorizationService; - _appCaches = appCaches; - _fileStreamSecurityValidator = streamSecurityValidator; - } - - [Obsolete("Use constructor overload that has fileStreamSecurityValidator, scheduled for removal in v14")] - public MediaController( - ICultureDictionary cultureDictionary, - ILoggerFactory loggerFactory, - IShortStringHelper shortStringHelper, - IEventMessagesFactory eventMessages, - ILocalizedTextService localizedTextService, - IOptionsSnapshot contentSettings, - IMediaTypeService mediaTypeService, - IMediaService mediaService, - IEntityService entityService, - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - IUmbracoMapper umbracoMapper, - IDataTypeService dataTypeService, - ISqlContext sqlContext, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - IRelationService relationService, - PropertyEditorCollection propertyEditors, - MediaFileManager mediaFileManager, - MediaUrlGeneratorCollection mediaUrlGenerators, - IHostingEnvironment hostingEnvironment, - IImageUrlGenerator imageUrlGenerator, - IJsonSerializer serializer, - IAuthorizationService authorizationService, - AppCaches appCaches) - : base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, serializer) - { - _shortStringHelper = shortStringHelper; - _contentSettings = contentSettings.Value; - _mediaTypeService = mediaTypeService; - _mediaService = mediaService; - _entityService = entityService; - _backofficeSecurityAccessor = backofficeSecurityAccessor; - _umbracoMapper = umbracoMapper; - _dataTypeService = dataTypeService; - _localizedTextService = localizedTextService; - _sqlContext = sqlContext; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _relationService = relationService; - _propertyEditors = propertyEditors; - _mediaFileManager = mediaFileManager; - _mediaUrlGenerators = mediaUrlGenerators; - _hostingEnvironment = hostingEnvironment; - _logger = loggerFactory.CreateLogger(); - _imageUrlGenerator = imageUrlGenerator; - _authorizationService = authorizationService; - _appCaches = appCaches; - } - - /// - /// Gets an empty content item for the - /// - /// - /// - /// - [OutgoingEditorModelEvent] - public ActionResult GetEmpty(string contentTypeAlias, int parentId) - { - IMediaType? contentType = _mediaTypeService.Get(contentTypeAlias); - if (contentType == null) - { - return NotFound(); - } - - IMedia emptyContent = _mediaService.CreateMedia("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - MediaItemDisplay? mapped = _umbracoMapper.Map(emptyContent); - - if (mapped is not null) - { - // remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); - } - - return mapped; - } - - /// - /// Returns an item to be used to display the recycle bin for media - /// - /// - public MediaItemDisplay GetRecycleBin() - { - var apps = new List - { - ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, "recycleBin", "media", Constants.DataTypes.DefaultMediaListView) - }; - apps[0].Active = true; - var display = new MediaItemDisplay - { - Id = Constants.System.RecycleBinMedia, - Alias = "recycleBin", - ParentId = -1, - Name = _localizedTextService.Localize("general", "recycleBin"), - ContentTypeAlias = "recycleBin", - CreateDate = DateTime.Now, - IsContainer = true, - Path = "-1," + Constants.System.RecycleBinMedia, - ContentApps = apps - }; - - return display; - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] - public MediaItemDisplay? GetById(int id) - { - IMedia? foundMedia = GetObjectFromRequest(() => _mediaService.GetById(id)); - - if (foundMedia == null) - { - HandleContentNotFound(id); - - // HandleContentNotFound will throw an exception - return null; - } - - return _umbracoMapper.Map(foundMedia); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] - public MediaItemDisplay? GetById(Guid id) - { - IMedia? foundMedia = GetObjectFromRequest(() => _mediaService.GetById(id)); - - if (foundMedia == null) - { - HandleContentNotFound(id); - //HandleContentNotFound will throw an exception - return null; - } - - return _umbracoMapper.Map(foundMedia); - } - - /// - /// Gets the media item by id - /// - /// - /// - [OutgoingEditorModelEvent] - [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] - public ActionResult GetById(Udi id) - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - return GetById(guidUdi.Guid); - } - - return NotFound(); - } - - /// - /// Return media for the specified ids - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable))] - public IEnumerable GetByIds([FromQuery] int[] ids) - { - IEnumerable foundMedia = _mediaService.GetByIds(ids); - return foundMedia.Select(media => _umbracoMapper.Map(media)); - } - - /// - /// Returns a paged result of media items known to be of a "Folder" type - /// - /// - /// - /// - /// - public PagedResult> GetChildFolders(int id, int pageNumber = 1, int pageSize = 1000) - { - // Suggested convention for folder mediatypes - we can make this more or less complicated as long as we document it... - // if you create a media type, which has an alias that ends with ...Folder then its a folder: ex: "secureFolder", "bannerFolder", "Folder" - var folderTypes = _mediaTypeService - .GetAll() - .Where(x => x.Alias.EndsWith("Folder")) - .Select(x => x.Id) - .ToArray(); - - if (folderTypes.Length == 0) - { - return new PagedResult>(0, pageNumber, pageSize); - } - - IEnumerable children = _mediaService.GetPagedChildren(id, pageNumber - 1, pageSize, out long total, - - // lookup these content types - _sqlContext.Query().Where(x => folderTypes.Contains(x.ContentTypeId)), - Ordering.By("Name")); - - return new PagedResult>(total, pageNumber, pageSize) - { - Items = children.Select(_umbracoMapper.Map>) - .WhereNotNull() - }; - } - - /// - /// Returns the root media objects - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>))] - public IEnumerable> GetRootMedia() => - - // TODO: Add permissions check! - _mediaService.GetRootMedia()? - .Select(_umbracoMapper.Map>).WhereNotNull() ?? - Enumerable.Empty>(); - - /// - /// Moves an item to the recycle bin, if it is already there then it will permanently delete it - /// - /// - /// - [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] - [HttpPost] - public IActionResult DeleteById(int id) - { - IMedia? foundMedia = GetObjectFromRequest(() => _mediaService.GetById(id)); - - if (foundMedia == null) - { - return HandleContentNotFound(id); - } - - // if the current item is in the recycle bin - if (foundMedia.Trashed == false) - { - Attempt moveResult = _mediaService.MoveToRecycleBin(foundMedia, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - if (moveResult == false) - { - return ValidationProblem(); - } - } - else - { - Attempt deleteResult = _mediaService.Delete(foundMedia, - _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - if (deleteResult == false) - { - return ValidationProblem(); - } - } - - return Ok(); - } - - /// - /// Change the sort order for media - /// - /// - /// - public async Task PostMove(MoveOrCopy move) - { - // Authorize... - var requirement = new MediaPermissionsResourceRequirement(); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync( - User, - new MediaPermissionsResource(_mediaService.GetById(move.Id)), - requirement); - if (!authorizationResult.Succeeded) - { - return Forbid(); - } - - ActionResult toMoveResult = ValidateMoveOrCopy(move); - IMedia? toMove = toMoveResult.Value; - if (toMove is null && toMoveResult is IConvertToActionResult convertToActionResult) - { - return convertToActionResult.Convert(); - } - - var destinationParentId = move.ParentId; - var sourceParentId = toMove?.ParentId; - - var moveResult = toMove is null - ? false - : _mediaService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - - if (sourceParentId == destinationParentId) - { - return ValidationProblem(new SimpleNotificationModel(new BackOfficeNotification( - string.Empty, - _localizedTextService.Localize("media", "moveToSameFolderFailed"), - NotificationStyle.Error))); - } - - if (moveResult == false) - { - return ValidationProblem(); - } - - return Content(toMove!.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); - } - - /// - /// Saves content - /// - /// - [MediaItemSaveValidation] - [OutgoingEditorModelEvent] - public ActionResult? PostSave( - [ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem) - { - // If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - // Don't update the name if it is empty - if (contentItem.Name.IsNullOrWhiteSpace() == false && contentItem.PersistedContent is not null) - { - contentItem.PersistedContent.Name = contentItem.Name; - } - - MapPropertyValuesForPersistence( - contentItem, - contentItem.PropertyCollectionDto, - (save, property) => property?.GetValue(), //get prop val - (save, property, v) => property?.SetValue(v), //set prop val - null); // media are all invariant - - // we will continue to save if model state is invalid, however we cannot save if critical data is missing. - // TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live' - if (!ModelState.IsValid) - { - // check for critical data validation issues, we can't continue saving if this data is invalid - if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem)) - { - // ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! - // add the model state to the outgoing object and throw validation response - MediaItemDisplay? forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); - return ValidationProblem(forDisplay, ModelState); - } - } - - if (contentItem.PersistedContent is null) - { - return null; - } - - // save the item - Attempt saveStatus = _mediaService.Save(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - - // return the updated model - MediaItemDisplay? display = _umbracoMapper.Map(contentItem.PersistedContent); - - // lastly, if it is not valid, add the model state to the outgoing object and throw a 403 - if (!ModelState.IsValid) - { - return ValidationProblem(display, ModelState, StatusCodes.Status403Forbidden); - } - - // put the correct msgs in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - if (saveStatus.Success) - { - display?.AddSuccessNotification( - _localizedTextService.Localize("speechBubbles", "editMediaSaved"), - _localizedTextService.Localize("speechBubbles", "editMediaSavedText")); - } - else - { - AddCancelMessage(display); - - // If the item is new and the operation was cancelled, we need to return a different - // status code so the UI can handle it since it won't be able to redirect since there - // is no Id to redirect to! - if (saveStatus.Result?.Result == OperationResultType.FailedCancelledByEvent && - IsCreatingAction(contentItem.Action)) - { - return ValidationProblem(display); - } - } - - break; - } - - return display; - } - - /// - /// Empties the recycle bin - /// - /// - [HttpDelete] - [HttpPost] - public IActionResult EmptyRecycleBin() - { - _mediaService.EmptyRecycleBin(_backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); - - return Ok(_localizedTextService.Localize("defaultdialogs", "recycleBinIsEmpty")); - } - - /// - /// Change the sort order for media - /// - /// - /// - public async Task PostSort(ContentSortOrder sorted) - { - if (sorted == null) - { - return NotFound(); - } - - // if there's nothing to sort just return ok - if (sorted.IdSortOrder?.Length == 0) - { - return Ok(); - } - - // Authorize... - var requirement = new MediaPermissionsResourceRequirement(); - var resource = new MediaPermissionsResource(sorted.ParentId); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(User, resource, requirement); - if (!authorizationResult.Succeeded) - { - return Forbid(); - } - - var sortedMedia = new List(); - try - { - sortedMedia.AddRange(sorted.IdSortOrder?.Select(_mediaService.GetById).WhereNotNull() ?? - Enumerable.Empty()); - - // Save Media with new sort order and update content xml in db accordingly - if (_mediaService.Sort(sortedMedia) == false) - { - _logger.LogWarning("Media sorting failed, this was probably caused by an event being cancelled"); - return ValidationProblem("Media sorting failed, this was probably caused by an event being cancelled"); - } - - return Ok(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Could not update media sort order"); - throw; - } - } - - public async Task> PostAddFolder(PostedFolder folder) - { - ActionResult? parentIdResult = await GetParentIdAsIntAsync(folder.ParentId, true); - if (parentIdResult?.Result is not null) - { - return new ActionResult(parentIdResult.Result); - } - - var parentId = parentIdResult?.Value; - if (!parentId.HasValue) - { - return NotFound("The passed id doesn't exist"); - } - - var isFolderAllowed = IsFolderCreationAllowedHere(parentId.Value); - if (isFolderAllowed == false) - { - return ValidationProblem(_localizedTextService.Localize("speechBubbles", "folderCreationNotAllowed")); - } - - IMedia f = _mediaService.CreateMedia(folder.Name, parentId.Value, Constants.Conventions.MediaTypes.Folder); - _mediaService.Save(f, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); - - return _umbracoMapper.Map(f); - } - - private bool IsFolderCreationAllowedHere(int parentId) - { - var allMediaTypes = _mediaTypeService.GetAll().ToList(); - var isFolderAllowed = false; - if (parentId == Constants.System.Root) - { - var typesAllowedAtRoot = allMediaTypes.Where(ct => ct.AllowedAsRoot).ToList(); - isFolderAllowed = typesAllowedAtRoot.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder); - } - else - { - IMedia? parentMediaType = _mediaService.GetById(parentId); - IMediaType? mediaFolderType = - allMediaTypes.FirstOrDefault(x => x.Alias == parentMediaType?.ContentType.Alias); - if (mediaFolderType != null) - { - isFolderAllowed = - mediaFolderType.AllowedContentTypes?.Any(x => x.Alias == Constants.Conventions.MediaTypes.Folder) ?? - false; - } - } - - return isFolderAllowed; - } - - private IMedia? FindInChildren(int mediaId, string nameToFind, string contentTypeAlias) - { - const int pageSize = 500; - var page = 0; - var total = long.MaxValue; - while (page * pageSize < total) - { - IEnumerable children = _mediaService.GetPagedChildren( - mediaId, - page++, - pageSize, - out total, - _sqlContext.Query().Where(x => x.Name == nameToFind)); - IMedia? match = children.FirstOrDefault(c => c.ContentType.Alias == contentTypeAlias); - if (match != null) - { - return match; - } - } - - return null; - } - - /// - /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT - /// - /// - /// - /// If true, this will check if the current user has access to the resolved integer parent id - /// and if that check fails an unauthorized exception will occur - /// - /// - private async Task?> GetParentIdAsIntAsync(string? parentId, bool validatePermissions) - { - - // test for udi - if (UdiParser.TryParse(parentId, out GuidUdi? parentUdi)) - { - parentId = parentUdi?.Guid.ToString(); - } - - // if it's not an INT then we'll check for GUID - if (int.TryParse(parentId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intParentId) == false) - { - // if a guid then try to look up the entity - if (Guid.TryParse(parentId, out Guid idGuid)) - { - IEntitySlim? entity = _entityService.Get(idGuid); - if (entity != null) - { - intParentId = entity.Id; - } - else - { - return null; - } - } - else - { - return ValidationProblem( - "The request was not formatted correctly, the parentId is not an integer, Guid or UDI"); - } - } - - // Authorize... - // ensure the user has access to this folder by parent id! - if (validatePermissions) - { - var requirement = new MediaPermissionsResourceRequirement(); - AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(User, - new MediaPermissionsResource(_mediaService.GetById(intParentId)), requirement); - if (!authorizationResult.Succeeded) - { - return ValidationProblem( - new SimpleNotificationModel(new BackOfficeNotification( - _localizedTextService.Localize("speechBubbles", "operationFailedHeader"), - _localizedTextService.Localize("speechBubbles", "invalidUserPermissionsText"), - NotificationStyle.Warning)), - StatusCodes.Status403Forbidden); - } - } - - return intParentId; - } - - /// - /// Ensures the item can be moved/copied to the new location - /// - /// - /// - private ActionResult ValidateMoveOrCopy(MoveOrCopy model) - { - if (model == null) - { - return NotFound(); - } - - - IMedia? toMove = _mediaService.GetById(model.Id); - if (toMove == null) - { - return NotFound(); - } - - if (model.ParentId < 0) - { - // cannot move if the content item is not allowed at the root unless there are - // none allowed at root (in which case all should be allowed at root) - IMediaTypeService mediaTypeService = _mediaTypeService; - if (toMove.ContentType.AllowedAsRoot == false && mediaTypeService.GetAll().Any(ct => ct.AllowedAsRoot)) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedAtRoot"), string.Empty); - return ValidationProblem(notificationModel); - } - } - else - { - IMedia? parent = _mediaService.GetById(model.ParentId); - if (parent == null) - { - return NotFound(); - } - - // check if the item is allowed under this one - IMediaType? parentContentType = _mediaTypeService.Get(parent.ContentTypeId); - if (parentContentType?.AllowedContentTypes?.Select(x => x.Key).ToArray() - .Any(x => x == toMove.ContentType.Key) == false) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification( - _localizedTextService.Localize("moveOrCopy", "notAllowedByContentType"), ""); - return ValidationProblem(notificationModel); - } - - // Check on paths - if ($",{parent.Path}," - .IndexOf($",{toMove.Id},", StringComparison.Ordinal) > -1) - { - var notificationModel = new SimpleNotificationModel(); - notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy", "notAllowedByPath"), string.Empty); - return ValidationProblem(notificationModel); - } - } - - return new ActionResult(toMove); - } - - #region GetChildren - - private int[]? _userStartNodes; - private readonly PropertyEditorCollection _propertyEditors; - private readonly MediaFileManager _mediaFileManager; - private readonly MediaUrlGeneratorCollection _mediaUrlGenerators; - private readonly IHostingEnvironment _hostingEnvironment; - - - protected int[] UserStartNodes => _userStartNodes ??= - _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.CalculateMediaStartNodeIds(_entityService, - _appCaches) ?? Array.Empty(); - - /// - /// Returns the child media objects - using the entity INT id - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public PagedResult> GetChildren( - int id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - // if a request is made for the root node data but the user's start node is not the default, then - // we need to return their start nodes - if (id == Constants.System.Root && UserStartNodes.Length > 0 && - UserStartNodes.Contains(Constants.System.Root) == false) - { - if (pageNumber > 0) - { - return new PagedResult>(0, 0, 0); - } - - IMedia[] nodes = _mediaService.GetByIds(UserStartNodes).ToArray(); - if (nodes.Length == 0) - { - return new PagedResult>(0, 0, 0); - } - - if (pageSize < nodes.Length) - { - pageSize = nodes.Length; // bah - } - - var pr = new PagedResult>(nodes.Length, pageNumber, pageSize) - { - Items = nodes.Select(_umbracoMapper.Map>) - .WhereNotNull() - }; - return pr; - } - - // else proceed as usual - long totalChildren; - List children; - if (pageNumber > 0 && pageSize > 0) - { - IQuery? queryFilter = null; - if (filter.IsNullOrWhiteSpace() == false) - { - int.TryParse(filter, out int filterAsIntId); - Guid.TryParse(filter, out Guid filterAsGuid); - // add the default text filter - queryFilter = _sqlContext.Query() - .Where(x => x.Name != null) - .Where(x => x.Name!.Contains(filter) - || x.Id == filterAsIntId || x.Key == filterAsGuid); - } - - children = _mediaService - .GetPagedChildren( - id, - pageNumber - 1, - pageSize, - out totalChildren, - queryFilter, - Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)).ToList(); - } - else - { - // better to not use this without paging where possible, currently only the sort dialog does - children = _mediaService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList(); - totalChildren = children.Count; - } - - if (totalChildren == 0) - { - return new PagedResult>(0, 0, 0); - } - - var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize) - { - Items = children - .Select(_umbracoMapper.Map>).WhereNotNull() - }; - - return pagedResult; - } - - /// - /// Returns the child media objects - using the entity GUID id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public ActionResult>> GetChildren(Guid id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - IEntitySlim? entity = _entityService.Get(id); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - - return NotFound(); - } - - /// - /// Returns the child media objects - using the entity UDI id - /// - /// - /// - /// - /// - /// - /// - /// - /// - [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] - public ActionResult>> GetChildren(Udi id, - int pageNumber = 0, - int pageSize = 0, - string orderBy = "SortOrder", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "") - { - var guidUdi = id as GuidUdi; - if (guidUdi != null) - { - IEntitySlim? entity = _entityService.Get(guidUdi.Guid); - if (entity != null) - { - return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); - } - } - - return NotFound(); - } - - #endregion -} diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs deleted file mode 100644 index 9c1aceaa6bca..000000000000 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ /dev/null @@ -1,826 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Globalization; -using System.Net.Mime; -using System.Text; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.ContentApps; -using Umbraco.Cms.Core.Dictionary; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Web.BackOffice.Filters; -using Umbraco.Cms.Web.BackOffice.ModelBinders; -using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Cms.Web.Common.Authorization; -using Umbraco.Cms.Web.Common.DependencyInjection; -using Umbraco.Cms.Web.Common.Filters; -using Umbraco.Cms.Web.Common.Security; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Web.BackOffice.Controllers; - -/// -/// This controller is decorated with the UmbracoApplicationAuthorizeAttribute which means that any user requesting -/// access to ALL of the methods on this controller will need access to the member application. -/// -[PluginController(Constants.Web.Mvc.BackOfficeApiArea)] -[Authorize(Policy = AuthorizationPolicies.SectionAccessMembers)] -[OutgoingNoHyphenGuidFormat] -public class MemberController : ContentControllerBase -{ - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IDataTypeService _dataTypeService; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILocalizedTextService _localizedTextService; - private readonly IMemberManager _memberManager; - private readonly IMemberService _memberService; - private readonly IMemberTypeService _memberTypeService; - private readonly IPasswordChanger _passwordChanger; - private readonly PropertyEditorCollection _propertyEditors; - private readonly ICoreScopeProvider _scopeProvider; - private readonly ITwoFactorLoginService _twoFactorLoginService; - private readonly IShortStringHelper _shortStringHelper; - private readonly IUmbracoMapper _umbracoMapper; - - /// - /// Initializes a new instance of the class. - /// - /// The culture dictionary - /// The logger factory - /// The string helper - /// The event messages factory - /// The entry point for localizing key services - /// The property editors - /// The mapper - /// The member service - /// The member type service - /// The member manager - /// The data-type service - /// The back office security accessor - /// The JSON serializer - /// The password changer - /// The core scope provider - /// The two factor login service - [ActivatorUtilitiesConstructor] - public MemberController( - ICultureDictionary cultureDictionary, - ILoggerFactory loggerFactory, - IShortStringHelper shortStringHelper, - IEventMessagesFactory eventMessages, - ILocalizedTextService localizedTextService, - PropertyEditorCollection propertyEditors, - IUmbracoMapper umbracoMapper, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberManager memberManager, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IJsonSerializer jsonSerializer, - IPasswordChanger passwordChanger, - ICoreScopeProvider scopeProvider, - ITwoFactorLoginService twoFactorLoginService) - : base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, jsonSerializer) - { - _propertyEditors = propertyEditors; - _umbracoMapper = umbracoMapper; - _memberService = memberService; - _memberTypeService = memberTypeService; - _memberManager = memberManager; - _dataTypeService = dataTypeService; - _localizedTextService = localizedTextService; - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _jsonSerializer = jsonSerializer; - _shortStringHelper = shortStringHelper; - _passwordChanger = passwordChanger; - _scopeProvider = scopeProvider; - _twoFactorLoginService = twoFactorLoginService; - } - - [Obsolete("Use constructor that also takes an ITwoFactorLoginService. Scheduled for removal in V13")] - public MemberController( - ICultureDictionary cultureDictionary, - ILoggerFactory loggerFactory, - IShortStringHelper shortStringHelper, - IEventMessagesFactory eventMessages, - ILocalizedTextService localizedTextService, - PropertyEditorCollection propertyEditors, - IUmbracoMapper umbracoMapper, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberManager memberManager, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IJsonSerializer jsonSerializer, - IPasswordChanger passwordChanger, - ICoreScopeProvider scopeProvider) - : this( - cultureDictionary, - loggerFactory, - shortStringHelper, - eventMessages, - localizedTextService, - propertyEditors, - umbracoMapper, - memberService, - memberTypeService, - memberManager, - dataTypeService, - backOfficeSecurityAccessor, - jsonSerializer, - passwordChanger, - scopeProvider, - StaticServiceProvider.Instance.GetRequiredService()) - { - } - - /// - /// The paginated list of members - /// - /// The page number to display - /// The size of the page - /// The ordering of the member list - /// The direction of the member list - /// The system field to order by - /// The current filter for the list - /// The member type - /// The paged result of members - public PagedResult GetPagedResults( - int pageNumber = 1, - int pageSize = 100, - string orderBy = "username", - Direction orderDirection = Direction.Ascending, - bool orderBySystemField = true, - string filter = "", - string? memberTypeAlias = null) - { - if (pageNumber <= 0 || pageSize <= 0) - { - throw new NotSupportedException("Both pageNumber and pageSize must be greater than zero"); - } - - IMember[] members = _memberService.GetAll( - pageNumber - 1, - pageSize, - out var totalRecords, - orderBy, - orderDirection, - orderBySystemField, - memberTypeAlias, - filter).ToArray(); - if (totalRecords == 0) - { - return new PagedResult(0, 0, 0); - } - - var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) - { - Items = members.Select(x => _umbracoMapper.Map(x)).WhereNotNull() - }; - return pagedResult; - } - - /// - /// Returns a display node with a list view to render members - /// - /// The member type to list - /// The member list for display - public MemberListDisplay GetListNodeDisplay(string listName) - { - IMemberType? foundType = _memberTypeService.Get(listName); - var name = foundType != null ? foundType.Name : listName; - - var apps = new List - { - ListViewContentAppFactory.CreateContentApp(_dataTypeService, _propertyEditors, listName, "member", Constants.DataTypes.DefaultMembersListView) - }; - apps[0].Active = true; - - var display = new MemberListDisplay - { - ContentTypeAlias = listName, - ContentTypeName = name, - Id = listName, - IsContainer = true, - Name = listName == Constants.Conventions.MemberTypes.AllMembersListId ? "All Members" : name, - Path = "-1," + listName, - ParentId = -1, - ContentApps = apps - }; - - return display; - } - - /// - /// Gets the content json for the member - /// - /// The Guid key of the member - /// The member for display - [OutgoingEditorModelEvent] - public MemberDisplay? GetByKey(Guid key) - { - IMember? foundMember = _memberService.GetByKey(key); - if (foundMember == null) - { - HandleContentNotFound(key); - } - - return _umbracoMapper.Map(foundMember); - } - - /// - /// Gets an empty content item for the - /// - /// The content type - /// The empty member for display - [OutgoingEditorModelEvent] - public ActionResult GetEmpty(string? contentTypeAlias = null) - { - if (contentTypeAlias == null) - { - return NotFound(); - } - - IMemberType? contentType = _memberTypeService.Get(contentTypeAlias); - if (contentType == null) - { - return NotFound(); - } - - var newPassword = _memberManager.GeneratePassword(); - - IMember emptyContent = new Member(contentType); - if (emptyContent.AdditionalData is not null) - { - emptyContent.AdditionalData["NewPassword"] = newPassword; - } - - return _umbracoMapper.Map(emptyContent); - } - - /// - /// Saves member - /// - /// The content item to save as a member - /// The resulting member display object - [OutgoingEditorModelEvent] - [MemberSaveValidation] - public async Task> PostSave([ModelBinder(typeof(MemberBinder))] MemberSave contentItem) - { - if (contentItem == null) - { - throw new ArgumentNullException("The member content item was null"); - } - - // If we've reached here it means: - // * Our model has been bound - // * and validated - // * any file attachments have been saved to their temporary location for us to use - // * we have a reference to the DTO object and the persisted object - // * Permissions are valid - - // map the properties to the persisted entity - MapPropertyValues(contentItem); - - await ValidateMemberDataAsync(contentItem); - - // Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors - if (ModelState.IsValid == false) - { - MemberDisplay? forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); - return ValidationProblem(forDisplay, ModelState); - } - - // Create a scope here which will wrap all child data operations in a single transaction. - // We'll complete this at the end of this method if everything succeeeds, else - // all data operations will roll back. - using ICoreScope scope = _scopeProvider.CreateCoreScope(); - - // Depending on the action we need to first do a create or update using the membership manager - // this ensures that passwords are formatted correctly and also performs the validation on the provider itself. - switch (contentItem.Action) - { - case ContentSaveAction.Save: - ActionResult updateSuccessful = await UpdateMemberAsync(contentItem); - if (!(updateSuccessful.Result is null)) - { - return updateSuccessful.Result; - } - - break; - case ContentSaveAction.SaveNew: - ActionResult createSuccessful = await CreateMemberAsync(contentItem); - if (!(createSuccessful.Result is null)) - { - return createSuccessful.Result; - } - - break; - default: - // we don't support anything else for members - return NotFound(); - } - - // return the updated model - MemberDisplay? display = _umbracoMapper.Map(contentItem.PersistedContent); - - // lastly, if it is not valid, add the model state to the outgoing object and throw a 403 - if (!ModelState.IsValid) - { - return ValidationProblem(display, ModelState, StatusCodes.Status403Forbidden); - } - - // put the correct messages in - switch (contentItem.Action) - { - case ContentSaveAction.Save: - case ContentSaveAction.SaveNew: - display?.AddSuccessNotification( - _localizedTextService.Localize("speechBubbles", "editMemberSaved"), - _localizedTextService.Localize("speechBubbles", "editMemberSaved")); - break; - } - - // Mark transaction to commit all changes - scope.Complete(); - - return display; - } - - /// - /// Maps the property values to the persisted entity - /// - /// The member content item to map properties from - private void MapPropertyValues(MemberSave contentItem) - { - if (contentItem.PersistedContent is not null) - { - // Don't update the name if it is empty - if (contentItem.Name.IsNullOrWhiteSpace() == false) - { - contentItem.PersistedContent.Name = contentItem.Name; - } - - // map the custom properties - this will already be set for new entities in our member binder - if (_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? false) - { - contentItem.PersistedContent.IsApproved = contentItem.IsApproved; - } - contentItem.PersistedContent.Email = contentItem.Email.Trim(); - contentItem.PersistedContent.Username = contentItem.Username; - } - - // use the base method to map the rest of the properties - MapPropertyValuesForPersistence( - contentItem, - contentItem.PropertyCollectionDto, - (save, property) => property?.GetValue(), // get prop val - (save, property, v) => property?.SetValue(v), // set prop val - null); // member are all invariant - } - - /// - /// Create a member from the supplied member content data - /// All member password processing and creation is done via the identity manager - /// - /// Member content data - /// The identity result of the created member - private async Task> CreateMemberAsync(MemberSave contentItem) - { - IMemberType? memberType = _memberTypeService.Get(contentItem.ContentTypeAlias); - if (memberType == null) - { - throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}"); - } - - var identityMember = MemberIdentityUser.CreateNew( - contentItem.Username, - contentItem.Email, - memberType.Alias, - contentItem.IsApproved, - contentItem.Name); - - IdentityResult created = await _memberManager.CreateAsync(identityMember, contentItem.Password?.NewPassword!); - - if (created.Succeeded == false) - { - MemberDisplay? forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); - foreach (IdentityError error in created.Errors) - { - switch (error.Code) - { - case nameof(IdentityErrorDescriber.InvalidUserName): - ModelState.AddPropertyError( - new ValidationResult(error.Description, new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case nameof(IdentityErrorDescriber.PasswordMismatch): - case nameof(IdentityErrorDescriber.PasswordRequiresDigit): - case nameof(IdentityErrorDescriber.PasswordRequiresLower): - case nameof(IdentityErrorDescriber.PasswordRequiresNonAlphanumeric): - case nameof(IdentityErrorDescriber.PasswordRequiresUniqueChars): - case nameof(IdentityErrorDescriber.PasswordRequiresUpper): - case nameof(IdentityErrorDescriber.PasswordTooShort): - ModelState.AddPropertyError( - new ValidationResult(error.Description, new[] { "value" }), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case nameof(IdentityErrorDescriber.InvalidEmail): - ModelState.AddPropertyError( - new ValidationResult(error.Description, new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case nameof(IdentityErrorDescriber.DuplicateUserName): - ModelState.AddPropertyError( - new ValidationResult(error.Description, new[] { "value" }), - string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - case nameof(IdentityErrorDescriber.DuplicateEmail): - ModelState.AddPropertyError( - new ValidationResult(error.Description, new[] { "value" }), - string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - break; - } - } - - return ValidationProblem(forDisplay, ModelState); - } - - // now re-look up the member, which will now exist - IMember? member = _memberService.GetByEmail(contentItem.Email); - - if (member is null) - { - return false; - } - - // map the save info over onto the user - member = _umbracoMapper.Map(contentItem, member); - - var creatorId = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1; - member.CreatorId = creatorId; - - // assign the mapped property values that are not part of the identity properties - var builtInAliases = ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Select(x => x.Key) - .ToArray(); - foreach (ContentPropertyBasic property in contentItem.Properties) - { - if (builtInAliases.Contains(property.Alias) == false) - { - member.Properties[property.Alias]?.SetValue(property.Value); - } - } - - // now the member has been saved via identity, resave the member with mapped content properties - _memberService.Save(member); - contentItem.PersistedContent = member; - - ActionResult rolesChanged = await AddOrUpdateRoles(contentItem.Groups, identityMember); - if (!rolesChanged.Value && rolesChanged.Result != null) - { - return rolesChanged.Result; - } - - return true; - } - - /// - /// Update existing member data - /// - /// The member to save - /// - /// We need to use both IMemberService and ASP.NET Identity to do our updates because Identity is responsible for - /// passwords/security. - /// When this method is called, the IMember will already have updated/mapped values from the http POST. - /// So then we do this in order: - /// 1. Deal with sensitive property values on IMember - /// 2. Use IMemberService to persist all changes - /// 3. Use ASP.NET and MemberUserManager to deal with lockouts - /// 4. Use ASP.NET, MemberUserManager and password changer to deal with passwords - /// 5. Deal with groups/roles - /// - private async Task> UpdateMemberAsync(MemberSave contentItem) - { - if (contentItem.PersistedContent is not null) - { - contentItem.PersistedContent.WriterId = - _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1; - } - - // If the user doesn't have access to sensitive values, then we need to check if any of the built in member property types - // have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted. - // There's only 3 special ones we need to deal with that are part of the MemberSave instance: Comments, IsApproved, IsLockedOut - // but we will take care of this in a generic way below so that it works for all props. - if (!_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? true) - { - IMemberType? memberType = contentItem.PersistedContent is null - ? null - : _memberTypeService.Get(contentItem.PersistedContent.ContentTypeId); - var sensitiveProperties = memberType? - .PropertyTypes.Where(x => memberType.IsSensitiveProperty(x.Alias)) - .ToList(); - - if (sensitiveProperties is not null) - { - foreach (IPropertyType sensitiveProperty in sensitiveProperties) - { - // TODO: This logic seems to deviate from the logic that is in v8 where we are explitly checking - // against 3 properties: Comments, IsApproved, IsLockedOut, is the v8 version incorrect? - - ContentPropertyBasic? destProp = - contentItem.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias); - if (destProp != null) - { - // if found, change the value of the contentItem model to the persisted value so it remains unchanged - var origValue = contentItem.PersistedContent?.GetValue(sensitiveProperty.Alias); - destProp.Value = origValue; - } - } - } - //thoese properties defaulting to sensitive, change the value of the contentItem model to the persisted value - if (contentItem.PersistedContent is not null) - { - contentItem.IsApproved = contentItem.PersistedContent.IsApproved; - contentItem.IsLockedOut = contentItem.PersistedContent.IsLockedOut; - } - contentItem.IsTwoFactorEnabled = await _twoFactorLoginService.IsTwoFactorEnabledAsync(contentItem.Key); - } - - if (contentItem.PersistedContent is not null) - { - // First save the IMember with mapped values before we start updating data with aspnet identity - _memberService.Save(contentItem.PersistedContent); - } - - var needsResync = false; - var memberId = contentItem.Id?.ToString(); - if (memberId is null) - { - return ValidationProblem("Member was not found"); - } - MemberIdentityUser? identityMember = await _memberManager.FindByIdAsync(memberId); - if (identityMember == null) - { - return ValidationProblem("Member was not found"); - } - - // Handle unlocking with the member manager (takes care of other nuances) - if (identityMember.IsLockedOut && contentItem.IsLockedOut == false) - { - IdentityResult unlockResult = - await _memberManager.SetLockoutEndDateAsync(identityMember, DateTimeOffset.Now.AddMinutes(-1)); - if (unlockResult.Succeeded == false) - { - return ValidationProblem( - $"Could not unlock for member {contentItem.Id} - error {unlockResult.Errors.ToErrorMessage()}"); - } - - needsResync = true; - } - else if (identityMember.IsLockedOut == false && contentItem.IsLockedOut) - { - // NOTE: This should not ever happen unless someone is mucking around with the request data. - // An admin cannot simply lock a user, they get locked out by password attempts, but an admin can unlock them - return ValidationProblem("An admin cannot lock a member"); - } - - // Handle disabling of 2FA - if (!contentItem.IsTwoFactorEnabled) - { - IEnumerable providers = await _twoFactorLoginService.GetEnabledTwoFactorProviderNamesAsync(contentItem.Key); - foreach (var provider in providers) - { - await _twoFactorLoginService.DisableAsync(contentItem.Key, provider); - } - } - - // If we're changing the password... - // Handle changing with the member manager & password changer (takes care of other nuances) - if (contentItem.Password != null) - { - IdentityResult validatePassword = - await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword); - if (validatePassword.Succeeded == false) - { - return ValidationProblem(validatePassword.Errors.ToErrorMessage()); - } - - if (!int.TryParse(identityMember.Id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) - { - return ValidationProblem("Member ID was not valid"); - } - - var changingPasswordModel = new ChangingPasswordModel - { - Id = intId, - OldPassword = contentItem.Password.OldPassword, - NewPassword = contentItem.Password.NewPassword - }; - - // Change and persist the password - Attempt passwordChangeResult = - await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _memberManager, _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser); - - if (!passwordChangeResult.Success) - { - foreach (var memberName in passwordChangeResult.Result?.Error?.MemberNames ?? - Enumerable.Empty()) - { - ModelState.AddModelError(memberName, passwordChangeResult.Result?.Error?.ErrorMessage ?? string.Empty); - } - - return ValidationProblem(ModelState); - } - - needsResync = true; - } - - // Update the roles and check for changes - ActionResult rolesChanged = await AddOrUpdateRoles(contentItem.Groups, identityMember); - if (!rolesChanged.Value && rolesChanged.Result != null) - { - return rolesChanged.Result; - } - - needsResync = true; - - // If there have been underlying changes made by ASP.NET Identity, then we need to resync the - // IMember on the PersistedContent with what is stored since it will be mapped to display. - if (needsResync && contentItem.PersistedContent is not null) - { - contentItem.PersistedContent = _memberService.GetById(contentItem.PersistedContent.Id)!; - } - - return true; - } - - private async Task ValidateMemberDataAsync(MemberSave contentItem) - { - if (contentItem.Name.IsNullOrWhiteSpace()) - { - ModelState.AddPropertyError( - new ValidationResult("Invalid user name", new[] { "value" }), - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login"); - return false; - } - - if (contentItem.Password != null && !contentItem.Password.NewPassword.IsNullOrWhiteSpace()) - { - IdentityResult validPassword = await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword); - if (!validPassword.Succeeded) - { - ModelState.AddPropertyError( - new ValidationResult("Invalid password: " + MapErrors(validPassword.Errors), new[] { "value" }), - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password"); - return false; - } - } - - IMember? byUsername = _memberService.GetByUsername(contentItem.Username); - if (byUsername != null && byUsername.Key != contentItem.Key) - { - ModelState.AddPropertyError( - new ValidationResult("Username is already in use", new[] { "value" }), - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login"); - return false; - } - - IMember? byEmail = _memberService.GetByEmail(contentItem.Email); - if (byEmail != null && byEmail.Key != contentItem.Key) - { - ModelState.AddPropertyError( - new ValidationResult("Email address is already in use", new[] { "value" }), - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email"); - return false; - } - - return true; - } - - private string MapErrors(IEnumerable result) - { - var sb = new StringBuilder(); - IEnumerable identityErrors = result.ToList(); - foreach (IdentityError error in identityErrors) - { - var errorString = $"{error.Description}"; - sb.AppendLine(errorString); - } - - return sb.ToString(); - } - - /// - /// Add or update the identity roles - /// - /// The groups to updates - /// The member as an identity user - private async Task> AddOrUpdateRoles(IEnumerable? groups, MemberIdentityUser identityMember) - { - var hasChanges = false; - - // We're gonna look up the current roles now because the below code can cause - // events to be raised and developers could be manually adding roles to members in - // their handlers. If we don't look this up now there's a chance we'll just end up - // removing the roles they've assigned. - IEnumerable currentRoles = await _memberManager.GetRolesAsync(identityMember); - - // find the ones to remove and remove them - IEnumerable roles = currentRoles.ToList(); - var rolesToRemove = roles.Except(groups ?? Enumerable.Empty()).ToArray(); - - // Now let's do the role provider stuff - now that we've saved the content item (that is important since - // if we are changing the username, it must be persisted before looking up the member roles). - if (rolesToRemove.Any()) - { - IdentityResult identityResult = await _memberManager.RemoveFromRolesAsync(identityMember, rolesToRemove); - if (!identityResult.Succeeded) - { - return ValidationProblem(identityResult.Errors.ToErrorMessage()); - } - - hasChanges = true; - } - - // find the ones to add and add them - var toAdd = groups?.Except(roles).ToArray(); - if (toAdd?.Any() ?? false) - { - // add the ones submitted - IdentityResult identityResult = await _memberManager.AddToRolesAsync(identityMember, toAdd); - if (!identityResult.Succeeded) - { - return ValidationProblem(identityResult.Errors.ToErrorMessage()); - } - - hasChanges = true; - } - - return hasChanges; - } - - /// - /// Permanently deletes a member - /// - /// Guid of the member to delete - /// The result of the deletion - [HttpPost] - public IActionResult DeleteByKey(Guid key) - { - IMember? foundMember = _memberService.GetByKey(key); - if (foundMember == null) - { - return HandleContentNotFound(key); - } - - _memberService.Delete(foundMember); - - return Ok(); - } - - /// - /// Exports member data based on their unique Id - /// - /// The unique member identifier - /// - /// - /// - [HttpGet] - public IActionResult ExportMemberData(Guid key) - { - IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - - if (currentUser?.HasAccessToSensitiveData() == false) - { - return Forbid(); - } - - MemberExportModel? member = ((MemberService)_memberService).ExportMember(key); - if (member is null) - { - throw new NullReferenceException("No member found with key " + key); - } - - var json = _jsonSerializer.Serialize(member); - - var fileName = $"{member.Name}_{member.Email}.txt"; - - // Set custom header so umbRequestHelper.downloadFile can save the correct filename - HttpContext.Response.Headers.Add("x-filename", fileName); - - return File(Encoding.UTF8.GetBytes(json), MediaTypeNames.Application.Octet, fileName); - } -} diff --git a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs deleted file mode 100644 index b4c4ca0a3819..000000000000 --- a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Dashboards; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Trees; -using Umbraco.Cms.Core.Sections; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Trees; -using Umbraco.Cms.Web.BackOffice.Trees; -using Umbraco.Cms.Web.Common.Attributes; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Web.BackOffice.Controllers; - -/// -/// The API controller used for using the list of sections -/// -[PluginController(Constants.Web.Mvc.BackOfficeApiArea)] -public class SectionController : UmbracoAuthorizedJsonController -{ - private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; - private readonly IControllerFactory _controllerFactory; - private readonly IDashboardService _dashboardService; - private readonly ILocalizedTextService _localizedTextService; - private readonly ISectionService _sectionService; - private readonly ITreeService _treeService; - private readonly IUmbracoMapper _umbracoMapper; - - public SectionController( - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - ILocalizedTextService localizedTextService, - IDashboardService dashboardService, - ISectionService sectionService, - ITreeService treeService, - IUmbracoMapper umbracoMapper, - IControllerFactory controllerFactory, - IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) - { - _backofficeSecurityAccessor = backofficeSecurityAccessor; - _localizedTextService = localizedTextService; - _dashboardService = dashboardService; - _sectionService = sectionService; - _treeService = treeService; - _umbracoMapper = umbracoMapper; - _controllerFactory = controllerFactory; - _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; - } - - public async Task>> GetSections() - { - IEnumerable sections = - _sectionService.GetAllowedSections(_backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? 0); - - Section[] sectionModels = sections.Select(_umbracoMapper.Map
).WhereNotNull().ToArray(); - - // this is a bit nasty since we'll be proxying via the app tree controller but we sort of have to do that - // since tree's by nature are controllers and require request contextual data - var appTreeController = - new ApplicationTreeController(_treeService, _sectionService, _localizedTextService, _controllerFactory, _actionDescriptorCollectionProvider) - { ControllerContext = ControllerContext }; - - IDictionary>> dashboards = - _dashboardService.GetDashboards(_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser); - - //now we can add metadata for each section so that the UI knows if there's actually anything at all to render for - //a dashboard for a given section, then the UI can deal with it accordingly (i.e. redirect to the first tree) - foreach (Section? section in sectionModels) - { - var hasDashboards = section?.Alias is not null && - dashboards.TryGetValue(section.Alias, out IEnumerable>? dashboardsForSection) && - dashboardsForSection.Any(); - if (hasDashboards) - { - continue; - } - - // get the first tree in the section and get its root node route path - ActionResult sectionRoot = - await appTreeController.GetApplicationTrees(section?.Alias, null, null); - - if (!(sectionRoot.Result is null)) - { - return sectionRoot.Result; - } - - if (section is not null) - { - section.RoutePath = GetRoutePathForFirstTree(sectionRoot.Value!); - } - } - - return sectionModels; - } - - /// - /// Returns the first non root/group node's route path - /// - /// - /// - private string? GetRoutePathForFirstTree(TreeRootNode rootNode) - { - if (!rootNode.IsContainer || !rootNode.ContainsTrees) - { - return rootNode.RoutePath; - } - - if (rootNode.Children is not null) - { - foreach (TreeNode node in rootNode.Children) - { - if (node is TreeRootNode groupRoot) - { - return GetRoutePathForFirstTree(groupRoot); //recurse to get the first tree in the group - } - - return node.RoutePath; - } - } - - return string.Empty; - } - - /// - /// Returns all the sections that the user has access to - /// - /// - public IEnumerable GetAllSections() - { - IEnumerable sections = _sectionService.GetSections(); - IEnumerable mapped = sections.Select(_umbracoMapper.Map
); - if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.IsAdmin() ?? false) - { - return mapped; - } - - return mapped.Where(x => - _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.AllowedSections.Contains(x?.Alias) ?? - false) - .ToArray(); - } -} diff --git a/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs deleted file mode 100644 index 95ca6adc10c5..000000000000 --- a/src/Umbraco.Web.BackOffice/Extensions/RuntimeMinifierExtensions.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Text; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.WebAssets; -using Umbraco.Cms.Infrastructure.WebAssets; - -namespace Umbraco.Extensions; - -public static class RuntimeMinifierExtensions -{ - /// - /// Returns the JavaScript to load the back office's assets - /// - /// - public static async Task GetScriptForLoadingBackOfficeAsync( - this IRuntimeMinifier minifier, - GlobalSettings globalSettings, - IHostingEnvironment hostingEnvironment, - ILegacyManifestParser legacyManifestParser) - { - var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); - foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName)) - { - files.Add(file); - } - - foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoExtensionsJsBundleName)) - { - files.Add(file); - } - - // process the independent bundles - if (legacyManifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Independent, - out IReadOnlyList? independentManifestAssetsList)) - { - foreach (LegacyManifestAssets manifestAssets in independentManifestAssetsList) - { - var bundleName = - BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Javascript); - foreach (var asset in await minifier.GetJsAssetPathsAsync(bundleName)) - { - files.Add(asset); - } - } - } - - // process the "None" bundles, meaning we'll just render the script as-is - foreach (var asset in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets - .UmbracoNonOptimizedPackageJsBundleName)) - { - files.Add(asset); - } - - var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization( - files, - "umbraco", - globalSettings, - hostingEnvironment); - - result += await GetStylesheetInitializationAsync(minifier, legacyManifestParser); - - return result; - } - - /// - /// Gets the back office css bundle paths and formats a JS call to lazy load them - /// - private static async Task GetStylesheetInitializationAsync( - IRuntimeMinifier minifier, - ILegacyManifestParser legacyManifestParser) - { - var files = new HashSet(StringComparer.InvariantCultureIgnoreCase); - foreach (var file in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName)) - { - files.Add(file); - } - - // process the independent bundles - if (legacyManifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Independent, - out IReadOnlyList? independentManifestAssetsList)) - { - foreach (LegacyManifestAssets manifestAssets in independentManifestAssetsList) - { - var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Css); - foreach (var asset in await minifier.GetCssAssetPathsAsync(bundleName)) - { - files.Add(asset); - } - } - } - - // process the "None" bundles, meaning we'll just render the script as-is - foreach (var asset in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets - .UmbracoNonOptimizedPackageCssBundleName)) - { - files.Add(asset); - } - - var sb = new StringBuilder(); - foreach (var file in files) - { - sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file); - } - - return sb.ToString(); - } -} diff --git a/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs index 592a5e61ecfb..61201bfb4f35 100644 --- a/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/OutgoingEditorModelEventAttribute.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mapping; @@ -121,9 +120,6 @@ public void OnActionExecuted(ActionExecutedContext context) case UserDisplay user: _eventAggregator.Publish(new SendingUserNotification(user, umbracoContext)); break; - case IEnumerable> dashboards: - _eventAggregator.Publish(new SendingDashboardsNotification(dashboards, umbracoContext)); - break; case IEnumerable allowedChildren: // Changing the Enumerable will generate a new instance, so we need to update the context result with the new content var notification = new SendingAllowedChildrenNotification(allowedChildren, umbracoContext); diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index 8492399e5bd6..e05fc3a95561 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -139,7 +139,6 @@ private void Map(ContentItemDisplay source, ContentItemDisplayWithSchedule targe target.AllowedActions = source.AllowedActions; target.AllowedTemplates = source.AllowedTemplates; target.AllowPreview = source.AllowPreview; - target.ContentApps = source.ContentApps; target.ContentDto = source.ContentDto; target.ContentTypeAlias = source.ContentTypeAlias; target.ContentTypeId = source.ContentTypeId; @@ -218,7 +217,6 @@ private static void Map(ContentItemDisplayWithSchedule source, ContentItemDispla target.AllowedActions = source.AllowedActions; target.AllowedTemplates = source.AllowedTemplates; target.AllowPreview = source.AllowPreview; - target.ContentApps = source.ContentApps; target.ContentDto = source.ContentDto; target.ContentTypeAlias = source.ContentTypeAlias; target.ContentTypeId = source.ContentTypeId; @@ -272,7 +270,6 @@ private void Map(IContent source, ContentItemDisplay target, target.AllowedActions = GetActions(source, parent, context); target.AllowedTemplates = GetAllowedTemplates(source); - target.ContentApps = _commonMapper.GetContentAppsForEntity(source); target.ContentTypeId = source.ContentType.Id; target.ContentTypeKey = source.ContentType.Key; target.ContentTypeAlias = source.ContentType.Alias; diff --git a/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs index 11dbd462fddc..3f0f04853c22 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/MediaMapDefinition.cs @@ -59,7 +59,6 @@ private static void Map(IMedia source, ContentPropertyCollectionDto target, Mapp // Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsContainer private void Map(IMedia source, MediaItemDisplay target, MapperContext context) { - target.ContentApps = _commonMapper.GetContentAppsForEntity(source); target.ContentType = _commonMapper.GetContentType(source, context); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; diff --git a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs index 7164974878c8..5428d586ef40 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/MemberMapDefinition.cs @@ -40,7 +40,6 @@ public void DefineMaps(IUmbracoMapper mapper) // Umbraco.Code.MapAll -Trashed -IsContainer -VariesByCulture private void Map(IMember source, MemberDisplay target, MapperContext context) { - target.ContentApps = _commonMapper.GetContentAppsForEntity(source); target.ContentType = _commonMapper.GetContentType(source, context); target.ContentTypeId = source.ContentType.Id; target.ContentTypeAlias = source.ContentType.Alias; diff --git a/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs index a0a0aeec8c12..e1ba04e95a01 100644 --- a/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs @@ -1,5 +1,4 @@ using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Web.Common.Controllers; @@ -45,7 +44,6 @@ public static IUmbracoBuilder AddModelsBuilderDashboard(this IUmbracoBuilder bui ///
public static IUmbracoBuilder RemoveModelsBuilderDashboard(this IUmbracoBuilder builder) { - builder.Dashboards().Remove(); builder.WithCollectionBuilder().Remove(); return builder; diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index 817f742137dd..5987dc9460ba 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Primitives; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Trees; -using Umbraco.Cms.Core.Sections; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; using Umbraco.Cms.Web.BackOffice.Controllers; @@ -27,7 +26,6 @@ namespace Umbraco.Cms.Web.BackOffice.Trees; public class ApplicationTreeController : UmbracoAuthorizedApiController { private readonly ITreeService _treeService; - private readonly ISectionService _sectionService; private readonly ILocalizedTextService _localizedTextService; private readonly IControllerFactory _controllerFactory; private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; @@ -37,13 +35,11 @@ public class ApplicationTreeController : UmbracoAuthorizedApiController ///
public ApplicationTreeController( ITreeService treeService, - ISectionService sectionService, ILocalizedTextService localizedTextService, IControllerFactory controllerFactory, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) { _treeService = treeService; - _sectionService = sectionService; _localizedTextService = localizedTextService; _controllerFactory = controllerFactory; _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; @@ -65,12 +61,6 @@ public async Task> GetApplicationTrees(string? applic return NotFound(); } - ISection? section = _sectionService.GetByAlias(application); - if (section == null) - { - return NotFound(); - } - // find all tree definitions that have the current application alias IDictionary> groupedTrees = _treeService.GetBySectionGrouped(application, use); var allTrees = groupedTrees.Values.SelectMany(x => x).ToList(); diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 1aadbd4526c8..c432fbed165e 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -156,7 +156,6 @@ public static IUmbracoBuilder AddUmbracoCore(this IUmbracoBuilder builder) // WebRootFileProviderFactory is just a wrapper around the IWebHostEnvironment.WebRootFileProvider, // therefore no need to register it as singleton - builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs b/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs index 22c86e49c240..9a85dc9de701 100644 --- a/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs +++ b/src/Umbraco.Web.Common/FileProviders/ContentAndWebRootFileProviderFactory.cs @@ -4,7 +4,7 @@ namespace Umbraco.Cms.Web.Common.FileProviders; -public class ContentAndWebRootFileProviderFactory : ILegacyPackageManifestFileProviderFactory, IPackageManifestFileProviderFactory +public class ContentAndWebRootFileProviderFactory : IPackageManifestFileProviderFactory { private readonly IWebHostEnvironment _webHostEnvironment; diff --git a/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs b/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs index d2e3901b131f..ac2db5d06a63 100644 --- a/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs +++ b/src/Umbraco.Web.Common/FileProviders/WebRootFileProviderFactory.cs @@ -4,7 +4,7 @@ namespace Umbraco.Cms.Web.Common.FileProviders; -public class WebRootFileProviderFactory : ILegacyPackageManifestFileProviderFactory, IGridEditorsConfigFileProviderFactory +public class WebRootFileProviderFactory : IGridEditorsConfigFileProviderFactory { private readonly IWebHostEnvironment _webHostEnvironment; diff --git a/tests/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs b/tests/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs index d7b70f13761f..87d1065d5ec2 100644 --- a/tests/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs +++ b/tests/Umbraco.Tests.Integration/TestServerTest/Controllers/EnsureNotAmbiguousActionNameControllerTests.cs @@ -78,16 +78,6 @@ public void EnsureNotAmbiguousActionName() EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetIcon(string.Empty))); - EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId))); - EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId))); - EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId))); - EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => - x.GetChildren(intId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty))); - EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => - x.GetChildren(guidId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty))); - EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => - x.GetChildren(udiId, 0, 1, "SortOrder", Direction.Ascending, true, string.Empty))); - EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(intId))); EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(guidId))); EnsureNotAmbiguousActionName(PrepareApiControllerUrl(x => x.GetById(udiId))); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/SectionServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/SectionServiceTests.cs deleted file mode 100644 index c4533946246b..000000000000 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/SectionServiceTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System.Linq; -using NUnit.Framework; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Tests.Common.Testing; -using Umbraco.Cms.Tests.Integration.Testing; - -namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; - -/// -/// Tests covering the SectionService -/// -[TestFixture] -[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] -public class SectionServiceTests : UmbracoIntegrationTest -{ - private ISectionService SectionService => GetRequiredService(); - - private IUserService UserService => GetRequiredService(); - - [Test] - public void SectionService_Can_Get_Allowed_Sections_For_User() - { - // Arrange - var user = CreateTestUser(); - - // Act - var result = SectionService.GetAllowedSections(user.Id).ToList(); - - // Assert - Assert.AreEqual(3, result.Count); - } - - private IUser CreateTestUser() - { - using var scope = ScopeProvider.CreateScope(autoComplete: true); - using var _ = scope.Notifications.Suppress(); - - var globalSettings = new GlobalSettings(); - var user = new User(globalSettings) { Name = "Test user", Username = "testUser", Email = "testuser@test.com" }; - UserService.Save(user); - - var userGroupA = new UserGroup(ShortStringHelper) { Alias = "GroupA", Name = "Group A" }; - userGroupA.AddAllowedSection("media"); - userGroupA.AddAllowedSection("settings"); - - // TODO: This is failing the test - UserService.Save(userGroupA, new[] { user.Id }); - - var userGroupB = new UserGroup(ShortStringHelper) { Alias = "GroupB", Name = "Group B" }; - userGroupB.AddAllowedSection("settings"); - userGroupB.AddAllowedSection("member"); - UserService.Save(userGroupB, new[] { user.Id }); - - return UserService.GetUserById(user.Id); - } -} diff --git a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs index 88d344796c12..855c46a7966a 100644 --- a/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs +++ b/tests/Umbraco.Tests.UnitTests/AutoFixture/Customizations/UmbracoCustomizations.cs @@ -32,7 +32,6 @@ public void Customize(IFixture fixture) .Customize(new ConstructorCustomization(typeof(UsersController), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(InstallController), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(PreviewController), new GreedyConstructorQuery())) - .Customize(new ConstructorCustomization(typeof(MemberController), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(BackOfficeController), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(MemberManager), new GreedyConstructorQuery())) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestContentAppTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestContentAppTests.cs deleted file mode 100644 index 4bb0c6183844..000000000000 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestContentAppTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System.Linq; -using Moq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Tests.UnitTests.TestHelpers; - -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest; - -[TestFixture] -public class LegacyManifestContentAppTests -{ - [Test] - public void Test() - { - var contentType = Mock.Of(); - Mock.Get(contentType).Setup(x => x.Alias).Returns("type1"); - var content = Mock.Of(); - Mock.Get(content).Setup(x => x.ContentType).Returns(new SimpleContentType(contentType)); - - var group1 = Mock.Of(); - Mock.Get(group1).Setup(x => x.Alias).Returns("group1"); - var group2 = Mock.Of(); - Mock.Get(group2).Setup(x => x.Alias).Returns("group2"); - - // no rule = ok - AssertDefinition(content, true, Array.Empty(), new[] { group1, group2 }); - - // wildcards = ok - AssertDefinition(content, true, new[] { "+content/*" }, new[] { group1, group2 }); - AssertDefinition(content, false, new[] { "+media/*" }, new[] { group1, group2 }); - - // explicitly enabling / disabling - AssertDefinition(content, true, new[] { "+content/type1" }, new[] { group1, group2 }); - AssertDefinition(content, false, new[] { "-content/type1" }, new[] { group1, group2 }); - - // when there are type rules, failing to approve the type = no app - AssertDefinition(content, false, new[] { "+content/type2" }, new[] { group1, group2 }); - AssertDefinition(content, false, new[] { "+media/type1" }, new[] { group1, group2 }); - - // can have multiple rule, first one that matches = end - AssertDefinition(content, false, new[] { "-content/type1", "+content/*" }, new[] { group1, group2 }); - AssertDefinition(content, true, new[] { "-content/type2", "+content/*" }, new[] { group1, group2 }); - AssertDefinition(content, true, new[] { "+content/*", "-content/type1" }, new[] { group1, group2 }); - - // when there are role rules, failing to approve a role = no app - AssertDefinition(content, false, new[] { "+role/group33" }, new[] { group1, group2 }); - - // wildcards = ok - AssertDefinition(content, true, new[] { "+role/*" }, new[] { group1, group2 }); - - // explicitly enabling / disabling - AssertDefinition(content, true, new[] { "+role/group1" }, new[] { group1, group2 }); - AssertDefinition(content, false, new[] { "-role/group1" }, new[] { group1, group2 }); - - // can have multiple rule, first one that matches = end - AssertDefinition(content, true, new[] { "+role/group1", "-role/group2" }, new[] { group1, group2 }); - - // mixed type and role rules, both are evaluated and need to match - AssertDefinition(content, true, new[] { "+role/group1", "+content/type1" }, new[] { group1, group2 }); - AssertDefinition(content, false, new[] { "+role/group1", "+content/type2" }, new[] { group1, group2 }); - AssertDefinition(content, false, new[] { "+role/group33", "+content/type1" }, new[] { group1, group2 }); - } - - private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) - { - var definition = JsonConvert.DeserializeObject("{" + - (show.Length == 0 - ? string.Empty - : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); - var factory = new LegacyManifestContentAppFactory(definition, TestHelper.IOHelper); - var app = factory.GetContentAppFor(source, groups); - if (expected) - { - Assert.IsNotNull(app); - } - else - { - Assert.IsNull(app); - } - } -} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestParserTests.cs deleted file mode 100644 index 132103403d20..000000000000 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/LegacyManifestParserTests.cs +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System.Linq; -using System.Text; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NUnit.Framework; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Dashboards; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Manifest; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.PropertyEditors.Validators; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.UnitTests.TestHelpers; - -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest; - -[TestFixture] -public class LegacyManifestParserTests -{ - [SetUp] - public void Setup() - { - var validators = new IManifestValueValidator[] - { - new RequiredValidator(), - new RegexValidator(), - new DelimitedValueValidator(), - }; - _ioHelper = TestHelper.IOHelper; - var loggerFactory = NullLoggerFactory.Instance; - _parser = new LegacyManifestParser( - AppCaches.Disabled, - new ManifestValueValidatorCollection(() => validators), - new LegacyManifestFilterCollection(() => Enumerable.Empty()), - loggerFactory.CreateLogger(), - _ioHelper, - TestHelper.GetHostingEnvironment(), - new SystemTextJsonSerializer(), - Mock.Of(), - Mock.Of(), - Mock.Of(), - Mock.Of()); - } - - private LegacyManifestParser _parser; - private IIOHelper _ioHelper; - - [Test] - public void CanParseComments() - { - const string json1 = @" -// this is a single-line comment -{ - ""x"": 2, // this is an end-of-line comment - ""y"": 3, /* this is a single line comment block -/* comment */ ""z"": /* comment */ 4, - ""t"": ""this is /* comment */ a string"", - ""u"": ""this is // more comment in a string"" -} -"; - - var jobject = (JObject)JsonConvert.DeserializeObject(json1); - Assert.AreEqual("2", jobject.Property("x").Value.ToString()); - Assert.AreEqual("3", jobject.Property("y").Value.ToString()); - Assert.AreEqual("4", jobject.Property("z").Value.ToString()); - Assert.AreEqual("this is /* comment */ a string", jobject.Property("t").Value.ToString()); - Assert.AreEqual("this is // more comment in a string", jobject.Property("u").Value.ToString()); - } - - [Test] - public void ThrowOnJsonError() - { - // invalid json, missing the final ']' on javascript - const string json = @"{ -propertyEditors: []/*we have empty property editors**/, -javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2.js' }"; - - // parsing fails - Assert.Throws(() => _parser.ParseManifest(json)); - } - - [Test] - public void CanParseManifest_ScriptsAndStylesheets() - { - var json = "{}"; - var manifest = _parser.ParseManifest(json); - Assert.AreEqual(0, manifest.Scripts.Length); - - json = "{javascript: []}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(0, manifest.Scripts.Length); - - json = "{javascript: ['~/test.js', '~/test2.js']}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.Scripts.Length); - - json = "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.Scripts.Length); - - Assert.AreEqual(_ioHelper.ResolveUrl("/test.js"), manifest.Scripts[0]); - Assert.AreEqual(_ioHelper.ResolveUrl("/test2.js"), manifest.Scripts[1]); - - // kludge is gone - must filter before parsing - json = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble()) + - "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js']}"; - Assert.Throws(() => _parser.ParseManifest(json)); - - json = "{}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(0, manifest.Stylesheets.Length); - - json = "{css: []}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(0, manifest.Stylesheets.Length); - - json = "{css: ['~/style.css', '~/folder-name/sdsdsd/stylesheet.css']}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.Stylesheets.Length); - - json = "{propertyEditors: [], css: ['~/stylesheet.css', '~/random-long-name.css']}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.Stylesheets.Length); - - json = - "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js'], css: ['~/stylesheet.css', '~/random-long-name.css']}"; - manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.Scripts.Length); - Assert.AreEqual(2, manifest.Stylesheets.Length); - } - - [Test] - public void CanParseManifest_ContentApps() - { - const string json = @"{'contentApps': [ - { - alias: 'myPackageApp1', - name: 'My App1', - icon: 'icon-foo', - view: '~/App_Plugins/MyPackage/ContentApps/MyApp1.html' - }, - { - alias: 'myPackageApp2', - name: 'My App2', - config: { key1: 'some config val' }, - icon: 'icon-bar', - view: '~/App_Plugins/MyPackage/ContentApps/MyApp2.html' - } -]}"; - - var manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.ContentApps.Length); - - Assert.IsInstanceOf(manifest.ContentApps[0]); - var app0 = manifest.ContentApps[0]; - Assert.AreEqual("myPackageApp1", app0.Alias); - Assert.AreEqual("My App1", app0.Name); - Assert.AreEqual("icon-foo", app0.Icon); - Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/ContentApps/MyApp1.html"), app0.View); - - Assert.IsInstanceOf(manifest.ContentApps[1]); - var app1 = manifest.ContentApps[1]; - Assert.AreEqual("myPackageApp2", app1.Alias); - Assert.AreEqual("My App2", app1.Name); - Assert.AreEqual("icon-bar", app1.Icon); - Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/ContentApps/MyApp2.html"), app1.View); - } - - [Test] - public void CanParseManifest_Dashboards() - { - const string json = @"{'dashboards': [ - { - 'alias': 'something', - 'view': '~/App_Plugins/MyPackage/Dashboards/one.html', - 'sections': [ 'content' ], - 'access': [ {'grant':'user'}, {'deny':'foo'} ] - - }, - { - 'alias': 'something.else', - 'weight': -1, - 'view': '~/App_Plugins/MyPackage/Dashboards/two.html', - 'sections': [ 'forms' ], - } -]}"; - - var manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.Dashboards.Length); - - Assert.IsInstanceOf(manifest.Dashboards[0]); - var db0 = manifest.Dashboards[0]; - Assert.AreEqual("something", db0.Alias); - Assert.AreEqual(100, db0.Weight); - Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/Dashboards/one.html"), db0.View); - Assert.AreEqual(1, db0.Sections.Length); - Assert.AreEqual("content", db0.Sections[0]); - Assert.AreEqual(2, db0.AccessRules.Length); - Assert.AreEqual(AccessRuleType.Grant, db0.AccessRules[0].Type); - Assert.AreEqual("user", db0.AccessRules[0].Value); - Assert.AreEqual(AccessRuleType.Deny, db0.AccessRules[1].Type); - Assert.AreEqual("foo", db0.AccessRules[1].Value); - - Assert.IsInstanceOf(manifest.Dashboards[1]); - var db1 = manifest.Dashboards[1]; - Assert.AreEqual("something.else", db1.Alias); - Assert.AreEqual(-1, db1.Weight); - Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/Dashboards/two.html"), db1.View); - Assert.AreEqual(1, db1.Sections.Length); - Assert.AreEqual("forms", db1.Sections[0]); - } - - [Test] - public void CanParseManifest_Sections() - { - const string json = @"{'sections': [ - { ""alias"": ""content"", ""name"": ""Content"" }, - { ""alias"": ""hello"", ""name"": ""World"" } -]}"; - - var manifest = _parser.ParseManifest(json); - Assert.AreEqual(2, manifest.Sections.Length); - Assert.AreEqual("content", manifest.Sections[0].Alias); - Assert.AreEqual("hello", manifest.Sections[1].Alias); - Assert.AreEqual("Content", manifest.Sections[0].Name); - Assert.AreEqual("World", manifest.Sections[1].Name); - } - - [Test] - public void CanParseManifest_Version() - { - const string json = @"{""name"": ""VersionPackage"", ""version"": ""1.0.0""}"; - var manifest = _parser.ParseManifest(json); - - Assert.Multiple(() => - { - Assert.AreEqual("VersionPackage", manifest.PackageName); - Assert.AreEqual("1.0.0", manifest.Version); - }); - } - - [Test] - public void CanParseManifest_TrackingAllowed() - { - const string json = @"{""allowPackageTelemetry"": false }"; - var manifest = _parser.ParseManifest(json); - - Assert.IsFalse(manifest.AllowPackageTelemetry); - } - - [Test] - public void CanParseManifest_ParameterEditors_SupportsReadOnly() - { - const string json = @"{'parameterEditors': [ - { - alias: 'parameter1', - name: 'My Parameter', - view: '~/App_Plugins/MyPackage/PropertyEditors/MyEditor.html', - supportsReadOnly: true - }]}"; - - - var manifest = _parser.ParseManifest(json); - Assert.IsTrue(manifest.ParameterEditors.FirstOrDefault().SupportsReadOnly); - } -} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs deleted file mode 100644 index e4be3621e38f..000000000000 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/MemberControllerUnitTests.cs +++ /dev/null @@ -1,785 +0,0 @@ -using System.Data; -using AutoFixture.NUnit3; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using NUnit.Framework; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.ContentApps; -using Umbraco.Cms.Core.Dictionary; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Mapping; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -using Umbraco.Cms.Infrastructure.Serialization; -using Umbraco.Cms.Tests.Common.Builders; -using Umbraco.Cms.Tests.UnitTests.AutoFixture; -using Umbraco.Cms.Tests.UnitTests.Umbraco.Core.ShortStringHelper; -using Umbraco.Cms.Web.BackOffice.Controllers; -using Umbraco.Cms.Web.BackOffice.Mapping; -using Umbraco.Cms.Web.Common.ActionsResults; -using Umbraco.Cms.Web.Common.Security; -using MemberMapDefinition = Umbraco.Cms.Web.BackOffice.Mapping.MemberMapDefinition; - -namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers; - -[TestFixture] -public class MemberControllerUnitTests -{ - private IUmbracoMapper _mapper; - - [Test] - [AutoMoqData] - public void PostSaveMember_WhenMemberIsNull_ExpectFailureResponse( - MemberController sut) - { - // arrange - // act - var exception = Assert.ThrowsAsync(() => sut.PostSave(null)); - - // assert - Assert.That( - exception.Message, - Is.EqualTo("Value cannot be null. (Parameter 'The member content item was null')")); - } - - [Test] - [AutoMoqData] - public void PostSaveMember_WhenModelStateIsNotValid_ExpectFailureResponse( - [Frozen] IMemberManager umbracoMembersUserManager, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IPasswordChanger passwordChanger, - IOptions globalSettings, - IUser user, - ITwoFactorLoginService twoFactorLoginService) - { - // arrange - SetupMemberTestData(out var fakeMemberData, out _, ContentSaveAction.SaveNew); - var sut = CreateSut( - memberService, - memberTypeService, - memberGroupService, - umbracoMembersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - passwordChanger, - globalSettings, - twoFactorLoginService); - sut.ModelState.AddModelError("key", "Invalid model state"); - - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.ValidatePasswordAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - - // act - var result = sut.PostSave(fakeMemberData).Result; - var validation = result.Result as ValidationErrorResult; - - // assert - Assert.IsNotNull(result.Result); - Assert.IsNull(result.Value); - Assert.AreEqual(StatusCodes.Status400BadRequest, validation?.StatusCode); - } - - [Test] - [AutoMoqData] - public async Task PostSaveMember_SaveNew_NoCustomField_WhenAllIsSetupCorrectly_ExpectSuccessResponse( - [Frozen] IMemberManager umbracoMembersUserManager, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, - IOptions globalSettings, - IUser user, - ITwoFactorLoginService twoFactorLoginService) - { - // arrange - var member = SetupMemberTestData(out var fakeMemberData, out var memberDisplay, ContentSaveAction.SaveNew); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.ValidatePasswordAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.GetRolesAsync(It.IsAny())) - .ReturnsAsync(() => Array.Empty()); - Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); - Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); - Mock.Get(memberService).SetupSequence( - x => x.GetByEmail(It.IsAny())) - .Returns(() => null) - .Returns(() => member); - Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny())).Returns(() => member); - - var sut = CreateSut( - memberService, - memberTypeService, - memberGroupService, - umbracoMembersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - passwordChanger, - globalSettings, - twoFactorLoginService); - - // act - var result = await sut.PostSave(fakeMemberData); - - // assert - Assert.IsNull(result.Result); - Assert.IsNotNull(result.Value); - AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value); - } - - [Test] - [AutoMoqData] - public async Task PostSaveMember_SaveNew_CustomField_WhenAllIsSetupCorrectly_ExpectSuccessResponse( - [Frozen] IMemberManager umbracoMembersUserManager, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, - IOptions globalSettings, - IUser user, - ITwoFactorLoginService twoFactorLoginService) - { - // arrange - var member = SetupMemberTestData(out var fakeMemberData, out var memberDisplay, ContentSaveAction.SaveNew); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.ValidatePasswordAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.GetRolesAsync(It.IsAny())) - .ReturnsAsync(() => Array.Empty()); - Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); - Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); - Mock.Get(memberService).SetupSequence( - x => x.GetByEmail(It.IsAny())) - .Returns(() => null) - .Returns(() => member); - Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny())).Returns(() => member); - - var sut = CreateSut( - memberService, - memberTypeService, - memberGroupService, - umbracoMembersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - passwordChanger, - globalSettings, - twoFactorLoginService); - - // act - var result = await sut.PostSave(fakeMemberData); - - // assert - Assert.IsNull(result.Result); - Assert.IsNotNull(result.Value); - AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value); - } - - [Test] - [AutoMoqData] - public async Task PostSaveMember_SaveExisting_WhenAllIsSetupCorrectly_ExpectSuccessResponse( - [Frozen] IMemberManager umbracoMembersUserManager, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, - IOptions globalSettings, - IUser user, - ITwoFactorLoginService twoFactorLoginService) - { - // arrange - var member = SetupMemberTestData(out var fakeMemberData, out var memberDisplay, ContentSaveAction.Save); - var membersIdentityUser = new MemberIdentityUser(123); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.FindByIdAsync(It.IsAny())) - .ReturnsAsync(() => membersIdentityUser); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.ValidatePasswordAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.UpdateAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.GetRolesAsync(It.IsAny())) - .ReturnsAsync(() => Array.Empty()); - Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); - Mock.Get(globalSettings); - - SetupUserAccess(backOfficeSecurityAccessor, backOfficeSecurity, user); - SetupPasswordSuccess(umbracoMembersUserManager, passwordChanger); - - Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny())).Returns(() => member); - Mock.Get(memberService).Setup(x => x.GetById(It.IsAny())).Returns(() => member); - Mock.Get(memberService).SetupSequence( - x => x.GetByEmail(It.IsAny())) - .Returns(() => null) - .Returns(() => member); - - var sut = CreateSut( - memberService, - memberTypeService, - memberGroupService, - umbracoMembersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - passwordChanger, - globalSettings, - twoFactorLoginService); - - // act - var result = await sut.PostSave(fakeMemberData); - - // assert - Assert.IsNull(result.Result); - Assert.IsNotNull(result.Value); - AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value); - } - - [Test] - [AutoMoqData] - public async Task PostSaveMember_SaveExisting_WhenAllIsSetupWithPasswordIncorrectly_ExpectFailureResponse( - [Frozen] IMemberManager umbracoMembersUserManager, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, - IOptions globalSettings, - IUser user, - ITwoFactorLoginService twoFactorLoginService) - { - // arrange - var member = SetupMemberTestData(out var fakeMemberData, out _, ContentSaveAction.Save); - var membersIdentityUser = new MemberIdentityUser(123); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.FindByIdAsync(It.IsAny())) - .ReturnsAsync(() => membersIdentityUser); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.ValidatePasswordAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.UpdateAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); - Mock.Get(globalSettings); - - SetupUserAccess(backOfficeSecurityAccessor, backOfficeSecurity, user); - SetupPasswordSuccess(umbracoMembersUserManager, passwordChanger, false); - - Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny())).Returns(() => member); - Mock.Get(memberService).SetupSequence( - x => x.GetByEmail(It.IsAny())) - .Returns(() => null) - .Returns(() => member); - - var sut = CreateSut( - memberService, - memberTypeService, - memberGroupService, - umbracoMembersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - passwordChanger, - globalSettings, - twoFactorLoginService); - - // act - var result = await sut.PostSave(fakeMemberData); - - // assert - Assert.IsNotNull(result.Result); - Assert.IsNull(result.Value); - } - - private static void SetupUserAccess(IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IBackOfficeSecurity backOfficeSecurity, IUser user) - { - Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); - Mock.Get(user).Setup(x => x.AllowedSections).Returns(new[] { "member" }); - Mock.Get(backOfficeSecurity).Setup(x => x.CurrentUser).Returns(user); - } - - private static void SetupPasswordSuccess( - IMemberManager umbracoMembersUserManager, - IPasswordChanger passwordChanger, - bool successful = true) - { - var passwordChanged = new PasswordChangedModel { Error = null, ResetPassword = null }; - if (!successful) - { - var attempt = Attempt.Fail(passwordChanged); - Mock.Get(passwordChanger) - .Setup(x => x.ChangePasswordWithIdentityAsync( - It.IsAny(), - umbracoMembersUserManager, - null)) - .ReturnsAsync(() => attempt); - } - else - { - var attempt = Attempt.Succeed(passwordChanged); - Mock.Get(passwordChanger) - .Setup(x => x.ChangePasswordWithIdentityAsync( - It.IsAny(), - umbracoMembersUserManager, - null)) - .ReturnsAsync(() => attempt); - } - } - - [Test] - [AutoMoqData] - public void PostSaveMember_SaveNew_WhenMemberEmailAlreadyExists_ExpectFailResponse( - [Frozen] IMemberManager umbracoMembersUserManager, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, - IOptions globalSettings, - IUser user, - ITwoFactorLoginService twoFactorLoginService) - { - // arrange - var member = SetupMemberTestData(out var fakeMemberData, out _, ContentSaveAction.SaveNew); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.CreateAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); - Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.ValidatePasswordAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.AddToRolesAsync(It.IsAny(), It.IsAny>())) - .ReturnsAsync(() => IdentityResult.Success); - - Mock.Get(memberService).SetupSequence( - x => x.GetByEmail(It.IsAny())) - .Returns(() => member); - - var sut = CreateSut( - memberService, - memberTypeService, - memberGroupService, - umbracoMembersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - passwordChanger, - globalSettings, - twoFactorLoginService); - - // act - var result = sut.PostSave(fakeMemberData).Result; - var validation = result.Result as ValidationErrorResult; - - // assert - Assert.IsNotNull(result.Result); - Assert.IsNull(result.Value); - Assert.AreEqual(StatusCodes.Status400BadRequest, validation?.StatusCode); - } - - [Test] - [AutoMoqData] - public async Task PostSaveMember_SaveExistingMember_WithNoRoles_Add1Role_ExpectSuccessResponse( - [Frozen] IMemberManager umbracoMembersUserManager, - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IBackOfficeSecurity backOfficeSecurity, - IPasswordChanger passwordChanger, - IOptions globalSettings, - IUser user, - ITwoFactorLoginService twoFactorLoginService) - { - // arrange - var roleName = "anyrole"; - IMember member = SetupMemberTestData(out var fakeMemberData, out var memberDisplay, ContentSaveAction.Save); - fakeMemberData.Groups = new List { roleName }; - var membersIdentityUser = new MemberIdentityUser(123); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.FindByIdAsync(It.IsAny())) - .ReturnsAsync(() => membersIdentityUser); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.ValidatePasswordAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.UpdateAsync(It.IsAny())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.AddToRolesAsync(It.IsAny(), It.IsAny>())) - .ReturnsAsync(() => IdentityResult.Success); - Mock.Get(umbracoMembersUserManager) - .Setup(x => x.GetRolesAsync(It.IsAny())) - .ReturnsAsync(() => Array.Empty()); - - Mock.Get(memberTypeService).Setup(x => x.GetDefault()).Returns("fakeAlias"); - Mock.Get(backOfficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(backOfficeSecurity); - Mock.Get(memberService).Setup(x => x.GetByUsername(It.IsAny())).Returns(() => member); - Mock.Get(memberService).Setup(x => x.GetById(It.IsAny())).Returns(() => member); - - SetupUserAccess(backOfficeSecurityAccessor, backOfficeSecurity, user); - SetupPasswordSuccess(umbracoMembersUserManager, passwordChanger); - - Mock.Get(memberService).SetupSequence( - x => x.GetByEmail(It.IsAny())) - .Returns(() => null) - .Returns(() => member); - var sut = CreateSut( - memberService, - memberTypeService, - memberGroupService, - umbracoMembersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - passwordChanger, - globalSettings, - twoFactorLoginService); - - // act - var result = await sut.PostSave(fakeMemberData); - - // assert - Assert.IsNull(result.Result); - Assert.IsNotNull(result.Value); - Mock.Get(umbracoMembersUserManager) - .Verify(u => u.GetRolesAsync(membersIdentityUser)); - Mock.Get(umbracoMembersUserManager) - .Verify(u => u.AddToRolesAsync(membersIdentityUser, new[] { roleName })); - Mock.Get(umbracoMembersUserManager) - .Verify(x => x.GetRolesAsync(It.IsAny())); - Mock.Get(memberService) - .Verify(m => m.Save(It.IsAny(), It.IsAny())); - AssertMemberDisplayPropertiesAreEqual(memberDisplay, result.Value); - } - - /// - /// Create member controller to test - /// - /// Member service - /// Member type service - /// Member group service - /// Members user manager - /// Data type service - /// Back office security accessor - /// Password changer class - /// The global settings - /// The two factor login service - /// A member controller for the tests - private MemberController CreateSut( - IMemberService memberService, - IMemberTypeService memberTypeService, - IMemberGroupService memberGroupService, - IUmbracoUserManager membersUserManager, - IDataTypeService dataTypeService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IPasswordChanger passwordChanger, - IOptions globalSettings, - ITwoFactorLoginService twoFactorLoginService) - { - var httpContextAccessor = new HttpContextAccessor(); - - var mockShortStringHelper = new MockShortStringHelper(); - var textService = new Mock(); - var contentTypeBaseServiceProvider = new Mock(); - contentTypeBaseServiceProvider.Setup(x => x.GetContentTypeOf(It.IsAny())) - .Returns(new ContentType(mockShortStringHelper, 123)); - var contentAppFactories = new Mock>(); - var mockContentAppFactoryCollection = new Mock>(); - var hybridBackOfficeSecurityAccessor = new BackOfficeSecurityAccessor(httpContextAccessor); - var contentAppFactoryCollection = new ContentAppFactoryCollection( - () => contentAppFactories.Object, - mockContentAppFactoryCollection.Object, - hybridBackOfficeSecurityAccessor); - var mockUserService = new Mock(); - var commonMapper = new CommonMapper( - mockUserService.Object, - contentTypeBaseServiceProvider.Object, - contentAppFactoryCollection, - textService.Object); - var mockCultureDictionary = new Mock(); - - var mockPasswordConfig = new Mock>(); - mockPasswordConfig.Setup(x => x.Value).Returns(() => new MemberPasswordConfigurationSettings()); - var dataEditor = Mock.Of( - x => x.Alias == Constants.PropertyEditors.Aliases.Label); - Mock.Get(dataEditor).Setup(x => x.GetValueEditor()).Returns(new TextOnlyValueEditor( - new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox), - Mock.Of(), - Mock.Of(), - Mock.Of())); - - var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor })); - - IMapDefinition memberMapDefinition = new MemberMapDefinition( - commonMapper, - new CommonTreeNodeMapper(Mock.Of()), - new MemberTabsAndPropertiesMapper( - mockCultureDictionary.Object, - backOfficeSecurityAccessor, - textService.Object, - memberTypeService, - memberService, - memberGroupService, - mockPasswordConfig.Object, - contentTypeBaseServiceProvider.Object, - propertyEditorCollection, - twoFactorLoginService)); - - var map = new MapDefinitionCollection(() => new List - { - new global::Umbraco.Cms.Core.Models.Mapping.MemberMapDefinition(), - memberMapDefinition, - new ContentTypeMapDefinition( - commonMapper, - propertyEditorCollection, - dataTypeService, - new Mock().Object, - new Mock().Object, - new Mock().Object, - memberTypeService, - new Mock().Object, - mockShortStringHelper, - globalSettings, - new Mock().Object, - new Mock>().Object), - }); - var scopeProvider = Mock.Of(x => x.CreateCoreScope( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()) == Mock.Of()); - - _mapper = new UmbracoMapper(map, scopeProvider, NullLogger.Instance); - - return new MemberController( - new DefaultCultureDictionary( - new Mock().Object, - NoAppCache.Instance), - new LoggerFactory(), - mockShortStringHelper, - new DefaultEventMessagesFactory( - new Mock().Object), - textService.Object, - propertyEditorCollection, - _mapper, - memberService, - memberTypeService, - (IMemberManager)membersUserManager, - dataTypeService, - backOfficeSecurityAccessor, - new SystemTextConfigurationEditorJsonSerializer(), - passwordChanger, - scopeProvider, - twoFactorLoginService); - } - - /// - /// Setup all standard member data for test - /// - private Member SetupMemberTestData( - out MemberSave fakeMemberData, - out MemberDisplay memberDisplay, - ContentSaveAction contentAction) - { - // arrange - var memberType = MemberTypeBuilder.CreateSimpleMemberType(); - var member = MemberBuilder.CreateSimpleMember(memberType, "Test Member", "test@example.com", "123", "test"); - var memberId = 123; - member.Id = memberId; - - // TODO: replace with builder for MemberSave and MemberDisplay - fakeMemberData = new MemberSave - { - Id = memberId, - SortOrder = member.SortOrder, - ContentTypeId = memberType.Id, - Key = member.Key, - Password = new ChangingPasswordModel { Id = 456, NewPassword = member.RawPasswordValue, OldPassword = null }, - Name = member.Name, - Email = member.Email, - Username = member.Username, - PersistedContent = member, - PropertyCollectionDto = new ContentPropertyCollectionDto(), - Groups = new List(), - - // Alias = "fakeAlias", - ContentTypeAlias = member.ContentTypeAlias, - Action = contentAction, - Icon = "icon-document", - Path = member.Path, - }; - - memberDisplay = new MemberDisplay - { - Id = memberId, - SortOrder = member.SortOrder, - ContentTypeId = memberType.Id, - Key = member.Key, - Name = member.Name, - Email = member.Email, - Username = member.Username, - - // Alias = "fakeAlias", - ContentTypeAlias = member.ContentTypeAlias, - ContentType = new ContentTypeBasic(), - ContentTypeName = member.ContentType.Name, - Icon = fakeMemberData.Icon, - Path = member.Path, - Tabs = new List> - { - new() - { - Alias = "test", - Id = 77, - Properties = new List - { - new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login" }, - new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email" }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}failedPasswordAttempts", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}approved", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lockedOut", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}twoFactorEnabled", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLockoutDate", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLoginDate", - }, - new() - { - Alias = - $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastPasswordChangeDate", - }, - }, - }, - }, - }; - - return member; - } - - /// - /// Check all member properties are equal - /// - /// - /// - private void AssertMemberDisplayPropertiesAreEqual(MemberDisplay memberDisplay, MemberDisplay resultValue) - { - Assert.AreNotSame(memberDisplay, resultValue); - Assert.AreEqual(memberDisplay.Id, resultValue.Id); - Assert.AreEqual(memberDisplay.Alias, resultValue.Alias); - Assert.AreEqual(memberDisplay.Username, resultValue.Username); - Assert.AreEqual(memberDisplay.Email, resultValue.Email); - Assert.AreEqual(memberDisplay.AdditionalData, resultValue.AdditionalData); - Assert.AreEqual(memberDisplay.ContentApps, resultValue.ContentApps); - Assert.AreEqual(memberDisplay.ContentType.Alias, resultValue.ContentType.Alias); - Assert.AreEqual(memberDisplay.ContentTypeAlias, resultValue.ContentTypeAlias); - Assert.AreEqual(memberDisplay.ContentTypeName, resultValue.ContentTypeName); - Assert.AreEqual(memberDisplay.ContentTypeId, resultValue.ContentTypeId); - Assert.AreEqual(memberDisplay.Icon, resultValue.Icon); - Assert.AreEqual(memberDisplay.Errors, resultValue.Errors); - Assert.AreEqual(memberDisplay.Key, resultValue.Key); - Assert.AreEqual(memberDisplay.Name, resultValue.Name); - Assert.AreEqual(memberDisplay.Path, resultValue.Path); - Assert.AreEqual(memberDisplay.SortOrder, resultValue.SortOrder); - Assert.AreEqual(memberDisplay.Trashed, resultValue.Trashed); - Assert.AreEqual(memberDisplay.TreeNodeUrl, resultValue.TreeNodeUrl); - - // TODO: can we check create/update dates when saving? - // Assert.AreEqual(memberDisplay.CreateDate, resultValue.CreateDate); - // Assert.AreEqual(memberDisplay.UpdateDate, resultValue.UpdateDate); - - // TODO: check all properties - Assert.AreEqual(memberDisplay.Properties.Count(), resultValue.Properties.Count()); - Assert.AreNotSame(memberDisplay.Properties, resultValue.Properties); - for (var index = 0; index < resultValue.Properties.Count(); index++) - { - Assert.AreNotSame( - memberDisplay.Properties.ElementAt(index), - resultValue.Properties.ElementAt(index)); - - // Assert.AreEqual(memberDisplay.Properties.GetItemByIndex(index), resultValue.Properties.GetItemByIndex(index)); - } - } -}