diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/RecycleBin/DeleteDocumentRecycleBinController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/RecycleBin/DeleteDocumentRecycleBinController.cs index 17bfcd758f..9fff59c872 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/RecycleBin/DeleteDocumentRecycleBinController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/RecycleBin/DeleteDocumentRecycleBinController.cs @@ -44,7 +44,7 @@ public class DeleteDocumentRecycleBinController : DocumentRecycleBinControllerBa { AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( User, - ContentPermissionResource.WithKeys(ActionDelete.ActionLetter, id), + ContentPermissionResource.RecycleBin(ActionDelete.ActionLetter), AuthorizationPolicies.ContentPermissionByResource); if (!authorizationResult.Succeeded) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/DeleteMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/DeleteMediaController.cs new file mode 100644 index 0000000000..26e3b6f277 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/DeleteMediaController.cs @@ -0,0 +1,56 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Security.Authorization.Media; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.Media; + +[ApiVersion("1.0")] +public class DeleteMediaController : MediaControllerBase +{ + private readonly IAuthorizationService _authorizationService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IMediaEditingService _mediaEditingService; + + public DeleteMediaController( + IAuthorizationService authorizationService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMediaEditingService mediaEditingService) + { + _authorizationService = authorizationService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _mediaEditingService = mediaEditingService; + } + + [HttpDelete("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Delete(Guid id) + { + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + User, + MediaPermissionResource.RecycleBin(), + AuthorizationPolicies.MediaPermissionByResource); + + if (!authorizationResult.Succeeded) + { + return Forbidden(); + } + + Attempt result = await _mediaEditingService.DeleteAsync(id, CurrentUserKey(_backOfficeSecurityAccessor)); + + return result.Success + ? Ok() + : ContentEditingOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/RecycleBin/DeleteMediaRecycleBinController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/RecycleBin/DeleteMediaRecycleBinController.cs new file mode 100644 index 0000000000..31e1cdad7d --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/RecycleBin/DeleteMediaRecycleBinController.cs @@ -0,0 +1,60 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Security.Authorization.Media; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.Media.RecycleBin; + +[ApiVersion("1.0")] +public class DeleteMediaRecycleBinController : MediaRecycleBinControllerBase +{ + private readonly IAuthorizationService _authorizationService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IMediaEditingService _mediaEditingService; + + public DeleteMediaRecycleBinController( + IEntityService entityService, + IAuthorizationService authorizationService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMediaEditingService mediaEditingService, + IMediaPresentationFactory mediaPresentationFactory) + : base(entityService,mediaPresentationFactory) + { + _authorizationService = authorizationService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _mediaEditingService = mediaEditingService; + } + + [HttpDelete("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Delete(Guid id) + { + AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync( + User, + MediaPermissionResource.WithKeys(id), + AuthorizationPolicies.MediaPermissionByResource); + + if (!authorizationResult.Succeeded) + { + return Forbidden(); + } + + Attempt result = await _mediaEditingService.DeleteFromRecycleBinAsync(id, CurrentUserKey(_backOfficeSecurityAccessor)); + + return result.Success + ? Ok() + : ContentEditingOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/RecycleBin/MediaRecycleBinControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/RecycleBin/MediaRecycleBinControllerBase.cs index 8e56d8e3b6..d7971c2115 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/RecycleBin/MediaRecycleBinControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/RecycleBin/MediaRecycleBinControllerBase.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; @@ -8,7 +7,6 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Api.Management.Controllers.RecycleBin; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.Filters; -using Umbraco.Cms.Api.Management.ViewModels.RecycleBin; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Api.Management.ViewModels.Media.RecycleBin; using Umbraco.Cms.Web.Common.Authorization; diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs index 43e6a7ce47..e42f8ce4f0 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs @@ -73,40 +73,41 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase mediaCache = AppCaches.IsolatedCaches.Get(); - if (anythingChanged) + foreach (JsonPayload payload in payloads) + { + if (payload.ChangeTypes == TreeChangeTypes.Remove) + { + _idKeyMap.ClearCache(payload.Id); + } + + if (!mediaCache.Success) + { + continue; + } + + // repository cache + // it *was* done for each pathId but really that does not make sense + // only need to do it for the current media + mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Key)); + + // remove those that are in the branch + if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) + { + var pathid = "," + payload.Id + ","; + mediaCache.Result?.ClearOfType((_, v) => v.Path?.Contains(pathid) ?? false); + } + } + + _publishedSnapshotService.Notify(payloads, out var hasPublishedDataChanged); + // we only need to clear this if the published cache has changed + if (hasPublishedDataChanged) { AppCaches.ClearPartialViewCache(); - AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey); - - Attempt mediaCache = AppCaches.IsolatedCaches.Get(); - - foreach (JsonPayload payload in payloads) - { - if (payload.ChangeTypes == TreeChangeTypes.Remove) - { - _idKeyMap.ClearCache(payload.Id); - } - - if (!mediaCache.Success) - { - continue; - } - - // repository cache - // it *was* done for each pathId but really that does not make sense - // only need to do it for the current media - mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); - mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Key)); - - // remove those that are in the branch - if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) - { - var pathid = "," + payload.Id + ","; - mediaCache.Result?.ClearOfType((_, v) => v.Path?.Contains(pathid) ?? false); - } - } } base.Refresh(payloads); diff --git a/src/Umbraco.Core/Services/IMediaEditingService.cs b/src/Umbraco.Core/Services/IMediaEditingService.cs index 45761e575f..0d5756766a 100644 --- a/src/Umbraco.Core/Services/IMediaEditingService.cs +++ b/src/Umbraco.Core/Services/IMediaEditingService.cs @@ -24,4 +24,5 @@ public interface IMediaEditingService Task> MoveAsync(Guid key, Guid? parentKey, Guid userKey); Task SortAsync(Guid? parentKey, IEnumerable sortingModels, Guid userKey); + Task> DeleteFromRecycleBinAsync(Guid key, Guid userKey); } diff --git a/src/Umbraco.Core/Services/MediaEditingService.cs b/src/Umbraco.Core/Services/MediaEditingService.cs index 0646b2c86e..2bfe858e7c 100644 --- a/src/Umbraco.Core/Services/MediaEditingService.cs +++ b/src/Umbraco.Core/Services/MediaEditingService.cs @@ -84,7 +84,10 @@ internal sealed class MediaEditingService => await HandleMoveToRecycleBinAsync(key, userKey); public async Task> DeleteAsync(Guid key, Guid userKey) - => await HandleDeleteAsync(key, userKey); + => await HandleDeleteAsync(key, userKey,false); + + public async Task> DeleteFromRecycleBinAsync(Guid key, Guid userKey) + => await HandleDeleteAsync(key, userKey, true); public async Task> MoveAsync(Guid key, Guid? parentKey, Guid userKey) => await HandleMoveAsync(key, parentKey, userKey); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs index 9abc8a0a3c..76b1fde451 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -152,7 +152,10 @@ internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core v if (temporaryFileKey.HasValue) { file = TryGetTemporaryFile(temporaryFileKey.Value); - _temporaryFileService.EnlistDeleteIfScopeCompletes(temporaryFileKey.Value, _scopeProvider); + if (file is not null) + { + _temporaryFileService.EnlistDeleteIfScopeCompletes(temporaryFileKey.Value, _scopeProvider); + } } if (file == null) // not uploading a file @@ -166,6 +169,11 @@ internal class ImageCropperPropertyValueEditor : DataValueEditor // TODO: core v return null; // clear } + if (editorImageCropperValue is not null && temporaryFileKey.HasValue) + { + // a plausible tempFile value was supplied, but could not be converted to an actual file => clear the src + editorImageCropperValue.Src = null; + } return _jsonSerializer.Serialize(editorImageCropperValue); // unchanged }