diff --git a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs
index 9219b89f23..897065a532 100644
--- a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs
+++ b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Notifications;
+
///
/// A notification that is used to trigger the IContentService when the SavedBlueprint method is called in the API.
///
@@ -14,8 +15,21 @@ public sealed class ContentSavedBlueprintNotification : ObjectNotification
/// Getting the saved blueprint object.
///
public IContent SavedBlueprint => Target;
+
+ ///
+ /// Getting the saved blueprint object.
+ ///
+ public IContent? CreatedFromContent { get; }
+
}
diff --git a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs
index 5e8357e453..d2e68ad327 100644
--- a/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs
+++ b/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs
@@ -122,7 +122,7 @@ internal sealed class ContentBlueprintEditingService
}
// Save blueprint
- await SaveAsync(blueprint, userKey);
+ await SaveAsync(blueprint, userKey, content);
return Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, new ContentCreateResult { Content = blueprint });
}
@@ -240,10 +240,10 @@ internal sealed class ContentBlueprintEditingService
protected override OperationResult? Delete(IContent content, int userId) => throw new NotImplementedException();
- private async Task SaveAsync(IContent blueprint, Guid userKey)
+ private async Task SaveAsync(IContent blueprint, Guid userKey, IContent? createdFromContent = null)
{
var currentUserId = await GetUserIdAsync(userKey);
- ContentService.SaveBlueprint(blueprint, currentUserId);
+ ContentService.SaveBlueprint(blueprint, createdFromContent, currentUserId);
}
private bool ValidateUniqueName(string name, IContent content)
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index 34ac8db8ff..0c3b61a1c3 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -3611,6 +3611,9 @@ public class ContentService : RepositoryService, IContentService
}
public void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId)
+ => SaveBlueprint(content, null, userId);
+
+ public void SaveBlueprint(IContent content, IContent? createdFromContent, int userId = Constants.Security.SuperUserId)
{
EventMessages evtMsgs = EventMessagesFactory.Get();
@@ -3631,7 +3634,7 @@ public class ContentService : RepositoryService, IContentService
Audit(AuditType.Save, userId, content.Id, $"Saved content template: {content.Name}");
- scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs));
+ scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, createdFromContent, evtMsgs));
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, evtMsgs));
scope.Complete();
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index 423f157874..a7bde2dc46 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -47,8 +47,17 @@ public interface IContentService : IContentServiceBase
///
/// Saves a blueprint.
///
+ [Obsolete("Please use the method taking all parameters. Scheduled for removal in Umbraco 18.")]
void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId);
+ ///
+ /// Saves a blueprint.
+ ///
+ void SaveBlueprint(IContent content, IContent? createdFromContent, int userId = Constants.Security.SuperUserId)
+#pragma warning disable CS0618 // Type or member is obsolete
+ => SaveBlueprint(content, userId);
+#pragma warning restore CS0618 // Type or member is obsolete
+
///
/// Deletes a blueprint.
///
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index d0c68d5aa5..66b3687cc2 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -356,11 +356,14 @@ public static partial class UmbracoBuilderExtensions
.AddNotificationHandler()
.AddNotificationHandler()
.AddNotificationHandler()
- .AddNotificationHandler()
+ .AddNotificationHandler()
+ .AddNotificationHandler()
+ .AddNotificationHandler()
.AddNotificationHandler()
- .AddNotificationHandler()
+ .AddNotificationHandler()
+ .AddNotificationHandler()
+ .AddNotificationHandler()
.AddNotificationHandler()
- .AddNotificationHandler()
.AddNotificationHandler()
.AddNotificationHandler()
.AddNotificationHandler()
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs
index d09783c061..2c48b1e93b 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MoveDocumentBlueprintsToFolders.cs
@@ -1,4 +1,4 @@
-using Umbraco.Cms.Core;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Services;
@@ -63,7 +63,7 @@ public class MoveDocumentBlueprintsToFolders : MigrationBase
}
blueprint.ParentId = container.Id;
- _contentService.SaveBlueprint(blueprint);
+ _contentService.SaveBlueprint(blueprint, null);
}
}
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs
index 22c7402b7b..241c817cec 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs
@@ -92,13 +92,10 @@ internal class FileUploadPropertyValueEditor : DataValueEditor
{
FileUploadValue? editorModelValue = _valueParser.Parse(editorValue.Value);
- // no change?
+ // No change or created from blueprint.
if (editorModelValue?.TemporaryFileId.HasValue is not true && string.IsNullOrEmpty(editorModelValue?.Src) is false)
{
- // since current value can be json string, we have to parse value
- FileUploadValue? currentModelValue = _valueParser.Parse(currentValue);
-
- return currentModelValue?.Src;
+ return editorModelValue.Src;
}
// the current editor value (if any) is the path to the file
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedOrScaffoldedNotificationHandler.cs
similarity index 67%
rename from src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs
rename to src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedOrScaffoldedNotificationHandler.cs
index b056d31c79..2222b65b3f 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedNotificationHandler.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentCopiedOrScaffoldedNotificationHandler.cs
@@ -14,24 +14,26 @@ using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers;
///
-/// Implements a notification handler that processes file uploads when content is copied, making sure the copied contetnt relates to a new instance
-/// of the file.
+/// Implements a notification handler that processes file uploads when content is copied or scaffolded from a blueprint, making
+/// sure the new content references a new instance of the file.
///
-internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNotificationHandlerBase, INotificationHandler
+internal sealed class FileUploadContentCopiedOrScaffoldedNotificationHandler : FileUploadNotificationHandlerBase,
+ INotificationHandler,
+ INotificationHandler,
+ INotificationHandler
{
private readonly IContentService _contentService;
-
private readonly BlockEditorValues _blockListEditorValues;
private readonly BlockEditorValues _blockGridEditorValues;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public FileUploadContentCopiedNotificationHandler(
+ public FileUploadContentCopiedOrScaffoldedNotificationHandler(
IJsonSerializer jsonSerializer,
MediaFileManager mediaFileManager,
IBlockEditorElementTypeCache elementTypeCache,
- ILogger logger,
+ ILogger logger,
IContentService contentService)
: base(jsonSerializer, mediaFileManager, elementTypeCache)
{
@@ -41,51 +43,66 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
}
///
- public void Handle(ContentCopiedNotification notification)
- {
- ArgumentNullException.ThrowIfNull(notification);
+ public void Handle(ContentCopiedNotification notification) => Handle(notification.Original, notification.Copy, (IContent c) => _contentService.Save(c));
+ ///
+ public void Handle(ContentScaffoldedNotification notification) => Handle(notification.Original, notification.Scaffold);
+
+ ///
+ public void Handle(ContentSavedBlueprintNotification notification)
+ {
+ if (notification.CreatedFromContent is null)
+ {
+ // If there is no original content, we don't need to copy files.
+ return;
+ }
+
+ Handle(notification.CreatedFromContent, notification.SavedBlueprint, (IContent c) => _contentService.SaveBlueprint(c, null));
+ }
+
+ private void Handle(IContent source, IContent destination, Action? postUpdateAction = null)
+ {
var isUpdated = false;
- foreach (IProperty property in notification.Original.Properties)
+ foreach (IProperty property in source.Properties)
{
if (IsUploadFieldPropertyType(property.PropertyType))
{
- isUpdated |= UpdateUploadFieldProperty(notification, property);
+ isUpdated |= UpdateUploadFieldProperty(destination, property);
continue;
}
if (IsBlockListPropertyType(property.PropertyType))
{
- isUpdated |= UpdateBlockProperty(notification, property, _blockListEditorValues);
+ isUpdated |= UpdateBlockProperty(destination, property, _blockListEditorValues);
continue;
}
if (IsBlockGridPropertyType(property.PropertyType))
{
- isUpdated |= UpdateBlockProperty(notification, property, _blockGridEditorValues);
+ isUpdated |= UpdateBlockProperty(destination, property, _blockGridEditorValues);
continue;
}
if (IsRichTextPropertyType(property.PropertyType))
{
- isUpdated |= UpdateRichTextProperty(notification, property);
+ isUpdated |= UpdateRichTextProperty(destination, property);
continue;
}
}
- // if updated, re-save the copy with the updated value
- if (isUpdated)
+ // If updated, re-save the destination with the updated value.
+ if (isUpdated && postUpdateAction is not null)
{
- _contentService.Save(notification.Copy);
+ postUpdateAction(destination);
}
}
- private bool UpdateUploadFieldProperty(ContentCopiedNotification notification, IProperty property)
+ private bool UpdateUploadFieldProperty(IContent content, IProperty property)
{
var isUpdated = false;
@@ -98,9 +115,9 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
continue;
}
- var copyUrl = CopyFile(sourceUrl, notification.Copy, property.PropertyType);
+ var copyUrl = CopyFile(sourceUrl, content, property.PropertyType);
- notification.Copy.SetValue(property.Alias, copyUrl, propertyValue.Culture, propertyValue.Segment);
+ content.SetValue(property.Alias, copyUrl, propertyValue.Culture, propertyValue.Segment);
isUpdated = true;
}
@@ -108,7 +125,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
return isUpdated;
}
- private bool UpdateBlockProperty(ContentCopiedNotification notification, IProperty property, BlockEditorValues blockEditorValues)
+ private bool UpdateBlockProperty(IContent content, IProperty property, BlockEditorValues blockEditorValues)
where TValue : BlockValue, new()
where TLayout : class, IBlockLayoutItem, new()
{
@@ -120,11 +137,11 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
BlockEditorData? blockEditorData = GetBlockEditorData(rawBlockPropertyValue, blockEditorValues);
- (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(notification, blockEditorData);
+ (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(content, blockEditorData);
if (hasUpdates)
{
- notification.Copy.SetValue(property.Alias, updatedValue, blockPropertyValue.Culture, blockPropertyValue.Segment);
+ content.SetValue(property.Alias, updatedValue, blockPropertyValue.Culture, blockPropertyValue.Segment);
}
isUpdated |= hasUpdates;
@@ -133,7 +150,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
return isUpdated;
}
- private (bool, string?) UpdateBlockEditorData(ContentCopiedNotification notification, BlockEditorData? blockEditorData)
+ private (bool, string?) UpdateBlockEditorData(IContent content, BlockEditorData? blockEditorData)
where TValue : BlockValue, new()
where TLayout : class, IBlockLayoutItem, new()
{
@@ -148,14 +165,14 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
.Concat(blockEditorData.BlockValue.SettingsData)
.SelectMany(x => x.Values);
- isUpdated = UpdateBlockPropertyValues(notification, isUpdated, blockPropertyValues);
+ isUpdated = UpdateBlockPropertyValues(content, isUpdated, blockPropertyValues);
var updatedValue = JsonSerializer.Serialize(blockEditorData.BlockValue);
return (isUpdated, updatedValue);
}
- private bool UpdateRichTextProperty(ContentCopiedNotification notification, IProperty property)
+ private bool UpdateRichTextProperty(IContent content, IProperty property)
{
var isUpdated = false;
@@ -165,7 +182,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
RichTextBlockValue? richTextBlockValue = GetRichTextBlockValue(rawBlockPropertyValue);
- (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(notification, richTextBlockValue);
+ (bool hasUpdates, string? updatedValue) = UpdateBlockEditorData(content, richTextBlockValue);
if (hasUpdates && string.IsNullOrEmpty(updatedValue) is false)
{
@@ -173,7 +190,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
if (richTextEditorValue is not null)
{
richTextEditorValue.Blocks = JsonSerializer.Deserialize(updatedValue);
- notification.Copy.SetValue(property.Alias, JsonSerializer.Serialize(richTextEditorValue), blockPropertyValue.Culture, blockPropertyValue.Segment);
+ content.SetValue(property.Alias, JsonSerializer.Serialize(richTextEditorValue), blockPropertyValue.Culture, blockPropertyValue.Segment);
}
}
@@ -183,7 +200,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
return isUpdated;
}
- private (bool, string?) UpdateBlockEditorData(ContentCopiedNotification notification, RichTextBlockValue? richTextBlockValue)
+ private (bool, string?) UpdateBlockEditorData(IContent content, RichTextBlockValue? richTextBlockValue)
{
var isUpdated = false;
@@ -196,14 +213,14 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
.Concat(richTextBlockValue.SettingsData)
.SelectMany(x => x.Values);
- isUpdated = UpdateBlockPropertyValues(notification, isUpdated, blockPropertyValues);
+ isUpdated = UpdateBlockPropertyValues(content, isUpdated, blockPropertyValues);
var updatedValue = JsonSerializer.Serialize(richTextBlockValue);
return (isUpdated, updatedValue);
}
- private bool UpdateBlockPropertyValues(ContentCopiedNotification notification, bool isUpdated, IEnumerable blockPropertyValues)
+ private bool UpdateBlockPropertyValues(IContent content, bool isUpdated, IEnumerable blockPropertyValues)
{
foreach (BlockPropertyValue blockPropertyValue in blockPropertyValues)
{
@@ -221,14 +238,14 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
if (IsUploadFieldPropertyType(propertyType))
{
- isUpdated |= UpdateUploadFieldBlockPropertyValue(blockPropertyValue, notification, propertyType);
+ isUpdated |= UpdateUploadFieldBlockPropertyValue(blockPropertyValue, content, propertyType);
continue;
}
if (IsBlockListPropertyType(propertyType))
{
- (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, notification, _blockListEditorValues);
+ (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, content, _blockListEditorValues);
isUpdated |= hasUpdates;
@@ -239,7 +256,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
if (IsBlockGridPropertyType(propertyType))
{
- (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, notification, _blockGridEditorValues);
+ (bool hasUpdates, string? newValue) = UpdateBlockPropertyValue(blockPropertyValue, content, _blockGridEditorValues);
isUpdated |= hasUpdates;
@@ -250,7 +267,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
if (IsRichTextPropertyType(propertyType))
{
- (bool hasUpdates, string? newValue) = UpdateRichTextPropertyValue(blockPropertyValue, notification);
+ (bool hasUpdates, string? newValue) = UpdateRichTextPropertyValue(blockPropertyValue, content);
if (hasUpdates && string.IsNullOrEmpty(newValue) is false)
{
@@ -271,7 +288,7 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
return isUpdated;
}
- private bool UpdateUploadFieldBlockPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification, IPropertyType propertyType)
+ private bool UpdateUploadFieldBlockPropertyValue(BlockPropertyValue blockItemDataValue, IContent content, IPropertyType propertyType)
{
FileUploadValue? fileUploadValue = FileUploadValueParser.Parse(blockItemDataValue.Value);
@@ -281,26 +298,26 @@ internal sealed class FileUploadContentCopiedNotificationHandler : FileUploadNot
return false;
}
- var copyFileUrl = CopyFile(fileUploadValue.Src, notification.Copy, propertyType);
+ var copyFileUrl = CopyFile(fileUploadValue.Src, content, propertyType);
blockItemDataValue.Value = copyFileUrl;
return true;
}
- private (bool, string?) UpdateBlockPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification, BlockEditorValues blockEditorValues)
+ private (bool, string?) UpdateBlockPropertyValue(BlockPropertyValue blockItemDataValue, IContent content, BlockEditorValues blockEditorValues)
where TValue : BlockValue, new()
where TLayout : class, IBlockLayoutItem, new()
{
BlockEditorData? blockItemEditorDataValue = GetBlockEditorData(blockItemDataValue.Value, blockEditorValues);
- return UpdateBlockEditorData(notification, blockItemEditorDataValue);
+ return UpdateBlockEditorData(content, blockItemEditorDataValue);
}
- private (bool, string?) UpdateRichTextPropertyValue(BlockPropertyValue blockItemDataValue, ContentCopiedNotification notification)
+ private (bool, string?) UpdateRichTextPropertyValue(BlockPropertyValue blockItemDataValue, IContent content)
{
RichTextBlockValue? richTextBlockValue = GetRichTextBlockValue(blockItemDataValue.Value);
- return UpdateBlockEditorData(notification, richTextBlockValue);
+ return UpdateBlockEditorData(content, richTextBlockValue);
}
private string CopyFile(string sourceUrl, IContent destinationContent, IPropertyType propertyType)
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs
index 681c31cc58..4203209b58 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadContentDeletedNotificationHandler.cs
@@ -1,17 +1,30 @@
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;
///
-/// Implements a notification handler that processes file uploads when content is deleted, removing associated files.
+/// Provides base class for notification handler that processes file uploads when a content entity is deleted, removing associated files.
///
-internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler
+internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadNotificationHandlerBase,
+ INotificationHandler,
+ INotificationHandler,
+ INotificationHandler,
+ INotificationHandler
{
+ private readonly BlockEditorValues _blockListEditorValues;
+ private readonly BlockEditorValues _blockGridEditorValues;
+
///
/// Initializes a new instance of the class.
///
@@ -20,10 +33,204 @@ internal sealed class FileUploadContentDeletedNotificationHandler : FileUploadEn
MediaFileManager mediaFileManager,
IBlockEditorElementTypeCache elementTypeCache,
ILogger logger)
- : base(jsonSerializer, mediaFileManager, elementTypeCache, 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);
+ }
}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs
deleted file mode 100644
index 40877d2fef..0000000000
--- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadEntityDeletedNotificationHandlerBase.cs
+++ /dev/null
@@ -1,218 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core;
-using Umbraco.Cms.Core.Cache.PropertyEditors;
-using Umbraco.Cms.Core.IO;
-using Umbraco.Cms.Core.Models;
-using Umbraco.Cms.Core.Models.Blocks;
-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 abstract class FileUploadEntityDeletedNotificationHandlerBase : FileUploadNotificationHandlerBase
-{
- private readonly BlockEditorValues _blockListEditorValues;
- private readonly BlockEditorValues _blockGridEditorValues;
-
- ///
- /// Initializes a new instance of the class.
- ///
- protected FileUploadEntityDeletedNotificationHandlerBase(
- 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);
- }
-
- ///
- /// Deletes all file upload property files contained within a collection of content entities.
- ///
- ///
- protected 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);
- }
-}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs
deleted file mode 100644
index 3a07193ec8..0000000000
--- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMediaDeletedNotificationHandler.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core.Cache.PropertyEditors;
-using Umbraco.Cms.Core.Events;
-using Umbraco.Cms.Core.IO;
-using Umbraco.Cms.Core.Notifications;
-using Umbraco.Cms.Core.Serialization;
-
-namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers;
-
-///
-/// Implements a notification handler that processes file uploads when media is deleted, removing associated files.
-///
-internal sealed class FileUploadMediaDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler
-{
- ///
- /// Initializes a new instance of the class.
- ///
- public FileUploadMediaDeletedNotificationHandler(
- IJsonSerializer jsonSerializer,
- MediaFileManager mediaFileManager,
- IBlockEditorElementTypeCache elementTypeCache,
- ILogger logger)
- : base(jsonSerializer, mediaFileManager, elementTypeCache, logger)
- {
- }
-
- ///
- public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities);
-}
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs
deleted file mode 100644
index 91433b88b9..0000000000
--- a/src/Umbraco.Infrastructure/PropertyEditors/NotificationHandlers/FileUploadMemberDeletedNotificationHandler.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core.Cache.PropertyEditors;
-using Umbraco.Cms.Core.Events;
-using Umbraco.Cms.Core.IO;
-using Umbraco.Cms.Core.Notifications;
-using Umbraco.Cms.Core.Serialization;
-
-namespace Umbraco.Cms.Infrastructure.PropertyEditors.NotificationHandlers;
-
-///
-/// Implements a notification handler that processes file uploads when a member is deleted, removing associated files.
-///
-internal sealed class FileUploadMemberDeletedNotificationHandler : FileUploadEntityDeletedNotificationHandlerBase, INotificationHandler
-{
- ///
- /// Initializes a new instance of the class.
- ///
- public FileUploadMemberDeletedNotificationHandler(
- IJsonSerializer jsonSerializer,
- MediaFileManager mediaFileManager,
- IBlockEditorElementTypeCache elementTypeCache,
- ILogger logger)
- : base(jsonSerializer, mediaFileManager, elementTypeCache, logger)
- {
- }
-
- ///
- public void Handle(MemberDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities);
-}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs
index 2c1ea1e278..3d04a9fa6c 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentServiceTests.cs
@@ -96,7 +96,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
blueprint.SetValue("keywords", "blueprint 3");
blueprint.SetValue("description", "blueprint 4");
- ContentService.SaveBlueprint(blueprint);
+ ContentService.SaveBlueprint(blueprint, null);
var found = ContentService.GetBlueprintsForContentTypes().ToArray();
Assert.AreEqual(1, found.Length);
@@ -121,7 +121,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
blueprint.SetValue("keywords", "blueprint 3");
blueprint.SetValue("description", "blueprint 4");
- ContentService.SaveBlueprint(blueprint);
+ ContentService.SaveBlueprint(blueprint, null);
ContentService.DeleteBlueprint(blueprint);
@@ -148,7 +148,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
ContentService.Save(originalPage);
var fromContent = ContentService.CreateBlueprintFromContent(originalPage, "hello world");
- ContentService.SaveBlueprint(fromContent);
+ ContentService.SaveBlueprint(fromContent, originalPage);
Assert.IsTrue(fromContent.HasIdentity);
Assert.AreEqual("blueprint 1", fromContent.Properties["title"]?.GetValue());
@@ -176,7 +176,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
{
var blueprint =
ContentBuilder.CreateTextpageContent(i % 2 == 0 ? ct1 : ct2, "hello" + i, Constants.System.Root);
- ContentService.SaveBlueprint(blueprint);
+ ContentService.SaveBlueprint(blueprint, null);
}
var found = ContentService.GetBlueprintsForContentTypes().ToArray();
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs
index c5d6548677..8cce10b716 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/TelemetryProviderTests.cs
@@ -116,7 +116,7 @@ internal sealed class TelemetryProviderTests : UmbracoIntegrationTest
blueprint.SetValue("keywords", "blueprint 3");
blueprint.SetValue("description", "blueprint 4");
- ContentService.SaveBlueprint(blueprint);
+ ContentService.SaveBlueprint(blueprint, null);
var fromBlueprint = await ContentBlueprintEditingService.GetScaffoldedAsync(blueprint.Key);
Assert.IsNotNull(fromBlueprint);