Migrate TreeChanged to notification in ContentService

This commit is contained in:
Mole
2021-04-12 15:03:40 +02:00
parent 8206defcf2
commit 3ecf8eb9ea
3 changed files with 204 additions and 155 deletions

View File

@@ -44,7 +44,8 @@ namespace Umbraco.Cms.Core.Cache
INotificationHandler<DomainSavedNotification>,
INotificationHandler<MacroSavedNotification>,
INotificationHandler<MacroDeletedNotification>,
INotificationHandler<MediaTreeChangeNotification>
INotificationHandler<MediaTreeChangeNotification>,
INotificationHandler<ContentTreeChangeNotification>
{
private List<Action> _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<IContent>.EventArgs args)
public void Handle(ContentTreeChangeNotification notification)
{
_distributedCache.RefreshContentCache(args.Changes.ToArray());
_distributedCache.RefreshContentCache(notification.Changes.ToArray());
}
//private void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs<IContent> e)

View File

@@ -90,6 +90,7 @@ namespace Umbraco.Cms.Core.Compose
.AddNotificationHandler<MacroSavedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MacroDeletedNotification, DistributedCacheBinder>()
.AddNotificationHandler<MediaTreeChangeNotification, DistributedCacheBinder>()
.AddNotificationHandler<ContentTreeChangeNotification, DistributedCacheBinder>()
;
// add notification handlers for auditing
builder

View File

@@ -735,34 +735,39 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <inheritdoc />
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<string> 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<IContent>(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);
}
/// <inheritdoc />
public OperationResult Save(IEnumerable<IContent> 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<IContent>(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);
}
/// <inheritdoc />
@@ -1066,11 +1071,13 @@ namespace Umbraco.Cms.Core.Services.Implement
/// </summary>
/// <param name="scope"></param>
/// <param name="content"></param>
/// <param name="allLangs"></param>
/// <param name="notificationState"></param>
/// <param name="userId"></param>
/// <param name="raiseEvents"></param>
/// <param name="branchOne"></param>
/// <param name="branchRoot"></param>
/// <param name="eventMessages"></param>
/// <returns></returns>
/// <remarks>
/// <para>
@@ -1079,21 +1086,34 @@ namespace Umbraco.Cms.Core.Services.Implement
/// </para>
/// </remarks>
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
EventMessages evtMsgs, IReadOnlyCollection<ILanguage> allLangs,
EventMessages eventMessages, IReadOnlyCollection<ILanguage> allLangs,
IDictionary<string, object> 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<IContent>(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<IContent>(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<IContent>(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<IContent>(content, changeType).ToEventArgs());
scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages));
return publishResult;
}
@@ -1632,32 +1667,46 @@ namespace Umbraco.Cms.Core.Services.Implement
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>, 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<PublishResult>();
var publishedDocuments = new List<IContent>();
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<IContent>(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
/// <inheritdoc />
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<IContent>(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
/// <inheritdoc />
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<IContent>(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<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
var moveInfo = moves
MoveEventInfo<IContent>[] moveInfo = moves
.Select(x => new MoveEventInfo<IContent>(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);
}
/// <summary>
@@ -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<IContent>(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<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
var moveInfo = moves //changes
MoveEventInfo<IContent>[] moveInfo = moves //changes
.Select(x => new MoveEventInfo<IContent>(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<IContent>();
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<IContent>().Where(x => x.ParentId == Cms.Core.Constants.System.RecycleBinContent);
var contents = _documentRepository.Get(query).ToArray();
foreach (var content in contents)
IQuery<IContent> query = Query<IContent>().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<IContent>(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
/// <returns>The newly created <see cref="IContent"/> object</returns>
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<IContent> 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<IContent>(copy, TreeChangeTypes.RefreshBranch).ToEventArgs());
foreach (var x in copies)
scope.Notifications.Publish(new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages));
foreach (Tuple<IContent, IContent> 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<IContent>();
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<IContent>(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<IContent>.EventArgs(new TreeChange<IContent>(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
/// <summary>
/// Occurs after change.
/// </summary>
/// <remarks>
/// This event needs to be rewritten using notifications instead
/// </remarks>
internal static event TypedEventHandler<IContentService, TreeChange<IContent>.EventArgs> TreeChanged;
#endregion
#region Publishing Strategies
/// <summary>
@@ -2735,20 +2788,20 @@ namespace Umbraco.Cms.Core.Services.Implement
var changes = new List<TreeChange<IContent>>();
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<IContent>().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
var contents = _documentRepository.Get(query).ToArray();
IQuery<IContent> query = Query<IContent>().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<IContent>().Where(x => x.ParentId == c.Id);
var children = _documentRepository.Get(childQuery);
foreach (var child in children)
IContent c = content;
IQuery<IContent> childQuery = Query<IContent>().Where(x => x.ParentId == c.Id);
IEnumerable<IContent> 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<IContent>(content, TreeChangeTypes.Remove));
}
var moveInfos = moves
MoveEventInfo<IContent>[] moveInfos = moves
.Select(x => new MoveEventInfo<IContent>(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
}
}