diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index 5cfc2808fc45..aeae64591006 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -5,62 +5,62 @@ public static partial class Constants public static class Icons { /// - /// System default icon + /// System default icon. /// public const string DefaultIcon = Content; /// - /// System blueprint icon + /// System blueprint icon. /// public const string Blueprint = "icon-blueprint"; /// - /// System content icon + /// System content icon. /// public const string Content = "icon-document"; /// - /// System content type icon + /// System content type icon. /// public const string ContentType = "icon-item-arrangement"; /// - /// System data type icon + /// System data type icon. /// public const string DataType = "icon-autofill"; /// - /// System dictionary icon + /// System dictionary icon. /// public const string Dictionary = "icon-book-alt"; /// - /// System generic folder icon + /// System generic folder icon. /// public const string Folder = "icon-folder"; /// - /// System language icon + /// System language icon. /// public const string Language = "icon-globe"; /// - /// System logviewer icon + /// System logviewer icon. /// public const string LogViewer = "icon-box-alt"; /// - /// System list view icon + /// System list view icon. /// public const string ListView = "icon-thumbnail-list"; /// - /// System macro icon + /// System macro icon. /// public const string Macro = "icon-settings-alt"; /// - /// System media file icon + /// System media file icon. /// public const string MediaFile = "icon-document"; @@ -70,92 +70,92 @@ public static class Icons public const string MediaVideo = "icon-video"; /// - /// System media audio icon + /// System media audio icon. /// public const string MediaAudio = "icon-sound-waves"; /// - /// System media article icon + /// System media article icon. /// public const string MediaArticle = "icon-article"; /// - /// System media vector icon + /// System media vector icon. /// public const string MediaVectorGraphics = "icon-picture"; /// - /// System media folder icon + /// System media folder icon. /// public const string MediaFolder = "icon-folder"; /// - /// System media image icon + /// System media image icon. /// public const string MediaImage = "icon-picture"; /// - /// System media type icon + /// System media type icon. /// public const string MediaType = "icon-thumbnails"; /// - /// System member icon + /// System member icon. /// public const string Member = "icon-user"; /// - /// System member group icon + /// System member group icon. /// public const string MemberGroup = "icon-users-alt"; /// - /// System member type icon + /// System member type icon. /// public const string MemberType = "icon-users"; /// - /// System packages icon + /// System packages icon. /// public const string Packages = "icon-box"; /// - /// System property editor icon + /// System property editor icon. /// public const string PartialView = "icon-article"; /// - /// System property editor icon + /// System property editor icon. /// public const string PropertyEditor = "icon-autofill"; /// - /// Relation type icon + /// Relation type icon. /// public const string RelationType = "icon-trafic"; /// - /// Script type icon + /// Script type icon. /// public const string Script = "icon-script"; /// - /// Stylesheet type icon + /// Stylesheet type icon. /// public const string Stylesheet = "icon-brackets"; /// - /// System member icon + /// System member icon. /// public const string Template = "icon-layout"; /// - /// System user icon + /// System user icon. /// public const string User = "icon-user"; /// - /// System user group icon + /// System user group icon. /// public const string UserGroup = "icon-users"; } diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 049a536690ff..9fa9d43137a3 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -19,6 +19,8 @@ public static class ObjectTypes public static readonly Guid MediaTypeContainer = new(Strings.MediaTypeContainer); + public static readonly Guid MemberTypeContainer = new(Strings.MemberTypeContainer); + public static readonly Guid DataType = new(Strings.DataType); public static readonly Guid Document = new(Strings.Document); @@ -73,6 +75,8 @@ public static class Strings public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; + public const string MemberTypeContainer = "59EF5767-7223-4ABC-B229-72821DC711B9"; + public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; diff --git a/src/Umbraco.Core/Constants-UdiEntityType.cs b/src/Umbraco.Core/Constants-UdiEntityType.cs index f65c29051614..65dc8c7d08e8 100644 --- a/src/Umbraco.Core/Constants-UdiEntityType.cs +++ b/src/Umbraco.Core/Constants-UdiEntityType.cs @@ -37,11 +37,15 @@ public static class UdiEntityType // TODO: What is this? This alias is only used for the blue print tree to render the blueprint's document type, it's not a real udi type public const string DocumentTypeBluePrints = "document-type-blueprints"; + public const string MediaType = "media-type"; public const string MediaTypeContainer = "media-type-container"; + public const string DataType = "data-type"; public const string DataTypeContainer = "data-type-container"; + public const string MemberType = "member-type"; + public const string MemberTypeContainer = "member-type-container"; public const string MemberGroup = "member-group"; public const string RelationType = "relation-type"; diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 8aaaf2d09d35..3b730ec956b4 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -344,6 +344,8 @@ Alle medlemmer Medlemgrupper har ingen yderligere egenskaber til redigering. Totrinsbekræftelse + Oprettelse af mappen under parent med id %0% fejlede + Omdøbning af mappen med id %0% fejlede Kopiering af indholdstypen fejlede diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index b153fbe62a36..a15b7ea42d22 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -349,6 +349,8 @@ All Members Member groups have no additional properties for editing. Two-Factor Authentication + Failed to create a folder under parent id %0% + Failed to rename the folder with id %0% Failed to copy content type diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index ea9be49ae7ed..697ac2b52e47 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -365,6 +365,8 @@ Lockout is not enabled for this member The member is not in group '%0%' Two-Factor Authentication + Failed to create a folder under parent id %0% + Failed to rename the folder with id %0% Failed to copy content type diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs index 03ed07f2fea3..a84e2e76c6f9 100644 --- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs @@ -162,6 +162,10 @@ public static GuidUdi GetUdi(this EntityContainer entity) { entityType = Constants.UdiEntityType.MediaTypeContainer; } + else if (entity.ContainedObjectType == Constants.ObjectTypes.MemberType) + { + entityType = Constants.UdiEntityType.MemberTypeContainer; + } else { throw new NotSupportedException(string.Format( diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 762297af079f..8f4bcb654502 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -12,6 +12,7 @@ public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity { Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer }, { Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer }, { Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer }, + { Constants.ObjectTypes.MemberType, Constants.ObjectTypes.MemberTypeContainer }, }; /// diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index acc9888bf4b2..cdeefc379553 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -8,19 +8,19 @@ namespace Umbraco.Cms.Core.Models; public enum UmbracoObjectTypes { /// - /// Default value + /// Default value. /// Unknown, /// - /// Root + /// Root. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] [FriendlyName("Root")] ROOT, /// - /// Document + /// Document. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.Document, typeof(IContent))] [FriendlyName("Document")] @@ -28,7 +28,7 @@ public enum UmbracoObjectTypes Document, /// - /// Media + /// Media. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.Media, typeof(IMedia))] [FriendlyName("Media")] @@ -36,7 +36,7 @@ public enum UmbracoObjectTypes Media, /// - /// Member Type + /// Member Type. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))] [FriendlyName("Member Type")] @@ -44,7 +44,7 @@ public enum UmbracoObjectTypes MemberType, /// - /// Template + /// Template. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.Template, typeof(ITemplate))] [FriendlyName("Template")] @@ -52,7 +52,7 @@ public enum UmbracoObjectTypes Template, /// - /// Member Group + /// Member Group. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup, typeof(IMemberGroup))] [FriendlyName("Member Group")] @@ -60,7 +60,7 @@ public enum UmbracoObjectTypes MemberGroup, /// - /// "Media Type + /// "Media Type. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaType, typeof(IMediaType))] [FriendlyName("Media Type")] @@ -68,7 +68,7 @@ public enum UmbracoObjectTypes MediaType, /// - /// Document Type + /// Document Type. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))] [FriendlyName("Document Type")] @@ -76,14 +76,14 @@ public enum UmbracoObjectTypes DocumentType, /// - /// Recycle Bin + /// Recycle Bin. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] [FriendlyName("Recycle Bin")] RecycleBin, /// - /// Member + /// Member. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.Member, typeof(IMember))] [FriendlyName("Member")] @@ -91,7 +91,7 @@ public enum UmbracoObjectTypes Member, /// - /// Data Type + /// Data Type. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.DataType, typeof(IDataType))] [FriendlyName("Data Type")] @@ -99,7 +99,7 @@ public enum UmbracoObjectTypes DataType, /// - /// Document type container + /// Document type container. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentTypeContainer)] [FriendlyName("Document Type Container")] @@ -107,7 +107,7 @@ public enum UmbracoObjectTypes DocumentTypeContainer, /// - /// Media type container + /// Media type container. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaTypeContainer)] [FriendlyName("Media Type Container")] @@ -115,7 +115,15 @@ public enum UmbracoObjectTypes MediaTypeContainer, /// - /// Media type container + /// Member type container. + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberTypeContainer)] + [FriendlyName("Member Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.MemberTypeContainer)] + MemberTypeContainer, + + /// + /// Media type container. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.DataTypeContainer)] [FriendlyName("Data Type Container")] @@ -123,7 +131,7 @@ public enum UmbracoObjectTypes DataTypeContainer, /// - /// Relation type + /// Relation type. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.RelationType)] [FriendlyName("Relation Type")] @@ -131,35 +139,35 @@ public enum UmbracoObjectTypes RelationType, /// - /// Forms Form + /// Forms Form. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] [FriendlyName("Form")] FormsForm, /// - /// Forms PreValue + /// Forms PreValue. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] [FriendlyName("PreValue")] FormsPreValue, /// - /// Forms DataSource + /// Forms DataSource. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] [FriendlyName("DataSource")] FormsDataSource, /// - /// Language + /// Language. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] [FriendlyName("Language")] Language, /// - /// Document Blueprint + /// Document Blueprint. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentBlueprint, typeof(IContent))] [FriendlyName("DocumentBlueprint")] @@ -167,7 +175,7 @@ public enum UmbracoObjectTypes DocumentBlueprint, /// - /// Reserved Identifier + /// Reserved Identifier. /// [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] [FriendlyName("Identifier Reservation")] diff --git a/src/Umbraco.Core/UdiEntityTypeHelper.cs b/src/Umbraco.Core/UdiEntityTypeHelper.cs index f0e8774cf89b..626979016d4d 100644 --- a/src/Umbraco.Core/UdiEntityTypeHelper.cs +++ b/src/Umbraco.Core/UdiEntityTypeHelper.cs @@ -32,6 +32,8 @@ public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) return Constants.UdiEntityType.DataTypeContainer; case UmbracoObjectTypes.MemberType: return Constants.UdiEntityType.MemberType; + case UmbracoObjectTypes.MemberTypeContainer: + return Constants.UdiEntityType.MemberTypeContainer; case UmbracoObjectTypes.MemberGroup: return Constants.UdiEntityType.MemberGroup; case UmbracoObjectTypes.RelationType: @@ -78,6 +80,8 @@ public static UmbracoObjectTypes ToUmbracoObjectType(string entityType) return UmbracoObjectTypes.DataTypeContainer; case Constants.UdiEntityType.MemberType: return UmbracoObjectTypes.MemberType; + case Constants.UdiEntityType.MemberTypeContainer: + return UmbracoObjectTypes.MemberTypeContainer; case Constants.UdiEntityType.MemberGroup: return UmbracoObjectTypes.MemberGroup; case Constants.UdiEntityType.RelationType: diff --git a/src/Umbraco.Core/UdiParser.cs b/src/Umbraco.Core/UdiParser.cs index 30448e1b4543..efef1bdbcd76 100644 --- a/src/Umbraco.Core/UdiParser.cs +++ b/src/Umbraco.Core/UdiParser.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -217,6 +217,7 @@ public static Dictionary GetKnownUdiTypes() => { Constants.UdiEntityType.DataType, UdiType.GuidUdi }, { Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi }, { Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, + { Constants.UdiEntityType.MemberTypeContainer, UdiType.GuidUdi }, { Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi }, { Constants.UdiEntityType.RelationType, UdiType.GuidUdi }, { Constants.UdiEntityType.FormsForm, UdiType.GuidUdi }, diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs index 7d3a9ab4fa00..749ca35f4b05 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -22,10 +22,14 @@ public EntityContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, { Guid[] allowedContainers = { - Constants.ObjectTypes.DocumentTypeContainer, Constants.ObjectTypes.MediaTypeContainer, + Constants.ObjectTypes.DocumentTypeContainer, + Constants.ObjectTypes.MediaTypeContainer, + Constants.ObjectTypes.MemberTypeContainer, Constants.ObjectTypes.DataTypeContainer, }; + NodeObjectTypeId = containerObjectType; + if (allowedContainers.Contains(NodeObjectTypeId) == false) { throw new InvalidOperationException("No container type exists with ID: " + NodeObjectTypeId); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs index dbde4e1f5be1..dedefb0a23a7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs @@ -1,31 +1,16 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Infrastructure.Scoping; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { - /// - /// A no-op implementation of , as containers aren't supported for members. - /// - /// - /// Introduced to avoid inconsistencies with nullability of dependencies for type repositories for content, media and members. - /// - internal class MemberTypeContainerRepository : IMemberTypeContainerRepository + internal class MemberTypeContainerRepository : EntityContainerRepository, IMemberTypeContainerRepository { - public void Delete(EntityContainer entity) - { - } - - public bool Exists(int id) => false; - - public EntityContainer? Get(Guid id) => null; - - public IEnumerable Get(string name, int level) => Enumerable.Empty(); - - public EntityContainer? Get(int id) => null; - - public IEnumerable GetMany(params int[]? ids) => Enumerable.Empty(); - - public void Save(EntityContainer entity) + public MemberTypeContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) + : base(scopeAccessor, cache, logger, Constants.ObjectTypes.MemberTypeContainer) { } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index 6a3e9bab02df..e693d907552f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -369,7 +369,7 @@ public ActionResult GetPropertyTypeScaffold(int id) } /// - /// Deletes a document type container with a given ID + /// Deletes a document type container with a given ID. /// [HttpDelete] [HttpPost] @@ -458,7 +458,7 @@ public IActionResult PostRenameContainer(int id, string name) } }); - if (!(savedCt.Result is null)) + if (savedCt.Result is not null) { return savedCt.Result; } @@ -628,7 +628,7 @@ public IEnumerable GetAllowedChildren(int contentId) } /// - /// Move the content type + /// Move the content type. /// /// /// @@ -640,7 +640,7 @@ public IActionResult PostMove(MoveOrCopy move) => (type, i) => _contentTypeService.Move(type, i)); /// - /// Copy the content type + /// Copy the content type. /// /// /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index 6377d119c312..b62d6e7fcb29 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -262,9 +262,8 @@ public IActionResult GetWhereCompositionIsUsedInContentTypes(GetAvailableComposi return dto; } - /// - /// Returns all media types + /// Returns all media types. /// [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] public IEnumerable GetAll() => @@ -272,7 +271,7 @@ public IEnumerable GetAll() => .Select(_umbracoMapper.Map).WhereNotNull(); /// - /// Deletes a media type container with a given ID + /// Deletes a media type container with a given ID. /// /// /// @@ -322,14 +321,13 @@ public IActionResult PostRenameContainer(int id, string name) i => _mediaTypeService.Get(i), type => _mediaTypeService.Save(type)); - if (!(savedCt.Result is null)) + if (savedCt.Result is not null) { return savedCt.Result; } MediaTypeDisplay? display = _umbracoMapper.Map(savedCt.Value); - display?.AddSuccessNotification( _localizedTextService.Localize("speechBubbles", "mediaTypeSavedHeader"), string.Empty); @@ -338,7 +336,7 @@ public IActionResult PostRenameContainer(int id, string name) } /// - /// Move the media type + /// Move the media type. /// /// /// @@ -350,7 +348,7 @@ public IActionResult PostMove(MoveOrCopy move) => (type, i) => _mediaTypeService.Move(type, i)); /// - /// Copy the media type + /// Copy the media type. /// /// /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs index 1c11c0fd59b5..6128fa83635d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs @@ -189,17 +189,83 @@ public IActionResult GetWhereCompositionIsUsedInMemberTypes(int contentTypeId) return Ok(result); } - public MemberTypeDisplay? GetEmpty() + //public MemberTypeDisplay? GetEmpty() + //{ + // var ct = new MemberType(_shortStringHelper, -1) + // { + // Icon = Constants.Icons.Member + // }; + + // MemberTypeDisplay? dto = _umbracoMapper.Map(ct); + // return dto; + //} + + [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] + public MemberTypeDisplay? GetEmpty(int parentId) { - var ct = new MemberType(_shortStringHelper, -1) + IMemberType mt; + if (parentId != Constants.System.Root) { - Icon = Constants.Icons.Member - }; + IMemberType? parent = _memberTypeService.Get(parentId); + mt = parent != null + ? new MemberType(_shortStringHelper, parent, string.Empty) + : new MemberType(_shortStringHelper, parentId); + } + else + { + mt = new MemberType(_shortStringHelper, parentId); + } - MemberTypeDisplay? dto = _umbracoMapper.Map(ct); + mt.Icon = Constants.Icons.Member; + + MemberTypeDisplay? dto = _umbracoMapper.Map(mt); return dto; } + /// + /// Deletes a member type container with a given ID. + /// + /// + /// + [HttpDelete] + [HttpPost] + [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] + public IActionResult DeleteContainer(int id) + { + _memberTypeService.DeleteContainer(id, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); + + return Ok(); + } + + [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] + public IActionResult PostCreateContainer(int parentId, string name) + { + Attempt?> result = + _memberTypeService.CreateContainer(parentId, Guid.NewGuid(), name, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); + + if (result.Success) + { + return Ok(result.Result); //return the id + } + + return ValidationProblem(result.Exception?.Message); + } + + [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] + public IActionResult PostRenameContainer(int id, string name) + { + Attempt?> result = + _memberTypeService.RenameContainer(id, name, _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1); + + if (result.Success) + { + return Ok(result.Result); //return the id + } + + return ValidationProblem(result.Exception?.Message); + } + + [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public ActionResult PostSave(MemberTypeSave contentTypeSave) { //get the persisted member type @@ -247,14 +313,12 @@ public IActionResult GetWhereCompositionIsUsedInMemberTypes(int contentTypeId) } } + ActionResult savedCt = PerformPostSave( + contentTypeSave, + i => _memberTypeService.Get(i), + type => _memberTypeService.Save(type)); - ActionResult savedCt = - PerformPostSave( - contentTypeSave, - i => ct, - type => _memberTypeService.Save(type)); - - if (!(savedCt.Result is null)) + if (savedCt.Result is not null) { return savedCt.Result; } @@ -269,7 +333,19 @@ public IActionResult GetWhereCompositionIsUsedInMemberTypes(int contentTypeId) } /// - /// Copy the member type + /// Move the member type. + /// + /// + /// + [Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] + public IActionResult PostMove(MoveOrCopy move) => + PerformMove( + move, + i => _memberTypeService.Get(i), + (type, i) => _memberTypeService.Move(type, i)); + + /// + /// Copy the member type. /// /// /// diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index f68b4f87830d..53c288f39b9c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -54,7 +54,8 @@ public async Task SearchAsync(string query, int pageSize, l protected override ActionResult CreateRootNode(FormCollection queryStrings) { ActionResult rootResult = base.CreateRootNode(queryStrings); - if (!(rootResult.Result is null)) + + if (rootResult.Result is not null) { return rootResult; } @@ -133,7 +134,7 @@ protected override ActionResult GetMenuForNode(string id, Fo if (id == Constants.System.RootString) { - //set the default to create + // set the default to create menu.DefaultMenuAlias = ActionNew.ActionAlias; // root actions @@ -154,7 +155,7 @@ protected override ActionResult GetMenuForNode(string id, Fo IEntitySlim? container = _entityService.Get(int.Parse(id, CultureInfo.InvariantCulture), UmbracoObjectTypes.DocumentTypeContainer); if (container != null) { - //set the default to create + // set the default to create menu.DefaultMenuAlias = ActionNew.ActionAlias; menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); @@ -167,7 +168,7 @@ protected override ActionResult GetMenuForNode(string id, Fo if (container.HasChildren == false) { - //can delete doc type + // can delete document type menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); } diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index d0164afa0618..27e079833a36 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -66,10 +66,10 @@ protected override ActionResult GetTreeNodes(string id, Form .OrderBy(entity => entity.Name) .Select(dt => { - TreeNode node = CreateTreeNode(dt, Constants.ObjectTypes.DataType, id, queryStrings, - Constants.Icons.Folder, dt.HasChildren); + TreeNode node = CreateTreeNode(dt, Constants.ObjectTypes.DataType, id, queryStrings, Constants.Icons.Folder, dt.HasChildren); node.Path = dt.Path; node.NodeType = "container"; + // TODO: This isn't the best way to ensure a no operation process for clicking a node but it works for now. node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; return node; @@ -93,8 +93,7 @@ protected override ActionResult GetTreeNodes(string id, Form .Select(dt => { IDataType dataType = dataTypes[dt.Id]; - TreeNode node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, - dataType.Editor?.Icon, false); + TreeNode node = CreateTreeNode(dt.Id.ToInvariantString(), id, queryStrings, dt.Name, dataType.Editor?.Icon, false); node.Path = dt.Path; return node; }) @@ -140,7 +139,7 @@ protected override ActionResult GetMenuForNode(string id, Fo if (id == Constants.System.RootString) { - //set the default to create + // set the default to create menu.DefaultMenuAlias = ActionNew.ActionAlias; // root actions @@ -153,7 +152,7 @@ protected override ActionResult GetMenuForNode(string id, Fo UmbracoObjectTypes.DataTypeContainer); if (container != null) { - //set the default to create + // set the default to create menu.DefaultMenuAlias = ActionNew.ActionAlias; menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs index 0732248182b8..ebc9e0e22786 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs @@ -64,10 +64,10 @@ protected override ActionResult GetTreeNodes(string id, Form .OrderBy(entity => entity.Name) .Select(dt => { - TreeNode node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, Constants.Icons.Folder, - dt.HasChildren, ""); + TreeNode node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, Constants.Icons.Folder, dt.HasChildren, string.Empty); node.Path = dt.Path; node.NodeType = "container"; + // TODO: This isn't the best way to ensure a no operation process for clicking a node but it works for now. node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; return node; @@ -91,8 +91,7 @@ protected override ActionResult GetTreeNodes(string id, Form // need this check to keep supporting sites where children have already been created. var hasChildren = dt.HasChildren; IMediaType? mt = mediaTypes.FirstOrDefault(x => x.Id == dt.Id); - TreeNode node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, - mt?.Icon ?? Constants.Icons.MediaType, hasChildren); + TreeNode node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, mt?.Icon ?? Constants.Icons.MediaType, hasChildren); node.Path = dt.Path; return node; @@ -113,11 +112,13 @@ protected override ActionResult GetMenuForNode(string id, Fo // root actions menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); menu.Items.Add(new RefreshNode(LocalizedTextService, true)); + return menu; } IEntitySlim? container = _entityService.Get(int.Parse(id, CultureInfo.InvariantCulture), UmbracoObjectTypes.MediaTypeContainer); + if (container != null) { // set the default to create @@ -133,8 +134,8 @@ protected override ActionResult GetMenuForNode(string id, Fo if (container.HasChildren == false) { - // can delete doc type - menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + // can delete media type + menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); } menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true)); @@ -146,7 +147,7 @@ protected override ActionResult GetMenuForNode(string id, Fo menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); - // no move action if this is a child doc type + // no move action if this is a child media type if (parent == null) { menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs index 8d457169d233..39b4c78d57aa 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs @@ -57,7 +57,8 @@ protected override IEnumerable GetTreeNodesFromService(string id, Form protected override ActionResult CreateRootNode(FormCollection queryStrings) { ActionResult rootResult = base.CreateRootNode(queryStrings); - if (!(rootResult.Result is null)) + + if (rootResult.Result is not null) { return rootResult.Result; } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs index 977205e89339..5e290b4e187e 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -5,12 +6,15 @@ using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Trees; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Extensions; +using static Umbraco.Cms.Core.Constants.Conventions; namespace Umbraco.Cms.Web.BackOffice.Trees; @@ -18,27 +22,63 @@ namespace Umbraco.Cms.Web.BackOffice.Trees; [CoreTree] public abstract class MemberTypeAndGroupTreeControllerBase : TreeController { + private readonly IEntityService _entityService; private readonly IMemberTypeService _memberTypeService; + [Obsolete("Use the constructor with IEntityService instead")] protected MemberTypeAndGroupTreeControllerBase( ILocalizedTextService localizedTextService, UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, IMenuItemCollectionFactory menuItemCollectionFactory, IEventAggregator eventAggregator, IMemberTypeService memberTypeService) + : this(localizedTextService, umbracoApiControllerTypeCollection, menuItemCollectionFactory, eventAggregator, memberTypeService, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + protected MemberTypeAndGroupTreeControllerBase( + ILocalizedTextService localizedTextService, + UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection, + IMenuItemCollectionFactory menuItemCollectionFactory, + IEventAggregator eventAggregator, + IMemberTypeService memberTypeService, + IEntityService entityService) : base(localizedTextService, umbracoApiControllerTypeCollection, eventAggregator) { MenuItemCollectionFactory = menuItemCollectionFactory; _memberTypeService = memberTypeService; + _entityService = entityService; } public IMenuItemCollectionFactory MenuItemCollectionFactory { get; } protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { + if (!int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) + { + throw new InvalidOperationException("Id must be an integer"); + } + var nodes = new TreeNodeCollection(); + if (queryStrings["tree"].ToString() == Constants.Trees.MemberTypes) + { + nodes.AddRange( + _entityService.GetChildren(intId, UmbracoObjectTypes.MemberTypeContainer) + .OrderBy(entity => entity.Name) + .Select(dt => + { + TreeNode node = CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, Constants.Icons.Folder, dt.HasChildren, string.Empty); + node.Path = dt.Path; + node.NodeType = "container"; + + // TODO: This isn't the best way to ensure a no operation process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + return node; + })); + } + // if the request is for folders only then just return if (queryStrings["foldersonly"].ToString().IsNullOrWhiteSpace() == false && queryStrings["foldersonly"].ToString() == "1") @@ -46,7 +86,30 @@ protected override ActionResult GetTreeNodes(string id, Form return nodes; } - nodes.AddRange(GetTreeNodesFromService(id, queryStrings)); + if (queryStrings["tree"].ToString() == Constants.Trees.MemberTypes) + { + IEnumerable memberTypes = _memberTypeService.GetAll(); + + nodes.AddRange( + _entityService.GetChildren(intId, UmbracoObjectTypes.MemberType) + .OrderBy(entity => entity.Name) + .Select(dt => + { + var hasChildren = dt.HasChildren; + IMemberType? mt = memberTypes.FirstOrDefault(x => x.Id == dt.Id); + TreeNode node = CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, mt?.Icon ?? Constants.Icons.MemberType, hasChildren); + + node.Path = dt.Path; + return node; + })); + + //nodes.AddRange(GetTreeNodesFromService(id, queryStrings)); + } + else + { + nodes.AddRange(GetTreeNodesFromService(id, queryStrings)); + } + return nodes; } @@ -56,20 +119,67 @@ protected override ActionResult GetMenuForNode(string id, Fo if (id == Constants.System.RootString) { + // set the default to create + menu.DefaultMenuAlias = ActionNew.ActionAlias; + // root actions - menu.Items.Add(new CreateChildEntity(LocalizedTextService)); + + if (queryStrings["tree"].ToString() == Constants.Trees.MemberGroups) + { + menu.Items.Add(new CreateChildEntity(LocalizedTextService)); + } + else + { + menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + } + menu.Items.Add(new RefreshNode(LocalizedTextService, true)); + return menu; } - IMemberType? memberType = _memberTypeService.Get(int.Parse(id)); - if (memberType != null) + if (queryStrings["tree"].ToString() == Constants.Trees.MemberTypes) { - menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); - } + IEntitySlim? container = _entityService.Get(int.Parse(id, CultureInfo.InvariantCulture), + UmbracoObjectTypes.MemberTypeContainer); + + if (container != null) + { + // set the default to create + menu.DefaultMenuAlias = ActionNew.ActionAlias; + + menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + + menu.Items.Add(new MenuItem("rename", LocalizedTextService.Localize("actions", "rename")) + { + Icon = "icon-edit", + UseLegacyIcon = false, + }); + + if (container.HasChildren == false) + { + // can delete member type + menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + } - // delete member type/group - menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + menu.Items.Add(new RefreshNode(LocalizedTextService, separatorBefore: true)); + } + else + { + IMemberType? ct = _memberTypeService.Get(int.Parse(id)); + + if (ct != null) + { + menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); + menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + } + } + } + else + { + menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); + } return menu; } diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs index 5325e7728ebc..ffa7bd539717 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs @@ -67,6 +67,5 @@ public async Task SearchAsync(string query, int pageSize, l protected override IEnumerable GetTreeNodesFromService(string id, FormCollection queryStrings) => _memberTypeService.GetAll() .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, - dt?.Icon ?? Constants.Icons.MemberType, false)); + .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, dt?.Icon ?? Constants.Icons.MemberType, false)); } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js index dfac875e5e8c..ccc80dee3707 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js @@ -323,7 +323,7 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca "contentTypeApiBaseUrl", "DeleteContainer", [{ id: id }])), - 'Failed to delete content type contaier'); + 'Failed to delete content type container'); }, /** diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js index f7bba87ad568..fce231edfad8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/mediatype.resource.js @@ -166,7 +166,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter, locali "mediaTypeApiBaseUrl", "DeleteContainer", [{ id: id }])), - 'Failed to delete content type contaier'); + 'Failed to delete media type container'); }, /** @@ -208,7 +208,7 @@ function mediaTypeResource($q, $http, umbRequestHelper, umbDataFormatter, locali * }); * * @param {Object} args arguments object - * @param {Int} args.idd the ID of the node to move + * @param {Int} args.id the ID of the node to move * @param {Int} args.parentId the ID of the parent node to move to * @returns {Promise} resourcePromise object. * diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js index f96ba02ecbbb..7c78b69975cb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/membertype.resource.js @@ -90,13 +90,27 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter, local 'Failed to delete member type'); }, - getScaffold: function () { + deleteContainerById: function (id) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "DeleteContainer", + [{ id: id }])), + 'Failed to delete member type container'); + }, + + getScaffold: function (parentId) { + + // For backwards compatibility since parentId parameter has been added. + parentId = parentId ?? -1; return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "memberTypeApiBaseUrl", - "GetEmpty")), + "GetEmpty", { parentId: parentId })), 'Failed to retrieve content type scaffold'); }, @@ -121,6 +135,49 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter, local 'Failed to save data for member type id ' + contentType.id); }, + /** + * @ngdoc method + * @name umbraco.resources.memberTypeResource#move + * @methodOf umbraco.resources.memberTypeResource + * + * @description + * Moves a node underneath a new parentId + * + * ##usage + *
+         * memberTypeResource.move({ parentId: 1244, id: 123 })
+         *    .then(function() {
+         *        alert("node was moved");
+         *    }, function(err){
+         *      alert("node didnt move:" + err.data.Message);
+         *    });
+         * 
+ * @param {Object} args arguments object + * @param {Int} args.id the ID of the node to move + * @param {Int} args.parentId the ID of the parent node to move to + * @returns {Promise} resourcePromise object. + * + */ + move: function (args) { + if (!args) { + throw "args cannot be null"; + } + if (!args.parentId) { + throw "args.parentId cannot be null"; + } + if (!args.id) { + throw "args.id cannot be null"; + } + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("memberTypeApiBaseUrl", "PostMove"), + { + parentId: args.parentId, + id: args.id + }, { responseType: 'text' }), + 'Failed to move member type'); + }, + copy: function (args) { if (!args) { throw "args cannot be null"; @@ -141,6 +198,32 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter, local id: args.id }, { responseType: 'text' }), promise); + }, + + createContainer: function (parentId, name) { + + var promise = localizationService.localize("member_createFolderFailed", [parentId]); + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "memberTypeApiBaseUrl", + "PostCreateContainer", + { parentId: parentId, name: encodeURIComponent(name) })), + promise); + }, + + renameContainer: function (id, name) { + + var promise = localizationService.localize("member_renameFolderFailed", [id]); + + return umbRequestHelper.resourcePromise( + $http.post(umbRequestHelper.getApiUrl("memberTypeApiBaseUrl", + "PostRenameContainer", + { id: id, name: name })), + promise + ); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/views/documentTypes/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/documentTypes/delete.controller.js index f0c8a1825111..2fb1b62e506f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documentTypes/delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documentTypes/delete.controller.js @@ -6,7 +6,7 @@ * @description * The controller for deleting content */ -function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService, localizationService) { +function DocumentTypesDeleteController($scope, contentTypeResource, treeService, navigationService, localizationService) { $scope.performDelete = function() { @@ -51,7 +51,7 @@ function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeReso $scope.labels = {}; localizationService .format(["contentTypeEditor_yesDelete", "contentTypeEditor_andAllDocuments"], "%0% " + $scope.currentNode.name + " %1%") - .then(function (data) { + .then(data => { $scope.labels.deleteConfirm = data; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/documentTypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documentTypes/edit.controller.js index d83cf350db97..2b95cc49b7f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documentTypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documentTypes/edit.controller.js @@ -200,11 +200,10 @@ vm.page.saveButtonState = "busy"; - localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) { - localizationService.localize("modelsBuilder_waitingMessage").then(function (msgValue) { - notificationsService.info(headerValue, msgValue); - }); - }); + localizationService.localizeMany(["modelsBuilder_buildingModels", "modelsBuilder_waitingMessage"]) + .then(values => { + notificationsService.info(values[0], values[1]); + }); contentTypeHelper.generateModels().then(function (result) { @@ -365,10 +364,9 @@ editorState.set($scope.content); } else { - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { - notificationsService.error(headerValue, msgValue); - }); + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]) + .then(values => { + notificationsService.error(values[0], values[1]); }); } vm.page.saveButtonState = "error"; @@ -406,7 +404,7 @@ // convert icons for content type convertLegacyIcons(contentType); - //set a shared state + // set a shared state editorState.set(contentType); vm.contentType = contentType; diff --git a/src/Umbraco.Web.UI.Client/src/views/documentTypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/documentTypes/move.controller.js index e63656cee3d7..3888af62ecdd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documentTypes/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documentTypes/move.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.Editors.DocumentTypes.MoveController", - function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { + function ($scope, contentTypeResource, treeService, navigationService, appState, eventsService) { $scope.dialogTreeApi = {}; $scope.source = _.clone($scope.currentNode); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js index 435ece4bb9cd..37cf611a8501 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.controller.js @@ -6,14 +6,16 @@ * @description * The controller for the media type creation dialog */ -function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) { +function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState) { $scope.model = { - folderName: "", - creatingFolder: false + allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container', + folderName: "", + creatingFolder: false }; var node = $scope.currentNode; + var section = appState.getSectionState("currentSection"); $scope.showCreateFolder = function() { $scope.model.creatingFolder = true; @@ -32,9 +34,7 @@ function MediaTypesCreateController($scope, $location, navigationService, mediaT formHelper.resetForm({ scope: $scope, formCtrl: $scope.createFolderForm }); - var section = appState.getSectionState("currentSection"); - - }, function (err) { + }, function(err) { formHelper.resetForm({ scope: $scope, formCtrl: $scope.createFolderForm, hasErrors: true }); $scope.error = err; }); @@ -43,7 +43,7 @@ function MediaTypesCreateController($scope, $location, navigationService, mediaT $scope.createMediaType = function() { $location.search('create', null); - $location.path("/settings/mediaTypes/edit/" + node.id).search("create", "true"); + $location.path("/" + section + "/mediaTypes/edit/" + node.id).search("create", "true"); navigationService.hideMenu(); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html index bd6ef087f4a5..097f4fee1bed 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/create.html @@ -10,20 +10,21 @@
    -
  • -
  • -
  • -
diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.controller.js index 2acad6f909f4..4163acb14464 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.controller.js @@ -49,7 +49,7 @@ function MediaTypesDeleteController($scope, mediaTypeResource, treeService, navi $scope.labels = {}; localizationService .format(["contentTypeEditor_yesDelete", "contentTypeEditor_andAllMediaItems"], "%0% " + $scope.currentNode.name + " %1%") - .then(function (data) { + .then(data => { $scope.labels.deleteConfirm = data; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.html b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.html index 051126f2d59e..2accde351a4a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/delete.html @@ -31,6 +31,5 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/edit.controller.js index 8c0ce561d9a1..51e69e90ee27 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/edit.controller.js @@ -205,11 +205,10 @@ vm.page.saveButtonState = "busy"; - localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) { - localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) { - notificationsService.info(headerValue, msgValue); - }); - }); + localizationService.localizeMany(["modelsBuilder_buildingModels", "modelsBuilder_waitingMessage"]) + .then(values => { + notificationsService.info(values[0], values[1]); + }); contentTypeHelper.generateModels().then(function (result) { @@ -328,11 +327,10 @@ editorState.set($scope.content); } else { - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { - notificationsService.error(headerValue, msgValue); - }); - }); + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]) + .then(values => { + notificationsService.error(values[0], values[1]); + }); } vm.page.saveButtonState = "error"; @@ -349,7 +347,7 @@ // convert icons for content type convertLegacyIcons(contentType); - //set a shared state + // set a shared state editorState.set(contentType); vm.contentType = contentType; diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.controller.js index 263cefea642a..bb63b9a4ce22 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.controller.js @@ -1,6 +1,6 @@ angular.module("umbraco") .controller("Umbraco.Editors.MediaTypes.MoveController", - function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) { + function ($scope, mediaTypeResource, treeService, navigationService, appState, eventsService) { $scope.dialogTreeApi = {}; $scope.source = _.clone($scope.currentNode); diff --git a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.html b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.html index 6bb1b6fa105d..79dbf2072961 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/mediaTypes/move.html @@ -4,7 +4,7 @@

