WIP on content service events

This commit is contained in:
Kenn Jacobsen
2021-03-01 13:10:37 +01:00
parent 01bf937497
commit 03cc342c10
6 changed files with 419 additions and 158 deletions

View File

@@ -1,77 +0,0 @@
using System.Collections.Generic;
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Events
{
/// <summary>
/// Represent event data for the Saving event.
/// </summary>
public class ContentSavingEventArgs : SaveEventArgs<IContent>
{
#region Factory Methods
/// <summary>
/// Converts <see cref="ContentSavingEventArgs"/> to <see cref="ContentSavedEventArgs"/> while preserving all args state
/// </summary>
/// <returns></returns>
public ContentSavedEventArgs ToContentSavedEventArgs()
{
return new ContentSavedEventArgs(EventObject, Messages, AdditionalData)
{
EventState = EventState
};
}
/// <summary>
/// Converts <see cref="ContentSavingEventArgs"/> to <see cref="ContentPublishedEventArgs"/> while preserving all args state
/// </summary>
/// <returns></returns>
public ContentPublishedEventArgs ToContentPublishedEventArgs()
{
return new ContentPublishedEventArgs(EventObject, false, Messages)
{
EventState = EventState,
AdditionalData = AdditionalData
};
}
/// <summary>
/// Converts <see cref="ContentSavingEventArgs"/> to <see cref="ContentPublishingEventArgs"/> while preserving all args state
/// </summary>
/// <returns></returns>
public ContentPublishingEventArgs ToContentPublishingEventArgs()
{
return new ContentPublishingEventArgs(EventObject, Messages)
{
EventState = EventState,
AdditionalData = AdditionalData
};
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ContentSavingEventArgs"/> class.
/// </summary>
public ContentSavingEventArgs(IEnumerable<IContent> eventObject, EventMessages eventMessages)
: base(eventObject, eventMessages)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ContentSavingEventArgs"/> class.
/// </summary>
public ContentSavingEventArgs(IContent eventObject, EventMessages eventMessages)
: base(eventObject, eventMessages)
{ }
#endregion
/// <summary>
/// Determines whether a culture is being saved, during a Saving event.
/// </summary>
public bool IsSavingCulture(IContent content, string culture)
=> content.CultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty();
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -126,8 +126,6 @@ namespace Umbraco.Cms.Core.Cache
() => MediaService.TreeChanged -= MediaService_TreeChanged);
// bind to content events
Bind(() => ContentService.Saved += ContentService_Saved, // needed for permissions
() => ContentService.Saved -= ContentService_Saved);
Bind(() => ContentService.Copied += ContentService_Copied, // needed for permissions
() => ContentService.Copied -= ContentService_Copied);
Bind(() => ContentService.TreeChanged += ContentService_TreeChanged,// handles all content changes
@@ -182,19 +180,6 @@ namespace Umbraco.Cms.Core.Cache
{
}
/// <summary>
/// Handles cache refreshing for when content is saved (not published)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks>
/// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to
/// stay up-to-date for unpublished content.
/// </remarks>
private void ContentService_Saved(IContentService sender, SaveEventArgs<IContent> e)
{
}
private void ContentService_TreeChanged(IContentService sender, TreeChange<IContent>.EventArgs args)
{
_distributedCache.RefreshContentCache(args.Changes.ToArray());

View File

@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -18,10 +18,162 @@ 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<SavedNotification<IContent>>,
INotificationHandler<SortedNotification<IContent>>
{
private readonly Notifier _notifier;
private readonly ActionCollection _actions;
private readonly IContentService _contentService;
public void Handle(SavedNotification<IContent> notification)
{
var newEntities = new List<IContent>();
var updatedEntities = new List<IContent>();
//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<ActionNew>(), newEntities.ToArray());
_notifier.Notify(_actions.GetAction<ActionUpdate>(), updatedEntities.ToArray());
}
public void Handle(SortedNotification<IContent> 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<ActionSort>(), new[] { parent });
}
/// <summary>
/// This class is used to send the notifications
/// </summary>
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<Notifier> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="Notifier"/> class.
/// </summary>
public Notifier(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IHostingEnvironment hostingEnvironment,
INotificationService notificationService,
IUserService userService,
ILocalizedTextService textService,
IOptions<GlobalSettings> globalSettings,
ILogger<Notifier> 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<IContent> 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
}));
}
}
}
}
public sealed class NotificationsComponent : IComponent
{
private readonly Notifier _notifier;
@@ -41,10 +193,10 @@ namespace Umbraco.Cms.Core.Compose
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 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
@@ -65,8 +217,8 @@ namespace Umbraco.Cms.Core.Compose
{
ContentService.SentToPublish -= ContentService_SentToPublish;
ContentService.Published -= ContentService_Published;
ContentService.Sorted -= ContentService_Sorted;
ContentService.Saved -= ContentService_Saved;
//ContentService.Sorted -= ContentService_Sorted;
//ContentService.Saved -= ContentService_Saved;
ContentService.Unpublished -= ContentService_Unpublished;
ContentService.Moved -= ContentService_Moved;
ContentService.Trashed -= ContentService_Trashed;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Umbraco.
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
@@ -7,10 +7,53 @@ 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<SavingNotification<IContent>>
{
private readonly string _editorAlias;
private readonly Func<string, bool, string> _formatPropertyValue;
protected ComplexPropertyEditorContentNotificationHandler(string editorAlias, Func<string, bool, string> formatPropertyValue)
{
_editorAlias = editorAlias;
_formatPropertyValue = formatPropertyValue;
}
public void Handle(SavingNotification<IContent> notification)
{
foreach (var entity in notification.SavedEntities)
{
var props = entity.GetPropertiesByEditor(_editorAlias);
UpdatePropertyValues(props, true);
}
}
private void UpdatePropertyValues(IEnumerable<IProperty> 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;
}
}
}
}
/// <summary>
/// Utility class for dealing with <see cref="ContentService"/> Copying/Saving events for complex editors
/// </summary>
@@ -26,7 +69,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
_editorAlias = editorAlias;
_formatPropertyValue = formatPropertyValue;
ContentService.Copying += ContentService_Copying;
ContentService.Saving += ContentService_Saving;
//ContentService.Saving += ContentService_Saving;
}
/// <summary>
@@ -84,7 +127,7 @@ namespace Umbraco.Cms.Core.PropertyEditors
if (disposing)
{
ContentService.Copying -= ContentService_Copying;
ContentService.Saving -= ContentService_Saving;
//ContentService.Saving -= ContentService_Saving;
}
_disposedValue = true;
}

