diff --git a/.gitignore b/.gitignore index 0cffac8343..95295e44c4 100644 --- a/.gitignore +++ b/.gitignore @@ -201,3 +201,4 @@ src/Umbraco.Tests/TEMP/ /src/Umbraco.Web.UI/config/umbracoSettings.config /src/Umbraco.Web.UI.NetCore/Umbraco/models/* +/src/Umbraco.Web.UI.NetCore/appsettings.Local.json diff --git a/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs b/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs deleted file mode 100644 index ceec857c64..0000000000 --- a/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represents event data for the Published event. - /// - public class ContentPublishedEventArgs : PublishEventArgs - { - /// - /// Initializes a new instance of the class. - /// - public ContentPublishedEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { } - - /// - /// Determines whether a culture has been published, during a Published event. - /// - public bool HasPublishedCulture(IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); - - /// - /// Determines whether a culture has been unpublished, during a Published event. - /// - public bool HasUnpublishedCulture(IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); - } -} diff --git a/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs b/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs deleted file mode 100644 index e7893ea195..0000000000 --- a/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represents event data for the Publishing event. - /// - public class ContentPublishingEventArgs : PublishEventArgs - { - /// - /// Initializes a new instance of the class. - /// - public ContentPublishingEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - /// - /// Determines whether a culture is being published, during a Publishing event. - /// - public bool IsPublishingCulture(IContent content, string culture) - => content.PublishCultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty(); - - /// - /// Determines whether a culture is being unpublished, during a Publishing event. - /// - public bool IsUnpublishingCulture(IContent content, string culture) - => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); //bit of a hack since we know that the content implementation tracks changes this way - } -} diff --git a/src/Umbraco.Core/Events/ContentSavedEventArgs.cs b/src/Umbraco.Core/Events/ContentSavedEventArgs.cs deleted file mode 100644 index 554330563a..0000000000 --- a/src/Umbraco.Core/Events/ContentSavedEventArgs.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represents event data for the Saved event. - /// - public class ContentSavedEventArgs : SaveEventArgs - { - /// - /// Initializes a new instance of the class. - /// - public ContentSavedEventArgs(IEnumerable eventObject, EventMessages messages, IDictionary additionalData) - : base(eventObject, false, messages, additionalData) - { } - - /// - /// Determines whether a culture has been saved, during a Saved event. - /// - public bool HasSavedCulture(IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UpdatedCulture + culture); - } -} 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.Core/Events/DeleteRevisionsEventArgs.cs b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs deleted file mode 100644 index a7eb48107e..0000000000 --- a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; - -namespace Umbraco.Cms.Core.Events -{ - public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable - { - public DeleteRevisionsEventArgs(int id, bool canCancel, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id, canCancel) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } - - public DeleteRevisionsEventArgs(int id, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } - - public bool DeletePriorVersions { get; } - public int SpecificVersion { get; } - public DateTime DateToRetain { get; } - - /// - /// Returns true if we are deleting a specific revision - /// - public bool IsDeletingSpecificRevision => SpecificVersion != default; - - public bool Equals(DeleteRevisionsEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((DeleteRevisionsEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); - hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); - hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return !Equals(left, right); - } - } -} diff --git a/src/Umbraco.Core/Events/ICancelableNotification.cs b/src/Umbraco.Core/Events/ICancelableNotification.cs new file mode 100644 index 0000000000..df1abc672e --- /dev/null +++ b/src/Umbraco.Core/Events/ICancelableNotification.cs @@ -0,0 +1,10 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Cms.Core.Events +{ + public interface ICancelableNotification : INotification + { + bool Cancel { get; set; } + } +} diff --git a/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs b/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs new file mode 100644 index 0000000000..96f8e771c0 --- /dev/null +++ b/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs @@ -0,0 +1,37 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; + +namespace Umbraco.Cms.Core.Events +{ + public interface IScopedNotificationPublisher + { + /// + /// Publishes a cancelable notification to the notification subscribers + /// + /// + /// True if the notification was cancelled by a subscriber, false otherwise + bool PublishCancelable(ICancelableNotification notification); + + /// + /// Publishes a cancelable notification to the notification subscribers + /// + /// + /// True if the notification was cancelled by a subscriber, false otherwise + Task PublishCancelableAsync(ICancelableNotification notification); + + /// + /// Publishes a notification to the notification subscribers + /// + /// + /// The notification is published upon successful completion of the current scope, i.e. when things have been saved/published/deleted etc. + void Publish(INotification notification); + + /// + /// Invokes publishing of all pending notifications within the current scope + /// + /// + void ScopeExit(bool completed); + } +} diff --git a/src/Umbraco.Core/Events/IStatefulNotification.cs b/src/Umbraco.Core/Events/IStatefulNotification.cs new file mode 100644 index 0000000000..dafebe6173 --- /dev/null +++ b/src/Umbraco.Core/Events/IStatefulNotification.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Events +{ + public interface IStatefulNotification : INotification + { + IDictionary State { get; set; } + } +} diff --git a/src/Umbraco.Core/Events/NotificationExtensions.cs b/src/Umbraco.Core/Events/NotificationExtensions.cs new file mode 100644 index 0000000000..de51f016e4 --- /dev/null +++ b/src/Umbraco.Core/Events/NotificationExtensions.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Events +{ + public static class NotificationExtensions + { + public static T WithState(this T notification, IDictionary state) where T : IStatefulNotification + { + notification.State = state; + return notification; + } + + public static T WithStateFrom(this T notification, TSource source) + where T : IStatefulNotification where TSource : IStatefulNotification + => notification.WithState(source.State); + } +} diff --git a/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs b/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs new file mode 100644 index 0000000000..206706716c --- /dev/null +++ b/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs @@ -0,0 +1,71 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Umbraco.Cms.Core.Events +{ + public class ScopedNotificationPublisher : IScopedNotificationPublisher + { + private readonly IEventAggregator _eventAggregator; + private readonly List _notificationOnScopeCompleted; + + public ScopedNotificationPublisher(IEventAggregator eventAggregator) + { + _eventAggregator = eventAggregator; + _notificationOnScopeCompleted = new List(); + } + + public bool PublishCancelable(ICancelableNotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + _eventAggregator.Publish(notification); + return notification.Cancel; + } + + public async Task PublishCancelableAsync(ICancelableNotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + await _eventAggregator.PublishAsync(notification); + return notification.Cancel; + } + + public void Publish(INotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + _notificationOnScopeCompleted.Add(notification); + } + + public void ScopeExit(bool completed) + { + try + { + if (completed) + { + foreach (var notification in _notificationOnScopeCompleted) + { + _eventAggregator.Publish(notification); + } + } + } + finally + { + _notificationOnScopeCompleted.Clear(); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 99c1d2b0ee..1aa4906029 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,10 +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 () => ContentService.TreeChanged -= ContentService_TreeChanged); @@ -182,32 +178,11 @@ 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()); } - // TODO: our weird events handling wants this for now - private void ContentService_Deleted(IContentService sender, DeleteEventArgs e) { } - private void ContentService_Moved(IContentService sender, MoveEventArgs e) { } - private void ContentService_Trashed(IContentService sender, MoveEventArgs e) { } - private void ContentService_EmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) { } - private void ContentService_Published(IContentService sender, PublishEventArgs e) { } - private void ContentService_Unpublished(IContentService sender, PublishEventArgs e) { } - //private void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs e) //{ // _distributedCache.RefreshUnpublishedPageCache(e.SavedEntities.ToArray()); @@ -413,13 +388,6 @@ namespace Umbraco.Cms.Core.Cache _distributedCache.RefreshMediaCache(args.Changes.ToArray()); } - // TODO: our weird events handling wants this for now - private void MediaService_Saved(IMediaService sender, SaveEventArgs e) { } - private void MediaService_Deleted(IMediaService sender, DeleteEventArgs e) { } - private void MediaService_Moved(IMediaService sender, MoveEventArgs e) { } - private void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { } - private void MediaService_EmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) { } - #endregion #region MemberService 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 068a8bceea..687fdbf294 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; @@ -13,7 +13,6 @@ 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; @@ -22,6 +21,9 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { + /// + /// TODO: this component must be removed entirely - there is some code duplication in in anticipation of this component being deleted + /// public sealed class NotificationsComponent : IComponent { private readonly Notifier _notifier; @@ -37,24 +39,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; @@ -63,15 +47,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; } @@ -82,72 +57,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(); @@ -158,22 +67,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/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs index 21e473bb87..c760c33b71 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs @@ -1,5 +1,13 @@ -using Umbraco.Cms.Core.Composing; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose @@ -11,6 +19,49 @@ namespace Umbraco.Cms.Core.Compose base.Compose(builder); builder.Services.AddUnique(); + + // add handlers for sending user notifications (i.e. emails) + builder.Services.AddUnique(); + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add handlers for building content relations + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add notification handlers for property editors + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add notification handlers for redirect tracking + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); } } } 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/RelateOnTrashComposer.cs b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComposer.cs deleted file mode 100644 index 8394dfc993..0000000000 --- a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - public sealed class RelateOnTrashComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Events/RelateOnCopyNotificationHandler.cs b/src/Umbraco.Infrastructure/Events/RelateOnCopyNotificationHandler.cs new file mode 100644 index 0000000000..f88b529461 --- /dev/null +++ b/src/Umbraco.Infrastructure/Events/RelateOnCopyNotificationHandler.cs @@ -0,0 +1,51 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Services.Notifications; + +namespace Umbraco.Cms.Core.Events +{ + public class RelateOnCopyNotificationHandler : INotificationHandler + { + private readonly IRelationService _relationService; + private readonly IAuditService _auditService; + + public RelateOnCopyNotificationHandler(IRelationService relationService, IAuditService auditService) + { + _relationService = relationService; + _auditService = auditService; + } + + public void Handle(ContentCopiedNotification notification) + { + if (notification.RelateToOriginal == false) + { + return; + } + + 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(notification.Original.Id, notification.Copy.Id, relationType); + _relationService.Save(relation); + + _auditService.Add( + AuditType.Copy, + notification.Copy.WriterId, + notification.Copy.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), + $"Copied content with Id: '{notification.Copy.Id}' related to original content with Id: '{notification.Original.Id}'"); + } + } +} diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs similarity index 58% rename from src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs rename to src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs index de00961357..0ea5844b92 100644 --- a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs @@ -1,15 +1,21 @@ -using System.Linq; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Events; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; 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.Notifications; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Compose +namespace Umbraco.Cms.Core.Events { - public sealed class RelateOnTrashComponent : IComponent + // TODO: lots of duplicate code in this one, refactor + public sealed class RelateOnTrashNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly IRelationService _relationService; private readonly IEntityService _entityService; @@ -17,7 +23,7 @@ namespace Umbraco.Cms.Core.Compose private readonly IAuditService _auditService; private readonly IScopeProvider _scopeProvider; - public RelateOnTrashComponent( + public RelateOnTrashNotificationHandler( IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, @@ -31,28 +37,11 @@ namespace Umbraco.Cms.Core.Compose _scopeProvider = scopeProvider; } - public void Initialize() + public void Handle(ContentMovedNotification 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(Constants.System.RecycleBinContentString))) { - - const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; var relations = _relationService.GetByChildId(item.Entity.Id); foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) @@ -62,44 +51,29 @@ 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(ContentMovedToRecycleBinNotification notification) { using (var scope = _scopeProvider.CreateScope()) { - const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) { - var documentObjectType = Cms.Core.Constants.ObjectTypes.Document; - const string relationTypeName = - Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; + var documentObjectType = Constants.ObjectTypes.Document; + const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; - relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, - documentObjectType); + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); _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 ? int.Parse(originalPath[originalPath.Count - 2]) - : Cms.Core.Constants.System.Root; + : Constants.System.Root; //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin @@ -107,17 +81,15 @@ namespace Umbraco.Cms.Core.Compose if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later - var relation = _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? - new Relation(originalParentId, item.Entity.Id, relationType); + var relation = _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? new Relation(originalParentId, item.Entity.Id, relationType); _relationService.Save(relation); _auditService.Add(AuditType.Delete, item.Entity.WriterId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), - string.Format(_textService.Localize( - "recycleBin/contentTrashed"), - item.Entity.Id, originalParentId)); + string.Format(_textService.Localize("recycleBin/contentTrashed"), item.Entity.Id, originalParentId) + ); } } @@ -125,51 +97,60 @@ namespace Umbraco.Cms.Core.Compose } } - public void MediaService_Trashed(IMediaService sender, MoveEventArgs e) + public void Handle(MediaMovedNotification notification) + { + foreach (var item in notification.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMediaString))) + { + const string relationTypeAlias = 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 Handle(MediaMovedToRecycleBinNotification notification) { using (var scope = _scopeProvider.CreateScope()) { - const string relationTypeAlias = - Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) { - var documentObjectType = Cms.Core.Constants.ObjectTypes.Document; - const string relationTypeName = - Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; - relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, - documentObjectType); + var documentObjectType = Constants.ObjectTypes.Document; + const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); _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 ? int.Parse(originalPath[originalPath.Count - 2]) - : Cms.Core.Constants.System.Root; + : Constants.System.Root; //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later - var relation = - _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? - new Relation(originalParentId, item.Entity.Id, relationType); + var relation = _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? new Relation(originalParentId, item.Entity.Id, relationType); _relationService.Save(relation); _auditService.Add(AuditType.Delete, item.Entity.CreatorId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Media), - string.Format(_textService.Localize( - "recycleBin/mediaTrashed"), - item.Entity.Id, originalParentId)); + string.Format(_textService.Localize("recycleBin/mediaTrashed"), item.Entity.Id, originalParentId) + ); } } scope.Complete(); } + } } } diff --git a/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs new file mode 100644 index 0000000000..62da73c28b --- /dev/null +++ b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs @@ -0,0 +1,215 @@ +// 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.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.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Events +{ + public sealed class UserNotificationsHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler + { + private readonly Notifier _notifier; + private readonly ActionCollection _actions; + private readonly IContentService _contentService; + + public UserNotificationsHandler(Notifier notifier, ActionCollection actions, IContentService contentService) + { + _notifier = notifier; + _actions = actions; + _contentService = contentService; + } + + public void Handle(ContentSavedNotification 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(ContentSortedNotification 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(ContentPublishedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.PublishedEntities.ToArray()); + + public void Handle(ContentMovedNotification 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(ContentMovedToRecycleBinNotification notification) => _notifier.Notify(_actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + public void Handle(ContentCopiedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Original); + + public void Handle(ContentRolledBackNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + + public void Handle(ContentSentToPublishNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + + public void Handle(ContentUnpublishedNotification 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/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..24c9b37cde 100644 --- a/src/Umbraco.Infrastructure/Compose/BlockEditorComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs @@ -1,36 +1,30 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +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 + 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 8098a5f8d4..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs +++ /dev/null @@ -1,102 +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.Extensions; - -namespace Umbraco.Cms.Core.PropertyEditors -{ - /// - /// 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..23fb498d77 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs @@ -0,0 +1,54 @@ +// 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.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.PropertyEditors +{ + public abstract class ComplexPropertyEditorContentNotificationHandler : + INotificationHandler, + INotificationHandler + { + protected abstract string EditorAlias { get; } + + protected abstract string FormatPropertyValue(string rawJson, bool onlyMissingKeys); + + public void Handle(ContentSavingNotification notification) + { + foreach (var entity in notification.SavedEntities) + { + var props = entity.GetPropertiesByEditor(EditorAlias); + UpdatePropertyValues(props, true); + } + } + + public void Handle(ContentCopyingNotification 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..15d56abfb3 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.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -24,7 +25,9 @@ namespace Umbraco.Cms.Core.PropertyEditors "fileupload", Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-download-alt")] - public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator + public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, + INotificationHandler, INotificationHandler, + INotificationHandler, INotificationHandler { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -32,6 +35,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 +46,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 +56,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _localizationService = localizationService; _localizedTextService = localizedTextService; _uploadAutoFillProperties = uploadAutoFillProperties; + _contentService = contentService; } /// @@ -86,16 +92,17 @@ namespace Umbraco.Cms.Core.PropertyEditors } /// - /// Ensures any files associated are removed + /// The paths to all file upload property files contained within a collection of content entities /// - /// - internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) - { - return deletedEntities.SelectMany(x => x.Properties) - .Where(IsUploadField) - .SelectMany(GetFilePathsFromPropertyValues) - .Distinct(); - } + /// + /// + /// This method must be made private once MemberService events have been replaced by notifications + /// + internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + .SelectMany(x => x.Properties) + .Where(IsUploadField) + .SelectMany(GetFilePathsFromPropertyValues) + .Distinct(); /// /// Look through all property values stored against the property and resolve any file paths stored @@ -119,15 +126,10 @@ 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) + public void Handle(ContentCopiedNotification notification) { // get the upload field properties with a value - var properties = args.Original.Properties.Where(IsUploadField); + var properties = notification.Original.Properties.Where(IsUploadField); // copy files var isUpdated = false; @@ -137,49 +139,41 @@ namespace Umbraco.Cms.Core.PropertyEditors foreach (var propertyValue in property.Values) { var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); - if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue; + 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); + 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) - sender.Save(args.Copy); + { + _contentService.Save(notification.Copy); + } } - /// - /// After a media has been created, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - internal void MediaServiceCreated(IMediaService sender, NewEventArgs args) + public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + private void DeleteContainedFiles(IEnumerable deletedEntities) { - AutoFillProperties(args.Entity); + var filePathsToDelete = ContainedFilePaths(deletedEntities); + _mediaFileSystem.DeleteMediaFiles(filePathsToDelete); } - /// - /// After a media has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void MediaServiceSaving(IMediaService sender, SaveEventArgs args) + public void Handle(MediaSavingNotification notification) { - foreach (var entity in args.SavedEntities) - AutoFillProperties(entity); - } - - /// - /// After a content item has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void ContentServiceSaving(IContentService sender, SaveEventArgs args) - { - foreach (var entity in args.SavedEntities) + foreach (var entity in notification.SavedEntities) + { AutoFillProperties(entity); + } } /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 74bd7823e3..52972e9f49 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.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -31,7 +32,9 @@ namespace Umbraco.Cms.Core.PropertyEditors HideLabel = false, Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-crop")] - public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator + public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, + INotificationHandler, INotificationHandler, + INotificationHandler, INotificationHandler { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -39,6 +42,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 +57,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 +66,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(); } @@ -122,16 +128,17 @@ namespace Umbraco.Cms.Core.PropertyEditors } /// - /// Ensures any files associated are removed + /// The paths to all image cropper property files contained within a collection of content entities /// - /// - internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) - { - return deletedEntities.SelectMany(x => x.Properties) - .Where(IsCropperField) - .SelectMany(GetFilePathsFromPropertyValues) - .Distinct(); - } + /// + /// + /// This method must be made private once MemberService events have been replaced by notifications + /// + internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + .SelectMany(x => x.Properties) + .Where(IsCropperField) + .SelectMany(GetFilePathsFromPropertyValues) + .Distinct(); /// /// Look through all property values stored against the property and resolve any file paths stored @@ -175,12 +182,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(ContentCopiedNotification 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,49 +196,40 @@ 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); + } } - /// - /// After a media has been created, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void MediaServiceCreated(IMediaService sender, NewEventArgs args) + public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + private void DeleteContainedFiles(IEnumerable deletedEntities) { - AutoFillProperties(args.Entity); + var filePathsToDelete = ContainedFilePaths(deletedEntities); + _mediaFileSystem.DeleteMediaFiles(filePathsToDelete); } - /// - /// After a media has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void MediaServiceSaving(IMediaService sender, SaveEventArgs args) + public void Handle(MediaSavingNotification notification) { - foreach (var entity in args.SavedEntities) - AutoFillProperties(entity); - } - - /// - /// After a content item has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void ContentServiceSaving(IContentService sender, SaveEventArgs args) - { - foreach (var entity in args.SavedEntities) + foreach (var entity in notification.SavedEntities) + { AutoFillProperties(entity); + } } /// 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..1bc5dd2f4b 100644 --- a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyHandler.cs @@ -1,29 +1,21 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +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 + 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 +70,5 @@ namespace Umbraco.Cms.Core.Compose } } } - } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs index f9cb83118d..b9e9e33889 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; @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Services.Implement; namespace Umbraco.Cms.Core.PropertyEditors { + // TODO: delete this component and make the "ContainedFilePaths" methods on FileUploadPropertyEditor and ImageCropperPropertyEditor private once MemberService uses notifications instead of static events public sealed class PropertyEditorsComponent : IComponent { private readonly PropertyEditorCollection _propertyEditors; @@ -40,40 +41,14 @@ namespace Umbraco.Cms.Core.PropertyEditors private void Initialize(FileUploadPropertyEditor fileUpload) { - 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())); + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ContainedFilePaths(args.DeletedEntities.Cast())); MemberService.Deleted += memberServiceDeleted; _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); } private void Initialize(ImageCropperPropertyEditor imageCropper) { - 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())); + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ContainedFilePaths(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 55% rename from src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs rename to src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs index f6d48fa057..19dc90181a 100644 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs @@ -1,36 +1,42 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +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.Notifications; 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 + 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) + private const string NotificationStateKey = "Umbraco.Cms.Core.Routing.RedirectTrackingHandler"; + + public RedirectTrackingHandler(IOptionsMonitor webRoutingSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor) { _webRoutingSettings = webRoutingSettings; _publishedSnapshotAccessor = publishedSnapshotAccessor; @@ -38,86 +44,53 @@ namespace Umbraco.Cms.Core.Routing _variationContextAccessor = variationContextAccessor; } - public void Initialize() + public void Handle(ContentPublishingNotification notification) => StoreOldRoutes(notification.PublishedEntities, notification); + + public void Handle(ContentPublishedNotification notification) => CreateRedirectsForOldRoutes(notification); + + public void Handle(ContentMovingNotification notification) => StoreOldRoutes(notification.MoveInfoCollection.Select(m => m.Entity), notification); + + public void Handle(ContentMovedNotification notification) => CreateRedirectsForOldRoutes(notification); + + private void StoreOldRoutes(IEnumerable entities, IStatefulNotification 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(notification); + foreach (var entity in entities) { StoreOldRoute(entity, oldRoutes); } } - private void ContentService_Published(IContentService sender, ContentPublishedEventArgs args) + private void CreateRedirectsForOldRoutes(IStatefulNotification 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(notification); CreateRedirects(oldRoutes); } - private void ContentService_Moving(IContentService sender, MoveEventArgs args) + private OldRoutesDictionary GetOldRoutes(IStatefulNotification notification) { - // 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 info in args.MoveInfoCollection) + if (notification.State.ContainsKey(NotificationStateKey) == false) { - StoreOldRoute(info.Entity, oldRoutes); - } - } - - private void ContentService_Moved(IContentService sender, MoveEventArgs 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); - CreateRedirects(oldRoutes); - } - - private OldRoutesDictionary GetOldRoutes(IDictionary eventState) - { - if (! eventState.ContainsKey(_eventStateKey)) - { - eventState[_eventStateKey] = new OldRoutesDictionary(); + notification.State[NotificationStateKey] = new OldRoutesDictionary(); } - return eventState[_eventStateKey] as OldRoutesDictionary; + return (OldRoutesDictionary)notification.State[NotificationStateKey]; } 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 +103,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 +117,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 +151,8 @@ namespace Umbraco.Cms.Core.Routing } private class OldRoutesDictionary : Dictionary - { } + { + + } } } diff --git a/src/Umbraco.Infrastructure/Scoping/IScope.cs b/src/Umbraco.Infrastructure/Scoping/IScope.cs index 4f55988d2f..26048f5ec2 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScope.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScope.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Infrastructure.Persistence; @@ -30,6 +30,11 @@ namespace Umbraco.Cms.Core.Scoping /// IEventDispatcher Events { get; } + /// + /// Gets the scope notification publisher + /// + IScopedNotificationPublisher Notifications { get; } + /// /// Gets the repositories cache mode. /// diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 66a4470645..d13c84da2e 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -20,6 +20,7 @@ namespace Umbraco.Cms.Core.Scoping private readonly ScopeProvider _scopeProvider; private readonly CoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; + private readonly IEventAggregator _eventAggregator; private readonly ILogger _logger; private readonly IsolationLevel _isolationLevel; @@ -36,12 +37,15 @@ namespace Umbraco.Cms.Core.Scoping private EventMessages _messages; private ICompletable _fscope; private IEventDispatcher _eventDispatcher; + // eventually this may need to be injectable - for now we'll create it explicitly and let future needs determine if it should be injectable + private IScopedNotificationPublisher _notificationPublisher; // initializes a new scope private Scope( ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, + IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, Scope parent, @@ -57,6 +61,7 @@ namespace Umbraco.Cms.Core.Scoping _scopeProvider = scopeProvider; _coreDebugSettings = coreDebugSettings; _mediaFileSystem = mediaFileSystem; + _eventAggregator = eventAggregator; _logger = logger; Context = scopeContext; @@ -70,6 +75,7 @@ namespace Umbraco.Cms.Core.Scoping Detachable = detachable; + #if DEBUG_SCOPES _scopeProvider.RegisterScope(this); #endif @@ -146,6 +152,7 @@ namespace Umbraco.Cms.Core.Scoping ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, + IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, bool detachable, @@ -156,7 +163,7 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, eventAggregator, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent @@ -164,6 +171,7 @@ namespace Umbraco.Cms.Core.Scoping ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, + IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, Scope parent, @@ -173,7 +181,7 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, eventAggregator, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } public Guid InstanceId { get; } = Guid.NewGuid(); @@ -381,6 +389,16 @@ namespace Umbraco.Cms.Core.Scoping } } + public IScopedNotificationPublisher Notifications + { + get + { + EnsureNotDisposed(); + if (ParentScope != null) return ParentScope.Notifications; + return _notificationPublisher ?? (_notificationPublisher = new ScopedNotificationPublisher(_eventAggregator)); + } + } + /// public bool Complete() { @@ -556,6 +574,7 @@ namespace Umbraco.Cms.Core.Scoping if (onException == false) { _eventDispatcher?.ScopeExit(completed); + _notificationPublisher?.ScopeExit(completed); } }, () => { diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index ca5b96e0db..15ccacdb9f 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -34,8 +34,9 @@ namespace Umbraco.Cms.Core.Scoping private static readonly AsyncLocal> s_scopeContextStack = new AsyncLocal>(); private static readonly string s_scopeItemKey = typeof(Scope).FullName; private static readonly string s_contextItemKey = typeof(ScopeProvider).FullName; + private readonly IEventAggregator _eventAggregator; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache, IEventAggregator eventAggregator) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; @@ -44,6 +45,7 @@ namespace Umbraco.Cms.Core.Scoping _logger = logger; _loggerFactory = loggerFactory; _requestCache = requestCache; + _eventAggregator = eventAggregator; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; } @@ -379,7 +381,7 @@ namespace Umbraco.Cms.Core.Scoping RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) - => new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + => new Scope(this, _coreDebugSettings, _mediaFileSystem, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); /// public void AttachScope(IScope other, bool callContext = false) @@ -458,7 +460,7 @@ namespace Umbraco.Cms.Core.Scoping { IScopeContext ambientContext = AmbientContext; ScopeContext newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! PushAmbientScope(scope); if (newContext != null) @@ -468,7 +470,7 @@ namespace Umbraco.Cms.Core.Scoping return scope; } - var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); PushAmbientScope(nested); return nested; } diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index b6e91c717d..7c3b3db346 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.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -747,8 +748,8 @@ 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))) + var savingNotification = new ContentSavingNotification(content, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -773,7 +774,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + scope.Notifications.Publish(new ContentSavedNotification(content, evtMsgs).WithStateFrom(savingNotification)); } var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); @@ -802,8 +803,8 @@ 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))) + var savingNotification = new ContentSavingNotification(contentsA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -823,7 +824,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + scope.Notifications.Publish(new ContentSavedNotification(contentsA, evtMsgs).WithStateFrom(savingNotification)); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Saved multiple content"); @@ -867,9 +868,11 @@ 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 savingNotification = new ContentSavingNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { 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 +884,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, savingNotification.State, userId, raiseEvents); scope.Complete(); return result; } @@ -905,9 +908,12 @@ 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))) + + var savingNotification = new ContentSavingNotification(content, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } var varies = content.ContentType.VariesByCulture(); @@ -927,7 +933,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, savingNotification.State, userId, raiseEvents); scope.Complete(); return result; } @@ -969,9 +975,11 @@ 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 savingNotification = new ContentSavingNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } // all cultures = unpublish whole if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null)) @@ -982,7 +990,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, savingNotification.State, userId); scope.Complete(); return result; } @@ -996,7 +1004,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, savingNotification.State, userId); scope.Complete(); @@ -1039,13 +1047,15 @@ 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 savingNotification = new ContentSavingNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { 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, savingNotification.State, userId, raiseEvents); scope.Complete(); return result; } @@ -1056,7 +1066,7 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - /// + /// /// /// /// @@ -1069,15 +1079,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, - ContentSavingEventArgs saveEventArgs, IReadOnlyCollection allLangs, + EventMessages evtMsgs, IReadOnlyCollection allLangs, + IDictionary notificationState, 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; @@ -1125,7 +1134,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, notificationState); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! @@ -1210,7 +1219,7 @@ namespace Umbraco.Cms.Core.Services.Implement // raise the Saved event, always if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + scope.Notifications.Publish(new ContentSavedNotification(content, evtMsgs).WithState(notificationState)); } if (unpublishing) // we have tried to unpublish - won't happen in a branch @@ -1218,7 +1227,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"); + scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs).WithState(notificationState)); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); if (culturesUnpublishing != null) @@ -1273,7 +1282,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)); + scope.Notifications.Publish(new ContentPublishedNotification(content, evtMsgs).WithState(notificationState)); } // it was not published and now is... descendants that were 'published' (but @@ -1282,7 +1291,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"); + scope.Notifications.Publish(new ContentPublishedNotification(descendants, evtMsgs).WithState(notificationState)); } switch (publishResult.Result) @@ -1375,8 +1384,8 @@ 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 savingNotification = new ContentSavingNotification(d, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); continue; @@ -1390,7 +1399,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, savingNotification.State, 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 +1445,11 @@ 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 savingNotification = new ContentSavingNotification(d, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); - continue; // this document is canceled move next + continue; } var publishing = true; @@ -1470,7 +1479,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, savingNotification.State, d.WriterId); if (result.Success == false) _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); @@ -1694,7 +1703,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)); + scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, evtMsgs)); scope.Complete(); } @@ -1718,9 +1727,11 @@ 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 savingNotification = new ContentSavingNotification(document, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document); + } // publish & check if values are valid if (!publishCultures(document, culturesToPublish, allLangs)) @@ -1729,7 +1740,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, savingNotification.State, userId, branchOne: true, branchRoot: isRoot); if (result.Success) publishedDocuments.Add(document); return result; @@ -1746,8 +1757,7 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(content, evtMsgs); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs, nameof(Deleting))) + if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(content, evtMsgs))) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -1759,9 +1769,11 @@ 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)); + { + scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs)); + } - DeleteLocked(scope, content); + DeleteLocked(scope, content, evtMsgs); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.Remove).ToEventArgs()); Audit(AuditType.Delete, userId, content.Id); @@ -1772,13 +1784,12 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Succeed(evtMsgs); } - private void DeleteLocked(IScope scope, IContent content) + private void DeleteLocked(IScope scope, IContent content, EventMessages evtMsgs) { 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)); + scope.Notifications.Publish(new ContentDeletedNotification(c, evtMsgs)); // media files deleted by QueuingEventDispatcher } @@ -1809,10 +1820,12 @@ 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 deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); return; @@ -1821,8 +1834,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); + scope.Notifications.Publish(new ContentDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version date)"); scope.Complete(); @@ -1839,9 +1851,12 @@ 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 deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, specificVersion: versionId); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); return; @@ -1858,7 +1873,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)); + scope.Notifications.Publish(new ContentDeletedVersionsNotification(id, evtMsgs, specificVersion: versionId).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version)"); scope.Complete(); @@ -1881,8 +1896,9 @@ 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 movingToRecycleBinNotification = new ContentMovingToRecycleBinNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); // causes rollback @@ -1901,9 +1917,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)); + scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfo, evtMsgs).WithStateFrom(movingToRecycleBinNotification)); Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin"); scope.Complete(); @@ -1932,6 +1946,8 @@ namespace Umbraco.Cms.Core.Services.Implement return; } + var evtMsgs = EventMessagesFactory.Get(); + var moves = new List<(IContent, string)>(); using (var scope = ScopeProvider.CreateScope()) @@ -1943,8 +1959,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 movingNotification = new ContentMovingNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingNotification)) { scope.Complete(); return; // causes rollback @@ -1973,9 +1990,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)); + scope.Notifications.Publish(new ContentMovedNotification(moveInfo, evtMsgs).WithStateFrom(movingNotification)); + Audit(AuditType.Move, userId, content.Id); scope.Complete(); @@ -2047,7 +2063,6 @@ namespace Umbraco.Cms.Core.Services.Implement /// public OperationResult EmptyRecycleBin(int userId = Cms.Core.Constants.Security.SuperUserId) { - var nodeObjectType = Cms.Core.Constants.ObjectTypes.Document; var deleted = new List(); var evtMsgs = EventMessagesFactory.Get(); @@ -2060,8 +2075,8 @@ 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 emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(evtMsgs); + if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -2072,13 +2087,11 @@ namespace Umbraco.Cms.Core.Services.Implement var contents = _documentRepository.Get(query).ToArray(); foreach (var content in contents) { - DeleteLocked(scope, content); + DeleteLocked(scope, content, evtMsgs); deleted.Add(content); } - recycleBinEventArgs.CanCancel = false; - recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?! - scope.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); + scope.Notifications.Publish(new ContentEmptiedRecycleBinNotification(evtMsgs).WithStateFrom(emptyingRecycleBinNotification)); 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"); @@ -2118,13 +2131,14 @@ 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)) + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, evtMsgs))) { scope.Complete(); return null; @@ -2179,8 +2193,10 @@ namespace Umbraco.Cms.Core.Services.Implement var descendantCopy = descendant.DeepCloneWithResetIdentities(); descendantCopy.ParentId = parentId; - if (scope.Events.DispatchCancelable(Copying, this, new CopyEventArgs(descendant, descendantCopy, parentId))) + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, evtMsgs))) + { continue; + } // a copy is not published (but not really unpublishing either) // update the create author and last edit author @@ -2204,7 +2220,9 @@ 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)); + { + scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, evtMsgs)); + } Audit(AuditType.Copy, userId, content.Id); scope.Complete(); @@ -2221,10 +2239,12 @@ 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 sendingToPublishNotification = new ContentSendingToPublishNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(sendingToPublishNotification)) { scope.Complete(); return false; @@ -2249,8 +2269,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (!saveResult.Success) return saveResult.Success; - sendToPublishEventArgs.CanCancel = false; - scope.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); + scope.Notifications.Publish(new ContentSentToPublishNotification(content, evtMsgs).WithStateFrom(sendingToPublishNotification)); if (culturesChanging != null) Audit(AuditType.SendToPublishVariant, userId, content.Id, $"Send To Publish for cultures: {culturesChanging}", culturesChanging); @@ -2322,16 +2341,21 @@ 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); + var sortingNotification = new ContentSortingNotification(itemsA, evtMsgs); + var savingNotification = new ContentSavingNotification(itemsA, evtMsgs); if (raiseEvents) { - //raise cancelable sorting event - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting))) + // raise cancelable sorting event + if (scope.Notifications.PublishCancelable(sortingNotification)) + { 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 + if (scope.Notifications.PublishCancelable(savingNotification)) + { + return OperationResult.Cancel(evtMsgs); + } } var published = new List(); @@ -2364,16 +2388,17 @@ 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)); + scope.Notifications.Publish(new ContentSavedNotification(itemsA, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new ContentSortedNotification(itemsA, evtMsgs).WithStateFrom(sortingNotification)); } 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"); + { + scope.Notifications.Publish(new ContentPublishedNotification(published, evtMsgs)); + } Audit(AuditType.Sort, userId, 0, "Sorting content performed by user"); return OperationResult.Succeed(evtMsgs); @@ -2453,140 +2478,13 @@ 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 before Save - /// - public static event TypedEventHandler Saving; - - /// - /// 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; + /// + /// This event needs to be rewritten using notifications instead + /// + internal static event TypedEventHandler.EventArgs> TreeChanged; #endregion @@ -2601,14 +2499,14 @@ 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, IDictionary notificationState) { - // raise Publishing event - if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs())) + // raise Publishing notification + if (scope.Notifications.PublishCancelable(new ContentPublishingNotification(content, evtMsgs).WithState(notificationState))) { _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); @@ -2767,8 +2665,8 @@ 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 + if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs))) { _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id); return new PublishResult(PublishResultType.FailedUnpublishCancelledByEvent, evtMsgs, content); @@ -2837,6 +2735,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, @@ -2849,7 +2748,7 @@ 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))) + if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(contents, evtMsgs))) { scope.Complete(); return; @@ -2863,7 +2762,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)); + { + scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs)); + } // if current content has children, move them to trash var c = content; @@ -2878,7 +2779,7 @@ namespace Umbraco.Cms.Core.Services.Implement // delete content // triggers the deleted event (and handles the files) - DeleteLocked(scope, content); + DeleteLocked(scope, content, evtMsgs); changes.Add(new TreeChange(content, TreeChangeTypes.Remove)); } @@ -2886,7 +2787,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)); + { + scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(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)}"); @@ -2963,6 +2866,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; @@ -2983,7 +2888,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"); + scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs)); scope.Complete(); } @@ -2991,11 +2896,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)); + scope.Notifications.Publish(new ContentDeletedBlueprintNotification(content, evtMsgs)); scope.Complete(); } } @@ -3065,6 +2972,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); @@ -3085,7 +2994,7 @@ namespace Umbraco.Cms.Core.Services.Implement _documentBlueprintRepository.Delete(blueprint); } - scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), nameof(DeletedBlueprint)); + scope.Notifications.Publish(new ContentDeletedBlueprintNotification(blueprints, evtMsgs)); scope.Complete(); } } @@ -3120,10 +3029,8 @@ 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 rollingBackNotification = new ContentRollingBackNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(rollingBackNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -3143,9 +3050,7 @@ namespace Umbraco.Cms.Core.Services.Implement } else { - //Emit RolledBack event aka after - rollbackEventArgs.CanCancel = false; - scope.Events.Dispatch(RolledBack, this, rollbackEventArgs); + scope.Notifications.Publish(new ContentRolledBackNotification(content, evtMsgs).WithStateFrom(rollingBackNotification)); //Logging & Audit message _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs index 4b9d6f8e5c..38a7ad7b28 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -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.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -289,19 +290,21 @@ namespace Umbraco.Cms.Core.Services.Implement private void CreateMedia(IScope scope, Core.Models.Media media, IMedia parent, int userId, bool withIdentity) { + var evtMsgs = EventMessagesFactory.Get(); + media.CreatorId = userId; if (withIdentity) { - // if saving is cancelled, media remains without an identity - var saveEventArgs = new SaveEventArgs(media); - if (Saving.IsRaisedEventCancelled(saveEventArgs, this)) + var savingNotification = new MediaSavingNotification(media, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return; + } _mediaRepository.Save(media); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MediaSavedNotification(media, evtMsgs).WithStateFrom(savingNotification)); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.RefreshNode).ToEventArgs()); } @@ -659,8 +662,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(media, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotification = new MediaSavingNotification(media, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -682,8 +685,7 @@ namespace Umbraco.Cms.Core.Services.Implement _mediaRepository.Save(media); if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MediaSavedNotification(media, evtMsgs).WithStateFrom(savingNotification)); } var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, changeType).ToEventArgs()); @@ -708,8 +710,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(mediasA, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, new SaveEventArgs(mediasA, evtMsgs))) + var savingNotification = new MediaSavingNotification(mediasA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -727,8 +729,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MediaSavedNotification(mediasA, evtMsgs).WithStateFrom(savingNotification)); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Bulk save media"); @@ -754,7 +755,7 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(media, evtMsgs))) + if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(media, evtMsgs))) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -762,7 +763,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Cms.Core.Constants.Locks.MediaTree); - DeleteLocked(scope, media); + DeleteLocked(scope, media, evtMsgs); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.Remove).ToEventArgs()); Audit(AuditType.Delete, userId, media.Id); @@ -773,13 +774,12 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(evtMsgs); } - private void DeleteLocked(IScope scope, IMedia media) + private void DeleteLocked(IScope scope, IMedia media, EventMessages evtMsgs) { void DoDelete(IMedia c) { _mediaRepository.Delete(c); - var args = new DeleteEventArgs(c, false); // raise event & get flagged files - scope.Events.Dispatch(Deleted, this, args); + scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs)); // media files deleted by QueuingEventDispatcher } @@ -815,36 +815,24 @@ namespace Umbraco.Cms.Core.Services.Implement { DeleteVersions(scope, true, id, versionDate, userId); scope.Complete(); - - //if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, dateToRetain: versionDate))) - //{ - // uow.Complete(); - // return; - //} - - //uow.WriteLock(Constants.Locks.MediaTree); - //var repository = uow.CreateRepository(); - //repository.DeleteVersions(id, versionDate); - - //uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate)); - //Audit(uow, AuditType.Delete, "Delete Media by version date, userId, Constants.System.Root); - - //uow.Complete(); } } private void DeleteVersions(IScope scope, bool wlock, int id, DateTime versionDate, int userId = Cms.Core.Constants.Security.SuperUserId) { - var args = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); - if (scope.Events.DispatchCancelable(DeletingVersions, this, args)) + var evtMsgs = EventMessagesFactory.Get(); + + var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) + { return; + } if (wlock) scope.WriteLock(Cms.Core.Constants.Locks.MediaTree); _mediaRepository.DeleteVersions(id, versionDate); - args.CanCancel = false; - scope.Events.Dispatch(DeletedVersions, this, args); + scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete Media by version date"); } @@ -858,10 +846,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User deleting versions of a Media 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()) { - var args = new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId); - if (scope.Events.DispatchCancelable(DeletingVersions, this, args)) + var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, specificVersion: versionId); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); return; @@ -879,8 +869,7 @@ namespace Umbraco.Cms.Core.Services.Implement _mediaRepository.DeleteVersion(versionId); - args.CanCancel = false; - scope.Events.Dispatch(DeletedVersions, this, args); + scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, specificVersion: versionId).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete Media by version"); scope.Complete(); @@ -911,8 +900,9 @@ namespace Umbraco.Cms.Core.Services.Implement var originalPath = media.Path; var moveEventInfo = new MoveEventInfo(media, originalPath, Cms.Core.Constants.System.RecycleBinMedia); - var moveEventArgs = new MoveEventArgs(true, evtMsgs, moveEventInfo); - if (scope.Events.DispatchCancelable(Trashing, this, moveEventArgs, nameof(Trashing))) + + var movingToRecycleBinNotification = new MediaMovingToRecycleBinNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -921,11 +911,8 @@ namespace Umbraco.Cms.Core.Services.Implement PerformMoveLocked(media, Cms.Core.Constants.System.RecycleBinMedia, null, userId, moves, true); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.RefreshBranch).ToEventArgs()); - var moveInfo = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) - .ToArray(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - scope.Events.Dispatch(Trashed, this, moveEventArgs, nameof(Trashed)); + var moveInfo = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)).ToArray(); + scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfo, evtMsgs).WithStateFrom(movingToRecycleBinNotification)); Audit(AuditType.Move, userId, media.Id, "Move Media to recycle bin"); scope.Complete(); @@ -962,8 +949,8 @@ namespace Umbraco.Cms.Core.Services.Implement throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback var moveEventInfo = new MoveEventInfo(media, media.Path, parentId); - var moveEventArgs = new MoveEventArgs(true, evtMsgs, moveEventInfo); - if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs, nameof(Moving))) + var movingNotification = new MediaMovingNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -979,9 +966,7 @@ namespace Umbraco.Cms.Core.Services.Implement var moveInfo = moves //changes .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)); + scope.Notifications.Publish(new MediaMovedNotification(moveInfo, evtMsgs).WithStateFrom(movingNotification)); Audit(AuditType.Move, userId, media.Id); scope.Complete(); } @@ -1050,7 +1035,6 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User emptying the Recycle Bin public OperationResult EmptyRecycleBin(int userId = Cms.Core.Constants.Security.SuperUserId) { - var nodeObjectType = Cms.Core.Constants.ObjectTypes.Media; var deleted = new List(); var evtMsgs = EventMessagesFactory.Get(); // TODO: and then? @@ -1063,23 +1047,22 @@ namespace Umbraco.Cms.Core.Services.Implement // v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since // each deleted items will have its own deleting/deleted events. so, files and such // are managed by Delete, and not here. - var args = new RecycleBinEventArgs(nodeObjectType, evtMsgs); - - if (scope.Events.DispatchCancelable(EmptyingRecycleBin, this, args)) + var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(evtMsgs); + if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); } + // emptying the recycle bin means deleting whatever is in there - do it properly! var query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.RecycleBinMedia); var medias = _mediaRepository.Get(query).ToArray(); foreach (var media in medias) { - DeleteLocked(scope, media); + DeleteLocked(scope, media, evtMsgs); deleted.Add(media); } - args.CanCancel = false; - scope.Events.Dispatch(EmptiedRecycleBin, this, args); + scope.Notifications.Publish(new MediaEmptiedRecycleBinNotification(new EventMessages()).WithStateFrom(emptyingRecycleBinNotification)); scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.RecycleBinMedia, "Empty Media recycle bin"); scope.Complete(); @@ -1105,10 +1088,12 @@ namespace Umbraco.Cms.Core.Services.Implement var itemsA = items.ToArray(); if (itemsA.Length == 0) return true; + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var args = new SaveEventArgs(itemsA); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, args)) + var savingNotification = new MediaSavingNotification(itemsA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return false; @@ -1137,8 +1122,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - args.CanCancel = false; - scope.Events.Dispatch(Saved, this, args); + scope.Notifications.Publish(new MediaSavedNotification(itemsA, evtMsgs).WithStateFrom(savingNotification)); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); Audit(AuditType.Sort, userId, 0); @@ -1216,70 +1200,13 @@ 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 Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Media is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Media 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 the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - /// /// Occurs after change. /// - public static event TypedEventHandler.EventArgs> TreeChanged; + /// + /// This event needs to be rewritten using notifications instead + /// + internal static event TypedEventHandler.EventArgs> TreeChanged; #endregion @@ -1307,6 +1234,7 @@ namespace Umbraco.Cms.Core.Services.Implement var changes = new List>(); var moves = new List<(IMedia, string)>(); var mediaTypeIdsA = mediaTypeIds.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) { @@ -1315,7 +1243,7 @@ namespace Umbraco.Cms.Core.Services.Implement var query = Query().WhereIn(x => x.ContentTypeId, mediaTypeIdsA); var medias = _mediaRepository.Get(query).ToArray(); - if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(medias))) + if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(medias, evtMsgs))) { scope.Complete(); return; @@ -1338,14 +1266,16 @@ namespace Umbraco.Cms.Core.Services.Implement // delete media // triggers the deleted event (and handles the files) - DeleteLocked(scope, media); + DeleteLocked(scope, media, evtMsgs); changes.Add(new TreeChange(media, TreeChangeTypes.Remove)); } var moveInfos = moves.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)); + { + scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfos, evtMsgs)); + } scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, $"Delete Media of types {string.Join(",", mediaTypeIdsA)}"); diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CancelableEnumerableObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CancelableEnumerableObjectNotification.cs new file mode 100644 index 0000000000..3875967530 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CancelableEnumerableObjectNotification.cs @@ -0,0 +1,18 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CancelableEnumerableObjectNotification : CancelableObjectNotification> + { + protected CancelableEnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) + { + } + protected CancelableEnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CancelableObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CancelableObjectNotification.cs new file mode 100644 index 0000000000..603ee9efbb --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CancelableObjectNotification.cs @@ -0,0 +1,23 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CancelableObjectNotification : ObjectNotification, ICancelableNotification 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); + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentCopiedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopiedNotification.cs new file mode 100644 index 0000000000..55946366e5 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopiedNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentCopiedNotification : CopiedNotification + { + public ContentCopiedNotification(IContent original, IContent copy, int parentId, bool relateToOriginal, EventMessages messages) + : base(original, copy, parentId, relateToOriginal, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentCopyingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopyingNotification.cs new file mode 100644 index 0000000000..85f5d1e189 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopyingNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentCopyingNotification : CopyingNotification + { + public ContentCopyingNotification(IContent original, IContent copy, int parentId, EventMessages messages) + : base(original, copy, parentId, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedBlueprintNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedBlueprintNotification.cs new file mode 100644 index 0000000000..55e61276d0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedBlueprintNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletedBlueprintNotification : EnumerableObjectNotification + { + public ContentDeletedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentDeletedBlueprintNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedBlueprints => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedNotification.cs new file mode 100644 index 0000000000..4c0d6c3e98 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletedNotification : DeletedNotification + { + public ContentDeletedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedVersionsNotification.cs new file mode 100644 index 0000000000..19a2ae45d3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletedVersionsNotification : DeletedVersionsNotification + { + public ContentDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingNotification.cs new file mode 100644 index 0000000000..d38eb13beb --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletingNotification : DeletingNotification + { + public ContentDeletingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingVersionsNotification.cs new file mode 100644 index 0000000000..7b791eafef --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletingVersionsNotification : DeletingVersionsNotification + { + public ContentDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptiedRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptiedRecycleBinNotification.cs new file mode 100644 index 0000000000..53bc62fe91 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptiedRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentEmptiedRecycleBinNotification : EmptiedRecycleBinNotification + { + public ContentEmptiedRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptyingRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptyingRecycleBinNotification.cs new file mode 100644 index 0000000000..5d0ab67e26 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptyingRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentEmptyingRecycleBinNotification : EmptyingRecycleBinNotification + { + public ContentEmptyingRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedNotification.cs new file mode 100644 index 0000000000..50bf0d1417 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovedNotification : MovedNotification + { + public ContentMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedToRecycleBinNotification.cs new file mode 100644 index 0000000000..b9da66e9aa --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovedToRecycleBinNotification : MovedToRecycleBinNotification + { + public ContentMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingNotification.cs new file mode 100644 index 0000000000..b57c9e6cf0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovingNotification : MovingNotification + { + public ContentMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingToRecycleBinNotification.cs new file mode 100644 index 0000000000..fc5a5f401a --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovingToRecycleBinNotification : MovingToRecycleBinNotification + { + public ContentMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentNotificationExtensions.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentNotificationExtensions.cs new file mode 100644 index 0000000000..c04a04ef87 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentNotificationExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public static class ContentNotificationExtensions + { + /// + /// Determines whether a culture is being saved, during a Saving notification + /// + public static bool IsSavingCulture(this SavingNotification notification, T content, string culture) where T : IContentBase + => content.CultureInfos.TryGetValue(culture, out ContentCultureInfos cultureInfo) && cultureInfo.IsDirty(); + + /// + /// Determines whether a culture has been saved, during a Saved notification + /// + public static bool HasSavedCulture(this SavedNotification notification, T content, string culture) where T : IContentBase + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UpdatedCulture + culture); + + /// + /// Determines whether a culture is being published, during a Publishing notification + /// + public static bool IsPublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) + => IsPublishingCulture(content, culture); + + /// + /// Determines whether a culture is being unpublished, during an Publishing notification + /// + public static bool IsUnpublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) + => IsUnpublishingCulture(content, culture); + + /// + /// Determines whether a culture is being unpublished, during a Unpublishing notification + /// + public static bool IsUnpublishingCulture(this ContentUnpublishingNotification notification, IContent content, string culture) + => IsUnpublishingCulture(content, culture); + + /// + /// Determines whether a culture has been published, during a Published notification + /// + public static bool HasPublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); + + /// + /// Determines whether a culture has been unpublished, during a Published notification + /// + public static bool HasUnpublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) + => HasUnpublishedCulture(content, culture); + + /// + /// Determines whether a culture has been unpublished, during an Unpublished notification + /// + public static bool HasUnpublishedCulture(this ContentUnpublishedNotification notification, IContent content, string culture) + => HasUnpublishedCulture(content, culture); + + private static bool IsUnpublishingCulture(IContent content, string culture) + => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); + + public static bool IsPublishingCulture(IContent content, string culture) + => content.PublishCultureInfos.TryGetValue(culture, out ContentCultureInfos cultureInfo) && cultureInfo.IsDirty(); + + public static bool HasUnpublishedCulture(IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishedNotification.cs new file mode 100644 index 0000000000..f19899136e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishedNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentPublishedNotification : EnumerableObjectNotification + { + public ContentPublishedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentPublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable PublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishingNotification.cs new file mode 100644 index 0000000000..b31b8fa391 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishingNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentPublishingNotification : CancelableEnumerableObjectNotification + { + public ContentPublishingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentPublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable PublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentRolledBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentRolledBackNotification.cs new file mode 100644 index 0000000000..fbc64eebb2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentRolledBackNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentRolledBackNotification : RolledBackNotification + { + public ContentRolledBackNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentRollingBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentRollingBackNotification.cs new file mode 100644 index 0000000000..ae3065ad58 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentRollingBackNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentRollingBackNotification : RollingBackNotification + { + public ContentRollingBackNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedBlueprintNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedBlueprintNotification.cs new file mode 100644 index 0000000000..a57f278ef0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedBlueprintNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSavedBlueprintNotification : ObjectNotification + { + public ContentSavedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public IContent SavedBlueprint => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedNotification.cs new file mode 100644 index 0000000000..0a79bf1285 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSavedNotification : SavedNotification + { + public ContentSavedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavingNotification.cs new file mode 100644 index 0000000000..c32f584cc6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSavingNotification : SavingNotification + { + public ContentSavingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSendingToPublishNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSendingToPublishNotification.cs new file mode 100644 index 0000000000..9719580c62 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSendingToPublishNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSendingToPublishNotification : CancelableObjectNotification + { + public ContentSendingToPublishNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public IContent Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSentToPublishNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSentToPublishNotification.cs new file mode 100644 index 0000000000..733fbfbb93 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSentToPublishNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSentToPublishNotification : ObjectNotification + { + public ContentSentToPublishNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public IContent Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSortedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortedNotification.cs new file mode 100644 index 0000000000..fa51155f96 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortedNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSortedNotification : SortedNotification + { + public ContentSortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSortingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortingNotification.cs new file mode 100644 index 0000000000..e5b011276d --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortingNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSortingNotification : SortingNotification + { + public ContentSortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishedNotification.cs new file mode 100644 index 0000000000..2beb8f2d55 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishedNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentUnpublishedNotification : EnumerableObjectNotification + { + public ContentUnpublishedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentUnpublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable UnpublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishingNotification.cs new file mode 100644 index 0000000000..85db0015c3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishingNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentUnpublishingNotification : CancelableEnumerableObjectNotification + { + public ContentUnpublishingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentUnpublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable UnpublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CopiedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CopiedNotification.cs new file mode 100644 index 0000000000..653f3913aa --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CopiedNotification.cs @@ -0,0 +1,24 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CopiedNotification : ObjectNotification where T : class + { + protected CopiedNotification(T original, T copy, int parentId, bool relateToOriginal, EventMessages messages) : base(original, messages) + { + Copy = copy; + ParentId = parentId; + RelateToOriginal = relateToOriginal; + } + + public T Original => Target; + + public T Copy { get; } + + public int ParentId { get; } + public bool RelateToOriginal { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CopyingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CopyingNotification.cs new file mode 100644 index 0000000000..ae658577b6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CopyingNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CopyingNotification : CancelableObjectNotification where T : class + { + protected 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; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletedNotification.cs new file mode 100644 index 0000000000..99016ca073 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletedNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletedNotification : EnumerableObjectNotification + { + protected DeletedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotification.cs new file mode 100644 index 0000000000..4e8e4d5ba4 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletedVersionsNotification : DeletedVersionsNotificationBase where T : class + { + protected DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotificationBase.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotificationBase.cs new file mode 100644 index 0000000000..938a174fbe --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotificationBase.cs @@ -0,0 +1,30 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletedVersionsNotificationBase : StatefulNotification where T : class + { + protected DeletedVersionsNotificationBase(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; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs new file mode 100644 index 0000000000..2dd8e09c6b --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletingNotification : CancelableEnumerableObjectNotification + { + protected DeletingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected DeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletingVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletingVersionsNotification.cs new file mode 100644 index 0000000000..4c6d8423fd --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletingVersionsNotification.cs @@ -0,0 +1,18 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletingVersionsNotification : DeletedVersionsNotificationBase, ICancelableNotification where T : class + { + protected 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/Notifications/EmptiedRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/EmptiedRecycleBinNotification.cs new file mode 100644 index 0000000000..1859687170 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/EmptiedRecycleBinNotification.cs @@ -0,0 +1,14 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class EmptiedRecycleBinNotification : StatefulNotification where T : class + { + protected EmptiedRecycleBinNotification(EventMessages messages) => Messages = messages; + + public EventMessages Messages { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/EmptyingRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/EmptyingRecycleBinNotification.cs new file mode 100644 index 0000000000..86ee7f303d --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/EmptyingRecycleBinNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class EmptyingRecycleBinNotification : StatefulNotification, ICancelableNotification where T : class + { + protected EmptyingRecycleBinNotification(EventMessages messages) => Messages = messages; + + public EventMessages Messages { get; } + + public bool Cancel { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/EnumerableObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/EnumerableObjectNotification.cs new file mode 100644 index 0000000000..0201040ecf --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/EnumerableObjectNotification.cs @@ -0,0 +1,19 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class EnumerableObjectNotification : ObjectNotification> + { + protected EnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) + { + } + + protected EnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedNotification.cs new file mode 100644 index 0000000000..c78bfa863d --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletedNotification : DeletedNotification + { + public MediaDeletedNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedVersionsNotification.cs new file mode 100644 index 0000000000..789079bd5e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletedVersionsNotification : DeletedVersionsNotification + { + public MediaDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingNotification.cs new file mode 100644 index 0000000000..524865bbfb --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletingNotification : DeletingNotification + { + public MediaDeletingNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + + public MediaDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingVersionsNotification.cs new file mode 100644 index 0000000000..fd979b5641 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletingVersionsNotification : DeletingVersionsNotification + { + public MediaDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptiedRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptiedRecycleBinNotification.cs new file mode 100644 index 0000000000..8d984eceab --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptiedRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaEmptiedRecycleBinNotification : EmptiedRecycleBinNotification + { + public MediaEmptiedRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptyingRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptyingRecycleBinNotification.cs new file mode 100644 index 0000000000..1fd72c7d99 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptyingRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaEmptyingRecycleBinNotification : EmptyingRecycleBinNotification + { + public MediaEmptyingRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedNotification.cs new file mode 100644 index 0000000000..43f8f8a3ff --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovedNotification : MovedNotification + { + public MediaMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedToRecycleBinNotification.cs new file mode 100644 index 0000000000..bb9274f941 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovedToRecycleBinNotification : MovedToRecycleBinNotification + { + public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingNotification.cs new file mode 100644 index 0000000000..407c95918a --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovingNotification : MovingNotification + { + public MediaMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingToRecycleBinNotification.cs new file mode 100644 index 0000000000..72e20ca424 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovingToRecycleBinNotification : MovingToRecycleBinNotification + { + public MediaMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavedNotification.cs new file mode 100644 index 0000000000..4337cd1eb3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaSavedNotification : SavedNotification + { + public MediaSavedNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + + public MediaSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavingNotification.cs new file mode 100644 index 0000000000..cca0de8d19 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaSavingNotification : SavingNotification + { + public MediaSavingNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + + public MediaSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovedNotification.cs new file mode 100644 index 0000000000..35b587f73c --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovedNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovedNotification : ObjectNotification>> + { + protected MovedNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + protected MovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovedToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovedToRecycleBinNotification.cs new file mode 100644 index 0000000000..4562cdab30 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovedToRecycleBinNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovedToRecycleBinNotification : ObjectNotification>> + { + protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + protected MovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovingNotification.cs new file mode 100644 index 0000000000..2781c0ba57 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovingNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovingNotification : CancelableObjectNotification>> + { + protected MovingNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) + { + } + + protected MovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovingToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovingToRecycleBinNotification.cs new file mode 100644 index 0000000000..38e989a418 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovingToRecycleBinNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovingToRecycleBinNotification : CancelableObjectNotification>> + { + protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + protected MovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ObjectNotification.cs new file mode 100644 index 0000000000..16c57f210e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ObjectNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class ObjectNotification : StatefulNotification where T : class + { + protected ObjectNotification(T target, EventMessages messages) + { + Messages = messages; + Target = target; + } + + public EventMessages Messages { get; } + + protected T Target { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/RolledBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/RolledBackNotification.cs new file mode 100644 index 0000000000..326b224478 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/RolledBackNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class RolledBackNotification : ObjectNotification where T : class + { + protected RolledBackNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/RollingBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/RollingBackNotification.cs new file mode 100644 index 0000000000..6afa46d6f1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/RollingBackNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class RollingBackNotification : CancelableObjectNotification where T : class + { + protected RollingBackNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SavedNotification.cs new file mode 100644 index 0000000000..b6a363019f --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SavedNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SavedNotification : EnumerableObjectNotification + { + protected SavedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected SavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SavedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SavingNotification.cs new file mode 100644 index 0000000000..df84628b93 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SavingNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SavingNotification : CancelableEnumerableObjectNotification + { + protected SavingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected SavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SavedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SortedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SortedNotification.cs new file mode 100644 index 0000000000..88b0e11cf6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SortedNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SortedNotification : EnumerableObjectNotification + { + protected SortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SortedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SortingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SortingNotification.cs new file mode 100644 index 0000000000..7d040a8b28 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SortingNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SortingNotification : CancelableEnumerableObjectNotification + { + protected SortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SortedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/StatefulNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/StatefulNotification.cs new file mode 100644 index 0000000000..07831611fe --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/StatefulNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class StatefulNotification : IStatefulNotification + { + private IDictionary _state; + + /// + /// This can be used by event subscribers to store state in the notification so they easily deal with custom state data between + /// a starting ("ing") and an ending ("ed") notification + /// + public IDictionary State + { + get => _state ??= new Dictionary(); + set => _state = value; + } + } +} diff --git a/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs index 73b5c5beea..9b8a1e9c98 100644 --- a/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using NUnit.Framework; @@ -82,25 +82,10 @@ namespace Umbraco.Cms.Tests.Integration.Cache new EventDefinition>(null, MemberGroupService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, MemberGroupService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MediaService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MediaService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Moved"), - new EventDefinition>(null, MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), - new EventDefinition(null, MediaService, new RecycleBinEventArgs(Guid.NewGuid())), - - new EventDefinition>(null, ContentService, new SaveEventArgs(Enumerable.Empty()), "Saved"), - new EventDefinition>(null, ContentService, new DeleteEventArgs(Enumerable.Empty()), "Deleted"), - // not managed //new EventDefinition>(null, ContentService, new SaveEventArgs(Enumerable.Empty()), "SavedBlueprint"), //new EventDefinition>(null, ContentService, new DeleteEventArgs(Enumerable.Empty()), "DeletedBlueprint"), - new EventDefinition>(null, ContentService, new CopyEventArgs(null, null, -1)), - new EventDefinition>(null, ContentService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), - new EventDefinition(null, ContentService, new RecycleBinEventArgs(Guid.NewGuid())), - new EventDefinition>(null, ContentService, new PublishEventArgs(Enumerable.Empty()), "Published"), - new EventDefinition>(null, ContentService, new PublishEventArgs(Enumerable.Empty()), "Unpublished"), - new EventDefinition>(null, PublicAccessService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, PublicAccessService, new DeleteEventArgs(Enumerable.Empty())), diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceEventTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs similarity index 53% rename from src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceEventTests.cs rename to src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs index 24bb2b3a84..db9c2634dc 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceEventTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs @@ -1,14 +1,17 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -22,7 +25,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services PublishedRepositoryEvents = true, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)] - public class ContentServiceEventTests : UmbracoIntegrationTest + public class ContentServiceNotificationTests : UmbracoIntegrationTest { private IContentTypeService ContentTypeService => GetRequiredService(); @@ -46,6 +49,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services CreateTestData(); } + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + private void CreateTestData() { Template template = TemplateBuilder.CreateTextPageTemplate(); @@ -82,36 +93,43 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // properties: title, bodyText, keywords, description document.SetValue("title", "title-en", "en-US"); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + var savedWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame(document, saved); - Assert.IsTrue(e.IsSavingCulture(saved, "en-US")); - Assert.IsFalse(e.IsSavingCulture(saved, "fr-FR")); - } + Assert.IsTrue(notification.IsSavingCulture(saved, "en-US")); + Assert.IsFalse(notification.IsSavingCulture(saved, "fr-FR")); - void OnSaved(IContentService sender, ContentSavedEventArgs e) + savingWasCalled = true; + }; + + ContentNotificationHandler.SavedContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame(document, saved); - Assert.IsTrue(e.HasSavedCulture(saved, "en-US")); - Assert.IsFalse(e.HasSavedCulture(saved, "fr-FR")); - } + Assert.IsTrue(notification.HasSavedCulture(saved, "en-US")); + Assert.IsFalse(notification.HasSavedCulture(saved, "fr-FR")); + + savedWasCalled = true; + }; - ContentService.Saving += OnSaving; - ContentService.Saved += OnSaved; try { ContentService.Save(document); + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); } finally { - ContentService.Saving -= OnSaving; - ContentService.Saved -= OnSaved; + ContentNotificationHandler.SavingContent = null; + ContentNotificationHandler.SavedContent = null; } } @@ -120,18 +138,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { IContent document = new Content("content", -1, _contentType); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + var savedWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); saved.SetValue("title", "title"); - } - void OnSaved(IContentService sender, ContentSavedEventArgs e) + savingWasCalled = true; + }; + + ContentNotificationHandler.SavedContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame("title", document.GetValue("title")); @@ -140,18 +163,20 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual("title", propValue.EditedValue); Assert.IsNull(propValue.PublishedValue); - } - ContentService.Saving += OnSaving; - ContentService.Saved += OnSaved; + savedWasCalled = true; + }; + try { ContentService.Save(document); + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); } finally { - ContentService.Saving -= OnSaving; - ContentService.Saved -= OnSaved; + ContentNotificationHandler.SavingContent = null; + ContentNotificationHandler.SavedContent = null; } } @@ -179,36 +204,43 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // re-get - dirty properties need resetting document = ContentService.GetById(document.Id); - void OnPublishing(IContentService sender, ContentPublishingEventArgs e) + var publishingWasCalled = false; + var publishedWasCalled = false; + + ContentNotificationHandler.PublishingContent += notification => { - IContent publishing = e.PublishedEntities.First(); + IContent publishing = notification.PublishedEntities.First(); Assert.AreSame(document, publishing); - Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US")); - Assert.IsTrue(e.IsPublishingCulture(publishing, "fr-FR")); - } + Assert.IsFalse(notification.IsPublishingCulture(publishing, "en-US")); + Assert.IsTrue(notification.IsPublishingCulture(publishing, "fr-FR")); - void OnPublished(IContentService sender, ContentPublishedEventArgs e) + publishingWasCalled = true; + }; + + ContentNotificationHandler.PublishedContent += notification => { - IContent published = e.PublishedEntities.First(); + IContent published = notification.PublishedEntities.First(); Assert.AreSame(document, published); - Assert.IsFalse(e.HasPublishedCulture(published, "en-US")); - Assert.IsTrue(e.HasPublishedCulture(published, "fr-FR")); - } + Assert.IsFalse(notification.HasPublishedCulture(published, "en-US")); + Assert.IsTrue(notification.HasPublishedCulture(published, "fr-FR")); + + publishedWasCalled = true; + }; - ContentService.Publishing += OnPublishing; - ContentService.Published += OnPublished; try { ContentService.SaveAndPublish(document, "fr-FR"); + Assert.IsTrue(publishingWasCalled); + Assert.IsTrue(publishedWasCalled); } finally { - ContentService.Publishing -= OnPublishing; - ContentService.Published -= OnPublished; + ContentNotificationHandler.PublishingContent = null; + ContentNotificationHandler.PublishedContent = null; } document = ContentService.GetById(document.Id); @@ -223,18 +255,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { IContent document = new Content("content", -1, _contentType); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + var savedWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); saved.SetValue("title", "title"); - } - void OnSaved(IContentService sender, ContentSavedEventArgs e) + savingWasCalled = true; + }; + + ContentNotificationHandler.SavedContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame("title", document.GetValue("title")); @@ -243,21 +280,20 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual("title", propValue.EditedValue); Assert.AreEqual("title", propValue.PublishedValue); - } - // We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be - // used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model - // are published, but Saving is raised BEFORE. - ContentService.Saving += OnSaving; - ContentService.Saved += OnSaved; + savedWasCalled = true; + }; + try { ContentService.SaveAndPublish(document); + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); } finally { - ContentService.Saving -= OnSaving; - ContentService.Saved -= OnSaved; + ContentNotificationHandler.SavingContent = null; + ContentNotificationHandler.SavedContent = null; } } @@ -278,27 +314,28 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // re-create it document = new Content("content", -1, _contentType); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); saved.SetValue("title", "title"); - } - // We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be - // used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model - // are published, but Saving is raised BEFORE. - ContentService.Saving += OnSaving; + savingWasCalled = true; + }; + try { result = ContentService.SaveAndPublish(document); Assert.IsTrue(result.Success); // will succeed now because we were able to specify the required value in the Saving event + Assert.IsTrue(savingWasCalled); } finally { - ContentService.Saving -= OnSaving; + ContentNotificationHandler.SavingContent = null; } } @@ -328,44 +365,53 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // re-get - dirty properties need resetting document = contentService.GetById(document.Id); - void OnPublishing(IContentService sender, ContentPublishingEventArgs e) + document.UnpublishCulture("fr-FR"); + + var publishingWasCalled = false; + var publishedWasCalled = false; + + // TODO: revisit this - it was migrated when removing static events, but the expected result seems illogic - why does this test bind to Published and not Unpublished? + + ContentNotificationHandler.PublishingContent += notification => { - IContent publishing = e.PublishedEntities.First(); - - Assert.AreSame(document, publishing); - - Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US")); - Assert.IsFalse(e.IsPublishingCulture(publishing, "fr-FR")); - - Assert.IsFalse(e.IsUnpublishingCulture(publishing, "en-US")); - Assert.IsTrue(e.IsUnpublishingCulture(publishing, "fr-FR")); - } - - void OnPublished(IContentService sender, ContentPublishedEventArgs e) - { - IContent published = e.PublishedEntities.First(); + IContent published = notification.PublishedEntities.First(); Assert.AreSame(document, published); - Assert.IsFalse(e.HasPublishedCulture(published, "en-US")); - Assert.IsFalse(e.HasPublishedCulture(published, "fr-FR")); + Assert.IsFalse(notification.IsPublishingCulture(published, "en-US")); + Assert.IsFalse(notification.IsPublishingCulture(published, "fr-FR")); - Assert.IsFalse(e.HasUnpublishedCulture(published, "en-US")); - Assert.IsTrue(e.HasUnpublishedCulture(published, "fr-FR")); - } + Assert.IsFalse(notification.IsUnpublishingCulture(published, "en-US")); + Assert.IsTrue(notification.IsUnpublishingCulture(published, "fr-FR")); - document.UnpublishCulture("fr-FR"); + publishingWasCalled = true; + }; + + ContentNotificationHandler.PublishedContent += notification => + { + IContent published = notification.PublishedEntities.First(); + + Assert.AreSame(document, published); + + Assert.IsFalse(notification.HasPublishedCulture(published, "en-US")); + Assert.IsFalse(notification.HasPublishedCulture(published, "fr-FR")); + + Assert.IsFalse(notification.HasUnpublishedCulture(published, "en-US")); + Assert.IsTrue(notification.HasUnpublishedCulture(published, "fr-FR")); + + publishedWasCalled = true; + }; - ContentService.Publishing += OnPublishing; - ContentService.Published += OnPublished; try { contentService.CommitDocumentChanges(document); + Assert.IsTrue(publishingWasCalled); + Assert.IsTrue(publishedWasCalled); } finally { - ContentService.Publishing -= OnPublishing; - ContentService.Published -= OnPublished; + ContentNotificationHandler.PublishingContent = null; + ContentNotificationHandler.PublishedContent = null; } document = contentService.GetById(document.Id); @@ -373,5 +419,38 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.IsFalse(document.IsCulturePublished("fr-FR")); Assert.IsTrue(document.IsCulturePublished("en-US")); } + + public class ContentNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler + { + public void Handle(ContentSavingNotification notification) => SavingContent?.Invoke(notification); + + public void Handle(ContentSavedNotification notification) => SavedContent?.Invoke(notification); + + public void Handle(ContentPublishingNotification notification) => PublishingContent?.Invoke(notification); + + public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); + + public void Handle(ContentUnpublishingNotification notification) => UnpublishingContent?.Invoke(notification); + + public void Handle(ContentUnpublishedNotification notification) => UnpublishedContent?.Invoke(notification); + + public static Action SavingContent { get; set; } + + public static Action SavedContent { get; set; } + + public static Action PublishingContent { get; set; } + + public static Action PublishedContent { get; set; } + + public static Action UnpublishingContent { get; set; } + + public static Action UnpublishedContent { get; set; } + } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index 5421add491..819e41c257 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -9,6 +9,7 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; @@ -20,6 +21,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Extensions; @@ -73,6 +75,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [SetUp] public void Setup() => ContentRepositoryBase.ThrowOnWarning = true; + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + [TearDown] public void Teardown() => ContentRepositoryBase.ThrowOnWarning = false; @@ -1076,7 +1083,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [Test] public void Can_Publish_Content_WithEvents() { - ContentService.Publishing += ContentServiceOnPublishing; + bool publishingWasCalled = false; + + ContentNotificationHandler.PublishingContent = notification => + { + Assert.AreEqual(1, notification.PublishedEntities.Count()); + IContent entity = notification.PublishedEntities.First(); + Assert.AreEqual("foo", entity.Name); + + IContent e = ContentService.GetById(entity.Id); + Assert.AreEqual("Home", e.Name); + + publishingWasCalled = true; + }; // tests that during 'publishing' event, what we get from the repo is the 'old' content, // because 'publishing' fires before the 'saved' event ie before the content is actually @@ -1094,23 +1113,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services IContent e = ContentService.GetById(content.Id); Assert.AreEqual("foo", e.Name); + + Assert.IsTrue(publishingWasCalled); } finally { - ContentService.Publishing -= ContentServiceOnPublishing; + ContentNotificationHandler.PublishingContent = null; } } - private void ContentServiceOnPublishing(IContentService sender, PublishEventArgs args) - { - Assert.AreEqual(1, args.PublishedEntities.Count()); - IContent entity = args.PublishedEntities.First(); - Assert.AreEqual("foo", entity.Name); - - IContent e = ContentService.GetById(entity.Id); - Assert.AreEqual("Home", e.Name); - } - [Test] public void Can_Not_Publish_Invalid_Cultures() { @@ -1899,26 +1910,31 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services public void Can_Copy_And_Modify_Content_With_Events() { // see https://github.com/umbraco/Umbraco-CMS/issues/5513 - static void Copying(IContentService sender, CopyEventArgs args) - { - args.Copy.SetValue("title", "1"); - args.Original.SetValue("title", "2"); - } - static void Copied(IContentService sender, CopyEventArgs args) + bool copyingWasCalled = false; + bool copiedWasCalled = false; + + ContentNotificationHandler.CopyingContent = notification => { - string copyVal = args.Copy.GetValue("title"); - string origVal = args.Original.GetValue("title"); + notification.Copy.SetValue("title", "1"); + notification.Original.SetValue("title", "2"); + + copyingWasCalled = true; + }; + + ContentNotificationHandler.CopiedContent = notification => + { + string copyVal = notification.Copy.GetValue("title"); + string origVal = notification.Original.GetValue("title"); Assert.AreEqual("1", copyVal); Assert.AreEqual("2", origVal); - } + + copiedWasCalled = true; + }; try { - ContentService.Copying += Copying; - ContentService.Copied += Copied; - Template template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -1930,11 +1946,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services IContent copy = ContentService.Copy(content, content.ParentId, false, Constants.Security.SuperUserId); Assert.AreEqual("1", copy.GetValue("title")); + + Assert.IsTrue(copyingWasCalled); + Assert.IsTrue(copiedWasCalled); } finally { - ContentService.Copying -= Copying; - ContentService.Copied -= Copied; + ContentNotificationHandler.CopyingContent = null; + ContentNotificationHandler.CopiedContent = null; } } @@ -3275,5 +3294,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services return content; } + + public class ContentNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler + { + public void Handle(ContentPublishingNotification notification) => PublishingContent?.Invoke(notification); + + public void Handle(ContentCopyingNotification notification) => CopyingContent?.Invoke(notification); + + public void Handle(ContentCopiedNotification notification) => CopiedContent?.Invoke(notification); + + public static Action PublishingContent { get; set; } + + public static Action CopyingContent { get; set; } + + public static Action CopiedContent { get; set; } + } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs index 556a909255..dff4e80e4c 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs @@ -6,11 +6,13 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -30,6 +32,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services private ContentTypeService ContentTypeService => (ContentTypeService)GetRequiredService(); + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler(); + [Test] public void CanSaveAndGetIsElement() { @@ -112,7 +117,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [Test] public void Deleting_Content_Types_With_Hierarchy_Of_Content_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items_1() { - ContentService.Trashed += ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = MovedContentToRecycleBin; try { @@ -151,14 +156,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services } finally { - ContentService.Trashed -= ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = null; } } [Test] public void Deleting_Content_Types_With_Hierarchy_Of_Content_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items_2() { - ContentService.Trashed += ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = MovedContentToRecycleBin; try { @@ -194,13 +199,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services } finally { - ContentService.Trashed -= ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = null; } } - private void ContentServiceOnTrashed(IContentService sender, MoveEventArgs e) + private void MovedContentToRecycleBin(ContentMovedToRecycleBinNotification notification) { - foreach (MoveEventInfo item in e.MoveInfoCollection) + foreach (MoveEventInfo item in notification.MoveInfoCollection) { // if this item doesn't exist then Fail! IContent exists = ContentService.GetById(item.Entity.Id); @@ -1729,5 +1734,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services return list.ToArray(); } + + public class ContentNotificationHandler : + INotificationHandler + { + public void Handle(ContentMovedToRecycleBinNotification notification) => MovedContentToRecycleBin?.Invoke(notification); + + public static Action MovedContentToRecycleBin { get; set; } + } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs index ad1d353039..b28e22e50a 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -6,10 +6,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using NUnit.Framework; +using Umbraco.Cms.Core.DependencyInjection; 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.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -25,6 +27,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services private IMediaTypeService MediaTypeService => GetRequiredService(); + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler(); + [Test] public void Get_With_Missing_Guid() { @@ -94,7 +99,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [Test] public void Deleting_Media_Types_With_Hierarchy_Of_Media_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items() { - MediaService.Trashed += MediaServiceOnTrashed; + ContentNotificationHandler.MovedMediaToRecycleBin = MovedMediaToRecycleBin; try { @@ -130,13 +135,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services } finally { - MediaService.Trashed -= MediaServiceOnTrashed; + ContentNotificationHandler.MovedMediaToRecycleBin = null; } } - private void MediaServiceOnTrashed(IMediaService sender, MoveEventArgs e) + private void MovedMediaToRecycleBin(MediaMovedToRecycleBinNotification notification) { - foreach (MoveEventInfo item in e.MoveInfoCollection) + foreach (MoveEventInfo item in notification.MoveInfoCollection) { // if this item doesn't exist then Fail! IMedia exists = MediaService.GetById(item.Entity.Id); @@ -213,5 +218,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreNotEqual(clonedMediaType.PropertyTypes.First(x => x.Alias.StartsWith("umbracoFile")).Id, originalMediaType.PropertyTypes.First(x => x.Alias.StartsWith("umbracoFile")).Id); Assert.AreNotEqual(clonedMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id, originalMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id); } + + public class ContentNotificationHandler : + INotificationHandler + { + public void Handle(MediaMovedToRecycleBinNotification notification) => MovedMediaToRecycleBin?.Invoke(notification); + + public static Action MovedMediaToRecycleBin { get; set; } + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index b06144d555..745e8309d7 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; @@ -50,7 +51,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components var fs = new FileSystems(mock.Object, loggerFactory.CreateLogger(), loggerFactory, IOHelper, Options.Create(globalSettings), Mock.Of()); var coreDebug = new CoreDebugSettings(); IMediaFileSystem mediaFileSystem = Mock.Of(); - var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); + IEventAggregator eventAggregator = Mock.Of(); + var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs index e1b9ec9264..590ff58222 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs @@ -7,7 +7,7 @@ using System.Linq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Compose; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Cannot_Have_Null_Udi() { - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var json = GetBlockListJson(null, string.Empty); Assert.Throws(() => component.ReplaceBlockListUdis(json)); } @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors var expected = ReplaceGuids(json, guids, ContentGuid1, ContentGuid2, SettingsGuid1); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); @@ -75,7 +75,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors // get the json with the subFeatures as escaped var json = GetBlockListJson(innerJsonEscaped); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); // the expected result is that the subFeatures data is no longer escaped @@ -119,7 +119,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors SubContentGuid2, SubSettingsGuid1); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); @@ -147,7 +147,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors var json = GetBlockListJson(complexEditorJsonEscaped); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); // the expected result is that the subFeatures data is no longer escaped diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs index d132f346b4..0ada6a20dd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -4,7 +4,7 @@ using System; using Newtonsoft.Json; using NUnit.Framework; -using Umbraco.Cms.Core.Compose; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors { @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Invalid_Json() { - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); Assert.DoesNotThrow(() => component.CreateNestedContentKeys("this is not json", true)); } @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -78,7 +78,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -126,7 +126,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -230,7 +230,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -248,7 +248,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors {""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} ]"; - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON @@ -291,7 +291,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors } ]"; - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON for each item @@ -390,7 +390,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors } ]"; - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON for each item diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs index 17bc26dc23..43d14677fa 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs @@ -87,7 +87,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping Mock.Of(), Mock.Of>(), instance, - Mock.Of() + Mock.Of(), + Mock.Of() ); } diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index 6b6be30dab..bb26b2e70e 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Models var contentSettings = new ContentSettings(); var mediaFileSystem = new MediaFileSystem(Mock.Of(), scheme, loggerFactory.CreateLogger(), ShortStringHelper); - var ignored = new FileUploadPropertyEditor(loggerFactory, mediaFileSystem, Microsoft.Extensions.Options.Options.Create(contentSettings), DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer); + var ignored = new FileUploadPropertyEditor(loggerFactory, mediaFileSystem, Microsoft.Extensions.Options.Options.Create(contentSettings), DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer, ContentService); var media = MockedMedia.CreateMediaImage(mediaType, -1); media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index a3a54a5fcf..7149938b39 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -38,8 +38,8 @@ namespace Umbraco.Tests.Routing var dataTypeService = Mock.Of(); var propertyEditors = new MediaUrlGeneratorCollection(new IMediaUrlGenerator[] { - new FileUploadPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer), - new ImageCropperPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, IOHelper, ShortStringHelper, LocalizedTextService, UploadAutoFillProperties, JsonNetSerializer), + new FileUploadPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer, ContentService), + new ImageCropperPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, IOHelper, ShortStringHelper, LocalizedTextService, UploadAutoFillProperties, JsonNetSerializer, ContentService), }); _mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors, UriUtility); } diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index d8d86c3162..718b54a939 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Models; @@ -16,12 +17,12 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Extensions; @@ -49,6 +50,14 @@ namespace Umbraco.Tests.Scoping Builder.Services.AddUnique(f => Mock.Of()); Builder.WithCollectionBuilder() .Add(() => Builder.TypeLoader.GetCacheRefreshers()); + Builder.AddNotificationHandler(); + } + + public class NotificationHandler : INotificationHandler + { + public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); + + public static Action PublishedContent { get; set; } } public override void TearDown() @@ -58,17 +67,9 @@ namespace Umbraco.Tests.Scoping _distributedCacheBinder?.UnbindEvents(); _distributedCacheBinder = null; - _onPublishedAssertAction = null; - ContentService.Published -= OnPublishedAssert; + NotificationHandler.PublishedContent = null; } - private void OnPublishedAssert(IContentService sender, PublishEventArgs args) - { - _onPublishedAssertAction?.Invoke(); - } - - private Action _onPublishedAssertAction; - protected override IPublishedSnapshotService CreatePublishedSnapshotService(GlobalSettings globalSettings = null) { var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; @@ -149,7 +150,7 @@ namespace Umbraco.Tests.Scoping // event handler var evented = 0; - _onPublishedAssertAction = () => + NotificationHandler.PublishedContent = notification => { evented++; @@ -171,8 +172,6 @@ namespace Umbraco.Tests.Scoping Assert.IsNotNull(x); Assert.AreEqual("name", x.Name(VariationContextAccessor)); - ContentService.Published += OnPublishedAssert; - using (var scope = ScopeProvider.CreateScope()) { item.Name = "changed"; diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 4a47f3689e..f943eada80 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -7,13 +7,13 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Infrastructure.Sync; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Extensions; @@ -41,6 +41,7 @@ namespace Umbraco.Tests.Scoping Builder.Services.AddUnique(f => Mock.Of()); Builder.WithCollectionBuilder() .Add(() => Builder.TypeLoader.GetCacheRefreshers()); + Builder.AddNotificationHandler(); } protected override void ComposeSettings() @@ -54,24 +55,24 @@ namespace Umbraco.Tests.Scoping Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings)); } + + public class NotificationHandler : INotificationHandler + { + public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); + + public static Action PublishedContent { get; set; } + } + [TearDown] public void Teardown() { _distributedCacheBinder?.UnbindEvents(); _distributedCacheBinder = null; - _onPublishedAssertAction = null; - ContentService.Published -= OnPublishedAssert; + NotificationHandler.PublishedContent = null; SafeXmlReaderWriter.Cloning = null; } - private void OnPublishedAssert(IContentService sender, PublishEventArgs args) - { - _onPublishedAssertAction?.Invoke(); - } - - private Action _onPublishedAssertAction; - // in 7.6, content.Instance // .XmlContent - comes from .XmlContentInternal and is cached in context items for current request // .XmlContentInternal - the actual main xml document @@ -111,7 +112,7 @@ namespace Umbraco.Tests.Scoping // event handler var evented = 0; - _onPublishedAssertAction = () => + NotificationHandler.PublishedContent = notification => { evented++; @@ -128,8 +129,6 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(beforeOuterXml, xml.OuterXml); }; - ContentService.Published += OnPublishedAssert; - using (var scope = ScopeProvider.CreateScope()) { ServiceContext.ContentService.SaveAndPublish(item); // should create an xml clone diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 6f3ac08219..c8be036d05 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -8,6 +8,7 @@ using NPoco; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Migrations.Install; @@ -86,7 +87,8 @@ namespace Umbraco.Tests.TestHelpers fileSystems ??= new FileSystems(Current.Factory, loggerFactory.CreateLogger(), loggerFactory, TestHelper.IOHelper, globalSettings, TestHelper.GetHostingEnvironment()); var coreDebug = TestHelper.CoreDebugSettings; var mediaFileSystem = Mock.Of(); - return new ScopeProvider(databaseFactory, fileSystems, Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); + var eventAggregator = Mock.Of(); + return new ScopeProvider(databaseFactory, fileSystems, Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 6150bf43e1..15634c6083 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -160,6 +160,7 @@ namespace Umbraco.Tests.Testing protected UmbracoMapper Mapper => Factory.GetRequiredService(); protected IHttpContextAccessor HttpContextAccessor => Factory.GetRequiredService(); + protected IContentService ContentService => Factory.GetRequiredService(); protected IRuntimeState RuntimeState => MockRuntimeState(RuntimeLevel.Run); private ILoggerFactory _loggerFactory; diff --git a/src/Umbraco.Web.UI.NetCore/Program.cs b/src/Umbraco.Web.UI.NetCore/Program.cs index 89ce7d92bc..7a6ee4dcfd 100644 --- a/src/Umbraco.Web.UI.NetCore/Program.cs +++ b/src/Umbraco.Web.UI.NetCore/Program.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -7,18 +8,20 @@ namespace Umbraco.Cms.Web.UI.NetCore public class Program { public static void Main(string[] args) - { - CreateHostBuilder(args) + => CreateHostBuilder(args) .Build() .Run(); - } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureLogging(x => - { - x.ClearProviders(); - }) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); +#if DEBUG + .ConfigureAppConfiguration(config + => config.AddJsonFile( + "appsettings.Local.json", + optional: true, + reloadOnChange: true)) +#endif + .ConfigureLogging(x => x.ClearProviders()) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); } } diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index 5aeb0e8bb1..2324c4c252 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -81,6 +81,7 @@ false + false diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index 937dc158fd..53a30cbcea 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -38,8 +38,8 @@ "ConvertUrlsToAscii": "try" }, "RuntimeMinification": { - "dataFolder": "App_Data/TEMP/Smidge", - "version": "637513704485999245" + "dataFolder": "umbraco/Data/TEMP/Smidge", + "version": "1" }, "Security": { "KeepUserLoggedIn": false,