From 07711aac28ab0c096238cb2e975eb803ac90bdd6 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 24 May 2024 08:59:47 +0200 Subject: [PATCH] Expose and manage system media types (#16343) * Expose and manage system media types * Removed license :D --- .../Tree/MediaTypeTreeControllerBase.cs | 2 ++ .../MediaType/MediaTypeMapDefinition.cs | 2 ++ src/Umbraco.Cms.Api.Management/OpenApi.json | 12 +++++++ .../MediaType/MediaTypeResponseModel.cs | 4 +++ .../Tree/MediaTypeTreeItemResponseModel.cs | 2 ++ src/Umbraco.Core/Models/MediaType.cs | 16 ++++++++++ .../MediaTypeEditingService.cs | 5 +++ ...peServiceBaseOfTRepositoryTItemTService.cs | 16 ++++++++++ src/Umbraco.Core/Services/MediaTypeService.cs | 4 +++ .../MediaTypeEditingServiceTests.Update.cs | 19 +++++++++++ .../Services/MediaTypeServiceTests.cs | 32 +++++++++++++++++-- 11 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs index 87d1c7d201..641ca09245 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree; @@ -38,6 +39,7 @@ public class MediaTypeTreeControllerBase : FolderTreeControllerBase AllowedMediaTypes { get; set; } = Enumerable.Empty(); public IEnumerable Compositions { get; set; } = Enumerable.Empty(); + + public bool IsDeletable { get; set; } + + public bool AliasCanBeChanged { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs index ccb0fed5e2..f1f8f55c38 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs @@ -3,4 +3,6 @@ public class MediaTypeTreeItemResponseModel : FolderTreeItemResponseModel { public string Icon { get; set; } = string.Empty; + + public bool IsDeletable { get; set; } } diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index 64683ae462..7e5e572cd4 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -1,5 +1,6 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models; @@ -45,6 +46,21 @@ public class MediaType : ContentTypeCompositionBase, IMediaType /// public override ISimpleContentType ToSimple() => new SimpleContentType(this); + /// + public override string Alias + { + get => base.Alias; + set + { + if (this.IsSystemMediaType() && value != Alias) + { + throw new InvalidOperationException("Cannot change the alias of a system media type"); + } + + base.Alias = value; + } + } + /// IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) => (IMediaType)DeepCloneWithResetIdentities(newAlias); diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs index 86b9989ef8..11def4a333 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs @@ -42,6 +42,11 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase> UpdateAsync(IMediaType mediaType, MediaTypeUpdateModel model, Guid userKey) { + if (mediaType.IsSystemMediaType() && mediaType.Alias != model.Alias) + { + return Attempt.FailWithStatus(ContentTypeOperationStatus.NotAllowed, null); + } + Attempt result = await ValidateAndMapForUpdateAsync(mediaType, model); if (result.Success) { diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index f44ebb3a28..8340f3c584 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -620,6 +620,11 @@ public abstract class ContentTypeServiceBase : ContentTypeSe return ContentTypeOperationStatus.NotFound; } + if (CanDelete(item) is false) + { + return ContentTypeOperationStatus.NotAllowed; + } + Delete(item, performingUserId); scope.Complete(); @@ -628,6 +633,11 @@ public abstract class ContentTypeServiceBase : ContentTypeSe public void Delete(TItem item, int userId = Constants.Security.SuperUserId) { + if (CanDelete(item) is false) + { + throw new InvalidOperationException("The item was not allowed to be deleted"); + } + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -695,6 +705,10 @@ public abstract class ContentTypeServiceBase : ContentTypeSe public void Delete(IEnumerable items, int userId = Constants.Security.SuperUserId) { TItem[] itemsA = items.ToArray(); + if (itemsA.All(CanDelete) is false) + { + throw new InvalidOperationException("One or more items were not allowed to be deleted"); + } using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { @@ -750,6 +764,8 @@ public abstract class ContentTypeServiceBase : ContentTypeSe protected abstract void DeleteItemsOfTypes(IEnumerable typeIds); + protected virtual bool CanDelete(TItem item) => true; + #endregion #region Copy diff --git a/src/Umbraco.Core/Services/MediaTypeService.cs b/src/Umbraco.Core/Services/MediaTypeService.cs index cc914e43bc..359b4a99a9 100644 --- a/src/Umbraco.Core/Services/MediaTypeService.cs +++ b/src/Umbraco.Core/Services/MediaTypeService.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Services.Locking; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services; @@ -77,6 +78,9 @@ public class MediaTypeService : ContentTypeServiceBase item.IsSystemMediaType() is false; + #region Notifications protected override SavingNotification GetSavingNotification( diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs index a2ea9ad916..8b1b7cfc78 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; @@ -113,4 +114,22 @@ public partial class MediaTypeEditingServiceTests Assert.AreEqual(2, mediaType.NoGroupPropertyTypes.Count()); } + + [TestCase(Constants.Conventions.MediaTypes.File)] + [TestCase(Constants.Conventions.MediaTypes.Folder)] + [TestCase(Constants.Conventions.MediaTypes.Image)] + public async Task Cannot_Change_Alias_Of_System_Media_Type(string mediaTypeAlias) + { + var mediaType = MediaTypeService.Get(mediaTypeAlias); + Assert.IsNotNull(mediaType); + + var updateModel = MediaTypeUpdateModel(mediaTypeAlias, $"{mediaTypeAlias}_updated"); + var result = await MediaTypeEditingService.UpdateAsync(mediaType, updateModel, Constants.Security.SuperUserKey); + + Assert.Multiple(() => + { + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentTypeOperationStatus.NotAllowed, result.Status); + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs index 6c779a3c6e..8fa1a658c1 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs @@ -1,10 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; -using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -222,6 +220,34 @@ public class MediaTypeServiceTests : UmbracoIntegrationTest originalMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id); } + [TestCase(Constants.Conventions.MediaTypes.File)] + [TestCase(Constants.Conventions.MediaTypes.Folder)] + [TestCase(Constants.Conventions.MediaTypes.Image)] + public void Cannot_Delete_System_Media_Type(string mediaTypeAlias) + { + // Arrange + // Act + var mediaType = MediaTypeService.Get(mediaTypeAlias); + Assert.IsNotNull(mediaType); + + // Assert + Assert.Throws(() => MediaTypeService.Delete(mediaType)); + } + + [TestCase(Constants.Conventions.MediaTypes.File)] + [TestCase(Constants.Conventions.MediaTypes.Folder)] + [TestCase(Constants.Conventions.MediaTypes.Image)] + public void Cannot_Change_Alias_Of_System_Media_Type(string mediaTypeAlias) + { + // Arrange + // Act + var mediaType = MediaTypeService.Get(mediaTypeAlias); + Assert.IsNotNull(mediaType); + + // Assert + Assert.Throws(() => mediaType.Alias += "_updated"); + } + public class ContentNotificationHandler : INotificationHandler {