diff --git a/src/Umbraco.Core/Events/ContentSavingEventArgs.cs b/src/Umbraco.Core/Events/ContentSavingEventArgs.cs deleted file mode 100644 index b1cded2eb4..0000000000 --- a/src/Umbraco.Core/Events/ContentSavingEventArgs.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represent event data for the Saving event. - /// - public class ContentSavingEventArgs : SaveEventArgs - { - #region Factory Methods - - /// - /// Converts to while preserving all args state - /// - /// - public ContentSavedEventArgs ToContentSavedEventArgs() - { - return new ContentSavedEventArgs(EventObject, Messages, AdditionalData) - { - EventState = EventState - }; - } - - /// - /// Converts to while preserving all args state - /// - /// - public ContentPublishedEventArgs ToContentPublishedEventArgs() - { - return new ContentPublishedEventArgs(EventObject, false, Messages) - { - EventState = EventState, - AdditionalData = AdditionalData - }; - } - - /// - /// Converts to while preserving all args state - /// - /// - public ContentPublishingEventArgs ToContentPublishingEventArgs() - { - return new ContentPublishingEventArgs(EventObject, Messages) - { - EventState = EventState, - AdditionalData = AdditionalData - }; - } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - public ContentSavingEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - /// - /// Initializes a new instance of the class. - /// - public ContentSavingEventArgs(IContent eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - #endregion - - /// - /// Determines whether a culture is being saved, during a Saving event. - /// - public bool IsSavingCulture(IContent content, string culture) - => content.CultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty(); - } -} diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 99c1d2b0ee..43dc1d0fcd 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -126,8 +126,6 @@ namespace Umbraco.Cms.Core.Cache () => MediaService.TreeChanged -= MediaService_TreeChanged); // bind to content events - Bind(() => ContentService.Saved += ContentService_Saved, // needed for permissions - () => ContentService.Saved -= ContentService_Saved); Bind(() => ContentService.Copied += ContentService_Copied, // needed for permissions () => ContentService.Copied -= ContentService_Copied); Bind(() => ContentService.TreeChanged += ContentService_TreeChanged,// handles all content changes @@ -182,19 +180,6 @@ namespace Umbraco.Cms.Core.Cache { } - /// - /// Handles cache refreshing for when content is saved (not published) - /// - /// - /// - /// - /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to - /// stay up-to-date for unpublished content. - /// - private void ContentService_Saved(IContentService sender, SaveEventArgs e) - { - } - private void ContentService_TreeChanged(IContentService sender, TreeChange.EventArgs args) { _distributedCache.RefreshContentCache(args.Changes.ToArray()); diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs index 068a8bceea..92e7d6c4d7 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -18,10 +18,162 @@ 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 + })); + } + } + + } + } + public sealed class NotificationsComponent : IComponent { private readonly Notifier _notifier; @@ -41,10 +193,10 @@ namespace Umbraco.Cms.Core.Compose 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 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 @@ -65,8 +217,8 @@ namespace Umbraco.Cms.Core.Compose { ContentService.SentToPublish -= ContentService_SentToPublish; ContentService.Published -= ContentService_Published; - ContentService.Sorted -= ContentService_Sorted; - ContentService.Saved -= ContentService_Saved; + //ContentService.Sorted -= ContentService_Sorted; + //ContentService.Saved -= ContentService_Saved; ContentService.Unpublished -= ContentService_Unpublished; ContentService.Moved -= ContentService_Moved; ContentService.Trashed -= ContentService_Trashed; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs index 8098a5f8d4..02b7d53108 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -7,10 +7,53 @@ 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 /// @@ -26,7 +69,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _editorAlias = editorAlias; _formatPropertyValue = formatPropertyValue; ContentService.Copying += ContentService_Copying; - ContentService.Saving += ContentService_Saving; + //ContentService.Saving += ContentService_Saving; } /// @@ -84,7 +127,7 @@ namespace Umbraco.Cms.Core.PropertyEditors if (disposing) { ContentService.Copying -= ContentService_Copying; - ContentService.Saving -= ContentService_Saving; + //ContentService.Saving -= ContentService_Saving; } _disposedValue = true; } diff --git a/src/Umbraco.Infrastructure/Services/CancelableNotification.cs b/src/Umbraco.Infrastructure/Services/CancelableNotification.cs new file mode 100644 index 0000000000..178ce3df9b --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/CancelableNotification.cs @@ -0,0 +1,124 @@ +using System.Collections.Generic; +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 + { + bool Cancel { get; set; } + } + + public abstract class ObjectNotification : INotification where T : class + { + protected ObjectNotification(T target, EventMessages messages) + { + Messages = messages; + Target = target; + } + + public EventMessages Messages { get; } + + protected T Target { get; } + } + + public abstract class EnumerableObjectNotification : ObjectNotification> + { + protected EnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) + { + } + + protected EnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } + + public abstract class CancelableObjectNotification : ObjectNotification where T : class + { + protected CancelableObjectNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public bool Cancel { get; set; } + + public void CancelOperation(EventMessage cancelationMessage) + { + Cancel = true; + cancelationMessage.IsDefaultEventMessage = true; + Messages.Add(cancelationMessage); + } + } + + public abstract class CancelableEnumerableObjectNotification : CancelableObjectNotification> + { + protected CancelableEnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) + { + } + protected CancelableEnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } + + public class DeletingNotification : CancelableEnumerableObjectNotification + { + public DeletingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedEntities => Target; + } + + public class DeletedNotification : EnumerableObjectNotification + { + public DeletedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedEntities => Target; + } + + public class SortingNotification : CancelableEnumerableObjectNotification + { + public SortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SortedEntities => Target; + } + + public class SortedNotification : EnumerableObjectNotification + { + public SortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SortedEntities => Target; + } + + public class SavingNotification : CancelableEnumerableObjectNotification + { + public SavingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public SavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SavedEntities => Target; + } + + public class SavedNotification : EnumerableObjectNotification + { + public SavedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public SavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SavedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index 017540ae3f..379ff9a0a2 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -32,6 +33,7 @@ namespace Umbraco.Cms.Core.Services.Implement private readonly IShortStringHelper _shortStringHelper; private readonly ILogger _logger; private IQuery _queryNotTrashed; + private readonly IEventAggregator _eventAggregator; #region Constructors @@ -39,7 +41,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) + Lazy propertyValidationService, IShortStringHelper shortStringHelper, IEventAggregator eventAggregator) : base(provider, loggerFactory, eventMessagesFactory) { _documentRepository = documentRepository; @@ -50,6 +52,7 @@ namespace Umbraco.Cms.Core.Services.Implement _languageRepository = languageRepository; _propertyValidationService = propertyValidationService; _shortStringHelper = shortStringHelper; + _eventAggregator = eventAggregator; _logger = loggerFactory.CreateLogger(); } @@ -747,11 +750,15 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + if (raiseEvents) { - scope.Complete(); - return OperationResult.Cancel(evtMsgs); + var notification = new SavingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { + scope.Complete(); + return OperationResult.Cancel(evtMsgs); + } } scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); @@ -773,7 +780,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + _eventAggregator.Publish(new SavedNotification(content, evtMsgs)); } var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); @@ -802,11 +809,15 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new ContentSavingEventArgs(contentsA, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + if (raiseEvents) { - scope.Complete(); - return OperationResult.Cancel(evtMsgs); + var notification = new SavingNotification(contentsA, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { + scope.Complete(); + return OperationResult.Cancel(evtMsgs); + } } var treeChanges = contentsA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)); @@ -823,7 +834,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + _eventAggregator.Publish(new SavedNotification(contentsA, evtMsgs)); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Saved multiple content"); @@ -867,9 +878,12 @@ namespace Umbraco.Cms.Core.Services.Implement var allLangs = _languageRepository.GetMany().ToList(); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var notification = new SavingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } // if culture is specific, first publish the invariant values, then publish the culture itself. // if culture is '*', then publish them all (including variants) @@ -881,7 +895,7 @@ namespace Umbraco.Cms.Core.Services.Implement // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. content.PublishCulture(impact); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, raiseEvents); scope.Complete(); return result; } @@ -905,9 +919,16 @@ namespace Umbraco.Cms.Core.Services.Implement var allLangs = _languageRepository.GetMany().ToList(); var evtMsgs = EventMessagesFactory.Get(); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) - return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + + if (raiseEvents) + { + var notification = new SavingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { + return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } + } var varies = content.ContentType.VariesByCulture(); @@ -927,7 +948,7 @@ namespace Umbraco.Cms.Core.Services.Implement foreach (var impact in impacts) content.PublishCulture(impact); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, raiseEvents); scope.Complete(); return result; } @@ -969,9 +990,12 @@ namespace Umbraco.Cms.Core.Services.Implement var allLangs = _languageRepository.GetMany().ToList(); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var notification = new SavingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } // all cultures = unpublish whole if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null)) @@ -982,7 +1006,7 @@ namespace Umbraco.Cms.Core.Services.Implement // to be non-routable so that when it's re-published all variants were as they were. content.PublishedState = PublishedState.Unpublishing; - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId); scope.Complete(); return result; } @@ -996,7 +1020,7 @@ namespace Umbraco.Cms.Core.Services.Implement var removed = content.UnpublishCulture(culture); //save and publish any changes - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId); scope.Complete(); @@ -1039,13 +1063,16 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var notification = new SavingNotification(content, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } var allLangs = _languageRepository.GetMany().ToList(); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, raiseEvents); scope.Complete(); return result; } @@ -1069,15 +1096,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, - ContentSavingEventArgs saveEventArgs, IReadOnlyCollection allLangs, + EventMessages evtMsgs, IReadOnlyCollection allLangs, int userId = Cms.Core.Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false) { if (scope == null) throw new ArgumentNullException(nameof(scope)); if (content == null) throw new ArgumentNullException(nameof(content)); - if (saveEventArgs == null) throw new ArgumentNullException(nameof(saveEventArgs)); - - var evtMsgs = saveEventArgs.Messages; + if (evtMsgs == null) throw new ArgumentNullException(nameof(evtMsgs)); PublishResult publishResult = null; PublishResult unpublishResult = null; @@ -1210,7 +1235,7 @@ namespace Umbraco.Cms.Core.Services.Implement // raise the Saved event, always if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + _eventAggregator.Publish(new SavedNotification(content, evtMsgs)); } if (unpublishing) // we have tried to unpublish - won't happen in a branch @@ -1375,8 +1400,9 @@ namespace Umbraco.Cms.Core.Services.Implement if (pendingCultures.Count == 0) continue; //shouldn't happen but no point in processing this document if there's nothing there - var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var notification = new SavingNotification(d, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); continue; @@ -1390,7 +1416,7 @@ namespace Umbraco.Cms.Core.Services.Implement d.UnpublishCulture(c); } - var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); + var result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId); if (result.Success == false) _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); results.Add(result); @@ -1436,11 +1462,12 @@ namespace Umbraco.Cms.Core.Services.Implement if (pendingCultures.Count == 0) continue; //shouldn't happen but no point in processing this document if there's nothing there - var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var notification = new SavingNotification(d, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) { results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); - continue; // this document is canceled move next + continue; } var publishing = true; @@ -1470,7 +1497,7 @@ namespace Umbraco.Cms.Core.Services.Implement else if (!publishing) result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d); else - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); + result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId); if (result.Success == false) _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); @@ -1718,9 +1745,12 @@ namespace Umbraco.Cms.Core.Services.Implement if (culturesToPublish.Count == 0) // empty = already published return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document); - var saveEventArgs = new ContentSavingEventArgs(document, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var notification = new SavingNotification(document, evtMsgs); + _eventAggregator.Publish(notification); + if (notification.Cancel) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document); + } // publish & check if values are valid if (!publishCultures(document, culturesToPublish, allLangs)) @@ -1729,7 +1759,7 @@ namespace Umbraco.Cms.Core.Services.Implement return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot); + var result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, userId, branchOne: true, branchRoot: isRoot); if (result.Success) publishedDocuments.Add(document); return result; @@ -1746,6 +1776,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))) { @@ -2322,16 +2355,23 @@ namespace Umbraco.Cms.Core.Services.Implement private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents) { - var saveEventArgs = new ContentSavingEventArgs(itemsA, evtMsgs); if (raiseEvents) { - //raise cancelable sorting event - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting))) + // raise cancelable sorting event + var sortingNotification = new SortingNotification(itemsA, evtMsgs); + _eventAggregator.Publish(sortingNotification); + if (sortingNotification.Cancel) + { return OperationResult.Cancel(evtMsgs); + } - //raise saving event (this one cannot be canceled) - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saving, this, saveEventArgs, nameof(Saving)); + // raise cancelable saving event + var savingNotification = new SavingNotification(itemsA, evtMsgs); + _eventAggregator.Publish(savingNotification); + if (savingNotification.Cancel) + { + return OperationResult.Cancel(evtMsgs); + } } var published = new List(); @@ -2364,10 +2404,9 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - var savedEventsArgs = saveEventArgs.ToContentSavedEventArgs(); //first saved, then sorted - scope.Events.Dispatch(Saved, this, savedEventsArgs, nameof(Saved)); - scope.Events.Dispatch(Sorted, this, savedEventsArgs, nameof(Sorted)); + _eventAggregator.Publish(new SavedNotification(itemsA, evtMsgs)); + _eventAggregator.Publish(new SortedNotification(itemsA, evtMsgs)); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); @@ -2483,11 +2522,6 @@ namespace Umbraco.Cms.Core.Services.Implement /// public static event TypedEventHandler> Sorted; - /// - /// Occurs before Save - /// - public static event TypedEventHandler Saving; - /// /// Occurs after Save ///