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