- Select the folder to move {{source.name}} to in the tree structure below + Select the folder to move {{source.name}} to in the tree structure below

diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js index 550ad7ed35fc..bbb22dd37d32 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.controller.js @@ -6,11 +6,12 @@ * @description * The controller for the member type creation dialog */ -function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) { +function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState) { $scope.model = { - folderName: "", - creatingFolder: false + allowCreateFolder: $scope.currentNode.parentId === null || $scope.currentNode.nodeType === 'container', + folderName: "", + creatingFolder: false }; var node = $scope.currentNode; @@ -23,7 +24,7 @@ function MemberTypesCreateController($scope, $location, navigationService, membe $scope.createContainer = function () { if (formHelper.submitForm({ scope: $scope, - formCtrl: this.createFolderForm + formCtrl: $scope.createFolderForm })) { memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { @@ -31,11 +32,11 @@ function MemberTypesCreateController($scope, $location, navigationService, membe var currPath = node.path ? node.path : "-1"; navigationService.syncTree({ tree: "memberTypes", path: currPath + "," + folderId, forceReload: true, activate: true }); - formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm }); + formHelper.resetForm({ scope: $scope, formCtrl: $scope.createFolderForm }); }, function(err) { - formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm, hasErrors: true }); - // TODO: Handle errors + formHelper.resetForm({ scope: $scope, formCtrl: $scope.createFolderForm, hasErrors: true }); + $scope.error = err; }); }; } @@ -45,6 +46,10 @@ function MemberTypesCreateController($scope, $location, navigationService, membe $location.path("/" + section + "/memberTypes/edit/" + node.id).search("create", "true"); navigationService.hideMenu(); } + $scope.close = function() { + const showMenu = true; + navigationService.hideDialog(showMenu); + }; } angular.module('umbraco').controller("Umbraco.Editors.MemberTypes.CreateController", MemberTypesCreateController); diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html index 5bcf6c42bf02..971f8a930394 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/create.html @@ -1,50 +1,77 @@
- diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/memberTypes/edit.controller.js index f5658ad1df05..9ffc04dc5abc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/edit.controller.js @@ -126,11 +126,10 @@ vm.page.saveButtonState = "busy"; - localizationService.localize("modelsBuilder_buildingModels").then(function (headerValue) { - localizationService.localize("modelsBuilder_waitingMessage").then(function(msgValue) { - notificationsService.info(headerValue, msgValue); - }); - }); + localizationService.localizeMany(["modelsBuilder_buildingModels", "modelsBuilder_waitingMessage"]) + .then(values => { + notificationsService.info(values[0], values[1]); + }); contentTypeHelper.generateModels().then(function (result) { @@ -205,7 +204,7 @@ function save() { // only save if there is no overlays open - if(overlayHelper.getNumberOfOverlays() === 0) { + if (overlayHelper.getNumberOfOverlays() === 0) { var deferred = $q.defer(); @@ -222,7 +221,7 @@ }).then(function (data) { //success - if(!infiniteMode) { + if (!infiniteMode) { syncTreeNode(vm.contentType, data.path); } @@ -236,7 +235,7 @@ vm.page.saveButtonState = "success"; - if(infiniteMode && $scope.model.submit) { + if (infiniteMode && $scope.model.submit) { $scope.model.submit(); } @@ -248,10 +247,9 @@ editorState.set($scope.content); } else { - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { - notificationsService.error(headerValue, msgValue); - }); + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]) + .then(values => { + notificationsService.error(values[0], values[1]); }); } @@ -270,7 +268,7 @@ // convert legacy icons convertLegacyIcons(contentType); - //set a shared state + // set a shared state editorState.set(contentType); vm.contentType = contentType; diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.controller.js b/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.controller.js index fe7288f4e2b1..25c3961c838b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.controller.js @@ -1,5 +1,67 @@ angular.module("umbraco") .controller("Umbraco.Editors.MemberTypes.MoveController", - function($scope){ + function ($scope, memberTypeResource, treeService, navigationService, appState, eventsService) { - }); + $scope.dialogTreeApi = {}; + $scope.source = _.clone($scope.currentNode); + + function nodeSelectHandler(args) { + args.event.preventDefault(); + args.event.stopPropagation(); + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + } + + $scope.move = function () { + + $scope.busy = true; + $scope.error = false; + + memberTypeResource.move({ parentId: $scope.target.id, id: $scope.source.id }) + .then(function (path) { + $scope.error = false; + $scope.success = true; + $scope.busy = false; + + //first we need to remove the node that launched the dialog + treeService.removeNode($scope.currentNode); + + //get the currently edited node (if any) + var activeNode = appState.getTreeState("selectedNode"); + + //we need to do a double sync here: first sync to the moved content - but don't activate the node, + //then sync to the currenlty edited content (note: this might not be the content that was moved!!) + + navigationService.syncTree({ tree: "memberTypes", path: path, forceReload: true, activate: false }).then(function (args) { + if (activeNode) { + var activeNodePath = treeService.getPath(activeNode).join(); + //sync to this node now - depending on what was copied this might already be synced but might not be + navigationService.syncTree({ tree: "memberTypes", path: activeNodePath, forceReload: false, activate: true }); + } + }); + + eventsService.emit('app.refreshEditor'); + + }, function (err) { + $scope.success = false; + $scope.error = err; + $scope.busy = false; + + }); + }; + + $scope.onTreeInit = function () { + $scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler); + }; + + $scope.close = function () { + navigationService.hideDialog(); + }; + + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.html b/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.html index 0eeadfab8044..731b4da27435 100644 --- a/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/move.html @@ -1,11 +1,54 @@ -
+
+
-

- Select the folder to move {{currentNode.name}} to. +

+ Select the folder to move {{source.name}} to in the tree structure below

+ +
+
+
{{error.errorMsg}}
+
{{error.data.message}}
+
+
+ +
+
+ {{source.name}} was moved underneath {{target.name}} +
+ +
+ +
+ +
+ + +
+ +
+
+ + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/memberTypes/rename.html b/src/Umbraco.Web.UI.Client/src/views/memberTypes/rename.html new file mode 100644 index 000000000000..14e4a7e58cfb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/memberTypes/rename.html @@ -0,0 +1,23 @@ + diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs index 27d0cce9859a..0124dc73fccc 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -22,11 +22,96 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Persistence.Repos [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class MemberTypeRepositoryTest : UmbracoIntegrationTest { - private MemberTypeRepository CreateRepository(IScopeProvider provider) + private IContentTypeCommonRepository CommonRepository => GetRequiredService(); + + private ILanguageRepository LanguageRepository => GetRequiredService(); + + [Test] + public void Can_Create_Container() { - var commonRepository = GetRequiredService(); - var languageRepository = GetRequiredService(); - return new MemberTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of>(), commonRepository, languageRepository, ShortStringHelper); + var provider = ScopeProvider; + using (var scope = provider.CreateScope()) + { + var containerRepository = CreateContainerRepository(provider); + + var container = new EntityContainer(Constants.ObjectTypes.MemberType) { Name = "blah" }; + containerRepository.Save(container); + + Assert.That(container.Id, Is.GreaterThan(0)); + + var found = containerRepository.Get(container.Id); + Assert.IsNotNull(found); + } + } + + [Test] + public void Can_Delete_Container() + { + var provider = ScopeProvider; + using (var scope = provider.CreateScope()) + { + var containerRepository = CreateContainerRepository(provider); + + var container = new EntityContainer(Constants.ObjectTypes.MemberType) { Name = "blah" }; + containerRepository.Save(container); + + Assert.That(container.Id, Is.GreaterThan(0)); + + // Act + containerRepository.Delete(container); + + var found = containerRepository.Get(container.Id); + Assert.IsNull(found); + } + } + + [Test] + public void Can_Create_Container_Containing_Member_Types() + { + var provider = ScopeProvider; + using (var scope = provider.CreateScope()) + { + var containerRepository = CreateContainerRepository(provider); + var repository = CreateRepository(provider); + + var container = new EntityContainer(Constants.ObjectTypes.MemberType) { Name = "blah" }; + containerRepository.Save(container); + + var contentType = MemberTypeBuilder.CreateSimpleMemberType(); + + contentType.ParentId = container.Id; + repository.Save(contentType); + + Assert.AreEqual(container.Id, contentType.ParentId); + } + } + + [Test] + public void Can_Delete_Container_Containing_Member_Types() + { + var provider = ScopeProvider; + using (var scope = provider.CreateScope()) + { + var containerRepository = CreateContainerRepository(provider); + var repository = CreateRepository(provider); + + var container = new EntityContainer(Constants.ObjectTypes.MediaType) { Name = "blah" }; + containerRepository.Save(container); + + IMemberType contentType = MemberTypeBuilder.CreateSimpleMemberType(); + contentType.ParentId = container.Id; + repository.Save(contentType); + + // Act + containerRepository.Delete(container); + + var found = containerRepository.Get(container.Id); + Assert.IsNull(found); + + contentType = repository.Get(contentType.Id); + Assert.IsNotNull(contentType); + Assert.AreEqual(-1, contentType.ParentId); + } } [Test] @@ -295,4 +380,10 @@ public void Can_Delete_MemberType() Assert.That(exists, Is.False); } } + + private MemberTypeRepository CreateRepository(IScopeProvider provider) => + new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), CommonRepository, LanguageRepository, ShortStringHelper); + + private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) => + new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Constants.ObjectTypes.MemberTypeContainer); }