View File

@@ -0,0 +1,124 @@
using System.Collections.Generic;
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
{
bool Cancel { get; set; }
}
public abstract class ObjectNotification<T> : INotification where T : class
{
protected ObjectNotification(T target, EventMessages messages)
{
Messages = messages;
Target = target;
}
public EventMessages Messages { get; }
protected T Target { get; }
}
public abstract class EnumerableObjectNotification<T> : ObjectNotification<IEnumerable<T>>
{
protected EnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages)
{
}
protected EnumerableObjectNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
}
public abstract class CancelableObjectNotification<T> : ObjectNotification<T> where T : class
{
protected CancelableObjectNotification(T target, EventMessages messages) : base(target, messages)
{
}
public bool Cancel { get; set; }
public void CancelOperation(EventMessage cancelationMessage)
{
Cancel = true;
cancelationMessage.IsDefaultEventMessage = true;
Messages.Add(cancelationMessage);
}
}
public abstract class CancelableEnumerableObjectNotification<T> : CancelableObjectNotification<IEnumerable<T>>
{
protected CancelableEnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages)
{
}
protected CancelableEnumerableObjectNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
}
public class DeletingNotification<T> : CancelableEnumerableObjectNotification<T>
{
public DeletingNotification(T target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> DeletedEntities => Target;
}
public class DeletedNotification<T> : EnumerableObjectNotification<T>
{
public DeletedNotification(T target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> DeletedEntities => Target;
}
public class SortingNotification<T> : CancelableEnumerableObjectNotification<T>
{
public SortingNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> SortedEntities => Target;
}
public class SortedNotification<T> : EnumerableObjectNotification<T>
{
public SortedNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> SortedEntities => Target;
}
public class SavingNotification<T> : CancelableEnumerableObjectNotification<T>
{
public SavingNotification(T target, EventMessages messages) : base(target, messages)
{
}
public SavingNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> SavedEntities => Target;
}
public class SavedNotification<T> : EnumerableObjectNotification<T>
{
public SavedNotification(T target, EventMessages messages) : base(target, messages)
{
}
public SavedNotification(IEnumerable<T> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<T> SavedEntities => Target;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
using Umbraco.Cms.Infrastructure.Services;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services.Implement
@@ -32,6 +33,7 @@ namespace Umbraco.Cms.Core.Services.Implement
private readonly IShortStringHelper _shortStringHelper;
private readonly ILogger<ContentService> _logger;
private IQuery<IContent> _queryNotTrashed;
private readonly IEventAggregator _eventAggregator;
#region Constructors
@@ -39,7 +41,7 @@ namespace Umbraco.Cms.Core.Services.Implement
IEventMessagesFactory eventMessagesFactory,
IDocumentRepository documentRepository, IEntityRepository entityRepository, IAuditRepository auditRepository,
IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository,
Lazy<IPropertyValidationService> propertyValidationService, IShortStringHelper shortStringHelper)
Lazy<IPropertyValidationService> propertyValidationService, IShortStringHelper shortStringHelper, IEventAggregator eventAggregator)
: base(provider, loggerFactory, eventMessagesFactory)
{
_documentRepository = documentRepository;
@@ -50,6 +52,7 @@ namespace Umbraco.Cms.Core.Services.Implement
_languageRepository = languageRepository;
_propertyValidationService = propertyValidationService;
_shortStringHelper = shortStringHelper;
_eventAggregator = eventAggregator;
_logger = loggerFactory.CreateLogger<ContentService>();
}
@@ -747,11 +750,15 @@ namespace Umbraco.Cms.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
if (raiseEvents)
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
var notification = new SavingNotification<IContent>(content, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
}
}
scope.WriteLock(Cms.Core.Constants.Locks.ContentTree);
@@ -773,7 +780,7 @@ namespace Umbraco.Cms.Core.Services.Implement
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
_eventAggregator.Publish(new SavedNotification<IContent>(content, evtMsgs));
}
var changeType = TreeChangeTypes.RefreshNode;
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, changeType).ToEventArgs());
@@ -802,11 +809,15 @@ namespace Umbraco.Cms.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new ContentSavingEventArgs(contentsA, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
if (raiseEvents)
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
var notification = new SavingNotification<IContent>(contentsA, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
}
}
var treeChanges = contentsA.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.RefreshNode));
@@ -823,7 +834,7 @@ namespace Umbraco.Cms.Core.Services.Implement
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
_eventAggregator.Publish(new SavedNotification<IContent>(contentsA, evtMsgs));
}
scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs());
Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Saved multiple content");
@@ -867,9 +878,12 @@ namespace Umbraco.Cms.Core.Services.Implement
var allLangs = _languageRepository.GetMany().ToList();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
var notification = new SavingNotification<IContent>(content, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}
// if culture is specific, first publish the invariant values, then publish the culture itself.
// if culture is '*', then publish them all (including variants)
@@ -881,7 +895,7 @@ namespace Umbraco.Cms.Core.Services.Implement
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -905,9 +919,16 @@ namespace Umbraco.Cms.Core.Services.Implement
var allLangs = _languageRepository.GetMany().ToList();
var evtMsgs = EventMessagesFactory.Get();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
if (raiseEvents)
{
var notification = new SavingNotification<IContent>(content, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}
}
var varies = content.ContentType.VariesByCulture();
@@ -927,7 +948,7 @@ namespace Umbraco.Cms.Core.Services.Implement
foreach (var impact in impacts)
content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -969,9 +990,12 @@ namespace Umbraco.Cms.Core.Services.Implement
var allLangs = _languageRepository.GetMany().ToList();
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
var notification = new SavingNotification<IContent>(content, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}
// all cultures = unpublish whole
if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
@@ -982,7 +1006,7 @@ namespace Umbraco.Cms.Core.Services.Implement
// to be non-routable so that when it's re-published all variants were as they were.
content.PublishedState = PublishedState.Unpublishing;
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId);
scope.Complete();
return result;
}
@@ -996,7 +1020,7 @@ namespace Umbraco.Cms.Core.Services.Implement
var removed = content.UnpublishCulture(culture);
//save and publish any changes
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId);
var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId);
scope.Complete();
@@ -1039,13 +1063,16 @@ namespace Umbraco.Cms.Core.Services.Implement
scope.WriteLock(Cms.Core.Constants.Locks.ContentTree);
var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
var notification = new SavingNotification<IContent>(content, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
}
var allLangs = _languageRepository.GetMany().ToList();
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents);
var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, userId, raiseEvents);
scope.Complete();
return result;
}
@@ -1069,15 +1096,13 @@ namespace Umbraco.Cms.Core.Services.Implement
/// </para>
/// </remarks>
private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content,
ContentSavingEventArgs saveEventArgs, IReadOnlyCollection<ILanguage> allLangs,
EventMessages evtMsgs, IReadOnlyCollection<ILanguage> allLangs,
int userId = Cms.Core.Constants.Security.SuperUserId,
bool raiseEvents = true, bool branchOne = false, bool branchRoot = false)
{
if (scope == null) throw new ArgumentNullException(nameof(scope));
if (content == null) throw new ArgumentNullException(nameof(content));
if (saveEventArgs == null) throw new ArgumentNullException(nameof(saveEventArgs));
var evtMsgs = saveEventArgs.Messages;
if (evtMsgs == null) throw new ArgumentNullException(nameof(evtMsgs));
PublishResult publishResult = null;
PublishResult unpublishResult = null;
@@ -1210,7 +1235,7 @@ namespace Umbraco.Cms.Core.Services.Implement
// raise the Saved event, always
if (raiseEvents)
{
scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved));
_eventAggregator.Publish(new SavedNotification<IContent>(content, evtMsgs));
}
if (unpublishing) // we have tried to unpublish - won't happen in a branch
@@ -1375,8 +1400,9 @@ namespace Umbraco.Cms.Core.Services.Implement
if (pendingCultures.Count == 0)
continue; //shouldn't happen but no point in processing this document if there's nothing there
var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
var notification = new SavingNotification<IContent>(d, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
continue;
@@ -1390,7 +1416,7 @@ namespace Umbraco.Cms.Core.Services.Implement
d.UnpublishCulture(c);
}
var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId);
var result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId);
if (result.Success == false)
_logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
results.Add(result);
@@ -1436,11 +1462,12 @@ namespace Umbraco.Cms.Core.Services.Implement
if (pendingCultures.Count == 0)
continue; //shouldn't happen but no point in processing this document if there's nothing there
var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
var notification = new SavingNotification<IContent>(d, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
continue; // this document is canceled move next
continue;
}
var publishing = true;
@@ -1470,7 +1497,7 @@ namespace Umbraco.Cms.Core.Services.Implement
else if (!publishing)
result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
else
result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId);
result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, d.WriterId);
if (result.Success == false)
_logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result);
@@ -1718,9 +1745,12 @@ namespace Umbraco.Cms.Core.Services.Implement
if (culturesToPublish.Count == 0) // empty = already published
return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
var saveEventArgs = new ContentSavingEventArgs(document, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
var notification = new SavingNotification<IContent>(document, evtMsgs);
_eventAggregator.Publish(notification);
if (notification.Cancel)
{
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
}
// publish & check if values are valid
if (!publishCultures(document, culturesToPublish, allLangs))
@@ -1729,7 +1759,7 @@ namespace Umbraco.Cms.Core.Services.Implement
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
}
var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot);
var result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, userId, branchOne: true, branchRoot: isRoot);
if (result.Success)
publishedDocuments.Add(document);
return result;
@@ -1746,6 +1776,9 @@ namespace Umbraco.Cms.Core.Services.Implement
using (var scope = ScopeProvider.CreateScope())
{
var deleteNotification = new DeletingNotification<IContent>(content, evtMsgs);
_eventAggregator.Publish(deleteNotification);
var deleteEventArgs = new DeleteEventArgs<IContent>(content, evtMsgs);
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs, nameof(Deleting)))
{
@@ -2322,16 +2355,23 @@ namespace Umbraco.Cms.Core.Services.Implement
private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents)
{
var saveEventArgs = new ContentSavingEventArgs(itemsA, evtMsgs);
if (raiseEvents)
{
//raise cancelable sorting event
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting)))
// raise cancelable sorting event
var sortingNotification = new SortingNotification<IContent>(itemsA, evtMsgs);
_eventAggregator.Publish(sortingNotification);
if (sortingNotification.Cancel)
{
return OperationResult.Cancel(evtMsgs);
}
//raise saving event (this one cannot be canceled)
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(Saving, this, saveEventArgs, nameof(Saving));
// raise cancelable saving event
var savingNotification = new SavingNotification<IContent>(itemsA, evtMsgs);
_eventAggregator.Publish(savingNotification);
if (savingNotification.Cancel)
{
return OperationResult.Cancel(evtMsgs);
}
}
var published = new List<IContent>();
@@ -2364,10 +2404,9 @@ namespace Umbraco.Cms.Core.Services.Implement
if (raiseEvents)
{
var savedEventsArgs = saveEventArgs.ToContentSavedEventArgs();
//first saved, then sorted
scope.Events.Dispatch(Saved, this, savedEventsArgs, nameof(Saved));
scope.Events.Dispatch(Sorted, this, savedEventsArgs, nameof(Sorted));
_eventAggregator.Publish(new SavedNotification<IContent>(itemsA, evtMsgs));
_eventAggregator.Publish(new SortedNotification<IContent>(itemsA, evtMsgs));
}
scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.RefreshNode)).ToEventArgs());
@@ -2483,11 +2522,6 @@ namespace Umbraco.Cms.Core.Services.Implement
/// </summary>
public static event TypedEventHandler<IContentService, SaveEventArgs<IContent>> Sorted;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IContentService, ContentSavingEventArgs> Saving;
/// <summary>
/// Occurs after Save
/// </summary>