using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PropertyEditors.ValueConverters; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Infrastructure.Extensions; namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers; /// /// Provides base class for notification handler that processes file uploads when a content entity is deleted, removing associated files. /// internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadNotificationHandlerBase, INotificationHandler, INotificationHandler, INotificationHandler, INotificationHandler { private readonly BlockEditorValues _blockListEditorValues; private readonly BlockEditorValues _blockGridEditorValues; /// /// Initializes a new instance of the class. /// public FileUploadContentDeletedNotificationHandler( IJsonSerializer jsonSerializer, MediaFileManager mediaFileManager, IBlockEditorElementTypeCache elementTypeCache, ILogger logger) : base(jsonSerializer, mediaFileManager, elementTypeCache) { _blockListEditorValues = new(new BlockListEditorDataConverter(jsonSerializer), elementTypeCache, logger); _blockGridEditorValues = new(new BlockGridEditorDataConverter(jsonSerializer), elementTypeCache, logger); } /// public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); /// public void Handle(ContentDeletedBlueprintNotification notification) => DeleteContainedFiles(notification.DeletedBlueprints); /// public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); /// public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); /// /// Deletes all file upload property files contained within a collection of content entities. /// /// private void DeleteContainedFiles(IEnumerable deletedEntities) { IReadOnlyList filePathsToDelete = ContainedFilePaths(deletedEntities); MediaFileManager.DeleteMediaFiles(filePathsToDelete); } /// /// Gets the paths to all file upload property files contained within a collection of content entities. /// private IReadOnlyList ContainedFilePaths(IEnumerable entities) { var paths = new List(); foreach (IProperty? property in entities.SelectMany(x => x.Properties)) { if (IsUploadFieldPropertyType(property.PropertyType)) { paths.AddRange(GetPathsFromUploadFieldProperty(property)); continue; } if (IsBlockListPropertyType(property.PropertyType)) { paths.AddRange(GetPathsFromBlockProperty(property, _blockListEditorValues)); continue; } if (IsBlockGridPropertyType(property.PropertyType)) { paths.AddRange(GetPathsFromBlockProperty(property, _blockGridEditorValues)); continue; } if (IsRichTextPropertyType(property.PropertyType)) { paths.AddRange(GetPathsFromRichTextProperty(property)); continue; } } return paths.Distinct().ToList().AsReadOnly(); } private IEnumerable GetPathsFromUploadFieldProperty(IProperty property) { foreach (IPropertyValue propertyValue in property.Values) { if (propertyValue.PublishedValue != null && propertyValue.PublishedValue is string publishedUrl && !string.IsNullOrWhiteSpace(publishedUrl)) { yield return MediaFileManager.FileSystem.GetRelativePath(publishedUrl); } if (propertyValue.EditedValue != null && propertyValue.EditedValue is string editedUrl && !string.IsNullOrWhiteSpace(editedUrl)) { yield return MediaFileManager.FileSystem.GetRelativePath(editedUrl); } } } private IReadOnlyCollection GetPathsFromBlockProperty(IProperty property, BlockEditorValues blockEditorValues) where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { var paths = new List(); foreach (IPropertyValue blockPropertyValue in property.Values) { paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.PublishedValue, blockEditorValues)?.BlockValue)); paths.AddRange(GetPathsFromBlockValue(GetBlockEditorData(blockPropertyValue.EditedValue, blockEditorValues)?.BlockValue)); } return paths; } private IReadOnlyCollection GetPathsFromBlockValue(BlockValue? blockValue) { var paths = new List(); if (blockValue is null) { return paths; } IEnumerable blockPropertyValues = blockValue.ContentData .Concat(blockValue.SettingsData) .SelectMany(x => x.Values); foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues) { if (blockPropertyValue.Value == null) { continue; } IPropertyType? propertyType = blockPropertyValue.PropertyType; if (propertyType == null) { continue; } if (IsUploadFieldPropertyType(propertyType)) { FileUploadValue? originalValue = FileUploadValueParser.Parse(blockPropertyValue.Value); if (string.IsNullOrWhiteSpace(originalValue?.Src)) { continue; } paths.Add(MediaFileManager.FileSystem.GetRelativePath(originalValue.Src)); continue; } if (IsBlockListPropertyType(propertyType)) { paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockListEditorValues)); continue; } if (IsBlockGridPropertyType(propertyType)) { paths.AddRange(GetPathsFromBlockPropertyValue(blockPropertyValue, _blockGridEditorValues)); continue; } if (IsRichTextPropertyType(propertyType)) { paths.AddRange(GetPathsFromRichTextPropertyValue(blockPropertyValue)); continue; } } return paths; } private IReadOnlyCollection GetPathsFromBlockPropertyValue(BlockPropertyValue blockItemDataValue, BlockEditorValues blockEditorValues) where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { BlockEditorData? blockItemEditorDataValue = GetBlockEditorData(blockItemDataValue.Value, blockEditorValues); return GetPathsFromBlockValue(blockItemEditorDataValue?.BlockValue); } private IReadOnlyCollection GetPathsFromRichTextProperty(IProperty property) { var paths = new List(); IPropertyValue? propertyValue = property.Values.FirstOrDefault(); if (propertyValue is null) { return paths; } paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.PublishedValue))); paths.AddRange(GetPathsFromBlockValue(GetRichTextBlockValue(propertyValue.EditedValue))); return paths; } private IReadOnlyCollection GetPathsFromRichTextPropertyValue(BlockPropertyValue blockItemDataValue) { RichTextEditorValue? richTextEditorValue = GetRichTextEditorValue(blockItemDataValue.Value); // Ensure the property type is populated on all blocks. richTextEditorValue?.EnsurePropertyTypePopulatedOnBlocks(ElementTypeCache); return GetPathsFromBlockValue(richTextEditorValue?.Blocks); } }