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