From 3ecf8eb9ea845e33a16858de37a4f2afd048ae69 Mon Sep 17 00:00:00 2001 From: Mole Date: Mon, 12 Apr 2021 15:03:40 +0200 Subject: [PATCH] Migrate TreeChanged to notification in ContentService --- .../Cache/DistributedCacheBinder_Handlers.cs | 12 +- .../Compose/NotificationsComposer.cs | 1 + .../Services/Implement/ContentService.cs | 346 ++++++++++-------- 3 files changed, 204 insertions(+), 155 deletions(-) diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 0fe9b62e4c..9bda0cd8c5 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs @@ -44,7 +44,8 @@ namespace Umbraco.Cms.Core.Cache INotificationHandler, INotificationHandler, INotificationHandler, - INotificationHandler + INotificationHandler, + INotificationHandler { private List _unbinders; @@ -83,10 +84,6 @@ namespace Umbraco.Cms.Core.Cache Bind(() => MemberTypeService.Changed += MemberTypeService_Changed, () => MemberTypeService.Changed -= MemberTypeService_Changed); - // bind to content events - Bind(() => ContentService.TreeChanged += ContentService_TreeChanged,// handles all content changes - () => ContentService.TreeChanged -= ContentService_TreeChanged); - // TreeChanged should also deal with this //Bind(() => ContentService.SavedBlueprint += ContentService_SavedBlueprint, // () => ContentService.SavedBlueprint -= ContentService_SavedBlueprint); @@ -124,9 +121,10 @@ namespace Umbraco.Cms.Core.Cache { } - private void ContentService_TreeChanged(IContentService sender, TreeChange.EventArgs args) + + public void Handle(ContentTreeChangeNotification notification) { - _distributedCache.RefreshContentCache(args.Changes.ToArray()); + _distributedCache.RefreshContentCache(notification.Changes.ToArray()); } //private void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs e) diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs index 3fb7bd34ac..97fcb37bf8 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs @@ -90,6 +90,7 @@ namespace Umbraco.Cms.Core.Compose .AddNotificationHandler() .AddNotificationHandler() .AddNotificationHandler() + .AddNotificationHandler() ; // add notification handlers for auditing builder diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index 6cf8f5c264..97e0bf6613 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -735,34 +735,39 @@ namespace Umbraco.Cms.Core.Services.Implement /// public OperationResult Save(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId, bool raiseEvents = true) { - var publishedState = content.PublishedState; + PublishedState publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) + { throw new InvalidOperationException($"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method."); + } if (content.Name != null && content.Name.Length > 255) { throw new InvalidOperationException($"Content with the name {content.Name} cannot be more than 255 characters in length."); } - var evtMsgs = EventMessagesFactory.Get(); + EventMessages eventMessages = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - var savingNotification = new ContentSavingNotification(content, evtMsgs); + var savingNotification = new ContentSavingNotification(content, eventMessages); if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); - return OperationResult.Cancel(evtMsgs); + return OperationResult.Cancel(eventMessages); } scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); if (content.HasIdentity == false) + { content.CreatorId = userId; + } + content.WriterId = userId; //track the cultures that have changed - var culturesChanging = content.ContentType.VariesByCulture() + List culturesChanging = content.ContentType.VariesByCulture() ? content.CultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; // TODO: Currently there's no way to change track which variant properties have changed, we only have change @@ -774,10 +779,9 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Notifications.Publish(new ContentSavedNotification(content, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new ContentSavedNotification(content, eventMessages).WithStateFrom(savingNotification)); } - var changeType = TreeChangeTypes.RefreshNode; - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, eventMessages)); if (culturesChanging != null) { @@ -792,31 +796,32 @@ namespace Umbraco.Cms.Core.Services.Implement scope.Complete(); } - return OperationResult.Succeed(evtMsgs); + return OperationResult.Succeed(eventMessages); } /// public OperationResult Save(IEnumerable contents, int userId = Cms.Core.Constants.Security.SuperUserId, bool raiseEvents = true) { - var evtMsgs = EventMessagesFactory.Get(); - var contentsA = contents.ToArray(); + EventMessages eventMessages = EventMessagesFactory.Get(); + IContent[] contentsA = contents.ToArray(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - var savingNotification = new ContentSavingNotification(contentsA, evtMsgs); + var savingNotification = new ContentSavingNotification(contentsA, eventMessages); if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); - return OperationResult.Cancel(evtMsgs); + return OperationResult.Cancel(eventMessages); } - var treeChanges = contentsA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)); - scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - foreach (var content in contentsA) + foreach (IContent content in contentsA) { if (content.HasIdentity == false) + { content.CreatorId = userId; + } + content.WriterId = userId; _documentRepository.Save(content); @@ -824,15 +829,15 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Notifications.Publish(new ContentSavedNotification(contentsA, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new ContentSavedNotification(contentsA, eventMessages).WithStateFrom(savingNotification)); } - scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(contentsA, TreeChangeTypes.RefreshNode, eventMessages)); Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Saved multiple content"); scope.Complete(); } - return OperationResult.Succeed(evtMsgs); + return OperationResult.Succeed(eventMessages); } /// @@ -1066,11 +1071,13 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// + /// /// /// /// /// /// + /// /// /// /// @@ -1079,21 +1086,34 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, - EventMessages evtMsgs, IReadOnlyCollection allLangs, + EventMessages eventMessages, 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 (evtMsgs == null) throw new ArgumentNullException(nameof(evtMsgs)); + if (scope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + + if (eventMessages == null) + { + throw new ArgumentNullException(nameof(eventMessages)); + } PublishResult publishResult = null; PublishResult unpublishResult = null; // nothing set = republish it all if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing) + { content.PublishedState = PublishedState.Publishing; + } // State here is either Publishing or Unpublishing // Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later @@ -1110,7 +1130,7 @@ namespace Umbraco.Cms.Core.Services.Implement : null; var isNew = !content.HasIdentity; - var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch; + TreeChangeTypes changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch; var previouslyPublished = content.HasIdentity && content.Published; //inline method to persist the document with the documentRepository since this logic could be called a couple times below @@ -1118,7 +1138,10 @@ namespace Umbraco.Cms.Core.Services.Implement { // save, always if (c.HasIdentity == false) + { c.CreatorId = userId; + } + c.WriterId = userId; // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing @@ -1134,11 +1157,11 @@ 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, allLangs, notificationState); + publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, eventMessages, allLangs, notificationState); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! - publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, evtMsgs); + publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages); //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos.Count == 0) @@ -1158,7 +1181,9 @@ namespace Umbraco.Cms.Core.Services.Implement { // in a branch, just give up if (branchOne && !branchRoot) + { return publishResult; + } //check for mandatory culture missing, and then unpublish document as a whole if (publishResult.Result == PublishResultType.FailedPublishMandatoryCultureMissing) @@ -1181,9 +1206,11 @@ namespace Umbraco.Cms.Core.Services.Implement if (unpublishing) // won't happen in a branch { - var newest = GetById(content.Id); // ensure we have the newest version - in scope + IContent newest = GetById(content.Id); // ensure we have the newest version - in scope if (content.VersionId != newest.VersionId) - return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, evtMsgs, content); + { + return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages, content); + } if (content.Published) { @@ -1191,9 +1218,11 @@ namespace Umbraco.Cms.Core.Services.Implement // handling events, business rules, etc // note: StrategyUnpublish flips the PublishedState to Unpublishing! // note: This unpublishes the entire document (not different variants) - unpublishResult = StrategyCanUnpublish(scope, content, evtMsgs); + unpublishResult = StrategyCanUnpublish(scope, content, eventMessages); if (unpublishResult.Success) - unpublishResult = StrategyUnpublish(content, evtMsgs); + { + unpublishResult = StrategyUnpublish(content, eventMessages); + } else { // reset published state from temp values (publishing, unpublishing) to original value @@ -1219,7 +1248,7 @@ namespace Umbraco.Cms.Core.Services.Implement // raise the Saved event, always if (raiseEvents) { - scope.Notifications.Publish(new ContentSavedNotification(content, evtMsgs).WithState(notificationState)); + scope.Notifications.Publish(new ContentSavedNotification(content, eventMessages).WithState(notificationState)); } if (unpublishing) // we have tried to unpublish - won't happen in a branch @@ -1227,8 +1256,8 @@ namespace Umbraco.Cms.Core.Services.Implement if (unpublishResult.Success) // and succeeded, trigger events { // events and audit - scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs).WithState(notificationState)); - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); + scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState)); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); if (culturesUnpublishing != null) { @@ -1249,23 +1278,23 @@ namespace Umbraco.Cms.Core.Services.Implement //log that the whole content item has been unpublished due to mandatory culture unpublished Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)"); - return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, evtMsgs, content); + return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, eventMessages, content); case PublishResultType.SuccessUnpublishCulture: //occurs when the last culture is unpublished Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)"); - return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, evtMsgs, content); + return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages, content); } } Audit(AuditType.Unpublish, userId, content.Id); - return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content); + return new PublishResult(PublishResultType.SuccessUnpublish, eventMessages, content); } // or, failed - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - return new PublishResult(PublishResultType.FailedUnpublish, evtMsgs, content); // bah + scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages)); + return new PublishResult(PublishResultType.FailedUnpublish, eventMessages, content); // bah } if (publishing) // we have tried to publish @@ -1273,16 +1302,20 @@ namespace Umbraco.Cms.Core.Services.Implement if (publishResult.Success) // and succeeded, trigger events { if (isNew == false && previouslyPublished == false) + { changeType = TreeChangeTypes.RefreshBranch; // whole branch + } else if (isNew == false && previouslyPublished) + { changeType = TreeChangeTypes.RefreshNode; // single node + } // invalidate the node/branch if (!branchOne) // for branches, handled by SaveAndPublishBranch { - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - scope.Notifications.Publish(new ContentPublishedNotification(content, evtMsgs).WithState(notificationState)); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages)); + scope.Notifications.Publish(new ContentPublishedNotification(content, eventMessages).WithState(notificationState)); } // it was not published and now is... descendants that were 'published' (but @@ -1290,8 +1323,8 @@ namespace Umbraco.Cms.Core.Services.Implement // but back as 'published' nevertheless if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id)) { - var descendants = GetPublishedDescendantsLocked(content).ToArray(); - scope.Notifications.Publish(new ContentPublishedNotification(descendants, evtMsgs).WithState(notificationState)); + IContent[] descendants = GetPublishedDescendantsLocked(content).ToArray(); + scope.Notifications.Publish(new ContentPublishedNotification(descendants, eventMessages).WithState(notificationState)); } switch (publishResult.Result) @@ -1325,7 +1358,9 @@ namespace Umbraco.Cms.Core.Services.Implement // should not happen if (branchOne && !branchRoot) + { throw new PanicException("branchOne && !branchRoot - should not happen"); + } //if publishing didn't happen or if it has failed, we still need to log which cultures were saved if (!branchOne && (publishResult == null || !publishResult.Success)) @@ -1344,7 +1379,7 @@ namespace Umbraco.Cms.Core.Services.Implement } // or, failed - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages)); return publishResult; } @@ -1632,32 +1667,46 @@ namespace Umbraco.Cms.Core.Services.Implement Func, IReadOnlyCollection, bool> publishCultures, int userId = Cms.Core.Constants.Security.SuperUserId) { - if (shouldPublish == null) throw new ArgumentNullException(nameof(shouldPublish)); - if (publishCultures == null) throw new ArgumentNullException(nameof(publishCultures)); + if (shouldPublish == null) + { + throw new ArgumentNullException(nameof(shouldPublish)); + } - var evtMsgs = EventMessagesFactory.Get(); + if (publishCultures == null) + { + throw new ArgumentNullException(nameof(publishCultures)); + } + + EventMessages eventMessages = EventMessagesFactory.Get(); var results = new List(); var publishedDocuments = new List(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); var allLangs = _languageRepository.GetMany().ToList(); if (!document.HasIdentity) + { throw new InvalidOperationException("Cannot not branch-publish a new document."); + } - var publishedState = document.PublishedState; + PublishedState publishedState = document.PublishedState; if (publishedState == PublishedState.Publishing) + { throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch."); + } // deal with the branch root - if it fails, abort - var result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, evtMsgs, userId, allLangs); + PublishResult result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs); if (result != null) { results.Add(result); - if (!result.Success) return results; + if (!result.Success) + { + return results; + } } // deal with descendants @@ -1672,7 +1721,7 @@ namespace Umbraco.Cms.Core.Services.Implement count = 0; // important to order by Path ASC so make it explicit in case defaults change // ReSharper disable once RedundantArgumentDefaultValue - foreach (var d in GetPagedDescendants(document.Id, page, pageSize, out _, ordering: Ordering.By("Path", Direction.Ascending))) + foreach (IContent d in GetPagedDescendants(document.Id, page, pageSize, out _, ordering: Ordering.By("Path", Direction.Ascending))) { count++; @@ -1684,11 +1733,14 @@ namespace Umbraco.Cms.Core.Services.Implement } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, evtMsgs, userId, allLangs); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs); if (result != null) { results.Add(result); - if (result.Success) continue; + if (result.Success) + { + continue; + } } // if we could not publish the document, cut its branch @@ -1702,8 +1754,8 @@ 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.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, evtMsgs)); + scope.Notifications.Publish(new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages)); + scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages)); scope.Complete(); } @@ -1753,14 +1805,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// public OperationResult Delete(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages eventMessages = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(content, evtMsgs))) + if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(content, eventMessages))) { scope.Complete(); - return OperationResult.Cancel(evtMsgs); + return OperationResult.Cancel(eventMessages); } scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); @@ -1770,18 +1822,18 @@ namespace Umbraco.Cms.Core.Services.Implement // just raise the event if (content.Trashed == false && content.Published) { - scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs)); + scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages)); } - DeleteLocked(scope, content, evtMsgs); + DeleteLocked(scope, content, eventMessages); - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.Remove).ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.Remove, eventMessages)); Audit(AuditType.Delete, userId, content.Id); scope.Complete(); } - return OperationResult.Succeed(evtMsgs); + return OperationResult.Succeed(eventMessages); } private void DeleteLocked(IScope scope, IContent content, EventMessages evtMsgs) @@ -1887,21 +1939,21 @@ namespace Umbraco.Cms.Core.Services.Implement /// public OperationResult MoveToRecycleBin(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages eventMessages = EventMessagesFactory.Get(); var moves = new List<(IContent, string)>(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); var originalPath = content.Path; var moveEventInfo = new MoveEventInfo(content, originalPath, Cms.Core.Constants.System.RecycleBinContent); - var movingToRecycleBinNotification = new ContentMovingToRecycleBinNotification(moveEventInfo, evtMsgs); + var movingToRecycleBinNotification = new ContentMovingToRecycleBinNotification(moveEventInfo, eventMessages); if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) { scope.Complete(); - return OperationResult.Cancel(evtMsgs); // causes rollback + return OperationResult.Cancel(eventMessages); // causes rollback } // if it's published we may want to force-unpublish it - that would be backward-compatible... but... @@ -1911,19 +1963,19 @@ namespace Umbraco.Cms.Core.Services.Implement //{ } PerformMoveLocked(content, Cms.Core.Constants.System.RecycleBinContent, null, userId, moves, true); - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); - var moveInfo = moves + MoveEventInfo[] moveInfo = moves .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfo, evtMsgs).WithStateFrom(movingToRecycleBinNotification)); + scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfo, eventMessages).WithStateFrom(movingToRecycleBinNotification)); Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin"); scope.Complete(); } - return OperationResult.Succeed(evtMsgs); + return OperationResult.Succeed(eventMessages); } /// @@ -1946,21 +1998,23 @@ namespace Umbraco.Cms.Core.Services.Implement return; } - var evtMsgs = EventMessagesFactory.Get(); + EventMessages eventMessages = EventMessagesFactory.Get(); var moves = new List<(IContent, string)>(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - var parent = parentId == Cms.Core.Constants.System.Root ? null : GetById(parentId); + IContent parent = parentId == Cms.Core.Constants.System.Root ? null : GetById(parentId); if (parentId != Cms.Core.Constants.System.Root && (parent == null || parent.Trashed)) + { throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback + } var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); - var movingNotification = new ContentMovingNotification(moveEventInfo, evtMsgs); + var movingNotification = new ContentMovingNotification(moveEventInfo, eventMessages); if (scope.Notifications.PublishCancelable(movingNotification)) { scope.Complete(); @@ -1984,13 +2038,13 @@ namespace Umbraco.Cms.Core.Services.Implement PerformMoveLocked(content, parentId, parent, userId, moves, trashed); - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); - var moveInfo = moves //changes + MoveEventInfo[] moveInfo = moves //changes .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - scope.Notifications.Publish(new ContentMovedNotification(moveInfo, evtMsgs).WithStateFrom(movingNotification)); + scope.Notifications.Publish(new ContentMovedNotification(moveInfo, eventMessages).WithStateFrom(movingNotification)); Audit(AuditType.Move, userId, content.Id); @@ -2064,9 +2118,9 @@ namespace Umbraco.Cms.Core.Services.Implement public OperationResult EmptyRecycleBin(int userId = Cms.Core.Constants.Security.SuperUserId) { var deleted = new List(); - var evtMsgs = EventMessagesFactory.Get(); + EventMessages eventMessages = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); @@ -2075,30 +2129,30 @@ 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 emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(evtMsgs); + var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(eventMessages); if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) { scope.Complete(); - return OperationResult.Cancel(evtMsgs); + return OperationResult.Cancel(eventMessages); } // emptying the recycle bin means deleting whatever is in there - do it properly! - var query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.RecycleBinContent); - var contents = _documentRepository.Get(query).ToArray(); - foreach (var content in contents) + IQuery query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.RecycleBinContent); + IContent[] contents = _documentRepository.Get(query).ToArray(); + foreach (IContent content in contents) { - DeleteLocked(scope, content, evtMsgs); + DeleteLocked(scope, content, eventMessages); deleted.Add(content); } - scope.Notifications.Publish(new ContentEmptiedRecycleBinNotification(evtMsgs).WithStateFrom(emptyingRecycleBinNotification)); - scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); + scope.Notifications.Publish(new ContentEmptiedRecycleBinNotification(eventMessages).WithStateFrom(emptyingRecycleBinNotification)); + scope.Notifications.Publish(new ContentTreeChangeNotification(deleted, TreeChangeTypes.Remove, eventMessages)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.RecycleBinContent, "Recycle bin emptied"); scope.Complete(); } - return OperationResult.Succeed(evtMsgs); + return OperationResult.Succeed(eventMessages); } #endregion @@ -2131,14 +2185,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(); + EventMessages eventMessages = EventMessagesFactory.Get(); - var copy = content.DeepCloneWithResetIdentities(); + IContent copy = content.DeepCloneWithResetIdentities(); copy.ParentId = parentId; - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, evtMsgs))) + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, eventMessages))) { scope.Complete(); return null; @@ -2155,12 +2209,15 @@ namespace Umbraco.Cms.Core.Services.Implement // a copy is not published (but not really unpublishing either) // update the create author and last edit author if (copy.Published) + { copy.Published = false; + } + copy.CreatorId = userId; copy.WriterId = userId; //get the current permissions, if there are any explicit ones they need to be copied - var currentPermissions = GetPermissions(content); + EntityPermissionCollection currentPermissions = GetPermissions(content); currentPermissions.RemoveWhere(p => p.IsDefaultPermissions); // save and flush because we need the ID for the recursive Copying events @@ -2184,16 +2241,19 @@ namespace Umbraco.Cms.Core.Services.Implement var total = long.MaxValue; while (page * pageSize < total) { - var descendants = GetPagedDescendants(content.Id, page++, pageSize, out total); - foreach (var descendant in descendants) + IEnumerable descendants = GetPagedDescendants(content.Id, page++, pageSize, out total); + foreach (IContent descendant in descendants) { // if parent has not been copied, skip, else gets its copy id - if (idmap.TryGetValue(descendant.ParentId, out parentId) == false) continue; + if (idmap.TryGetValue(descendant.ParentId, out parentId) == false) + { + continue; + } - var descendantCopy = descendant.DeepCloneWithResetIdentities(); + IContent descendantCopy = descendant.DeepCloneWithResetIdentities(); descendantCopy.ParentId = parentId; - if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, evtMsgs))) + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, eventMessages))) { continue; } @@ -2201,7 +2261,10 @@ namespace Umbraco.Cms.Core.Services.Implement // a copy is not published (but not really unpublishing either) // update the create author and last edit author if (descendantCopy.Published) + { descendantCopy.Published = false; + } + descendantCopy.CreatorId = userId; descendantCopy.WriterId = userId; @@ -2218,10 +2281,10 @@ namespace Umbraco.Cms.Core.Services.Implement // - tags should be handled by the content repository // - a copy is unpublished and therefore has no impact on tags in DB - scope.Events.Dispatch(TreeChanged, this, new TreeChange(copy, TreeChangeTypes.RefreshBranch).ToEventArgs()); - foreach (var x in copies) + scope.Notifications.Publish(new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages)); + foreach (Tuple x in copies) { - scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, evtMsgs)); + scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, eventMessages)); } Audit(AuditType.Copy, userId, content.Id); @@ -2339,22 +2402,22 @@ namespace Umbraco.Cms.Core.Services.Implement } } - private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents) + private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages eventMessages, bool raiseEvents) { - var sortingNotification = new ContentSortingNotification(itemsA, evtMsgs); - var savingNotification = new ContentSavingNotification(itemsA, evtMsgs); + var sortingNotification = new ContentSortingNotification(itemsA, eventMessages); + var savingNotification = new ContentSavingNotification(itemsA, eventMessages); if (raiseEvents) { // raise cancelable sorting event if (scope.Notifications.PublishCancelable(sortingNotification)) { - return OperationResult.Cancel(evtMsgs); + return OperationResult.Cancel(eventMessages); } // raise cancelable saving event if (scope.Notifications.PublishCancelable(savingNotification)) { - return OperationResult.Cancel(evtMsgs); + return OperationResult.Cancel(eventMessages); } } @@ -2362,7 +2425,7 @@ namespace Umbraco.Cms.Core.Services.Implement var saved = new List(); var sortOrder = 0; - foreach (var content in itemsA) + foreach (IContent content in itemsA) { // if the current sort order equals that of the content we don't // need to update it, so just increment the sort order and continue. @@ -2379,7 +2442,9 @@ namespace Umbraco.Cms.Core.Services.Implement // if it's published, register it, no point running StrategyPublish // since we're not really publishing it and it cannot be cancelled etc if (content.Published) + { published.Add(content); + } // save saved.Add(content); @@ -2389,34 +2454,34 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { //first saved, then sorted - scope.Notifications.Publish(new ContentSavedNotification(itemsA, evtMsgs).WithStateFrom(savingNotification)); - scope.Notifications.Publish(new ContentSortedNotification(itemsA, evtMsgs).WithStateFrom(sortingNotification)); + scope.Notifications.Publish(new ContentSavedNotification(itemsA, eventMessages).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new ContentSortedNotification(itemsA, eventMessages).WithStateFrom(sortingNotification)); } - scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, eventMessages)); if (raiseEvents && published.Any()) { - scope.Notifications.Publish(new ContentPublishedNotification(published, evtMsgs)); + scope.Notifications.Publish(new ContentPublishedNotification(published, eventMessages)); } Audit(AuditType.Sort, userId, 0, "Sorting content performed by user"); - return OperationResult.Succeed(evtMsgs); + return OperationResult.Succeed(eventMessages); } public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - var report = _documentRepository.CheckDataIntegrity(options); + ContentDataIntegrityReport report = _documentRepository.CheckDataIntegrity(options); if (report.FixedIssues.Count > 0) { //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref var root = new Content("root", -1, new ContentType(_shortStringHelper, -1)) {Id = -1, Key = Guid.Empty}; - scope.Events.Dispatch(TreeChanged, this, new TreeChange.EventArgs(new TreeChange(root, TreeChangeTypes.RefreshAll))); + scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); } return report; @@ -2476,18 +2541,6 @@ namespace Umbraco.Cms.Core.Services.Implement #endregion - #region Event Handlers - - /// - /// Occurs after change. - /// - /// - /// This event needs to be rewritten using notifications instead - /// - internal static event TypedEventHandler.EventArgs> TreeChanged; - - #endregion - #region Publishing Strategies /// @@ -2735,20 +2788,20 @@ namespace Umbraco.Cms.Core.Services.Implement var changes = new List>(); var moves = new List<(IContent, string)>(); var contentTypeIdsA = contentTypeIds.ToArray(); - var evtMsgs = EventMessagesFactory.Get(); + EventMessages eventMessages = EventMessagesFactory.Get(); // using an immediate uow here because we keep making changes with // PerformMoveLocked and DeleteLocked that must be applied immediately, // no point queuing operations // - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - var query = Query().WhereIn(x => x.ContentTypeId, contentTypeIdsA); - var contents = _documentRepository.Get(query).ToArray(); + IQuery query = Query().WhereIn(x => x.ContentTypeId, contentTypeIdsA); + IContent[] contents = _documentRepository.Get(query).ToArray(); - if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(contents, evtMsgs))) + if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(contents, eventMessages))) { scope.Complete(); return; @@ -2756,21 +2809,21 @@ namespace Umbraco.Cms.Core.Services.Implement // order by level, descending, so deepest first - that way, we cannot move // a content of the deleted type, to the recycle bin (and then delete it...) - foreach (var content in contents.OrderByDescending(x => x.ParentId)) + foreach (IContent content in contents.OrderByDescending(x => x.ParentId)) { // if it's not trashed yet, and published, we should unpublish // 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.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs)); + scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages)); } // if current content has children, move them to trash - var c = content; - var childQuery = Query().Where(x => x.ParentId == c.Id); - var children = _documentRepository.Get(childQuery); - foreach (var child in children) + IContent c = content; + IQuery childQuery = Query().Where(x => x.ParentId == c.Id); + IEnumerable children = _documentRepository.Get(childQuery); + foreach (IContent child in children) { // see MoveToRecycleBin PerformMoveLocked(child, Cms.Core.Constants.System.RecycleBinContent, null, userId, moves, true); @@ -2779,18 +2832,18 @@ namespace Umbraco.Cms.Core.Services.Implement // delete content // triggers the deleted event (and handles the files) - DeleteLocked(scope, content, evtMsgs); + DeleteLocked(scope, content, eventMessages); changes.Add(new TreeChange(content, TreeChangeTypes.Remove)); } - var moveInfos = moves + MoveEventInfo[] moveInfos = moves .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); if (moveInfos.Length > 0) { - scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfos, evtMsgs)); + scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfos, eventMessages)); } - scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs()); + scope.Notifications.Publish(new ContentTreeChangeNotification(changes, eventMessages)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, $"Delete content of type {string.Join(",", contentTypeIdsA)}"); @@ -3064,8 +3117,5 @@ namespace Umbraco.Cms.Core.Services.Implement } #endregion - - - } }