From ab2b35904f68c8d8764cc6751851d43d8e07ed89 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 1 Mar 2021 17:05:59 +0100 Subject: [PATCH] Remove a bunch more events from ContentService --- .../Cache/DistributedCacheBinder_Handlers.cs | 2 - .../Compose/BlockEditorComposer.cs | 10 - .../Compose/NestedContentPropertyComposer.cs | 10 - .../Compose/NotificationsComponent.cs | 263 +-------------- .../Compose/NotificationsHandler.cs | 217 ++++++++++++ .../Compose/RelateOnCopyComponent.cs | 59 ---- .../Compose/RelateOnCopyComposer.cs | 7 - .../Compose/RelateOnTrashComponent.cs | 98 +++--- .../BlockEditorPropertyHandler.cs} | 26 +- ...omplexPropertyEditorContentEventHandler.cs | 145 -------- ...ropertyEditorContentNotificationHandler.cs | 55 +++ .../FileUploadPropertyEditor.cs | 76 +++-- .../ImageCropperPropertyEditor.cs | 33 +- .../NestedContentPropertyHandler.cs} | 25 +- .../PropertyEditorsComponent.cs | 14 +- .../Routing/RedirectTrackingComposer.cs | 15 - ...omponent.cs => RedirectTrackingHandler.cs} | 106 +++--- .../Services/CancelableNotification.cs | 269 ++++++++++++++- .../Services/Implement/ContentService.cs | 314 +++++++----------- 19 files changed, 849 insertions(+), 895 deletions(-) delete mode 100644 src/Umbraco.Infrastructure/Compose/BlockEditorComposer.cs delete mode 100644 src/Umbraco.Infrastructure/Compose/NestedContentPropertyComposer.cs create mode 100644 src/Umbraco.Infrastructure/Compose/NotificationsHandler.cs delete mode 100644 src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs delete mode 100644 src/Umbraco.Infrastructure/Compose/RelateOnCopyComposer.cs rename src/Umbraco.Infrastructure/{Compose/BlockEditorComponent.cs => PropertyEditors/BlockEditorPropertyHandler.cs} (92%) delete mode 100644 src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs create mode 100644 src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs rename src/Umbraco.Infrastructure/{Compose/NestedContentPropertyComponent.cs => PropertyEditors/NestedContentPropertyHandler.cs} (78%) delete mode 100644 src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs rename src/Umbraco.Infrastructure/Routing/{RedirectTrackingComponent.cs => RedirectTrackingHandler.cs} (58%) diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 43dc1d0fcd..eaa3c0df86 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs @@ -126,8 +126,6 @@ namespace Umbraco.Cms.Core.Cache () => MediaService.TreeChanged -= MediaService_TreeChanged); // bind to content events - Bind(() => ContentService.Copied += ContentService_Copied, // needed for permissions - () => ContentService.Copied -= ContentService_Copied); Bind(() => ContentService.TreeChanged += ContentService_TreeChanged,// handles all content changes () => ContentService.TreeChanged -= ContentService_TreeChanged); diff --git a/src/Umbraco.Infrastructure/Compose/BlockEditorComposer.cs b/src/Umbraco.Infrastructure/Compose/BlockEditorComposer.cs deleted file mode 100644 index bcc70e1748..0000000000 --- a/src/Umbraco.Infrastructure/Compose/BlockEditorComposer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - /// - /// A composer for Block editors to run a component - /// - public class BlockEditorComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComposer.cs b/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComposer.cs deleted file mode 100644 index c8cddd6d08..0000000000 --- a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComposer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - /// - /// A composer for nested content to run a component - /// - public class NestedContentPropertyComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs index 92e7d6c4d7..af65a654b7 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs @@ -13,167 +13,15 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; -using Umbraco.Cms.Infrastructure.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { - public sealed class NotificationsHandler : - INotificationHandler>, - INotificationHandler> - { - private readonly Notifier _notifier; - private readonly ActionCollection _actions; - private readonly IContentService _contentService; - - public void Handle(SavedNotification notification) - { - var newEntities = new List(); - var updatedEntities = new List(); - - //need to determine if this is updating or if it is new - foreach (var entity in notification.SavedEntities) - { - var dirty = (IRememberBeingDirty)entity; - if (dirty.WasPropertyDirty("Id")) - { - //it's new - newEntities.Add(entity); - } - else - { - //it's updating - updatedEntities.Add(entity); - } - } - _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); - _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); - } - - public void Handle(SortedNotification notification) - { - var parentId = notification.SortedEntities.Select(x => x.ParentId).Distinct().ToList(); - if (parentId.Count != 1) - return; // this shouldn't happen, for sorting all entities will have the same parent id - - // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. - // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. - if (parentId[0] <= 0) - return; - - var parent = _contentService.GetById(parentId[0]); - if (parent == null) - return; // this shouldn't happen - - _notifier.Notify(_actions.GetAction(), new[] { parent }); - } - - /// - /// This class is used to send the notifications - /// - public sealed class Notifier - { - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly INotificationService _notificationService; - private readonly IUserService _userService; - private readonly ILocalizedTextService _textService; - private readonly GlobalSettings _globalSettings; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - public Notifier( - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IHostingEnvironment hostingEnvironment, - INotificationService notificationService, - IUserService userService, - ILocalizedTextService textService, - IOptions globalSettings, - ILogger logger) - { - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _hostingEnvironment = hostingEnvironment; - _notificationService = notificationService; - _userService = userService; - _textService = textService; - _globalSettings = globalSettings.Value; - _logger = logger; - } - - public void Notify(IAction action, params IContent[] entities) - { - var user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - - //if there is no current user, then use the admin - if (user == null) - { - _logger.LogDebug("There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = _userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - _logger.LogWarning("Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } - - SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); - } - - private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) - { - if (sender == null) - throw new ArgumentNullException(nameof(sender)); - if (siteUri == null) - { - _logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)"); - return; - } - - //group by the content type variation since the emails will be different - foreach (var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) - { - _notificationService.SendNotifications( - sender, - contentVariantGroup, - action.Letter.ToString(CultureInfo.InvariantCulture), - _textService.Localize("actions", action.Alias), - siteUri, - ((IUser user, NotificationEmailSubjectParams subject) x) - => _textService.Localize( - "notifications/mailSubject", - x.user.GetUserCulture(_textService, _globalSettings), - new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), - ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) - => _textService.Localize( - x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", - x.user.GetUserCulture(_textService, _globalSettings), - new[] - { - x.body.RecipientName, - x.body.Action, - x.body.ItemName, - x.body.EditedUser, - x.body.SiteUrl, - x.body.ItemId, - //format the summary depending on if it's variant or not - contentVariantGroup.Key == ContentVariation.Culture - ? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary })) - : x.body.Summary, - x.body.ItemUrl - })); - } - } - - } - } - + // TODO: this component must be removed entirely - there is some code duplication in NotificationsHandler in anticipation of this component being deleted public sealed class NotificationsComponent : IComponent { private readonly Notifier _notifier; @@ -189,24 +37,6 @@ namespace Umbraco.Cms.Core.Compose public void Initialize() { - //Send notifications for the send to publish action - ContentService.SentToPublish += ContentService_SentToPublish; - //Send notifications for the published action - ContentService.Published += ContentService_Published; - ////Send notifications for the saved action - //ContentService.Sorted += ContentService_Sorted; - ////Send notifications for the update and created actions - //ContentService.Saved += ContentService_Saved; - //Send notifications for the unpublish action - ContentService.Unpublished += ContentService_Unpublished; - //Send notifications for the move/move to recycle bin and restore actions - ContentService.Moved += ContentService_Moved; - //Send notifications for the delete action when content is moved to the recycle bin - ContentService.Trashed += ContentService_Trashed; - //Send notifications for the copy action - ContentService.Copied += ContentService_Copied; - //Send notifications for the rollback action - ContentService.RolledBack += ContentService_RolledBack; //Send notifications for the public access changed action PublicAccessService.Saved += PublicAccessService_Saved; @@ -215,15 +45,6 @@ namespace Umbraco.Cms.Core.Compose public void Terminate() { - ContentService.SentToPublish -= ContentService_SentToPublish; - ContentService.Published -= ContentService_Published; - //ContentService.Sorted -= ContentService_Sorted; - //ContentService.Saved -= ContentService_Saved; - ContentService.Unpublished -= ContentService_Unpublished; - ContentService.Moved -= ContentService_Moved; - ContentService.Trashed -= ContentService_Trashed; - ContentService.Copied -= ContentService_Copied; - ContentService.RolledBack -= ContentService_RolledBack; PublicAccessService.Saved -= PublicAccessService_Saved; UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned; } @@ -234,72 +55,6 @@ namespace Umbraco.Cms.Core.Compose private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs args) => PublicAccessServiceSaved(args, _contentService); - private void ContentService_RolledBack(IContentService sender, RollbackEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.Entity); - - private void ContentService_Copied(IContentService sender, CopyEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.Original); - - private void ContentService_Trashed(IContentService sender, MoveEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); - - private void ContentService_Moved(IContentService sender, MoveEventArgs args) - => ContentServiceMoved(args); - - private void ContentService_Unpublished(IContentService sender, PublishEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - - private void ContentService_Saved(IContentService sender, ContentSavedEventArgs args) - => ContentServiceSaved(args); - - private void ContentService_Sorted(IContentService sender, SaveEventArgs args) - => ContentServiceSorted(sender, args); - - private void ContentService_Published(IContentService sender, ContentPublishedEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - - private void ContentService_SentToPublish(IContentService sender, SendToPublishEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.Entity); - - private void ContentServiceSorted(IContentService sender, SaveEventArgs args) - { - var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); - if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id - - // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. - // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. - if (parentId[0] <= 0) return; - - var parent = sender.GetById(parentId[0]); - if (parent == null) return; // this shouldn't happen - - _notifier.Notify(_actions.GetAction(), new[] { parent }); - } - - private void ContentServiceSaved(SaveEventArgs args) - { - var newEntities = new List(); - var updatedEntities = new List(); - - //need to determine if this is updating or if it is new - foreach (var entity in args.SavedEntities) - { - var dirty = (IRememberBeingDirty)entity; - if (dirty.WasPropertyDirty("Id")) - { - //it's new - newEntities.Add(entity); - } - else - { - //it's updating - updatedEntities.Add(entity); - } - } - _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); - _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); - } - private void UserServiceUserGroupPermissionsAssigned(SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray(); @@ -310,22 +65,6 @@ namespace Umbraco.Cms.Core.Compose _notifier.Notify(_actions.GetAction(), entities); } - private void ContentServiceMoved(MoveEventArgs args) - { - // notify about the move for all moved items - _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); - - // for any items being moved from the recycle bin (restored), explicitly notify about that too - var restoredEntities = args.MoveInfoCollection - .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString)) - .Select(m => m.Entity) - .ToArray(); - if (restoredEntities.Any()) - { - _notifier.Notify(_actions.GetAction(), restoredEntities); - } - } - private void PublicAccessServiceSaved(SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsHandler.cs b/src/Umbraco.Infrastructure/Compose/NotificationsHandler.cs new file mode 100644 index 0000000000..34c855286f --- /dev/null +++ b/src/Umbraco.Infrastructure/Compose/NotificationsHandler.cs @@ -0,0 +1,217 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Actions; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Compose +{ + // TODO: insert these notification handlers in core composition + public sealed class NotificationsHandler : + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler> + { + private readonly Notifier _notifier; + private readonly ActionCollection _actions; + private readonly IContentService _contentService; + + public NotificationsHandler(Notifier notifier, ActionCollection actions, IContentService contentService) + { + _notifier = notifier; + _actions = actions; + _contentService = contentService; + } + + public void Handle(SavedNotification notification) + { + var newEntities = new List(); + var updatedEntities = new List(); + + //need to determine if this is updating or if it is new + foreach (var entity in notification.SavedEntities) + { + var dirty = (IRememberBeingDirty)entity; + if (dirty.WasPropertyDirty("Id")) + { + //it's new + newEntities.Add(entity); + } + else + { + //it's updating + updatedEntities.Add(entity); + } + } + _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); + } + + public void Handle(SortedNotification notification) + { + var parentId = notification.SortedEntities.Select(x => x.ParentId).Distinct().ToList(); + if (parentId.Count != 1) + return; // this shouldn't happen, for sorting all entities will have the same parent id + + // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. + // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. + if (parentId[0] <= 0) + return; + + var parent = _contentService.GetById(parentId[0]); + if (parent == null) + return; // this shouldn't happen + + _notifier.Notify(_actions.GetAction(), new[] { parent }); + } + + public void Handle(PublishedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.PublishedEntities.ToArray()); + + public void Handle(MovedNotification notification) + { + // notify about the move for all moved items + _notifier.Notify(_actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + // for any items being moved from the recycle bin (restored), explicitly notify about that too + var restoredEntities = notification.MoveInfoCollection + .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString)) + .Select(m => m.Entity) + .ToArray(); + if (restoredEntities.Any()) + { + _notifier.Notify(_actions.GetAction(), restoredEntities); + } + } + + public void Handle(TrashedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + public void Handle(CopiedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Original); + + public void Handle(RolledBackNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + + public void Handle(SentToPublishNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + + public void Handle(UnpublishedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.UnpublishedEntities.ToArray()); + + /// + /// This class is used to send the notifications + /// + public sealed class Notifier + { + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly INotificationService _notificationService; + private readonly IUserService _userService; + private readonly ILocalizedTextService _textService; + private readonly GlobalSettings _globalSettings; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public Notifier( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IHostingEnvironment hostingEnvironment, + INotificationService notificationService, + IUserService userService, + ILocalizedTextService textService, + IOptions globalSettings, + ILogger logger) + { + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _hostingEnvironment = hostingEnvironment; + _notificationService = notificationService; + _userService = userService; + _textService = textService; + _globalSettings = globalSettings.Value; + _logger = logger; + } + + public void Notify(IAction action, params IContent[] entities) + { + var user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; + + //if there is no current user, then use the admin + if (user == null) + { + _logger.LogDebug("There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = _userService.GetUserById(Constants.Security.SuperUserId); + if (user == null) + { + _logger.LogWarning("Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); + return; + } + } + + SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); + } + + private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) + { + if (sender == null) + throw new ArgumentNullException(nameof(sender)); + if (siteUri == null) + { + _logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)"); + return; + } + + //group by the content type variation since the emails will be different + foreach (var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) + { + _notificationService.SendNotifications( + sender, + contentVariantGroup, + action.Letter.ToString(CultureInfo.InvariantCulture), + _textService.Localize("actions", action.Alias), + siteUri, + ((IUser user, NotificationEmailSubjectParams subject) x) + => _textService.Localize( + "notifications/mailSubject", + x.user.GetUserCulture(_textService, _globalSettings), + new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), + ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) + => _textService.Localize( + x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", + x.user.GetUserCulture(_textService, _globalSettings), + new[] + { + x.body.RecipientName, + x.body.Action, + x.body.ItemName, + x.body.EditedUser, + x.body.SiteUrl, + x.body.ItemId, + //format the summary depending on if it's variant or not + contentVariantGroup.Key == ContentVariation.Culture + ? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary })) + : x.body.Summary, + x.body.ItemUrl + })); + } + } + + } + } +} diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs deleted file mode 100644 index c24e7614e3..0000000000 --- a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; - -namespace Umbraco.Cms.Core.Compose -{ - // TODO: This should just exist in the content service/repo! - public sealed class RelateOnCopyComponent : IComponent - { - private readonly IRelationService _relationService; - private readonly IAuditService _auditService; - - public RelateOnCopyComponent(IRelationService relationService, IAuditService auditService) - { - _relationService = relationService; - _auditService = auditService; - } - - public void Initialize() - { - ContentService.Copied += ContentServiceCopied; - } - - public void Terminate() - { - ContentService.Copied -= ContentServiceCopied; - } - - private void ContentServiceCopied(IContentService sender, CopyEventArgs e) - { - if (e.RelateToOriginal == false) return; - - - var relationType = _relationService.GetRelationTypeByAlias(Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); - - if (relationType == null) - { - relationType = new RelationType(Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, - Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, - true, - Cms.Core.Constants.ObjectTypes.Document, - Cms.Core.Constants.ObjectTypes.Document); - - _relationService.Save(relationType); - } - - var relation = new Relation(e.Original.Id, e.Copy.Id, relationType); - _relationService.Save(relation); - - _auditService.Add( - AuditType.Copy, - e.Copy.WriterId, - e.Copy.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), - $"Copied content with Id: '{e.Copy.Id}' related to original content with Id: '{e.Original.Id}'"); - } - } -} diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComposer.cs b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComposer.cs deleted file mode 100644 index ad2a3db78d..0000000000 --- a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - public sealed class RelateOnCopyComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs index de00961357..cdba2d49bc 100644 --- a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs @@ -1,15 +1,20 @@ -using System.Linq; +using System.Linq; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { - public sealed class RelateOnTrashComponent : IComponent + // TODO: insert these notification handlers in core composition + // TODO: lots of duplicate code in this one, refactor + public sealed class RelateOnTrashHandler : + INotificationHandler>, + INotificationHandler> { private readonly IRelationService _relationService; private readonly IEntityService _entityService; @@ -17,7 +22,7 @@ namespace Umbraco.Cms.Core.Compose private readonly IAuditService _auditService; private readonly IScopeProvider _scopeProvider; - public RelateOnTrashComponent( + public RelateOnTrashHandler( IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, @@ -31,27 +36,10 @@ namespace Umbraco.Cms.Core.Compose _scopeProvider = scopeProvider; } - public void Initialize() + public void Handle(MovedNotification notification) { - ContentService.Moved += ContentService_Moved; - ContentService.Trashed += ContentService_Trashed; - MediaService.Moved += MediaService_Moved; - MediaService.Trashed += MediaService_Trashed; - } - - public void Terminate() - { - ContentService.Moved -= ContentService_Moved; - ContentService.Trashed -= ContentService_Trashed; - MediaService.Moved -= MediaService_Moved; - MediaService.Trashed -= MediaService_Trashed; - } - - private void ContentService_Moved(IContentService sender, MoveEventArgs e) - { - foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Cms.Core.Constants.System.RecycleBinContentString))) + foreach (var item in notification.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Cms.Core.Constants.System.RecycleBinContentString))) { - const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; var relations = _relationService.GetByChildId(item.Entity.Id); @@ -62,20 +50,7 @@ namespace Umbraco.Cms.Core.Compose } } - private void MediaService_Moved(IMediaService sender, MoveEventArgs e) - { - foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Cms.Core.Constants.System.RecycleBinMediaString))) - { - const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; - var relations = _relationService.GetByChildId(item.Entity.Id); - foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) - { - _relationService.Delete(relation); - } - } - } - - private void ContentService_Trashed(IContentService sender, MoveEventArgs e) + public void Handle(TrashedNotification notification) { using (var scope = _scopeProvider.CreateScope()) { @@ -94,7 +69,7 @@ namespace Umbraco.Cms.Core.Compose _relationService.Save(relationType); } - foreach (var item in e.MoveInfoCollection) + foreach (var item in notification.MoveInfoCollection) { var originalPath = item.OriginalPath.ToDelimitedList(); var originalParentId = originalPath.Count > 2 @@ -124,6 +99,55 @@ namespace Umbraco.Cms.Core.Compose scope.Complete(); } } + } + + + public sealed class RelateOnTrashComponent : IComponent + { + private readonly IRelationService _relationService; + private readonly IEntityService _entityService; + private readonly ILocalizedTextService _textService; + private readonly IAuditService _auditService; + private readonly IScopeProvider _scopeProvider; + + public RelateOnTrashComponent( + IRelationService relationService, + IEntityService entityService, + ILocalizedTextService textService, + IAuditService auditService, + IScopeProvider scopeProvider) + { + _relationService = relationService; + _entityService = entityService; + _textService = textService; + _auditService = auditService; + _scopeProvider = scopeProvider; + } + + public void Initialize() + { + MediaService.Moved += MediaService_Moved; + MediaService.Trashed += MediaService_Trashed; + } + + public void Terminate() + { + MediaService.Moved -= MediaService_Moved; + MediaService.Trashed -= MediaService_Trashed; + } + + private void MediaService_Moved(IMediaService sender, MoveEventArgs e) + { + foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Cms.Core.Constants.System.RecycleBinMediaString))) + { + const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; + var relations = _relationService.GetByChildId(item.Entity.Id); + foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) + { + _relationService.Delete(relation); + } + } + } public void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { diff --git a/src/Umbraco.Infrastructure/Compose/BlockEditorComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs similarity index 92% rename from src/Umbraco.Infrastructure/Compose/BlockEditorComponent.cs rename to src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs index f54b52db51..5ce37d7b75 100644 --- a/src/Umbraco.Infrastructure/Compose/BlockEditorComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs @@ -1,36 +1,28 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.Blocks; -using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Compose +namespace Umbraco.Cms.Core.PropertyEditors { /// - /// A component for Block editors used to bind to events + /// A handler for Block editors used to bind to notifications /// - public class BlockEditorComponent : IComponent + // TODO: insert these notification handlers in core composition + public class BlockEditorPropertyHandler : ComplexPropertyEditorContentNotificationHandler { - private ComplexPropertyEditorContentEventHandler _handler; private readonly BlockListEditorDataConverter _converter = new BlockListEditorDataConverter(); - public void Initialize() - { - _handler = new ComplexPropertyEditorContentEventHandler( - Constants.PropertyEditors.Aliases.BlockList, - ReplaceBlockListUdis); - } + protected override string EditorAlias => Constants.PropertyEditors.Aliases.BlockList; - public void Terminate() => _handler?.Dispose(); - - private string ReplaceBlockListUdis(string rawJson, bool onlyMissingUdis) + protected override string FormatPropertyValue(string rawJson, bool onlyMissingKeys) { // the block editor doesn't ever have missing UDIs so when this is true there's nothing to process - if (onlyMissingUdis) return rawJson; + if (onlyMissingKeys) + return rawJson; return ReplaceBlockListUdis(rawJson, null); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs deleted file mode 100644 index 02b7d53108..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; -using Umbraco.Cms.Infrastructure.Services; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.PropertyEditors -{ - // TODO: add copying handling - public abstract class ComplexPropertyEditorContentNotificationHandler - : INotificationHandler> - { - private readonly string _editorAlias; - private readonly Func _formatPropertyValue; - - protected ComplexPropertyEditorContentNotificationHandler(string editorAlias, Func formatPropertyValue) - { - _editorAlias = editorAlias; - _formatPropertyValue = formatPropertyValue; - } - - public void Handle(SavingNotification notification) - { - foreach (var entity in notification.SavedEntities) - { - var props = entity.GetPropertiesByEditor(_editorAlias); - UpdatePropertyValues(props, true); - } - } - - private void UpdatePropertyValues(IEnumerable props, bool onlyMissingKeys) - { - foreach (var prop in props) - { - // A Property may have one or more values due to cultures - var propVals = prop.Values; - foreach (var cultureVal in propVals) - { - // Remove keys from published value & any nested properties - var updatedPublishedVal = _formatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); - cultureVal.PublishedValue = updatedPublishedVal; - - // Remove keys from edited/draft value & any nested properties - var updatedEditedVal = _formatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys); - cultureVal.EditedValue = updatedEditedVal; - } - } - } - } - - /// - /// Utility class for dealing with Copying/Saving events for complex editors - /// - public class ComplexPropertyEditorContentEventHandler : IDisposable - { - private readonly string _editorAlias; - private readonly Func _formatPropertyValue; - private bool _disposedValue; - - public ComplexPropertyEditorContentEventHandler(string editorAlias, - Func formatPropertyValue) - { - _editorAlias = editorAlias; - _formatPropertyValue = formatPropertyValue; - ContentService.Copying += ContentService_Copying; - //ContentService.Saving += ContentService_Saving; - } - - /// - /// Copying event handler - /// - /// - /// - private void ContentService_Copying(IContentService sender, CopyEventArgs e) - { - var props = e.Copy.GetPropertiesByEditor(_editorAlias); - UpdatePropertyValues(props, false); - } - - /// - /// Saving event handler - /// - /// - /// - private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) - { - foreach (var entity in e.SavedEntities) - { - var props = entity.GetPropertiesByEditor(_editorAlias); - UpdatePropertyValues(props, true); - } - } - - private void UpdatePropertyValues(IEnumerable props, bool onlyMissingKeys) - { - foreach (var prop in props) - { - // A Property may have one or more values due to cultures - var propVals = prop.Values; - foreach (var cultureVal in propVals) - { - // Remove keys from published value & any nested properties - var updatedPublishedVal = _formatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); - cultureVal.PublishedValue = updatedPublishedVal; - - // Remove keys from edited/draft value & any nested properties - var updatedEditedVal = _formatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys); - cultureVal.EditedValue = updatedEditedVal; - } - } - } - - /// - /// Unbinds from events - /// - /// - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - ContentService.Copying -= ContentService_Copying; - //ContentService.Saving -= ContentService_Saving; - } - _disposedValue = true; - } - } - - /// - /// Unbinds from events - /// - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs new file mode 100644 index 0000000000..083dce1a96 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs @@ -0,0 +1,55 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.PropertyEditors +{ + // TODO: insert these notification handlers in core composition + public abstract class ComplexPropertyEditorContentNotificationHandler : + INotificationHandler>, + INotificationHandler> + { + protected abstract string EditorAlias { get; } + + protected abstract string FormatPropertyValue(string rawJson, bool onlyMissingKeys); + + public void Handle(SavingNotification notification) + { + foreach (var entity in notification.SavedEntities) + { + var props = entity.GetPropertiesByEditor(EditorAlias); + UpdatePropertyValues(props, true); + } + } + + public void Handle(CopyingNotification notification) + { + var props = notification.Copy.GetPropertiesByEditor(EditorAlias); + UpdatePropertyValues(props, false); + } + + private void UpdatePropertyValues(IEnumerable props, bool onlyMissingKeys) + { + foreach (var prop in props) + { + // A Property may have one or more values due to cultures + var propVals = prop.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested properties + var updatedPublishedVal = FormatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested properties + var updatedEditedVal = FormatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs index 28d35e60d2..aa9e8089f2 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -24,7 +25,8 @@ namespace Umbraco.Cms.Core.PropertyEditors "fileupload", Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-download-alt")] - public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator + // TODO: insert these notification handlers in core composition + public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler>, INotificationHandler> { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -32,6 +34,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IDataTypeService _dataTypeService; private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _localizedTextService; + private readonly IContentService _contentService; public FileUploadPropertyEditor( ILoggerFactory loggerFactory, @@ -42,7 +45,8 @@ namespace Umbraco.Cms.Core.PropertyEditors ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, UploadAutoFillProperties uploadAutoFillProperties, - IJsonSerializer jsonSerializer) + IJsonSerializer jsonSerializer, + IContentService contentService) : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); @@ -51,6 +55,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _localizationService = localizationService; _localizedTextService = localizedTextService; _uploadAutoFillProperties = uploadAutoFillProperties; + _contentService = contentService; } /// @@ -119,37 +124,6 @@ namespace Umbraco.Cms.Core.PropertyEditors } } - /// - /// After a content has been copied, also copy uploaded files. - /// - /// The event sender. - /// The event arguments. - internal void ContentServiceCopied(IContentService sender, CopyEventArgs args) - { - // get the upload field properties with a value - var properties = args.Original.Properties.Where(IsUploadField); - - // copy files - var isUpdated = false; - foreach (var property in properties) - { - //copy each of the property values (variants, segments) to the destination - foreach (var propertyValue in property.Values) - { - var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); - if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue; - var sourcePath = _mediaFileSystem.GetRelativePath(str); - var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); - args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.Culture, propertyValue.Segment); - isUpdated = true; - } - } - - // if updated, re-save the copy with the updated value - if (isUpdated) - sender.Save(args.Copy); - } - /// /// After a media has been created, auto-fill the properties. /// @@ -182,6 +156,40 @@ namespace Umbraco.Cms.Core.PropertyEditors AutoFillProperties(entity); } + public void Handle(CopiedNotification notification) + { + // get the upload field properties with a value + var properties = notification.Original.Properties.Where(IsUploadField); + + // copy files + var isUpdated = false; + foreach (var property in properties) + { + //copy each of the property values (variants, segments) to the destination + foreach (var propertyValue in property.Values) + { + var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); + if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) + { + continue; + } + + var sourcePath = _mediaFileSystem.GetRelativePath(str); + var copyPath = _mediaFileSystem.CopyFile(notification.Copy, property.PropertyType, sourcePath); + notification.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.Culture, propertyValue.Segment); + isUpdated = true; + } + } + + // if updated, re-save the copy with the updated value + if (isUpdated) + { + _contentService.Save(notification.Copy); + } + } + + public void Handle(DeletedNotification notification) => notification.MediaFilesToDelete.AddRange(ServiceDeleted(notification.DeletedEntities.OfType())); + /// /// Auto-fill properties (or clear). /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 74bd7823e3..26b78cade5 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -16,6 +16,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -31,7 +32,8 @@ namespace Umbraco.Cms.Core.PropertyEditors HideLabel = false, Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-crop")] - public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator + // TODO: insert these notification handlers in core composition + public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler>, INotificationHandler> { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -39,6 +41,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IIOHelper _ioHelper; private readonly UploadAutoFillProperties _autoFillProperties; private readonly ILogger _logger; + private readonly IContentService _contentService; /// /// Initializes a new instance of the class. @@ -53,7 +56,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService, UploadAutoFillProperties uploadAutoFillProperties, - IJsonSerializer jsonSerializer) + IJsonSerializer jsonSerializer, + IContentService contentService) : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); @@ -61,6 +65,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); _autoFillProperties = uploadAutoFillProperties ?? throw new ArgumentNullException(nameof(uploadAutoFillProperties)); + _contentService = contentService; _logger = loggerFactory.CreateLogger(); } @@ -175,12 +180,10 @@ namespace Umbraco.Cms.Core.PropertyEditors /// /// After a content has been copied, also copy uploaded files. /// - /// The event sender. - /// The event arguments. - public void ContentServiceCopied(IContentService sender, CopyEventArgs args) + public void Handle(CopiedNotification notification) { // get the image cropper field properties - var properties = args.Original.Properties.Where(IsCropperField); + var properties = notification.Original.Properties.Where(IsCropperField); // copy files var isUpdated = false; @@ -191,19 +194,27 @@ namespace Umbraco.Cms.Core.PropertyEditors { var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); var src = GetFileSrcFromPropertyValue(propVal, out var jo); - if (src == null) continue; + if (src == null) + { + continue; + } var sourcePath = _mediaFileSystem.GetRelativePath(src); - var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); + var copyPath = _mediaFileSystem.CopyFile(notification.Copy, property.PropertyType, sourcePath); jo["src"] = _mediaFileSystem.GetUrl(copyPath); - args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment); + notification.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment); isUpdated = true; } } // if updated, re-save the copy with the updated value if (isUpdated) - sender.Save(args.Copy); + { + _contentService.Save(notification.Copy); + } + } + public void Handle(DeletedNotification notification) => notification.MediaFilesToDelete.AddRange(ServiceDeleted(notification.DeletedEntities.OfType())); + /// /// After a media has been created, auto-fill the properties. /// diff --git a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyHandler.cs similarity index 78% rename from src/Umbraco.Infrastructure/Compose/NestedContentPropertyComponent.cs rename to src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyHandler.cs index 332c2464ad..887c547778 100644 --- a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyHandler.cs @@ -1,29 +1,19 @@ -using System; +using System; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Compose +namespace Umbraco.Cms.Core.PropertyEditors { /// - /// A component for NestedContent used to bind to events + /// A handler for NestedContent used to bind to notifications /// - public class NestedContentPropertyComponent : IComponent + // TODO: insert these notification handlers in core composition + public class NestedContentPropertyHandler : ComplexPropertyEditorContentNotificationHandler { - private ComplexPropertyEditorContentEventHandler _handler; + protected override string EditorAlias => Constants.PropertyEditors.Aliases.NestedContent; - public void Initialize() - { - _handler = new ComplexPropertyEditorContentEventHandler( - Constants.PropertyEditors.Aliases.NestedContent, - CreateNestedContentKeys); - } - - public void Terminate() => _handler?.Dispose(); - - private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) => CreateNestedContentKeys(rawJson, onlyMissingKeys, null); + protected override string FormatPropertyValue(string rawJson, bool onlyMissingKeys) => CreateNestedContentKeys(rawJson, onlyMissingKeys, null); // internal for tests internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func createGuid = null) @@ -78,6 +68,5 @@ namespace Umbraco.Cms.Core.Compose } } } - } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs index f9cb83118d..c870d85139 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -42,17 +42,11 @@ namespace Umbraco.Cms.Core.PropertyEditors { MediaService.Saving += fileUpload.MediaServiceSaving; _terminate.Add(() => MediaService.Saving -= fileUpload.MediaServiceSaving); - ContentService.Copied += fileUpload.ContentServiceCopied; - _terminate.Add(() => ContentService.Copied -= fileUpload.ContentServiceCopied); void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); MediaService.Deleted += mediaServiceDeleted; _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); - void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += contentServiceDeleted; - _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); - void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); MemberService.Deleted += memberServiceDeleted; _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); @@ -62,17 +56,11 @@ namespace Umbraco.Cms.Core.PropertyEditors { MediaService.Saving += imageCropper.MediaServiceSaving; _terminate.Add(() => MediaService.Saving -= imageCropper.MediaServiceSaving); - ContentService.Copied += imageCropper.ContentServiceCopied; - _terminate.Add(() => ContentService.Copied -= imageCropper.ContentServiceCopied); void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); MediaService.Deleted += mediaServiceDeleted; _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); - void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += contentServiceDeleted; - _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); - void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); MemberService.Deleted += memberServiceDeleted; _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs deleted file mode 100644 index e56fbda4d7..0000000000 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Routing -{ - /// - /// Implements an Application Event Handler for managing redirect URLs tracking. - /// - /// - /// when content is renamed or moved, we want to create a permanent 301 redirect from it's old URL - /// not managing domains because we don't know how to do it - changing domains => must create a higher level strategy using rewriting rules probably - /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same - /// - public class RedirectTrackingComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs similarity index 58% rename from src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs rename to src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs index f6d48fa057..c1456af8b3 100644 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs @@ -1,36 +1,38 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Routing { - /// Implements an Application Event Handler for managing redirect URLs tracking. + /// Implements a notification handler for managing redirect URLs tracking. /// when content is renamed or moved, we want to create a permanent 301 redirect from it's old URL /// /// not managing domains because we don't know how to do it - changing domains => must create a higher level /// strategy using rewriting rules probably /// /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same - public sealed class RedirectTrackingComponent : IComponent + // TODO: insert these notification handlers in core composition + public sealed class RedirectTrackingHandler : + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler> { - private const string _eventStateKey = "Umbraco.Web.Redirects.RedirectTrackingEventHandler"; - private readonly IOptionsMonitor _webRoutingSettings; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IRedirectUrlService _redirectUrlService; private readonly IVariationContextAccessor _variationContextAccessor; - public RedirectTrackingComponent(IOptionsMonitor webRoutingSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor) + public RedirectTrackingHandler(IOptionsMonitor webRoutingSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor) { _webRoutingSettings = webRoutingSettings; _publishedSnapshotAccessor = publishedSnapshotAccessor; @@ -38,86 +40,62 @@ namespace Umbraco.Cms.Core.Routing _variationContextAccessor = variationContextAccessor; } - public void Initialize() + public void Handle(PublishingNotification notification) { - ContentService.Publishing += ContentService_Publishing; - ContentService.Published += ContentService_Published; - ContentService.Moving += ContentService_Moving; - ContentService.Moved += ContentService_Moved; + // don't let the notification handlers kick in if Redirect Tracking is turned off in the config + if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) + return; - // kill all redirects once a content is deleted - //ContentService.Deleted += ContentService_Deleted; - // BUT, doing it here would prevent content deletion due to FK - // so the rows are actually deleted by the ContentRepository (see GetDeleteClauses) - - // rolled back items have to be published, so publishing will take care of that - } - - public void Terminate() - { - ContentService.Publishing -= ContentService_Publishing; - ContentService.Published -= ContentService_Published; - ContentService.Moving -= ContentService_Moving; - ContentService.Moved -= ContentService_Moved; - } - - private void ContentService_Publishing(IContentService sender, PublishEventArgs args) - { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; - - var oldRoutes = GetOldRoutes(args.EventState); - foreach (var entity in args.PublishedEntities) + var oldRoutes = GetOldRoutes(); + foreach (var entity in notification.PublishedEntities) { StoreOldRoute(entity, oldRoutes); } } - private void ContentService_Published(IContentService sender, ContentPublishedEventArgs args) + public void Handle(PublishedNotification notification) { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; + // don't let the notification handlers kick in if Redirect Tracking is turned off in the config + if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) + return; - var oldRoutes = GetOldRoutes(args.EventState); + var oldRoutes = GetOldRoutes(); CreateRedirects(oldRoutes); } - private void ContentService_Moving(IContentService sender, MoveEventArgs args) + public void Handle(MovingNotification notification) { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; + // don't let the notification handlers kick in if Redirect Tracking is turned off in the config + if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) + return; - var oldRoutes = GetOldRoutes(args.EventState); - foreach (var info in args.MoveInfoCollection) + var oldRoutes = GetOldRoutes(); + foreach (var info in notification.MoveInfoCollection) { StoreOldRoute(info.Entity, oldRoutes); } } - private void ContentService_Moved(IContentService sender, MoveEventArgs args) + // TODO refactor (this is duplicate code, see published notification handling above) + public void Handle(MovedNotification notification) { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; + // don't let the notification handlers kick in if Redirect Tracking is turned off in the config + if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) + return; - var oldRoutes = GetOldRoutes(args.EventState); + var oldRoutes = GetOldRoutes(); CreateRedirects(oldRoutes); } - private OldRoutesDictionary GetOldRoutes(IDictionary eventState) - { - if (! eventState.ContainsKey(_eventStateKey)) - { - eventState[_eventStateKey] = new OldRoutesDictionary(); - } - - return eventState[_eventStateKey] as OldRoutesDictionary; - } + // TODO: figure out how to do notification state / temp state + private OldRoutesDictionary GetOldRoutes() => new OldRoutesDictionary(); private void StoreOldRoute(IContent entity, OldRoutesDictionary oldRoutes) { var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content; var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) return; + if (entityContent == null) + return; // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() @@ -130,7 +108,8 @@ namespace Umbraco.Cms.Core.Routing foreach (var culture in cultures) { var route = contentCache.GetRouteById(x.Id, culture); - if (IsNotRoute(route)) continue; + if (IsNotRoute(route)) + continue; oldRoutes[new ContentIdAndCulture(x.Id, culture)] = new ContentKeyAndOldRoute(x.Key, route); } } @@ -143,7 +122,8 @@ namespace Umbraco.Cms.Core.Routing foreach (var oldRoute in oldRoutes) { var newRoute = contentCache.GetRouteById(oldRoute.Key.ContentId, oldRoute.Key.Culture); - if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute) continue; + if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute) + continue; _redirectUrlService.Register(oldRoute.Value.OldRoute, oldRoute.Value.ContentKey, oldRoute.Key.Culture); } } @@ -176,6 +156,8 @@ namespace Umbraco.Cms.Core.Routing } private class OldRoutesDictionary : Dictionary - { } + { + + } } } diff --git a/src/Umbraco.Infrastructure/Services/CancelableNotification.cs b/src/Umbraco.Infrastructure/Services/CancelableNotification.cs index 178ce3df9b..bc4de2ce49 100644 --- a/src/Umbraco.Infrastructure/Services/CancelableNotification.cs +++ b/src/Umbraco.Infrastructure/Services/CancelableNotification.cs @@ -1,11 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Events; namespace Umbraco.Cms.Infrastructure.Services { // TODO split this file into several small classes and move to another namespace - public interface ICancelableNotification + public interface ICancelableNotification : INotification { bool Cancel { get; set; } } @@ -34,7 +39,7 @@ namespace Umbraco.Cms.Infrastructure.Services } } - public abstract class CancelableObjectNotification : ObjectNotification where T : class + public abstract class CancelableObjectNotification : ObjectNotification, ICancelableNotification where T : class { protected CancelableObjectNotification(T target, EventMessages messages) : base(target, messages) { @@ -66,16 +71,33 @@ namespace Umbraco.Cms.Infrastructure.Services { } + public DeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + public IEnumerable DeletedEntities => Target; } public class DeletedNotification : EnumerableObjectNotification { - public DeletedNotification(T target, EventMessages messages) : base(target, messages) + public DeletedNotification(T target, EventMessages messages) : base(target, messages) => MediaFilesToDelete = new List(); + + public IEnumerable DeletedEntities => Target; + + public List MediaFilesToDelete { get; } + } + + public class DeletedBlueprintNotification : EnumerableObjectNotification + { + public DeletedBlueprintNotification(T target, EventMessages messages) : base(target, messages) { } - public IEnumerable DeletedEntities => Target; + public DeletedBlueprintNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedBlueprints => Target; } public class SortingNotification : CancelableEnumerableObjectNotification @@ -121,4 +143,243 @@ namespace Umbraco.Cms.Infrastructure.Services public IEnumerable SavedEntities => Target; } + + public class SavedBlueprintNotification : ObjectNotification where T : class + { + public SavedBlueprintNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T SavedBlueprint => Target; + } + + public class PublishingNotification : CancelableEnumerableObjectNotification + { + public PublishingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public PublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable PublishedEntities => Target; + } + + public class PublishedNotification : EnumerableObjectNotification + { + public PublishedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public PublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable PublishedEntities => Target; + } + + public class MovingNotification : CancelableObjectNotification>> + { + public MovingNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) + { + } + + public MovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } + + public class MovedNotification : ObjectNotification>> + { + public MovedNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + public MovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } + + public class TrashingNotification : CancelableObjectNotification>> + { + public TrashingNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + public TrashingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } + + public class TrashedNotification : ObjectNotification>> + { + public TrashedNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + public TrashedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } + + public class CopyingNotification : CancelableObjectNotification where T : class + { + public CopyingNotification(T original, T copy, int parentId, EventMessages messages) : base(original, messages) + { + Copy = copy; + ParentId = parentId; + } + + public T Original => Target; + + public T Copy { get; } + + public int ParentId { get; } + } + + public class CopiedNotification : ObjectNotification where T : class + { + public CopiedNotification(T original, T copy, int parentId, EventMessages messages) : base(original, messages) + { + Copy = copy; + ParentId = parentId; + } + + public T Original => Target; + + public T Copy { get; } + + public int ParentId { get; } + } + + public class RollingBackNotification : CancelableObjectNotification where T : class + { + public RollingBackNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } + + public class RolledBackNotification : ObjectNotification where T : class + { + public RolledBackNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } + + public class SendingToPublishNotification : CancelableObjectNotification where T : class + { + public SendingToPublishNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } + + public class SentToPublishNotification : ObjectNotification where T : class + { + public SentToPublishNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } + + + public class UnpublishingNotification : CancelableEnumerableObjectNotification + { + public UnpublishingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public UnpublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable UnpublishedEntities => Target; + } + + public class UnpublishedNotification : EnumerableObjectNotification + { + public UnpublishedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public UnpublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable UnpublishedEntities => Target; + } + + public class EmptiedRecycleBinNotification : INotification + { + public EmptiedRecycleBinNotification(Guid nodeObjectType, EventMessages messages) + { + NodeObjectType = nodeObjectType; + Messages = messages; + } + + public Guid NodeObjectType { get; } + + public EventMessages Messages { get; } + + public bool IsContentRecycleBin => NodeObjectType == Constants.ObjectTypes.Document; + + public bool IsMediaRecycleBin => NodeObjectType == Constants.ObjectTypes.Media; + } + + public class EmptyingRecycleBinNotification : EmptiedRecycleBinNotification, ICancelableNotification + { + public EmptyingRecycleBinNotification(Guid nodeObjectType, EventMessages messages) + : base(nodeObjectType, messages) + { + } + + public bool Cancel { get; set; } + } + + public class DeletedVersionsNotification : INotification + { + public DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + { + Id = id; + Messages = messages; + SpecificVersion = specificVersion; + DeletePriorVersions = deletePriorVersions; + DateToRetain = dateToRetain; + } + + public int Id { get; } + + public EventMessages Messages { get; } + + public int SpecificVersion { get; } + + public bool DeletePriorVersions { get; } + + public DateTime DateToRetain { get; } + } + + public class DeletingVersionsNotification : DeletedVersionsNotification, ICancelableNotification + { + public DeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + + public bool Cancel { get; set; } + } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index 379ff9a0a2..64ff7260c9 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -34,6 +34,7 @@ namespace Umbraco.Cms.Core.Services.Implement private readonly ILogger _logger; private IQuery _queryNotTrashed; private readonly IEventAggregator _eventAggregator; + private readonly IRelationService _relationService; #region Constructors @@ -41,7 +42,7 @@ namespace Umbraco.Cms.Core.Services.Implement IEventMessagesFactory eventMessagesFactory, IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository, IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository, - Lazy propertyValidationService, IShortStringHelper shortStringHelper, IEventAggregator eventAggregator) + Lazy propertyValidationService, IShortStringHelper shortStringHelper, IEventAggregator eventAggregator, IRelationService relationService) : base(provider, loggerFactory, eventMessagesFactory) { _documentRepository = documentRepository; @@ -53,6 +54,7 @@ namespace Umbraco.Cms.Core.Services.Implement _propertyValidationService = propertyValidationService; _shortStringHelper = shortStringHelper; _eventAggregator = eventAggregator; + _relationService = relationService; _logger = loggerFactory.CreateLogger(); } @@ -1150,7 +1152,7 @@ namespace Umbraco.Cms.Core.Services.Implement : null; // ensure that the document can be published, and publish handling events, business rules, etc - publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs); + publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, allLangs); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! @@ -1243,7 +1245,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (unpublishResult.Success) // and succeeded, trigger events { // events and audit - scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), "Unpublished"); + _eventAggregator.Publish(new UnpublishedNotification(content, evtMsgs)); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); if (culturesUnpublishing != null) @@ -1298,7 +1300,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (!branchOne) // for branches, handled by SaveAndPublishBranch { scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - scope.Events.Dispatch(Published, this, saveEventArgs.ToContentPublishedEventArgs(), nameof(Published)); + _eventAggregator.Publish(new PublishingNotification(content, evtMsgs)); } // it was not published and now is... descendants that were 'published' (but @@ -1307,7 +1309,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id)) { var descendants = GetPublishedDescendantsLocked(content).ToArray(); - scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(descendants, false, evtMsgs), "Published"); + _eventAggregator.Publish(new PublishedNotification(descendants, evtMsgs)); } switch (publishResult.Result) @@ -1721,7 +1723,7 @@ namespace Umbraco.Cms.Core.Services.Implement // trigger events for the entire branch // (SaveAndPublishBranchOne does *not* do it) scope.Events.Dispatch(TreeChanged, this, new TreeChange(document, TreeChangeTypes.RefreshBranch).ToEventArgs()); - scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(publishedDocuments, false, evtMsgs), nameof(Published)); + _eventAggregator.Publish(new PublishedNotification(publishedDocuments, evtMsgs)); scope.Complete(); } @@ -1776,11 +1778,9 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var deleteNotification = new DeletingNotification(content, evtMsgs); - _eventAggregator.Publish(deleteNotification); - - var deleteEventArgs = new DeleteEventArgs(content, evtMsgs); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs, nameof(Deleting))) + var notification = new DeletingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -1792,7 +1792,9 @@ namespace Umbraco.Cms.Core.Services.Implement // but... Unpublishing event makes no sense (not going to cancel?) and no need to save // just raise the event if (content.Trashed == false && content.Published) - scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), nameof(Unpublished)); + { + _eventAggregator.Publish(new UnpublishedNotification(content, evtMsgs)); + } DeleteLocked(scope, content); @@ -1807,11 +1809,12 @@ namespace Umbraco.Cms.Core.Services.Implement private void DeleteLocked(IScope scope, IContent content) { + var evtMsgs = EventMessagesFactory.Get(); + void DoDelete(IContent c) { _documentRepository.Delete(c); - var args = new DeleteEventArgs(c, false); // raise event & get flagged files - scope.Events.Dispatch(Deleted, this, args, nameof(Deleted)); + _eventAggregator.Publish(new DeletedNotification(c, evtMsgs)); // media files deleted by QueuingEventDispatcher } @@ -1842,10 +1845,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User deleting versions of a Content object public void DeleteVersions(int id, DateTime versionDate, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); - if (scope.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs)) + var notification = new DeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return; @@ -1854,8 +1860,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); _documentRepository.DeleteVersions(id, versionDate); - deleteRevisionsEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs); + _eventAggregator.Publish(new DeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version date)"); scope.Complete(); @@ -1872,9 +1877,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User deleting versions of a Content object public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - if (scope.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId))) + var notification = new DeletingVersionsNotification(id, evtMsgs, specificVersion: versionId); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return; @@ -1891,7 +1900,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (c.VersionId != versionId && c.PublishedVersionId != versionId) // don't delete the current or published version _documentRepository.DeleteVersion(versionId); - scope.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId)); + _eventAggregator.Publish(new DeletedVersionsNotification(id, evtMsgs, specificVersion: versionId)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version)"); scope.Complete(); @@ -1914,8 +1923,10 @@ namespace Umbraco.Cms.Core.Services.Implement var originalPath = content.Path; var moveEventInfo = new MoveEventInfo(content, originalPath, Cms.Core.Constants.System.RecycleBinContent); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (scope.Events.DispatchCancelable(Trashing, this, moveEventArgs, nameof(Trashing))) + + var notification = new TrashingNotification(moveEventInfo, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return OperationResult.Cancel(evtMsgs); // causes rollback @@ -1934,9 +1945,7 @@ namespace Umbraco.Cms.Core.Services.Implement .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - moveEventArgs.CanCancel = false; - moveEventArgs.MoveInfoCollection = moveInfo; - scope.Events.Dispatch(Trashed, this, moveEventArgs, nameof(Trashed)); + _eventAggregator.Publish(new TrashedNotification(moveInfo, evtMsgs)); Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin"); scope.Complete(); @@ -1965,6 +1974,8 @@ namespace Umbraco.Cms.Core.Services.Implement return; } + var evtMsgs = EventMessagesFactory.Get(); + var moves = new List<(IContent, string)>(); using (var scope = ScopeProvider.CreateScope()) @@ -1976,8 +1987,9 @@ namespace Umbraco.Cms.Core.Services.Implement throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); - var moveEventArgs = new MoveEventArgs(moveEventInfo); - if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs, nameof(Moving))) + var notification = new MovingNotification(moveEventInfo, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return; // causes rollback @@ -2006,9 +2018,8 @@ namespace Umbraco.Cms.Core.Services.Implement .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved)); + _eventAggregator.Publish(new MovedNotification(moveInfo, evtMsgs)); + Audit(AuditType.Move, userId, content.Id); scope.Complete(); @@ -2093,8 +2104,9 @@ namespace Umbraco.Cms.Core.Services.Implement // are managed by Delete, and not here. // no idea what those events are for, keep a simplified version - var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, evtMsgs); - if (scope.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs)) + var notification = new EmptyingRecycleBinNotification(nodeObjectType, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -2109,9 +2121,7 @@ namespace Umbraco.Cms.Core.Services.Implement deleted.Add(content); } - recycleBinEventArgs.CanCancel = false; - recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?! - scope.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); + _eventAggregator.Publish(new EmptiedRecycleBinNotification(nodeObjectType, evtMsgs)); scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.RecycleBinContent, "Recycle bin emptied"); @@ -2151,13 +2161,16 @@ namespace Umbraco.Cms.Core.Services.Implement /// The newly created object public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + var copy = content.DeepCloneWithResetIdentities(); copy.ParentId = parentId; using (var scope = ScopeProvider.CreateScope()) { - var copyEventArgs = new CopyEventArgs(content, copy, true, parentId, relateToOriginal); - if (scope.Events.DispatchCancelable(Copying, this, copyEventArgs)) + var notification = new CopyingNotification(content, copy, parentId, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return null; @@ -2212,8 +2225,12 @@ namespace Umbraco.Cms.Core.Services.Implement var descendantCopy = descendant.DeepCloneWithResetIdentities(); descendantCopy.ParentId = parentId; - if (scope.Events.DispatchCancelable(Copying, this, new CopyEventArgs(descendant, descendantCopy, parentId))) + notification = new CopyingNotification(descendant, descendantCopy, parentId, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { continue; + } // a copy is not published (but not really unpublishing either) // update the create author and last edit author @@ -2237,7 +2254,14 @@ namespace Umbraco.Cms.Core.Services.Implement scope.Events.Dispatch(TreeChanged, this, new TreeChange(copy, TreeChangeTypes.RefreshBranch).ToEventArgs()); foreach (var x in copies) - scope.Events.Dispatch(Copied, this, new CopyEventArgs(x.Item1, x.Item2, false, x.Item2.ParentId, relateToOriginal)); + { + if (relateToOriginal) + { + RelateOnCopy(x.Item1, x.Item2); + } + + _eventAggregator.Publish(new CopiedNotification(x.Item1, x.Item2, parentId, evtMsgs)); + } Audit(AuditType.Copy, userId, content.Id); scope.Complete(); @@ -2246,6 +2270,26 @@ namespace Umbraco.Cms.Core.Services.Implement return copy; } + private void RelateOnCopy(IContent original, IContent copy) + { + var relationType = _relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); + if (relationType == null) + { + relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, + Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, + true, + Constants.ObjectTypes.Document, + Constants.ObjectTypes.Document); + + _relationService.Save(relationType); + } + + var relation = new Relation(original.Id, copy.Id, relationType); + _relationService.Save(relation); + + Audit(AuditType.Copy, copy.WriterId, copy.Id, $"Copied content with Id: '{copy.Id}' related to original content with Id: '{original.Id}'"); + } + /// /// Sends an to Publication, which executes handlers and events for the 'Send to Publication' action. /// @@ -2254,10 +2298,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// True if sending publication was successful otherwise false public bool SendToPublication(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var sendToPublishEventArgs = new SendToPublishEventArgs(content); - if (scope.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs)) + var notification = new SendingToPublishNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return false; @@ -2282,8 +2329,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (!saveResult.Success) return saveResult.Success; - sendToPublishEventArgs.CanCancel = false; - scope.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); + _eventAggregator.Publish(new SentToPublishNotification(content, evtMsgs)); if (culturesChanging != null) Audit(AuditType.SendToPublishVariant, userId, content.Id, $"Send To Publish for cultures: {culturesChanging}", culturesChanging); @@ -2412,7 +2458,9 @@ namespace Umbraco.Cms.Core.Services.Implement scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); if (raiseEvents && published.Any()) - scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(published, false, evtMsgs), "Published"); + { + _eventAggregator.Publish(new PublishedNotification(published, evtMsgs)); + } Audit(AuditType.Sort, userId, 0, "Sorting content performed by user"); return OperationResult.Succeed(evtMsgs); @@ -2492,136 +2540,11 @@ namespace Umbraco.Cms.Core.Services.Implement #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Delete Versions - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete Versions - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Sorting - /// - public static event TypedEventHandler> Sorting; - - /// - /// Occurs after Sorting - /// - public static event TypedEventHandler> Sorted; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler Saved; - - /// - /// Occurs before Copy - /// - public static event TypedEventHandler> Copying; - - /// - /// Occurs after Copy - /// - public static event TypedEventHandler> Copied; - - /// - /// Occurs before Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before Rollback - /// - public static event TypedEventHandler> RollingBack; - - /// - /// Occurs after Rollback - /// - public static event TypedEventHandler> RolledBack; - - /// - /// Occurs before Send to Publish - /// - public static event TypedEventHandler> SendingToPublish; - - /// - /// Occurs after Send to Publish - /// - public static event TypedEventHandler> SentToPublish; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - - /// - /// Occurs before publish - /// - public static event TypedEventHandler Publishing; - - /// - /// Occurs after publish - /// - public static event TypedEventHandler Published; - - /// - /// Occurs before unpublish - /// - public static event TypedEventHandler> Unpublishing; - - /// - /// Occurs after unpublish - /// - public static event TypedEventHandler> Unpublished; - /// /// Occurs after change. /// public static event TypedEventHandler.EventArgs> TreeChanged; - /// - /// Occurs after a blueprint has been saved. - /// - public static event TypedEventHandler> SavedBlueprint; - - /// - /// Occurs after a blueprint has been deleted. - /// - public static event TypedEventHandler> DeletedBlueprint; - #endregion #region Publishing Strategies @@ -2635,14 +2558,15 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - /// + /// /// private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, - IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs, - IReadOnlyCollection allLangs) + IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, IReadOnlyCollection allLangs) { - // raise Publishing event - if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs())) + // raise Publishing notification + var notification = new PublishingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); @@ -2801,8 +2725,10 @@ namespace Umbraco.Cms.Core.Services.Implement /// private PublishResult StrategyCanUnpublish(IScope scope, IContent content, EventMessages evtMsgs) { - // raise Unpublishing event - if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs(content, evtMsgs))) + // raise Unpublishing notification + var notification = new UnpublishingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id); return new PublishResult(PublishResultType.FailedUnpublishCancelledByEvent, evtMsgs, content); @@ -2871,6 +2797,7 @@ namespace Umbraco.Cms.Core.Services.Implement var changes = new List>(); var moves = new List<(IContent, string)>(); var contentTypeIdsA = contentTypeIds.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); // using an immediate uow here because we keep making changes with // PerformMoveLocked and DeleteLocked that must be applied immediately, @@ -2883,7 +2810,9 @@ namespace Umbraco.Cms.Core.Services.Implement var query = Query().WhereIn(x => x.ContentTypeId, contentTypeIdsA); var contents = _documentRepository.Get(query).ToArray(); - if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contents), nameof(Deleting))) + var notification = new DeletingNotification(contents, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return; @@ -2897,7 +2826,9 @@ namespace Umbraco.Cms.Core.Services.Implement // but... Unpublishing event makes no sense (not going to cancel?) and no need to save // just raise the event if (content.Trashed == false && content.Published) - scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), nameof(Unpublished)); + { + _eventAggregator.Publish(new UnpublishedNotification(content, evtMsgs)); + } // if current content has children, move them to trash var c = content; @@ -2920,7 +2851,9 @@ namespace Umbraco.Cms.Core.Services.Implement .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); if (moveInfos.Length > 0) - scope.Events.Dispatch(Trashed, this, new MoveEventArgs(false, moveInfos), nameof(Trashed)); + { + _eventAggregator.Publish(new TrashedNotification(moveInfos, evtMsgs)); + } scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, $"Delete content of type {string.Join(",", contentTypeIdsA)}"); @@ -2997,6 +2930,8 @@ namespace Umbraco.Cms.Core.Services.Implement public void SaveBlueprint(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + //always ensure the blueprint is at the root if (content.ParentId != -1) content.ParentId = -1; @@ -3017,7 +2952,7 @@ namespace Umbraco.Cms.Core.Services.Implement Audit(AuditType.Save, Cms.Core.Constants.Security.SuperUserId, content.Id, $"Saved content template: {content.Name}"); - scope.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); + _eventAggregator.Publish(new SavedBlueprintNotification(content, evtMsgs)); scope.Complete(); } @@ -3025,11 +2960,13 @@ namespace Umbraco.Cms.Core.Services.Implement public void DeleteBlueprint(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); _documentBlueprintRepository.Delete(content); - scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), nameof(DeletedBlueprint)); + _eventAggregator.Publish(new DeletedBlueprintNotification(content, evtMsgs)); scope.Complete(); } } @@ -3099,6 +3036,8 @@ namespace Umbraco.Cms.Core.Services.Implement public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); @@ -3119,7 +3058,7 @@ namespace Umbraco.Cms.Core.Services.Implement _documentBlueprintRepository.Delete(blueprint); } - scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), nameof(DeletedBlueprint)); + _eventAggregator.Publish(new DeletedBlueprintNotification(blueprints, evtMsgs)); scope.Complete(); } } @@ -3154,10 +3093,9 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var rollbackEventArgs = new RollbackEventArgs(content); - - //Emit RollingBack event aka before - if (scope.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) + var notification = new RollingBackNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -3177,9 +3115,7 @@ namespace Umbraco.Cms.Core.Services.Implement } else { - //Emit RolledBack event aka after - rollbackEventArgs.CanCancel = false; - scope.Events.Dispatch(RolledBack, this, rollbackEventArgs); + _eventAggregator.Publish(new RolledBackNotification(content, evtMsgs)); //Logging & Audit message _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId);