Skip to content

Commit

Permalink
Expose and manage system media types (#16343)
Browse files Browse the repository at this point in the history
* Expose and manage system media types

* Removed license :D
  • Loading branch information
kjac authored May 24, 2024
1 parent 8383532 commit 07711aa
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;

Expand Down Expand Up @@ -38,6 +39,7 @@ protected override MediaTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid?
if (mediaTypes.TryGetValue(entity.Id, out IMediaType? mediaType))
{
responseModel.Icon = mediaType.Icon ?? responseModel.Icon;
responseModel.IsDeletable = mediaType.IsSystemMediaType() is false;
}
return responseModel;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ private void Map(IMediaType source, MediaTypeResponseModel target, MapperContext
MediaType = referenceByIdModel,
CompositionType = compositionType,
});
target.IsDeletable = source.IsSystemMediaType() is false;
target.AliasCanBeChanged = source.IsSystemMediaType() is false;
}

// Umbraco.Code.MapAll
Expand Down
12 changes: 12 additions & 0 deletions src/Umbraco.Cms.Api.Management/OpenApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -38642,12 +38642,14 @@
"MediaTypeResponseModel": {
"required": [
"alias",
"aliasCanBeChanged",
"allowedAsRoot",
"allowedMediaTypes",
"compositions",
"containers",
"icon",
"id",
"isDeletable",
"isElement",
"name",
"properties",
Expand Down Expand Up @@ -38735,6 +38737,12 @@
}
]
}
},
"isDeletable": {
"type": "boolean"
},
"aliasCanBeChanged": {
"type": "boolean"
}
},
"additionalProperties": false
Expand Down Expand Up @@ -38765,6 +38773,7 @@
"hasChildren",
"icon",
"id",
"isDeletable",
"isFolder",
"name"
],
Expand Down Expand Up @@ -38793,6 +38802,9 @@
},
"icon": {
"type": "string"
},
"isDeletable": {
"type": "boolean"
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ public class MediaTypeResponseModel : ContentTypeResponseModelBase<MediaTypeProp
public IEnumerable<MediaTypeSort> AllowedMediaTypes { get; set; } = Enumerable.Empty<MediaTypeSort>();

public IEnumerable<MediaTypeComposition> Compositions { get; set; } = Enumerable.Empty<MediaTypeComposition>();

public bool IsDeletable { get; set; }

public bool AliasCanBeChanged { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
public class MediaTypeTreeItemResponseModel : FolderTreeItemResponseModel
{
public string Icon { get; set; } = string.Empty;

public bool IsDeletable { get; set; }
}
16 changes: 16 additions & 0 deletions src/Umbraco.Core/Models/MediaType.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;

namespace Umbraco.Cms.Core.Models;

Expand Down Expand Up @@ -45,6 +46,21 @@ public MediaType(IShortStringHelper shortStringHelper, IMediaType parent, string
/// <inheritdoc />
public override ISimpleContentType ToSimple() => new SimpleContentType(this);

/// <inheritdoc />
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;
}
}

/// <inheritdoc />
IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) =>
(IMediaType)DeepCloneWithResetIdentities(newAlias);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ public MediaTypeEditingService(

public async Task<Attempt<IMediaType?, ContentTypeOperationStatus>> UpdateAsync(IMediaType mediaType, MediaTypeUpdateModel model, Guid userKey)
{
if (mediaType.IsSystemMediaType() && mediaType.Alias != model.Alias)
{
return Attempt.FailWithStatus<IMediaType?, ContentTypeOperationStatus>(ContentTypeOperationStatus.NotAllowed, null);
}

Attempt<IMediaType?, ContentTypeOperationStatus> result = await ValidateAndMapForUpdateAsync(mediaType, model);
if (result.Success)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,11 @@ public async Task<ContentTypeOperationStatus> DeleteAsync(Guid key, Guid perform
return ContentTypeOperationStatus.NotFound;
}

if (CanDelete(item) is false)
{
return ContentTypeOperationStatus.NotAllowed;
}

Delete(item, performingUserId);

scope.Complete();
Expand All @@ -628,6 +633,11 @@ public async Task<ContentTypeOperationStatus> DeleteAsync(Guid key, Guid perform

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();
Expand Down Expand Up @@ -695,6 +705,10 @@ public void Delete(TItem item, int userId = Constants.Security.SuperUserId)
public void Delete(IEnumerable<TItem> 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())
{
Expand Down Expand Up @@ -750,6 +764,8 @@ public void Delete(IEnumerable<TItem> items, int userId = Constants.Security.Sup

protected abstract void DeleteItemsOfTypes(IEnumerable<int> typeIds);

protected virtual bool CanDelete(TItem item) => true;

#endregion

#region Copy
Expand Down
4 changes: 4 additions & 0 deletions src/Umbraco.Core/Services/MediaTypeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;

Expand Down Expand Up @@ -77,6 +78,9 @@ protected override void DeleteItemsOfTypes(IEnumerable<int> typeIds)
}
}

protected override bool CanDelete(IMediaType item)
=> item.IsSystemMediaType() is false;

#region Notifications

protected override SavingNotification<IMediaType> GetSavingNotification(
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -113,4 +114,22 @@ public async Task Can_Edit_Properties()

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);
});
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -222,6 +220,34 @@ public void Can_Copy_MediaType_To_New_Parent_By_Performing_Clone()
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<InvalidOperationException>(() => 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<InvalidOperationException>(() => mediaType.Alias += "_updated");
}

public class ContentNotificationHandler :
INotificationHandler<MediaMovedToRecycleBinNotification>
{
Expand Down

0 comments on commit 07711aa

Please sign in to comment.