diff --git a/.gitignore b/.gitignore index 8aa2769368..1c54100176 100644 --- a/.gitignore +++ b/.gitignore @@ -202,4 +202,5 @@ src/Umbraco.Tests/TEMP/ /src/Umbraco.Web.UI/config/umbracoSettings.config /src/Umbraco.Web.UI.NetCore/Umbraco/models/* src/Umbraco.Tests.UnitTests/umbraco/Data/TEMP/ +/src/Umbraco.Web.UI.NetCore/appsettings.Local.json src/Umbraco.Tests.Integration/DatabaseContextTests.sdf diff --git a/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs b/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs index 360fd44ba8..8106da11e6 100644 --- a/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs @@ -1,17 +1,17 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; namespace Umbraco.Cms.Core.Cache { - public sealed class ApplicationCacheRefresher : CacheRefresherBase + public sealed class ApplicationCacheRefresher : CacheRefresherBase { - public ApplicationCacheRefresher(AppCaches appCaches) - : base(appCaches) - { } + public ApplicationCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) + { + } #region Define - protected override ApplicationCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("B15F34A1-BC1D-4F8B-8369-3222728AB4C8"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/ApplicationCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/ApplicationCacheRefresherNotification.cs new file mode 100644 index 0000000000..3602a1488f --- /dev/null +++ b/src/Umbraco.Core/Cache/ApplicationCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class ApplicationCacheRefresherNotification : CacheRefresherNotification + { + public ApplicationCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs index d3a09dbf8f..c5f3d903ab 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Sync; @@ -10,33 +10,22 @@ namespace Umbraco.Cms.Core.Cache /// /// The actual cache refresher type. /// The actual cache refresher type is used for strongly typed events. - public abstract class CacheRefresherBase : ICacheRefresher - where TInstanceType : class, ICacheRefresher + public abstract class CacheRefresherBase : ICacheRefresher + where TNotification : CacheRefresherNotification { /// /// Initializes a new instance of the . /// /// A cache helper. - protected CacheRefresherBase(AppCaches appCaches) + protected CacheRefresherBase(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) { AppCaches = appCaches; + EventAggregator = eventAggregator; + NotificationFactory = factory; } - /// - /// Triggers when the cache is updated on the server. - /// - /// - /// Triggers on each server configured for an Umbraco project whenever a cache refresher is updated. - /// - public static event TypedEventHandler CacheUpdated; - #region Define - /// - /// Gets the typed 'this' for events. - /// - protected abstract TInstanceType This { get; } - /// /// Gets the unique identifier of the refresher. /// @@ -47,6 +36,11 @@ namespace Umbraco.Cms.Core.Cache /// public abstract string Name { get; } + /// + /// Gets the for + /// + protected ICacheRefresherNotificationFactory NotificationFactory { get; } + #endregion #region Refresher @@ -56,7 +50,7 @@ namespace Umbraco.Cms.Core.Cache /// public virtual void RefreshAll() { - OnCacheUpdated(This, new CacheRefresherEventArgs(null, MessageType.RefreshAll)); + OnCacheUpdated(NotificationFactory.Create(null, MessageType.RefreshAll)); } /// @@ -65,7 +59,7 @@ namespace Umbraco.Cms.Core.Cache /// The entity's identifier. public virtual void Refresh(int id) { - OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); + OnCacheUpdated(NotificationFactory.Create(id, MessageType.RefreshById)); } /// @@ -74,7 +68,7 @@ namespace Umbraco.Cms.Core.Cache /// The entity's identifier. public virtual void Refresh(Guid id) { - OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RefreshById)); + OnCacheUpdated(NotificationFactory.Create(id, MessageType.RefreshById)); } /// @@ -83,7 +77,7 @@ namespace Umbraco.Cms.Core.Cache /// The entity's identifier. public virtual void Remove(int id) { - OnCacheUpdated(This, new CacheRefresherEventArgs(id, MessageType.RemoveById)); + OnCacheUpdated(NotificationFactory.Create(id, MessageType.RemoveById)); } #endregion @@ -95,6 +89,8 @@ namespace Umbraco.Cms.Core.Cache /// protected AppCaches AppCaches { get; } + protected IEventAggregator EventAggregator { get; } + /// /// Clears the cache for all repository entities of a specified type. /// @@ -110,9 +106,9 @@ namespace Umbraco.Cms.Core.Cache /// /// The event sender. /// The event arguments. - protected static void OnCacheUpdated(TInstanceType sender, CacheRefresherEventArgs args) + protected void OnCacheUpdated(CacheRefresherNotification notification) { - CacheUpdated?.Invoke(sender, args); + EventAggregator.Publish(notification); } #endregion diff --git a/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs b/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs deleted file mode 100644 index e1d04a7095..0000000000 --- a/src/Umbraco.Core/Cache/CacheRefresherEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Umbraco.Cms.Core.Sync; - -namespace Umbraco.Cms.Core.Cache -{ - /// - /// Event args for cache refresher updates - /// - public class CacheRefresherEventArgs : EventArgs - { - public CacheRefresherEventArgs(object msgObject, MessageType type) - { - MessageType = type; - MessageObject = msgObject; - } - public object MessageObject { get; private set; } - public MessageType MessageType { get; private set; } - } -} diff --git a/src/Umbraco.Core/Cache/CacheRefresherNotification.cs b/src/Umbraco.Core/Cache/CacheRefresherNotification.cs new file mode 100644 index 0000000000..442fad0147 --- /dev/null +++ b/src/Umbraco.Core/Cache/CacheRefresherNotification.cs @@ -0,0 +1,22 @@ +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Sync; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Cache +{ + /// + /// Base class for cache refresher notifications + /// + public abstract class CacheRefresherNotification : INotification + { + public CacheRefresherNotification(object messageObject, MessageType messageType) + { + MessageObject = messageObject ?? throw new ArgumentNullException(nameof(messageObject)); + MessageType = messageType; + } + + public object MessageObject { get; } + public MessageType MessageType { get; } + } +} diff --git a/src/Umbraco.Core/Cache/CacheRefresherNotificationFactory.cs b/src/Umbraco.Core/Cache/CacheRefresherNotificationFactory.cs new file mode 100644 index 0000000000..fc9cdefe27 --- /dev/null +++ b/src/Umbraco.Core/Cache/CacheRefresherNotificationFactory.cs @@ -0,0 +1,23 @@ +using System; +using Umbraco.Extensions; +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + /// + /// A that uses ActivatorUtilities to create the instances + /// + public sealed class CacheRefresherNotificationFactory : ICacheRefresherNotificationFactory + { + private readonly IServiceProvider _serviceProvider; + + public CacheRefresherNotificationFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + /// + /// Create a using ActivatorUtilities + /// + /// The to create + public TNotification Create(object msgObject, MessageType type) where TNotification : CacheRefresherNotification + => _serviceProvider.CreateInstance(new object[] { msgObject, type }); + } +} diff --git a/src/Umbraco.Core/Cache/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentCacheRefresher.cs index e77fa7abef..26cf00a2d9 100644 --- a/src/Umbraco.Core/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentCacheRefresher.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PublishedCache; @@ -11,14 +12,21 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { - public sealed class ContentCacheRefresher : PayloadCacheRefresherBase + public sealed class ContentCacheRefresher : PayloadCacheRefresherBase { private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IIdKeyMap _idKeyMap; private readonly IDomainService _domainService; - public ContentCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService, IIdKeyMap idKeyMap, IDomainService domainService) - : base(appCaches, serializer) + public ContentCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IIdKeyMap idKeyMap, + IDomainService domainService, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { _publishedSnapshotService = publishedSnapshotService; _idKeyMap = idKeyMap; @@ -27,8 +35,6 @@ namespace Umbraco.Cms.Core.Cache #region Define - protected override ContentCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("900A4FBE-DF3C-41E6-BB77-BE896CD158EA"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/ContentCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/ContentCacheRefresherNotification.cs new file mode 100644 index 0000000000..dd76786393 --- /dev/null +++ b/src/Umbraco.Core/Cache/ContentCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class ContentCacheRefresherNotification : CacheRefresherNotification + { + public ContentCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs index 8a1ba1234e..b093df85b8 100644 --- a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Linq; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Persistence.Repositories; @@ -11,15 +12,23 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { - public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase + public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase { private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IPublishedModelFactory _publishedModelFactory; private readonly IContentTypeCommonRepository _contentTypeCommonRepository; private readonly IIdKeyMap _idKeyMap; - public ContentTypeCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IIdKeyMap idKeyMap, IContentTypeCommonRepository contentTypeCommonRepository) - : base(appCaches, serializer) + public ContentTypeCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IPublishedModelFactory publishedModelFactory, + IIdKeyMap idKeyMap, + IContentTypeCommonRepository contentTypeCommonRepository, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { _publishedSnapshotService = publishedSnapshotService; _publishedModelFactory = publishedModelFactory; @@ -29,8 +38,6 @@ namespace Umbraco.Cms.Core.Cache #region Define - protected override ContentTypeCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("6902E22C-9C10-483C-91F3-66B7CAE9E2F5"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/ContentTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/ContentTypeCacheRefresherNotification.cs new file mode 100644 index 0000000000..17bd955d03 --- /dev/null +++ b/src/Umbraco.Core/Cache/ContentTypeCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class ContentTypeCacheRefresherNotification : CacheRefresherNotification + { + public ContentTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs index d5e11e17d3..dfbf9230e2 100644 --- a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -9,14 +10,21 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { - public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase + public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase { private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IPublishedModelFactory _publishedModelFactory; private readonly IIdKeyMap _idKeyMap; - public DataTypeCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IIdKeyMap idKeyMap) - : base(appCaches, serializer) + public DataTypeCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IPublishedModelFactory publishedModelFactory, + IIdKeyMap idKeyMap, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { _publishedSnapshotService = publishedSnapshotService; _publishedModelFactory = publishedModelFactory; @@ -25,8 +33,6 @@ namespace Umbraco.Cms.Core.Cache #region Define - protected override DataTypeCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/DataTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/DataTypeCacheRefresherNotification.cs new file mode 100644 index 0000000000..d64dd53431 --- /dev/null +++ b/src/Umbraco.Core/Cache/DataTypeCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class DataTypeCacheRefresherNotification : CacheRefresherNotification + { + public DataTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs b/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs index 922afab8da..c812a4aadc 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs @@ -1,18 +1,17 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Cache { - public sealed class DictionaryCacheRefresher : CacheRefresherBase + public sealed class DictionaryCacheRefresher : CacheRefresherBase { - public DictionaryCacheRefresher(AppCaches appCaches) - : base(appCaches) + public DictionaryCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator , factory) { } #region Define - protected override DictionaryCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("D1D7E227-F817-4816-BFE9-6C39B6152884"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/DictionaryCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/DictionaryCacheRefresherNotification.cs new file mode 100644 index 0000000000..57474466d0 --- /dev/null +++ b/src/Umbraco.Core/Cache/DictionaryCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class DictionaryCacheRefresherNotification : CacheRefresherNotification + { + public DictionaryCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs index 2773ca2d0f..228baf4b9a 100644 --- a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Serialization; @@ -6,20 +7,23 @@ using Umbraco.Cms.Core.Services.Changes; namespace Umbraco.Cms.Core.Cache { - public sealed class DomainCacheRefresher : PayloadCacheRefresherBase + public sealed class DomainCacheRefresher : PayloadCacheRefresherBase { private readonly IPublishedSnapshotService _publishedSnapshotService; - public DomainCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService) - : base(appCaches, serializer) + public DomainCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { _publishedSnapshotService = publishedSnapshotService; } #region Define - protected override DomainCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("11290A79-4B57-4C99-AD72-7748A3CF38AF"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/DomainCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/DomainCacheRefresherNotification.cs new file mode 100644 index 0000000000..53495ec6cc --- /dev/null +++ b/src/Umbraco.Core/Cache/DomainCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class DomainCacheRefresherNotification : CacheRefresherNotification + { + public DomainCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs b/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs new file mode 100644 index 0000000000..c79f7579a7 --- /dev/null +++ b/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs @@ -0,0 +1,16 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + /// + /// Factory for creating cache refresher notification instances + /// + public interface ICacheRefresherNotificationFactory + { + /// + /// Creates a + /// + /// The to create + TNotification Create(object msgObject, MessageType type) where TNotification : CacheRefresherNotification; + } +} diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index 3e70bc54eb..f0946e0e52 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Cache @@ -8,8 +9,8 @@ namespace Umbraco.Cms.Core.Cache /// /// The actual cache refresher type. /// The actual cache refresher type is used for strongly typed events. - public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher - where TInstanceType : class, ICacheRefresher + public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher + where TNotification : CacheRefresherNotification { protected IJsonSerializer JsonSerializer { get; } @@ -17,7 +18,12 @@ namespace Umbraco.Cms.Core.Cache /// Initializes a new instance of the . /// /// A cache helper. - protected JsonCacheRefresherBase(AppCaches appCaches, IJsonSerializer jsonSerializer) : base(appCaches) + protected JsonCacheRefresherBase( + AppCaches appCaches, + IJsonSerializer jsonSerializer, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { JsonSerializer = jsonSerializer; } @@ -28,7 +34,7 @@ namespace Umbraco.Cms.Core.Cache /// The json payload. public virtual void Refresh(string json) { - OnCacheUpdated(This, new CacheRefresherEventArgs(json, MessageType.RefreshByJson)); + OnCacheUpdated(NotificationFactory.Create(json, MessageType.RefreshByJson)); } #region Json diff --git a/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs index b15d247ddf..fb65aaa58d 100644 --- a/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Serialization; @@ -7,18 +8,21 @@ using static Umbraco.Cms.Core.Cache.LanguageCacheRefresher.JsonPayload; namespace Umbraco.Cms.Core.Cache { - public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase + public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase { - public LanguageCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService) - : base(appCaches, serializer) + public LanguageCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { _publishedSnapshotService = publishedSnapshotService; } #region Define - protected override LanguageCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654"); private readonly IPublishedSnapshotService _publishedSnapshotService; diff --git a/src/Umbraco.Core/Cache/LanguageCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/LanguageCacheRefresherNotification.cs new file mode 100644 index 0000000000..fde0090c28 --- /dev/null +++ b/src/Umbraco.Core/Cache/LanguageCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class LanguageCacheRefresherNotification : CacheRefresherNotification + { + public LanguageCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index dd4c4c73de..fa4dca5ecc 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -1,23 +1,26 @@ -using System; +using System; using System.Linq; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Serialization; namespace Umbraco.Cms.Core.Cache { - public sealed class MacroCacheRefresher : PayloadCacheRefresherBase + public sealed class MacroCacheRefresher : PayloadCacheRefresherBase { - public MacroCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer) - : base(appCaches, jsonSerializer) + public MacroCacheRefresher( + AppCaches appCaches, + IJsonSerializer jsonSerializer, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, jsonSerializer, eventAggregator, factory) { } #region Define - protected override MacroCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("7B1E683C-5F34-43dd-803D-9699EA1E98CA"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/MacroCacheRefresherNotification.cs new file mode 100644 index 0000000000..f5ca7985c8 --- /dev/null +++ b/src/Umbraco.Core/Cache/MacroCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class MacroCacheRefresherNotification : CacheRefresherNotification + { + public MacroCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs index 997083b0a7..a0101ab66c 100644 --- a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PublishedCache; @@ -9,13 +10,13 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { - public sealed class MediaCacheRefresher : PayloadCacheRefresherBase + public sealed class MediaCacheRefresher : PayloadCacheRefresherBase { private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IIdKeyMap _idKeyMap; - public MediaCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService, IIdKeyMap idKeyMap) - : base(appCaches, serializer) + public MediaCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { _publishedSnapshotService = publishedSnapshotService; _idKeyMap = idKeyMap; @@ -23,8 +24,6 @@ namespace Umbraco.Cms.Core.Cache #region Define - protected override MediaCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("B29286DD-2D40-4DDB-B325-681226589FEC"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/MediaCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/MediaCacheRefresherNotification.cs new file mode 100644 index 0000000000..afdd60bb4a --- /dev/null +++ b/src/Umbraco.Core/Cache/MediaCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class MediaCacheRefresherNotification : CacheRefresherNotification + { + public MediaCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs index 0932725fe4..b416889363 100644 --- a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs @@ -1,6 +1,7 @@ //using Newtonsoft.Json; using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Serialization; @@ -9,12 +10,12 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache { - public sealed class MemberCacheRefresher : PayloadCacheRefresherBase + public sealed class MemberCacheRefresher : PayloadCacheRefresherBase { private readonly IIdKeyMap _idKeyMap; - public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap) - : base(appCaches, serializer) + public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { _idKeyMap = idKeyMap; } @@ -36,8 +37,6 @@ namespace Umbraco.Cms.Core.Cache #region Define - protected override MemberCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/MemberCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/MemberCacheRefresherNotification.cs new file mode 100644 index 0000000000..6154505947 --- /dev/null +++ b/src/Umbraco.Core/Cache/MemberCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class MemberCacheRefresherNotification : CacheRefresherNotification + { + public MemberCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs index 2db947d026..1f019e8f30 100644 --- a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs @@ -1,21 +1,20 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; namespace Umbraco.Cms.Core.Cache { - public sealed class MemberGroupCacheRefresher : PayloadCacheRefresherBase + public sealed class MemberGroupCacheRefresher : PayloadCacheRefresherBase { - public MemberGroupCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer) - : base(appCaches, jsonSerializer) + public MemberGroupCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, jsonSerializer, eventAggregator, factory) { } #region Define - protected override MemberGroupCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("187F236B-BD21-4C85-8A7C-29FBA3D6C00C"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/MemberGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/MemberGroupCacheRefresherNotification.cs new file mode 100644 index 0000000000..643e9bd51e --- /dev/null +++ b/src/Umbraco.Core/Cache/MemberGroupCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class MemberGroupCacheRefresherNotification : CacheRefresherNotification + { + public MemberGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs index 08d3e65506..f7867ae3fe 100644 --- a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Cache @@ -9,8 +10,8 @@ namespace Umbraco.Cms.Core.Cache /// The actual cache refresher type. /// The payload type. /// The actual cache refresher type is used for strongly typed events. - public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher - where TInstanceType : class, ICacheRefresher + public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher + where TNotification : CacheRefresherNotification { /// @@ -18,7 +19,8 @@ namespace Umbraco.Cms.Core.Cache /// /// A cache helper. /// - protected PayloadCacheRefresherBase(AppCaches appCaches, IJsonSerializer serializer) : base(appCaches, serializer) + protected PayloadCacheRefresherBase(AppCaches appCaches, IJsonSerializer serializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { } @@ -37,7 +39,7 @@ namespace Umbraco.Cms.Core.Cache /// The payload. public virtual void Refresh(TPayload[] payloads) { - OnCacheUpdated(This, new CacheRefresherEventArgs(payloads, MessageType.RefreshByPayload)); + OnCacheUpdated(NotificationFactory.Create(payloads, MessageType.RefreshByPayload)); } #endregion diff --git a/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs b/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs index 19064a8031..d833a0aea5 100644 --- a/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs @@ -1,18 +1,17 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Cache { - public sealed class PublicAccessCacheRefresher : CacheRefresherBase + public sealed class PublicAccessCacheRefresher : CacheRefresherBase { - public PublicAccessCacheRefresher(AppCaches appCaches) - : base(appCaches) + public PublicAccessCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { } #region Define - protected override PublicAccessCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("1DB08769-B104-4F8B-850E-169CAC1DF2EC"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/PublicAccessCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/PublicAccessCacheRefresherNotification.cs new file mode 100644 index 0000000000..d3334571b3 --- /dev/null +++ b/src/Umbraco.Core/Cache/PublicAccessCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class PublicAccessCacheRefresherNotification : CacheRefresherNotification + { + public PublicAccessCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs index daa954b257..6f15d09554 100644 --- a/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs @@ -1,19 +1,18 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; namespace Umbraco.Cms.Core.Cache { - public sealed class RelationTypeCacheRefresher : CacheRefresherBase + public sealed class RelationTypeCacheRefresher : CacheRefresherBase { - public RelationTypeCacheRefresher(AppCaches appCaches) - : base(appCaches) + public RelationTypeCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { } #region Define - protected override RelationTypeCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/RelationTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/RelationTypeCacheRefresherNotification.cs new file mode 100644 index 0000000000..851eba915d --- /dev/null +++ b/src/Umbraco.Core/Cache/RelationTypeCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class RelationTypeCacheRefresherNotification : CacheRefresherNotification + { + public RelationTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs index d02d3190eb..c098ccb967 100644 --- a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs @@ -1,17 +1,18 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Cache { - public sealed class TemplateCacheRefresher : CacheRefresherBase + public sealed class TemplateCacheRefresher : CacheRefresherBase { private readonly IIdKeyMap _idKeyMap; private readonly IContentTypeCommonRepository _contentTypeCommonRepository; - public TemplateCacheRefresher(AppCaches appCaches, IIdKeyMap idKeyMap, IContentTypeCommonRepository contentTypeCommonRepository) - : base(appCaches) + public TemplateCacheRefresher(AppCaches appCaches, IIdKeyMap idKeyMap, IContentTypeCommonRepository contentTypeCommonRepository, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { _idKeyMap = idKeyMap; _contentTypeCommonRepository = contentTypeCommonRepository; @@ -19,8 +20,6 @@ namespace Umbraco.Cms.Core.Cache #region Define - protected override TemplateCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("DD12B6A0-14B9-46e8-8800-C154F74047C8"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/TemplateCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/TemplateCacheRefresherNotification.cs new file mode 100644 index 0000000000..88ff2284cb --- /dev/null +++ b/src/Umbraco.Core/Cache/TemplateCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class TemplateCacheRefresherNotification : CacheRefresherNotification + { + public TemplateCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/UserCacheRefresher.cs b/src/Umbraco.Core/Cache/UserCacheRefresher.cs index 6cb3eb7f88..201ecc1f19 100644 --- a/src/Umbraco.Core/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/UserCacheRefresher.cs @@ -1,19 +1,18 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Repositories; namespace Umbraco.Cms.Core.Cache { - public sealed class UserCacheRefresher : CacheRefresherBase + public sealed class UserCacheRefresher : CacheRefresherBase { - public UserCacheRefresher(AppCaches appCaches) - : base(appCaches) + public UserCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { } #region Define - protected override UserCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("E057AF6D-2EE6-41F4-8045-3694010F0AA6"); public override Guid RefresherUniqueId => UniqueId; @@ -47,7 +46,7 @@ namespace Umbraco.Cms.Core.Cache userCache.Result.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id); userCache.Result.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id); } - + base.Remove(id); } diff --git a/src/Umbraco.Core/Cache/UserCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/UserCacheRefresherNotification.cs new file mode 100644 index 0000000000..b91a7b93c8 --- /dev/null +++ b/src/Umbraco.Core/Cache/UserCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class UserCacheRefresherNotification : CacheRefresherNotification + { + public UserCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs b/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs index 7519994069..2d278972ec 100644 --- a/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Repositories; @@ -10,16 +11,14 @@ namespace Umbraco.Cms.Core.Cache /// /// This also needs to clear the user cache since IReadOnlyUserGroup's are attached to IUser objects /// - public sealed class UserGroupCacheRefresher : CacheRefresherBase + public sealed class UserGroupCacheRefresher : CacheRefresherBase { - public UserGroupCacheRefresher(AppCaches appCaches) - : base(appCaches) + public UserGroupCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { } #region Define - protected override UserGroupCacheRefresher This => this; - public static readonly Guid UniqueId = Guid.Parse("45178038-B232-4FE8-AA1A-F2B949C44762"); public override Guid RefresherUniqueId => UniqueId; diff --git a/src/Umbraco.Core/Cache/UserGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Cache/UserGroupCacheRefresherNotification.cs new file mode 100644 index 0000000000..a0adf915d8 --- /dev/null +++ b/src/Umbraco.Core/Cache/UserGroupCacheRefresherNotification.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Core.Sync; + +namespace Umbraco.Cms.Core.Cache +{ + public class UserGroupCacheRefresherNotification : CacheRefresherNotification + { + public UserGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) + { + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 5249676fb6..82cf6ffa84 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -189,6 +189,7 @@ namespace Umbraco.Cms.Core.DependencyInjection // register distributed cache Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), f.GetRequiredService())); + Services.AddUnique(); // register the http context and umbraco context accessors // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when diff --git a/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs b/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs deleted file mode 100644 index ceec857c64..0000000000 --- a/src/Umbraco.Core/Events/ContentPublishedEventArgs.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represents event data for the Published event. - /// - public class ContentPublishedEventArgs : PublishEventArgs - { - /// - /// Initializes a new instance of the class. - /// - public ContentPublishedEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { } - - /// - /// Determines whether a culture has been published, during a Published event. - /// - public bool HasPublishedCulture(IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); - - /// - /// Determines whether a culture has been unpublished, during a Published event. - /// - public bool HasUnpublishedCulture(IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); - } -} diff --git a/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs b/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs deleted file mode 100644 index e7893ea195..0000000000 --- a/src/Umbraco.Core/Events/ContentPublishingEventArgs.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represents event data for the Publishing event. - /// - public class ContentPublishingEventArgs : PublishEventArgs - { - /// - /// Initializes a new instance of the class. - /// - public ContentPublishingEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - /// - /// Determines whether a culture is being published, during a Publishing event. - /// - public bool IsPublishingCulture(IContent content, string culture) - => content.PublishCultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty(); - - /// - /// Determines whether a culture is being unpublished, during a Publishing event. - /// - public bool IsUnpublishingCulture(IContent content, string culture) - => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); //bit of a hack since we know that the content implementation tracks changes this way - } -} diff --git a/src/Umbraco.Core/Events/ContentSavedEventArgs.cs b/src/Umbraco.Core/Events/ContentSavedEventArgs.cs deleted file mode 100644 index 554330563a..0000000000 --- a/src/Umbraco.Core/Events/ContentSavedEventArgs.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represents event data for the Saved event. - /// - public class ContentSavedEventArgs : SaveEventArgs - { - /// - /// Initializes a new instance of the class. - /// - public ContentSavedEventArgs(IEnumerable eventObject, EventMessages messages, IDictionary additionalData) - : base(eventObject, false, messages, additionalData) - { } - - /// - /// Determines whether a culture has been saved, during a Saved event. - /// - public bool HasSavedCulture(IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UpdatedCulture + culture); - } -} diff --git a/src/Umbraco.Core/Events/ContentSavingEventArgs.cs b/src/Umbraco.Core/Events/ContentSavingEventArgs.cs deleted file mode 100644 index b1cded2eb4..0000000000 --- a/src/Umbraco.Core/Events/ContentSavingEventArgs.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Events -{ - /// - /// Represent event data for the Saving event. - /// - public class ContentSavingEventArgs : SaveEventArgs - { - #region Factory Methods - - /// - /// Converts to while preserving all args state - /// - /// - public ContentSavedEventArgs ToContentSavedEventArgs() - { - return new ContentSavedEventArgs(EventObject, Messages, AdditionalData) - { - EventState = EventState - }; - } - - /// - /// Converts to while preserving all args state - /// - /// - public ContentPublishedEventArgs ToContentPublishedEventArgs() - { - return new ContentPublishedEventArgs(EventObject, false, Messages) - { - EventState = EventState, - AdditionalData = AdditionalData - }; - } - - /// - /// Converts to while preserving all args state - /// - /// - public ContentPublishingEventArgs ToContentPublishingEventArgs() - { - return new ContentPublishingEventArgs(EventObject, Messages) - { - EventState = EventState, - AdditionalData = AdditionalData - }; - } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - public ContentSavingEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - /// - /// Initializes a new instance of the class. - /// - public ContentSavingEventArgs(IContent eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } - - #endregion - - /// - /// Determines whether a culture is being saved, during a Saving event. - /// - public bool IsSavingCulture(IContent content, string culture) - => content.CultureInfos.TryGetValue(culture, out var cultureInfo) && cultureInfo.IsDirty(); - } -} diff --git a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs b/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs deleted file mode 100644 index a7eb48107e..0000000000 --- a/src/Umbraco.Core/Events/DeleteRevisionsEventArgs.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; - -namespace Umbraco.Cms.Core.Events -{ - public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable - { - public DeleteRevisionsEventArgs(int id, bool canCancel, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id, canCancel) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } - - public DeleteRevisionsEventArgs(int id, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id) - { - DeletePriorVersions = deletePriorVersions; - SpecificVersion = specificVersion; - DateToRetain = dateToRetain; - } - - public bool DeletePriorVersions { get; } - public int SpecificVersion { get; } - public DateTime DateToRetain { get; } - - /// - /// Returns true if we are deleting a specific revision - /// - public bool IsDeletingSpecificRevision => SpecificVersion != default; - - public bool Equals(DeleteRevisionsEventArgs other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((DeleteRevisionsEventArgs) obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode(); - hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode(); - hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right) - { - return !Equals(left, right); - } - } -} diff --git a/src/Umbraco.Core/Events/ICancelableNotification.cs b/src/Umbraco.Core/Events/ICancelableNotification.cs new file mode 100644 index 0000000000..df1abc672e --- /dev/null +++ b/src/Umbraco.Core/Events/ICancelableNotification.cs @@ -0,0 +1,10 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Cms.Core.Events +{ + public interface ICancelableNotification : INotification + { + bool Cancel { get; set; } + } +} diff --git a/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs b/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs new file mode 100644 index 0000000000..96f8e771c0 --- /dev/null +++ b/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs @@ -0,0 +1,37 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading.Tasks; + +namespace Umbraco.Cms.Core.Events +{ + public interface IScopedNotificationPublisher + { + /// + /// Publishes a cancelable notification to the notification subscribers + /// + /// + /// True if the notification was cancelled by a subscriber, false otherwise + bool PublishCancelable(ICancelableNotification notification); + + /// + /// Publishes a cancelable notification to the notification subscribers + /// + /// + /// True if the notification was cancelled by a subscriber, false otherwise + Task PublishCancelableAsync(ICancelableNotification notification); + + /// + /// Publishes a notification to the notification subscribers + /// + /// + /// The notification is published upon successful completion of the current scope, i.e. when things have been saved/published/deleted etc. + void Publish(INotification notification); + + /// + /// Invokes publishing of all pending notifications within the current scope + /// + /// + void ScopeExit(bool completed); + } +} diff --git a/src/Umbraco.Core/Events/IStatefulNotification.cs b/src/Umbraco.Core/Events/IStatefulNotification.cs new file mode 100644 index 0000000000..dafebe6173 --- /dev/null +++ b/src/Umbraco.Core/Events/IStatefulNotification.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Events +{ + public interface IStatefulNotification : INotification + { + IDictionary State { get; set; } + } +} diff --git a/src/Umbraco.Core/Events/NotificationExtensions.cs b/src/Umbraco.Core/Events/NotificationExtensions.cs new file mode 100644 index 0000000000..de51f016e4 --- /dev/null +++ b/src/Umbraco.Core/Events/NotificationExtensions.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Umbraco.Cms.Core.Events +{ + public static class NotificationExtensions + { + public static T WithState(this T notification, IDictionary state) where T : IStatefulNotification + { + notification.State = state; + return notification; + } + + public static T WithStateFrom(this T notification, TSource source) + where T : IStatefulNotification where TSource : IStatefulNotification + => notification.WithState(source.State); + } +} diff --git a/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs b/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs new file mode 100644 index 0000000000..206706716c --- /dev/null +++ b/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs @@ -0,0 +1,71 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Umbraco.Cms.Core.Events +{ + public class ScopedNotificationPublisher : IScopedNotificationPublisher + { + private readonly IEventAggregator _eventAggregator; + private readonly List _notificationOnScopeCompleted; + + public ScopedNotificationPublisher(IEventAggregator eventAggregator) + { + _eventAggregator = eventAggregator; + _notificationOnScopeCompleted = new List(); + } + + public bool PublishCancelable(ICancelableNotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + _eventAggregator.Publish(notification); + return notification.Cancel; + } + + public async Task PublishCancelableAsync(ICancelableNotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + await _eventAggregator.PublishAsync(notification); + return notification.Cancel; + } + + public void Publish(INotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + _notificationOnScopeCompleted.Add(notification); + } + + public void ScopeExit(bool completed) + { + try + { + if (completed) + { + foreach (var notification in _notificationOnScopeCompleted) + { + _eventAggregator.Publish(notification); + } + } + } + finally + { + _notificationOnScopeCompleted.Clear(); + } + } + } +} diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs index 7569b64cb7..57c69ee9aa 100644 --- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs @@ -101,6 +101,12 @@ namespace Umbraco.Extensions /// True if ClaimsIdentity public static bool VerifyBackOfficeIdentity(this ClaimsIdentity identity, out ClaimsIdentity verifiedIdentity) { + if (identity is null) + { + verifiedIdentity = null; + return false; + } + // Validate that all required claims exist foreach (var claimType in RequiredBackOfficeClaimTypes) { @@ -112,7 +118,7 @@ namespace Umbraco.Extensions } } - verifiedIdentity = new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType); + verifiedIdentity = identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType ? identity : new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType); return true; } diff --git a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs index ce0e0eb774..1ee5699868 100644 --- a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs +++ b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs @@ -7,31 +7,53 @@ using System.Linq; using System.Security.Claims; using System.Security.Principal; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Security; namespace Umbraco.Extensions { public static class ClaimsPrincipalExtensions { + + public static bool IsBackOfficeAuthenticationType(this ClaimsIdentity claimsIdentity) + { + if (claimsIdentity is null) + { + return false; + } + + return claimsIdentity.IsAuthenticated && claimsIdentity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType; + } /// /// This will return the current back office identity if the IPrincipal is the correct type and authenticated. /// - /// + /// /// - public static ClaimsIdentity GetUmbracoIdentity(this IPrincipal user) + public static ClaimsIdentity GetUmbracoIdentity(this IPrincipal principal) { - // Check if the identity is a ClaimsIdentity, and that's it's authenticated and has all required claims. - if (user.Identity is ClaimsIdentity claimsIdentity - && claimsIdentity.IsAuthenticated - && claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity umbracoIdentity)) + //If it's already a UmbracoBackOfficeIdentity + if (principal.Identity is ClaimsIdentity claimsIdentity + && claimsIdentity.IsBackOfficeAuthenticationType() + && claimsIdentity.VerifyBackOfficeIdentity(out var backOfficeIdentity)) { - if (claimsIdentity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType) - { - return claimsIdentity; - } - return umbracoIdentity; + return backOfficeIdentity; } + //Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that + // We can have assigned more identities if it is a preview request. + if (principal is ClaimsPrincipal claimsPrincipal ) + { + claimsIdentity = claimsPrincipal.Identities.FirstOrDefault(x=>x.IsBackOfficeAuthenticationType()); + if (claimsIdentity.VerifyBackOfficeIdentity(out backOfficeIdentity)) + { + return backOfficeIdentity; + } + } + + //Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd + if (principal.Identity is ClaimsIdentity claimsIdentity2 + && claimsIdentity2.VerifyBackOfficeIdentity(out backOfficeIdentity)) + { + return backOfficeIdentity; + } return null; } diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 99c1d2b0ee..1aa4906029 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -126,10 +126,6 @@ namespace Umbraco.Cms.Core.Cache () => MediaService.TreeChanged -= MediaService_TreeChanged); // bind to content events - Bind(() => ContentService.Saved += ContentService_Saved, // needed for permissions - () => ContentService.Saved -= ContentService_Saved); - Bind(() => ContentService.Copied += ContentService_Copied, // needed for permissions - () => ContentService.Copied -= ContentService_Copied); Bind(() => ContentService.TreeChanged += ContentService_TreeChanged,// handles all content changes () => ContentService.TreeChanged -= ContentService_TreeChanged); @@ -182,32 +178,11 @@ namespace Umbraco.Cms.Core.Cache { } - /// - /// Handles cache refreshing for when content is saved (not published) - /// - /// - /// - /// - /// When an entity is saved we need to notify other servers about the change in order for the Examine indexes to - /// stay up-to-date for unpublished content. - /// - private void ContentService_Saved(IContentService sender, SaveEventArgs e) - { - } - private void ContentService_TreeChanged(IContentService sender, TreeChange.EventArgs args) { _distributedCache.RefreshContentCache(args.Changes.ToArray()); } - // TODO: our weird events handling wants this for now - private void ContentService_Deleted(IContentService sender, DeleteEventArgs e) { } - private void ContentService_Moved(IContentService sender, MoveEventArgs e) { } - private void ContentService_Trashed(IContentService sender, MoveEventArgs e) { } - private void ContentService_EmptiedRecycleBin(IContentService sender, RecycleBinEventArgs e) { } - private void ContentService_Published(IContentService sender, PublishEventArgs e) { } - private void ContentService_Unpublished(IContentService sender, PublishEventArgs e) { } - //private void ContentService_SavedBlueprint(IContentService sender, SaveEventArgs e) //{ // _distributedCache.RefreshUnpublishedPageCache(e.SavedEntities.ToArray()); @@ -413,13 +388,6 @@ namespace Umbraco.Cms.Core.Cache _distributedCache.RefreshMediaCache(args.Changes.ToArray()); } - // TODO: our weird events handling wants this for now - private void MediaService_Saved(IMediaService sender, SaveEventArgs e) { } - private void MediaService_Deleted(IMediaService sender, DeleteEventArgs e) { } - private void MediaService_Moved(IMediaService sender, MoveEventArgs e) { } - private void MediaService_Trashed(IMediaService sender, MoveEventArgs e) { } - private void MediaService_EmptiedRecycleBin(IMediaService sender, RecycleBinEventArgs e) { } - #endregion #region MemberService diff --git a/src/Umbraco.Infrastructure/Compose/BlockEditorComposer.cs b/src/Umbraco.Infrastructure/Compose/BlockEditorComposer.cs deleted file mode 100644 index bcc70e1748..0000000000 --- a/src/Umbraco.Infrastructure/Compose/BlockEditorComposer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - /// - /// A composer for Block editors to run a component - /// - public class BlockEditorComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComposer.cs b/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComposer.cs deleted file mode 100644 index c8cddd6d08..0000000000 --- a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComposer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - /// - /// A composer for nested content to run a component - /// - public class NestedContentPropertyComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs index 068a8bceea..687fdbf294 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -13,7 +13,6 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; @@ -22,6 +21,9 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { + /// + /// TODO: this component must be removed entirely - there is some code duplication in in anticipation of this component being deleted + /// public sealed class NotificationsComponent : IComponent { private readonly Notifier _notifier; @@ -37,24 +39,6 @@ namespace Umbraco.Cms.Core.Compose public void Initialize() { - //Send notifications for the send to publish action - ContentService.SentToPublish += ContentService_SentToPublish; - //Send notifications for the published action - ContentService.Published += ContentService_Published; - //Send notifications for the saved action - ContentService.Sorted += ContentService_Sorted; - //Send notifications for the update and created actions - ContentService.Saved += ContentService_Saved; - //Send notifications for the unpublish action - ContentService.Unpublished += ContentService_Unpublished; - //Send notifications for the move/move to recycle bin and restore actions - ContentService.Moved += ContentService_Moved; - //Send notifications for the delete action when content is moved to the recycle bin - ContentService.Trashed += ContentService_Trashed; - //Send notifications for the copy action - ContentService.Copied += ContentService_Copied; - //Send notifications for the rollback action - ContentService.RolledBack += ContentService_RolledBack; //Send notifications for the public access changed action PublicAccessService.Saved += PublicAccessService_Saved; @@ -63,15 +47,6 @@ namespace Umbraco.Cms.Core.Compose public void Terminate() { - ContentService.SentToPublish -= ContentService_SentToPublish; - ContentService.Published -= ContentService_Published; - ContentService.Sorted -= ContentService_Sorted; - ContentService.Saved -= ContentService_Saved; - ContentService.Unpublished -= ContentService_Unpublished; - ContentService.Moved -= ContentService_Moved; - ContentService.Trashed -= ContentService_Trashed; - ContentService.Copied -= ContentService_Copied; - ContentService.RolledBack -= ContentService_RolledBack; PublicAccessService.Saved -= PublicAccessService_Saved; UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned; } @@ -82,72 +57,6 @@ namespace Umbraco.Cms.Core.Compose private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs args) => PublicAccessServiceSaved(args, _contentService); - private void ContentService_RolledBack(IContentService sender, RollbackEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.Entity); - - private void ContentService_Copied(IContentService sender, CopyEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.Original); - - private void ContentService_Trashed(IContentService sender, MoveEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); - - private void ContentService_Moved(IContentService sender, MoveEventArgs args) - => ContentServiceMoved(args); - - private void ContentService_Unpublished(IContentService sender, PublishEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - - private void ContentService_Saved(IContentService sender, ContentSavedEventArgs args) - => ContentServiceSaved(args); - - private void ContentService_Sorted(IContentService sender, SaveEventArgs args) - => ContentServiceSorted(sender, args); - - private void ContentService_Published(IContentService sender, ContentPublishedEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - - private void ContentService_SentToPublish(IContentService sender, SendToPublishEventArgs args) - => _notifier.Notify(_actions.GetAction(), args.Entity); - - private void ContentServiceSorted(IContentService sender, SaveEventArgs args) - { - var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); - if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id - - // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. - // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. - if (parentId[0] <= 0) return; - - var parent = sender.GetById(parentId[0]); - if (parent == null) return; // this shouldn't happen - - _notifier.Notify(_actions.GetAction(), new[] { parent }); - } - - private void ContentServiceSaved(SaveEventArgs args) - { - var newEntities = new List(); - var updatedEntities = new List(); - - //need to determine if this is updating or if it is new - foreach (var entity in args.SavedEntities) - { - var dirty = (IRememberBeingDirty)entity; - if (dirty.WasPropertyDirty("Id")) - { - //it's new - newEntities.Add(entity); - } - else - { - //it's updating - updatedEntities.Add(entity); - } - } - _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); - _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); - } - private void UserServiceUserGroupPermissionsAssigned(SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray(); @@ -158,22 +67,6 @@ namespace Umbraco.Cms.Core.Compose _notifier.Notify(_actions.GetAction(), entities); } - private void ContentServiceMoved(MoveEventArgs args) - { - // notify about the move for all moved items - _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); - - // for any items being moved from the recycle bin (restored), explicitly notify about that too - var restoredEntities = args.MoveInfoCollection - .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString)) - .Select(m => m.Entity) - .ToArray(); - if (restoredEntities.Any()) - { - _notifier.Notify(_actions.GetAction(), restoredEntities); - } - } - private void PublicAccessServiceSaved(SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs index 21e473bb87..c760c33b71 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs @@ -1,5 +1,13 @@ -using Umbraco.Cms.Core.Composing; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose @@ -11,6 +19,49 @@ namespace Umbraco.Cms.Core.Compose base.Compose(builder); builder.Services.AddUnique(); + + // add handlers for sending user notifications (i.e. emails) + builder.Services.AddUnique(); + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add handlers for building content relations + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add notification handlers for property editors + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + + // add notification handlers for redirect tracking + builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); } } } diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs deleted file mode 100644 index c24e7614e3..0000000000 --- a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComponent.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; - -namespace Umbraco.Cms.Core.Compose -{ - // TODO: This should just exist in the content service/repo! - public sealed class RelateOnCopyComponent : IComponent - { - private readonly IRelationService _relationService; - private readonly IAuditService _auditService; - - public RelateOnCopyComponent(IRelationService relationService, IAuditService auditService) - { - _relationService = relationService; - _auditService = auditService; - } - - public void Initialize() - { - ContentService.Copied += ContentServiceCopied; - } - - public void Terminate() - { - ContentService.Copied -= ContentServiceCopied; - } - - private void ContentServiceCopied(IContentService sender, CopyEventArgs e) - { - if (e.RelateToOriginal == false) return; - - - var relationType = _relationService.GetRelationTypeByAlias(Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); - - if (relationType == null) - { - relationType = new RelationType(Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, - Cms.Core.Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, - true, - Cms.Core.Constants.ObjectTypes.Document, - Cms.Core.Constants.ObjectTypes.Document); - - _relationService.Save(relationType); - } - - var relation = new Relation(e.Original.Id, e.Copy.Id, relationType); - _relationService.Save(relation); - - _auditService.Add( - AuditType.Copy, - e.Copy.WriterId, - e.Copy.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), - $"Copied content with Id: '{e.Copy.Id}' related to original content with Id: '{e.Original.Id}'"); - } - } -} diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComposer.cs b/src/Umbraco.Infrastructure/Compose/RelateOnCopyComposer.cs deleted file mode 100644 index ad2a3db78d..0000000000 --- a/src/Umbraco.Infrastructure/Compose/RelateOnCopyComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - public sealed class RelateOnCopyComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComposer.cs b/src/Umbraco.Infrastructure/Compose/RelateOnTrashComposer.cs deleted file mode 100644 index 8394dfc993..0000000000 --- a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - public sealed class RelateOnTrashComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs index 6057ce8a82..da83a7c913 100644 --- a/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs +++ b/src/Umbraco.Infrastructure/Configuration/JsonConfigManipulator.cs @@ -5,12 +5,12 @@ using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.FileProviders; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core.Configuration; namespace Umbraco.Cms.Core.Configuration { public class JsonConfigManipulator : IConfigManipulator { + private static readonly object s_locker = new object(); private readonly IConfiguration _configuration; public JsonConfigManipulator(IConfiguration configuration) @@ -162,22 +162,26 @@ namespace Umbraco.Cms.Core.Configuration token?.Parent?.Remove(); } + + private static void SaveJson(JsonConfigurationProvider provider, JObject json) { - if (provider.Source.FileProvider is PhysicalFileProvider physicalFileProvider) + lock (s_locker) { - var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path); + if (provider.Source.FileProvider is PhysicalFileProvider physicalFileProvider) + { + var jsonFilePath = Path.Combine(physicalFileProvider.Root, provider.Source.Path); - using (var sw = new StreamWriter(jsonFilePath, false)) - using (var jsonTextWriter = new JsonTextWriter(sw) - { - Formatting = Formatting.Indented, - }) - { - json?.WriteTo(jsonTextWriter); + using (var sw = new StreamWriter(jsonFilePath, false)) + using (var jsonTextWriter = new JsonTextWriter(sw) + { + Formatting = Formatting.Indented, + }) + { + json?.WriteTo(jsonTextWriter); + } } } - } private static JObject GetJson(JsonConfigurationProvider provider) diff --git a/src/Umbraco.Infrastructure/Search/ExamineComposer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs similarity index 69% rename from src/Umbraco.Infrastructure/Search/ExamineComposer.cs rename to src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs index 45ba3c461e..033ab76298 100644 --- a/src/Umbraco.Infrastructure/Search/ExamineComposer.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Examine.cs @@ -1,25 +1,25 @@ using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Search; using Umbraco.Extensions; -namespace Umbraco.Cms.Infrastructure.Search +namespace Umbraco.Cms.Infrastructure.DependencyInjection { /// - /// Configures and installs Examine. + /// Provides extension methods to the class. /// - public sealed class ExamineComposer : ComponentComposer, ICoreComposer + public static partial class UmbracoBuilderExtensions { - public override void Compose(IUmbracoBuilder builder) + public static IUmbracoBuilder AddExamine(this IUmbracoBuilder builder) { - base.Compose(builder); - // populators are not a collection: one cannot remove ours, and can only add more // the container can inject IEnumerable and get them all builder.Services.AddSingleton(); @@ -49,6 +49,15 @@ namespace Umbraco.Cms.Infrastructure.Search builder.Services.AddUnique, MediaValueSetBuilder>(); builder.Services.AddUnique, MemberValueSetBuilder>(); builder.Services.AddUnique(); + + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + + return builder; } } } diff --git a/src/Umbraco.Infrastructure/Events/RelateOnCopyNotificationHandler.cs b/src/Umbraco.Infrastructure/Events/RelateOnCopyNotificationHandler.cs new file mode 100644 index 0000000000..f88b529461 --- /dev/null +++ b/src/Umbraco.Infrastructure/Events/RelateOnCopyNotificationHandler.cs @@ -0,0 +1,51 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Services.Notifications; + +namespace Umbraco.Cms.Core.Events +{ + public class RelateOnCopyNotificationHandler : INotificationHandler + { + private readonly IRelationService _relationService; + private readonly IAuditService _auditService; + + public RelateOnCopyNotificationHandler(IRelationService relationService, IAuditService auditService) + { + _relationService = relationService; + _auditService = auditService; + } + + public void Handle(ContentCopiedNotification notification) + { + if (notification.RelateToOriginal == false) + { + return; + } + + var relationType = _relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); + + if (relationType == null) + { + relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, + Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, + true, + Constants.ObjectTypes.Document, + Constants.ObjectTypes.Document); + + _relationService.Save(relationType); + } + + var relation = new Relation(notification.Original.Id, notification.Copy.Id, relationType); + _relationService.Save(relation); + + _auditService.Add( + AuditType.Copy, + notification.Copy.WriterId, + notification.Copy.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), + $"Copied content with Id: '{notification.Copy.Id}' related to original content with Id: '{notification.Original.Id}'"); + } + } +} diff --git a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs similarity index 58% rename from src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs rename to src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs index de00961357..0ea5844b92 100644 --- a/src/Umbraco.Infrastructure/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Infrastructure/Events/RelateOnTrashNotificationHandler.cs @@ -1,15 +1,21 @@ -using System.Linq; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Events; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Compose +namespace Umbraco.Cms.Core.Events { - public sealed class RelateOnTrashComponent : IComponent + // TODO: lots of duplicate code in this one, refactor + public sealed class RelateOnTrashNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly IRelationService _relationService; private readonly IEntityService _entityService; @@ -17,7 +23,7 @@ namespace Umbraco.Cms.Core.Compose private readonly IAuditService _auditService; private readonly IScopeProvider _scopeProvider; - public RelateOnTrashComponent( + public RelateOnTrashNotificationHandler( IRelationService relationService, IEntityService entityService, ILocalizedTextService textService, @@ -31,28 +37,11 @@ namespace Umbraco.Cms.Core.Compose _scopeProvider = scopeProvider; } - public void Initialize() + public void Handle(ContentMovedNotification notification) { - ContentService.Moved += ContentService_Moved; - ContentService.Trashed += ContentService_Trashed; - MediaService.Moved += MediaService_Moved; - MediaService.Trashed += MediaService_Trashed; - } - - public void Terminate() - { - ContentService.Moved -= ContentService_Moved; - ContentService.Trashed -= ContentService_Trashed; - MediaService.Moved -= MediaService_Moved; - MediaService.Trashed -= MediaService_Trashed; - } - - private void ContentService_Moved(IContentService sender, MoveEventArgs e) - { - foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Cms.Core.Constants.System.RecycleBinContentString))) + foreach (var item in notification.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContentString))) { - - const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; var relations = _relationService.GetByChildId(item.Entity.Id); foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) @@ -62,44 +51,29 @@ namespace Umbraco.Cms.Core.Compose } } - private void MediaService_Moved(IMediaService sender, MoveEventArgs e) - { - foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Cms.Core.Constants.System.RecycleBinMediaString))) - { - const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; - var relations = _relationService.GetByChildId(item.Entity.Id); - foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) - { - _relationService.Delete(relation); - } - } - } - - private void ContentService_Trashed(IContentService sender, MoveEventArgs e) + public void Handle(ContentMovedToRecycleBinNotification notification) { using (var scope = _scopeProvider.CreateScope()) { - const string relationTypeAlias = Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias; var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) { - var documentObjectType = Cms.Core.Constants.ObjectTypes.Document; - const string relationTypeName = - Cms.Core.Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; + var documentObjectType = Constants.ObjectTypes.Document; + const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName; - relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, - documentObjectType); + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); _relationService.Save(relationType); } - foreach (var item in e.MoveInfoCollection) + foreach (var item in notification.MoveInfoCollection) { var originalPath = item.OriginalPath.ToDelimitedList(); var originalParentId = originalPath.Count > 2 ? int.Parse(originalPath[originalPath.Count - 2]) - : Cms.Core.Constants.System.Root; + : Constants.System.Root; //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin @@ -107,17 +81,15 @@ namespace Umbraco.Cms.Core.Compose if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later - var relation = _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? - new Relation(originalParentId, item.Entity.Id, relationType); + var relation = _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? new Relation(originalParentId, item.Entity.Id, relationType); _relationService.Save(relation); _auditService.Add(AuditType.Delete, item.Entity.WriterId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document), - string.Format(_textService.Localize( - "recycleBin/contentTrashed"), - item.Entity.Id, originalParentId)); + string.Format(_textService.Localize("recycleBin/contentTrashed"), item.Entity.Id, originalParentId) + ); } } @@ -125,51 +97,60 @@ namespace Umbraco.Cms.Core.Compose } } - public void MediaService_Trashed(IMediaService sender, MoveEventArgs e) + public void Handle(MediaMovedNotification notification) + { + foreach (var item in notification.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinMediaString))) + { + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; + var relations = _relationService.GetByChildId(item.Entity.Id); + foreach (var relation in relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias))) + { + _relationService.Delete(relation); + } + } + + } + + public void Handle(MediaMovedToRecycleBinNotification notification) { using (var scope = _scopeProvider.CreateScope()) { - const string relationTypeAlias = - Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; + const string relationTypeAlias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; var relationType = _relationService.GetRelationTypeByAlias(relationTypeAlias); // check that the relation-type exists, if not, then recreate it if (relationType == null) { - var documentObjectType = Cms.Core.Constants.ObjectTypes.Document; - const string relationTypeName = - Cms.Core.Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; - relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, - documentObjectType); + var documentObjectType = Constants.ObjectTypes.Document; + const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName; + relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType); _relationService.Save(relationType); } - foreach (var item in e.MoveInfoCollection) + foreach (var item in notification.MoveInfoCollection) { var originalPath = item.OriginalPath.ToDelimitedList(); var originalParentId = originalPath.Count > 2 ? int.Parse(originalPath[originalPath.Count - 2]) - : Cms.Core.Constants.System.Root; + : Constants.System.Root; //before we can create this relation, we need to ensure that the original parent still exists which //may not be the case if the encompassing transaction also deleted it when this item was moved to the bin if (_entityService.Exists(originalParentId)) { // Add a relation for the item being deleted, so that we can know the original parent for if we need to restore later - var relation = - _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? - new Relation(originalParentId, item.Entity.Id, relationType); + var relation = _relationService.GetByParentAndChildId(originalParentId, item.Entity.Id, relationType) ?? new Relation(originalParentId, item.Entity.Id, relationType); _relationService.Save(relation); _auditService.Add(AuditType.Delete, item.Entity.CreatorId, item.Entity.Id, ObjectTypes.GetName(UmbracoObjectTypes.Media), - string.Format(_textService.Localize( - "recycleBin/mediaTrashed"), - item.Entity.Id, originalParentId)); + string.Format(_textService.Localize("recycleBin/mediaTrashed"), item.Entity.Id, originalParentId) + ); } } scope.Complete(); } + } } } diff --git a/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs new file mode 100644 index 0000000000..62da73c28b --- /dev/null +++ b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs @@ -0,0 +1,215 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Actions; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Services.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Events +{ + public sealed class UserNotificationsHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler + { + private readonly Notifier _notifier; + private readonly ActionCollection _actions; + private readonly IContentService _contentService; + + public UserNotificationsHandler(Notifier notifier, ActionCollection actions, IContentService contentService) + { + _notifier = notifier; + _actions = actions; + _contentService = contentService; + } + + public void Handle(ContentSavedNotification notification) + { + var newEntities = new List(); + var updatedEntities = new List(); + + //need to determine if this is updating or if it is new + foreach (var entity in notification.SavedEntities) + { + var dirty = (IRememberBeingDirty)entity; + if (dirty.WasPropertyDirty("Id")) + { + //it's new + newEntities.Add(entity); + } + else + { + //it's updating + updatedEntities.Add(entity); + } + } + _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); + } + + public void Handle(ContentSortedNotification notification) + { + var parentId = notification.SortedEntities.Select(x => x.ParentId).Distinct().ToList(); + if (parentId.Count != 1) + return; // this shouldn't happen, for sorting all entities will have the same parent id + + // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. + // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. + if (parentId[0] <= 0) + return; + + var parent = _contentService.GetById(parentId[0]); + if (parent == null) + return; // this shouldn't happen + + _notifier.Notify(_actions.GetAction(), new[] { parent }); + } + + public void Handle(ContentPublishedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.PublishedEntities.ToArray()); + + public void Handle(ContentMovedNotification notification) + { + // notify about the move for all moved items + _notifier.Notify(_actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + // for any items being moved from the recycle bin (restored), explicitly notify about that too + var restoredEntities = notification.MoveInfoCollection + .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString)) + .Select(m => m.Entity) + .ToArray(); + if (restoredEntities.Any()) + { + _notifier.Notify(_actions.GetAction(), restoredEntities); + } + } + + public void Handle(ContentMovedToRecycleBinNotification notification) => _notifier.Notify(_actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + public void Handle(ContentCopiedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Original); + + public void Handle(ContentRolledBackNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + + public void Handle(ContentSentToPublishNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + + public void Handle(ContentUnpublishedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.UnpublishedEntities.ToArray()); + + /// + /// This class is used to send the notifications + /// + public sealed class Notifier + { + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly INotificationService _notificationService; + private readonly IUserService _userService; + private readonly ILocalizedTextService _textService; + private readonly GlobalSettings _globalSettings; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public Notifier( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IHostingEnvironment hostingEnvironment, + INotificationService notificationService, + IUserService userService, + ILocalizedTextService textService, + IOptions globalSettings, + ILogger logger) + { + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _hostingEnvironment = hostingEnvironment; + _notificationService = notificationService; + _userService = userService; + _textService = textService; + _globalSettings = globalSettings.Value; + _logger = logger; + } + + public void Notify(IAction action, params IContent[] entities) + { + var user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; + + //if there is no current user, then use the admin + if (user == null) + { + _logger.LogDebug("There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = _userService.GetUserById(Constants.Security.SuperUserId); + if (user == null) + { + _logger.LogWarning("Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); + return; + } + } + + SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); + } + + private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) + { + if (sender == null) + throw new ArgumentNullException(nameof(sender)); + if (siteUri == null) + { + _logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)"); + return; + } + + //group by the content type variation since the emails will be different + foreach (var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) + { + _notificationService.SendNotifications( + sender, + contentVariantGroup, + action.Letter.ToString(CultureInfo.InvariantCulture), + _textService.Localize("actions", action.Alias), + siteUri, + ((IUser user, NotificationEmailSubjectParams subject) x) + => _textService.Localize( + "notifications/mailSubject", + x.user.GetUserCulture(_textService, _globalSettings), + new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), + ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) + => _textService.Localize( + x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", + x.user.GetUserCulture(_textService, _globalSettings), + new[] + { + x.body.RecipientName, + x.body.Action, + x.body.ItemName, + x.body.EditedUser, + x.body.SiteUrl, + x.body.ItemId, + //format the summary depending on if it's variant or not + contentVariantGroup.Key == ContentVariation.Culture + ? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary })) + : x.body.Summary, + x.body.ItemUrl + })); + } + } + + } + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs index 88ffe1a66c..0211e6b108 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_9_0_0/MigrateLogViewerQueriesFromFileToDb.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using Newtonsoft.Json; @@ -66,8 +65,6 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0 public override void Migrate() { - Debugger.Launch(); - Debugger.Break(); CreateDatabaseTable(); MigrateFileContentToDB(); } diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/LiveModelsProvider.cs b/src/Umbraco.Infrastructure/ModelsBuilder/LiveModelsProvider.cs index 2c0a71016a..9ff03e4d45 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/LiveModelsProvider.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/LiveModelsProvider.cs @@ -12,7 +12,10 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.ModelsBuilder { // supports LiveAppData - but not PureLive - public sealed class LiveModelsProvider : INotificationHandler, INotificationHandler + public sealed class LiveModelsProvider : INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private static int s_req; private readonly ILogger _logger; @@ -53,16 +56,6 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder { return; } - - // Must register with maindom in order to function. - // If registration is not successful then events are not bound - // and we also don't generate models. - _mainDom.Register(() => - { - // anything changes, and we want to re-generate models. - ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; - DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; - }); } // NOTE @@ -72,8 +65,13 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder // need to be generated. Could be by another request. Anyway. We could // have collisions but... you know the risk. - private void RequestModelsGeneration(object sender, EventArgs args) + private void RequestModelsGeneration() { + if (!_mainDom.IsMainDom) + { + return; + } + _logger.LogDebug("Requested to generate models."); Interlocked.Exchange(ref s_req, 1); } @@ -121,5 +119,9 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder GenerateModelsIfRequested(); } } + + public void Handle(ContentTypeCacheRefresherNotification notification) => RequestModelsGeneration(); + + public void Handle(DataTypeCacheRefresherNotification notification) => RequestModelsGeneration(); } } diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs b/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs index 65a7ac3ef8..8b14a6030b 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/OutOfDateModelsStatus.cs @@ -11,7 +11,8 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder /// /// Used to track if ModelsBuilder models are out of date/stale /// - public sealed class OutOfDateModelsStatus : INotificationHandler + public sealed class OutOfDateModelsStatus : INotificationHandler, + INotificationHandler { private readonly ModelsBuilderSettings _config; private readonly IHostingEnvironment _hostingEnvironment; @@ -47,22 +48,6 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder } } - /// - /// Handles the notification - /// - public void Handle(UmbracoApplicationStarting notification) => Install(); - - private void Install() - { - // don't run if not configured - if (!IsEnabled) - { - return; - } - - ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); - DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); - } private string GetFlagPath() { @@ -77,6 +62,12 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder private void Write() { + // don't run if not configured + if (!IsEnabled) + { + return; + } + var path = GetFlagPath(); if (path == null || File.Exists(path)) { @@ -101,5 +92,9 @@ namespace Umbraco.Cms.Infrastructure.ModelsBuilder File.Delete(path); } + + public void Handle(ContentTypeCacheRefresherNotification notification) => Write(); + + public void Handle(DataTypeCacheRefresherNotification notification) => Write(); } } diff --git a/src/Umbraco.Infrastructure/Compose/BlockEditorComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs similarity index 92% rename from src/Umbraco.Infrastructure/Compose/BlockEditorComponent.cs rename to src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs index f54b52db51..24c9b37cde 100644 --- a/src/Umbraco.Infrastructure/Compose/BlockEditorComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/BlockEditorPropertyHandler.cs @@ -1,36 +1,30 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.Blocks; -using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Compose +namespace Umbraco.Cms.Core.PropertyEditors { /// - /// A component for Block editors used to bind to events + /// A handler for Block editors used to bind to notifications /// - public class BlockEditorComponent : IComponent + public class BlockEditorPropertyHandler : ComplexPropertyEditorContentNotificationHandler { - private ComplexPropertyEditorContentEventHandler _handler; private readonly BlockListEditorDataConverter _converter = new BlockListEditorDataConverter(); - public void Initialize() - { - _handler = new ComplexPropertyEditorContentEventHandler( - Constants.PropertyEditors.Aliases.BlockList, - ReplaceBlockListUdis); - } + protected override string EditorAlias => Constants.PropertyEditors.Aliases.BlockList; - public void Terminate() => _handler?.Dispose(); - - private string ReplaceBlockListUdis(string rawJson, bool onlyMissingUdis) + protected override string FormatPropertyValue(string rawJson, bool onlyMissingKeys) { // the block editor doesn't ever have missing UDIs so when this is true there's nothing to process - if (onlyMissingUdis) return rawJson; + if (onlyMissingKeys) + return rawJson; return ReplaceBlockListUdis(rawJson, null); } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs deleted file mode 100644 index 8098a5f8d4..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.PropertyEditors -{ - /// - /// Utility class for dealing with Copying/Saving events for complex editors - /// - public class ComplexPropertyEditorContentEventHandler : IDisposable - { - private readonly string _editorAlias; - private readonly Func _formatPropertyValue; - private bool _disposedValue; - - public ComplexPropertyEditorContentEventHandler(string editorAlias, - Func formatPropertyValue) - { - _editorAlias = editorAlias; - _formatPropertyValue = formatPropertyValue; - ContentService.Copying += ContentService_Copying; - ContentService.Saving += ContentService_Saving; - } - - /// - /// Copying event handler - /// - /// - /// - private void ContentService_Copying(IContentService sender, CopyEventArgs e) - { - var props = e.Copy.GetPropertiesByEditor(_editorAlias); - UpdatePropertyValues(props, false); - } - - /// - /// Saving event handler - /// - /// - /// - private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e) - { - foreach (var entity in e.SavedEntities) - { - var props = entity.GetPropertiesByEditor(_editorAlias); - UpdatePropertyValues(props, true); - } - } - - private void UpdatePropertyValues(IEnumerable props, bool onlyMissingKeys) - { - foreach (var prop in props) - { - // A Property may have one or more values due to cultures - var propVals = prop.Values; - foreach (var cultureVal in propVals) - { - // Remove keys from published value & any nested properties - var updatedPublishedVal = _formatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); - cultureVal.PublishedValue = updatedPublishedVal; - - // Remove keys from edited/draft value & any nested properties - var updatedEditedVal = _formatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys); - cultureVal.EditedValue = updatedEditedVal; - } - } - } - - /// - /// Unbinds from events - /// - /// - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - ContentService.Copying -= ContentService_Copying; - ContentService.Saving -= ContentService_Saving; - } - _disposedValue = true; - } - } - - /// - /// Unbinds from events - /// - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs new file mode 100644 index 0000000000..23fb498d77 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/ComplexPropertyEditorContentNotificationHandler.cs @@ -0,0 +1,54 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Services.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.PropertyEditors +{ + public abstract class ComplexPropertyEditorContentNotificationHandler : + INotificationHandler, + INotificationHandler + { + protected abstract string EditorAlias { get; } + + protected abstract string FormatPropertyValue(string rawJson, bool onlyMissingKeys); + + public void Handle(ContentSavingNotification notification) + { + foreach (var entity in notification.SavedEntities) + { + var props = entity.GetPropertiesByEditor(EditorAlias); + UpdatePropertyValues(props, true); + } + } + + public void Handle(ContentCopyingNotification notification) + { + var props = notification.Copy.GetPropertiesByEditor(EditorAlias); + UpdatePropertyValues(props, false); + } + + private void UpdatePropertyValues(IEnumerable props, bool onlyMissingKeys) + { + foreach (var prop in props) + { + // A Property may have one or more values due to cultures + var propVals = prop.Values; + foreach (var cultureVal in propVals) + { + // Remove keys from published value & any nested properties + var updatedPublishedVal = FormatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys); + cultureVal.PublishedValue = updatedPublishedVal; + + // Remove keys from edited/draft value & any nested properties + var updatedEditedVal = FormatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys); + cultureVal.EditedValue = updatedEditedVal; + } + } + } + } +} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs index 28d35e60d2..15d56abfb3 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -24,7 +25,9 @@ namespace Umbraco.Cms.Core.PropertyEditors "fileupload", Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-download-alt")] - public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator + public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, + INotificationHandler, INotificationHandler, + INotificationHandler, INotificationHandler { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -32,6 +35,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IDataTypeService _dataTypeService; private readonly ILocalizationService _localizationService; private readonly ILocalizedTextService _localizedTextService; + private readonly IContentService _contentService; public FileUploadPropertyEditor( ILoggerFactory loggerFactory, @@ -42,7 +46,8 @@ namespace Umbraco.Cms.Core.PropertyEditors ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, UploadAutoFillProperties uploadAutoFillProperties, - IJsonSerializer jsonSerializer) + IJsonSerializer jsonSerializer, + IContentService contentService) : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); @@ -51,6 +56,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _localizationService = localizationService; _localizedTextService = localizedTextService; _uploadAutoFillProperties = uploadAutoFillProperties; + _contentService = contentService; } /// @@ -86,16 +92,17 @@ namespace Umbraco.Cms.Core.PropertyEditors } /// - /// Ensures any files associated are removed + /// The paths to all file upload property files contained within a collection of content entities /// - /// - internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) - { - return deletedEntities.SelectMany(x => x.Properties) - .Where(IsUploadField) - .SelectMany(GetFilePathsFromPropertyValues) - .Distinct(); - } + /// + /// + /// This method must be made private once MemberService events have been replaced by notifications + /// + internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + .SelectMany(x => x.Properties) + .Where(IsUploadField) + .SelectMany(GetFilePathsFromPropertyValues) + .Distinct(); /// /// Look through all property values stored against the property and resolve any file paths stored @@ -119,15 +126,10 @@ namespace Umbraco.Cms.Core.PropertyEditors } } - /// - /// After a content has been copied, also copy uploaded files. - /// - /// The event sender. - /// The event arguments. - internal void ContentServiceCopied(IContentService sender, CopyEventArgs args) + public void Handle(ContentCopiedNotification notification) { // get the upload field properties with a value - var properties = args.Original.Properties.Where(IsUploadField); + var properties = notification.Original.Properties.Where(IsUploadField); // copy files var isUpdated = false; @@ -137,49 +139,41 @@ namespace Umbraco.Cms.Core.PropertyEditors foreach (var propertyValue in property.Values) { var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); - if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue; + if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) + { + continue; + } + var sourcePath = _mediaFileSystem.GetRelativePath(str); - var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); - args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.Culture, propertyValue.Segment); + var copyPath = _mediaFileSystem.CopyFile(notification.Copy, property.PropertyType, sourcePath); + notification.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.Culture, propertyValue.Segment); isUpdated = true; } } // if updated, re-save the copy with the updated value if (isUpdated) - sender.Save(args.Copy); + { + _contentService.Save(notification.Copy); + } } - /// - /// After a media has been created, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - internal void MediaServiceCreated(IMediaService sender, NewEventArgs args) + public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + private void DeleteContainedFiles(IEnumerable deletedEntities) { - AutoFillProperties(args.Entity); + var filePathsToDelete = ContainedFilePaths(deletedEntities); + _mediaFileSystem.DeleteMediaFiles(filePathsToDelete); } - /// - /// After a media has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void MediaServiceSaving(IMediaService sender, SaveEventArgs args) + public void Handle(MediaSavingNotification notification) { - foreach (var entity in args.SavedEntities) - AutoFillProperties(entity); - } - - /// - /// After a content item has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void ContentServiceSaving(IContentService sender, SaveEventArgs args) - { - foreach (var entity in args.SavedEntities) + foreach (var entity in notification.SavedEntities) + { AutoFillProperties(entity); + } } /// diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index 74bd7823e3..52972e9f49 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -16,6 +16,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors @@ -31,7 +32,9 @@ namespace Umbraco.Cms.Core.PropertyEditors HideLabel = false, Group = Constants.PropertyEditors.Groups.Media, Icon = "icon-crop")] - public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator + public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, + INotificationHandler, INotificationHandler, + INotificationHandler, INotificationHandler { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -39,6 +42,7 @@ namespace Umbraco.Cms.Core.PropertyEditors private readonly IIOHelper _ioHelper; private readonly UploadAutoFillProperties _autoFillProperties; private readonly ILogger _logger; + private readonly IContentService _contentService; /// /// Initializes a new instance of the class. @@ -53,7 +57,8 @@ namespace Umbraco.Cms.Core.PropertyEditors IShortStringHelper shortStringHelper, ILocalizedTextService localizedTextService, UploadAutoFillProperties uploadAutoFillProperties, - IJsonSerializer jsonSerializer) + IJsonSerializer jsonSerializer, + IContentService contentService) : base(loggerFactory, dataTypeService, localizationService, localizedTextService, shortStringHelper, jsonSerializer) { _mediaFileSystem = mediaFileSystem ?? throw new ArgumentNullException(nameof(mediaFileSystem)); @@ -61,6 +66,7 @@ namespace Umbraco.Cms.Core.PropertyEditors _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); _autoFillProperties = uploadAutoFillProperties ?? throw new ArgumentNullException(nameof(uploadAutoFillProperties)); + _contentService = contentService; _logger = loggerFactory.CreateLogger(); } @@ -122,16 +128,17 @@ namespace Umbraco.Cms.Core.PropertyEditors } /// - /// Ensures any files associated are removed + /// The paths to all image cropper property files contained within a collection of content entities /// - /// - internal IEnumerable ServiceDeleted(IEnumerable deletedEntities) - { - return deletedEntities.SelectMany(x => x.Properties) - .Where(IsCropperField) - .SelectMany(GetFilePathsFromPropertyValues) - .Distinct(); - } + /// + /// + /// This method must be made private once MemberService events have been replaced by notifications + /// + internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + .SelectMany(x => x.Properties) + .Where(IsCropperField) + .SelectMany(GetFilePathsFromPropertyValues) + .Distinct(); /// /// Look through all property values stored against the property and resolve any file paths stored @@ -175,12 +182,10 @@ namespace Umbraco.Cms.Core.PropertyEditors /// /// After a content has been copied, also copy uploaded files. /// - /// The event sender. - /// The event arguments. - public void ContentServiceCopied(IContentService sender, CopyEventArgs args) + public void Handle(ContentCopiedNotification notification) { // get the image cropper field properties - var properties = args.Original.Properties.Where(IsCropperField); + var properties = notification.Original.Properties.Where(IsCropperField); // copy files var isUpdated = false; @@ -191,49 +196,40 @@ namespace Umbraco.Cms.Core.PropertyEditors { var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); var src = GetFileSrcFromPropertyValue(propVal, out var jo); - if (src == null) continue; + if (src == null) + { + continue; + } var sourcePath = _mediaFileSystem.GetRelativePath(src); - var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); + var copyPath = _mediaFileSystem.CopyFile(notification.Copy, property.PropertyType, sourcePath); jo["src"] = _mediaFileSystem.GetUrl(copyPath); - args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment); + notification.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment); isUpdated = true; } } // if updated, re-save the copy with the updated value if (isUpdated) - sender.Save(args.Copy); + { + _contentService.Save(notification.Copy); + } } - /// - /// After a media has been created, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void MediaServiceCreated(IMediaService sender, NewEventArgs args) + public void Handle(ContentDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + public void Handle(MediaDeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + + private void DeleteContainedFiles(IEnumerable deletedEntities) { - AutoFillProperties(args.Entity); + var filePathsToDelete = ContainedFilePaths(deletedEntities); + _mediaFileSystem.DeleteMediaFiles(filePathsToDelete); } - /// - /// After a media has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void MediaServiceSaving(IMediaService sender, SaveEventArgs args) + public void Handle(MediaSavingNotification notification) { - foreach (var entity in args.SavedEntities) - AutoFillProperties(entity); - } - - /// - /// After a content item has been saved, auto-fill the properties. - /// - /// The event sender. - /// The event arguments. - public void ContentServiceSaving(IContentService sender, SaveEventArgs args) - { - foreach (var entity in args.SavedEntities) + foreach (var entity in notification.SavedEntities) + { AutoFillProperties(entity); + } } /// diff --git a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyHandler.cs similarity index 78% rename from src/Umbraco.Infrastructure/Compose/NestedContentPropertyComponent.cs rename to src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyHandler.cs index 332c2464ad..1bc5dd2f4b 100644 --- a/src/Umbraco.Infrastructure/Compose/NestedContentPropertyComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyHandler.cs @@ -1,29 +1,21 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; using Newtonsoft.Json.Linq; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Compose +namespace Umbraco.Cms.Core.PropertyEditors { /// - /// A component for NestedContent used to bind to events + /// A handler for NestedContent used to bind to notifications /// - public class NestedContentPropertyComponent : IComponent + public class NestedContentPropertyHandler : ComplexPropertyEditorContentNotificationHandler { - private ComplexPropertyEditorContentEventHandler _handler; + protected override string EditorAlias => Constants.PropertyEditors.Aliases.NestedContent; - public void Initialize() - { - _handler = new ComplexPropertyEditorContentEventHandler( - Constants.PropertyEditors.Aliases.NestedContent, - CreateNestedContentKeys); - } - - public void Terminate() => _handler?.Dispose(); - - private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) => CreateNestedContentKeys(rawJson, onlyMissingKeys, null); + protected override string FormatPropertyValue(string rawJson, bool onlyMissingKeys) => CreateNestedContentKeys(rawJson, onlyMissingKeys, null); // internal for tests internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func createGuid = null) @@ -78,6 +70,5 @@ namespace Umbraco.Cms.Core.Compose } } } - } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs index f9cb83118d..b9e9e33889 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -12,6 +12,7 @@ using Umbraco.Cms.Core.Services.Implement; namespace Umbraco.Cms.Core.PropertyEditors { + // TODO: delete this component and make the "ContainedFilePaths" methods on FileUploadPropertyEditor and ImageCropperPropertyEditor private once MemberService uses notifications instead of static events public sealed class PropertyEditorsComponent : IComponent { private readonly PropertyEditorCollection _propertyEditors; @@ -40,40 +41,14 @@ namespace Umbraco.Cms.Core.PropertyEditors private void Initialize(FileUploadPropertyEditor fileUpload) { - MediaService.Saving += fileUpload.MediaServiceSaving; - _terminate.Add(() => MediaService.Saving -= fileUpload.MediaServiceSaving); - ContentService.Copied += fileUpload.ContentServiceCopied; - _terminate.Add(() => ContentService.Copied -= fileUpload.ContentServiceCopied); - - void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - MediaService.Deleted += mediaServiceDeleted; - _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); - - void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += contentServiceDeleted; - _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); - - void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ContainedFilePaths(args.DeletedEntities.Cast())); MemberService.Deleted += memberServiceDeleted; _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); } private void Initialize(ImageCropperPropertyEditor imageCropper) { - MediaService.Saving += imageCropper.MediaServiceSaving; - _terminate.Add(() => MediaService.Saving -= imageCropper.MediaServiceSaving); - ContentService.Copied += imageCropper.ContentServiceCopied; - _terminate.Add(() => ContentService.Copied -= imageCropper.ContentServiceCopied); - - void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - MediaService.Deleted += mediaServiceDeleted; - _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); - - void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += contentServiceDeleted; - _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); - - void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ContainedFilePaths(args.DeletedEntities.Cast())); MemberService.Deleted += memberServiceDeleted; _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); } diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs deleted file mode 100644 index e56fbda4d7..0000000000 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComposer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Routing -{ - /// - /// Implements an Application Event Handler for managing redirect URLs tracking. - /// - /// - /// when content is renamed or moved, we want to create a permanent 301 redirect from it's old URL - /// not managing domains because we don't know how to do it - changing domains => must create a higher level strategy using rewriting rules probably - /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same - /// - public class RedirectTrackingComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs similarity index 55% rename from src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs rename to src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs index f6d48fa057..19dc90181a 100644 --- a/src/Umbraco.Infrastructure/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTrackingHandler.cs @@ -1,36 +1,42 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Routing { - /// Implements an Application Event Handler for managing redirect URLs tracking. + /// Implements a notification handler for managing redirect URLs tracking. /// when content is renamed or moved, we want to create a permanent 301 redirect from it's old URL /// /// not managing domains because we don't know how to do it - changing domains => must create a higher level /// strategy using rewriting rules probably /// /// recycle bin = moving to and from does nothing: to = the node is gone, where would we redirect? from = same - public sealed class RedirectTrackingComponent : IComponent + public sealed class RedirectTrackingHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { - private const string _eventStateKey = "Umbraco.Web.Redirects.RedirectTrackingEventHandler"; - private readonly IOptionsMonitor _webRoutingSettings; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; private readonly IRedirectUrlService _redirectUrlService; private readonly IVariationContextAccessor _variationContextAccessor; - public RedirectTrackingComponent(IOptionsMonitor webRoutingSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor) + private const string NotificationStateKey = "Umbraco.Cms.Core.Routing.RedirectTrackingHandler"; + + public RedirectTrackingHandler(IOptionsMonitor webRoutingSettings, IPublishedSnapshotAccessor publishedSnapshotAccessor, IRedirectUrlService redirectUrlService, IVariationContextAccessor variationContextAccessor) { _webRoutingSettings = webRoutingSettings; _publishedSnapshotAccessor = publishedSnapshotAccessor; @@ -38,86 +44,53 @@ namespace Umbraco.Cms.Core.Routing _variationContextAccessor = variationContextAccessor; } - public void Initialize() + public void Handle(ContentPublishingNotification notification) => StoreOldRoutes(notification.PublishedEntities, notification); + + public void Handle(ContentPublishedNotification notification) => CreateRedirectsForOldRoutes(notification); + + public void Handle(ContentMovingNotification notification) => StoreOldRoutes(notification.MoveInfoCollection.Select(m => m.Entity), notification); + + public void Handle(ContentMovedNotification notification) => CreateRedirectsForOldRoutes(notification); + + private void StoreOldRoutes(IEnumerable entities, IStatefulNotification notification) { - ContentService.Publishing += ContentService_Publishing; - ContentService.Published += ContentService_Published; - ContentService.Moving += ContentService_Moving; - ContentService.Moved += ContentService_Moved; + // don't let the notification handlers kick in if Redirect Tracking is turned off in the config + if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) + return; - // kill all redirects once a content is deleted - //ContentService.Deleted += ContentService_Deleted; - // BUT, doing it here would prevent content deletion due to FK - // so the rows are actually deleted by the ContentRepository (see GetDeleteClauses) - - // rolled back items have to be published, so publishing will take care of that - } - - public void Terminate() - { - ContentService.Publishing -= ContentService_Publishing; - ContentService.Published -= ContentService_Published; - ContentService.Moving -= ContentService_Moving; - ContentService.Moved -= ContentService_Moved; - } - - private void ContentService_Publishing(IContentService sender, PublishEventArgs args) - { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; - - var oldRoutes = GetOldRoutes(args.EventState); - foreach (var entity in args.PublishedEntities) + var oldRoutes = GetOldRoutes(notification); + foreach (var entity in entities) { StoreOldRoute(entity, oldRoutes); } } - private void ContentService_Published(IContentService sender, ContentPublishedEventArgs args) + private void CreateRedirectsForOldRoutes(IStatefulNotification notification) { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; + // don't let the notification handlers kick in if Redirect Tracking is turned off in the config + if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) + return; - var oldRoutes = GetOldRoutes(args.EventState); + var oldRoutes = GetOldRoutes(notification); CreateRedirects(oldRoutes); } - private void ContentService_Moving(IContentService sender, MoveEventArgs args) + private OldRoutesDictionary GetOldRoutes(IStatefulNotification notification) { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; - - var oldRoutes = GetOldRoutes(args.EventState); - foreach (var info in args.MoveInfoCollection) + if (notification.State.ContainsKey(NotificationStateKey) == false) { - StoreOldRoute(info.Entity, oldRoutes); - } - } - - private void ContentService_Moved(IContentService sender, MoveEventArgs args) - { - // don't let the event handlers kick in if Redirect Tracking is turned off in the config - if (_webRoutingSettings.CurrentValue.DisableRedirectUrlTracking) return; - - var oldRoutes = GetOldRoutes(args.EventState); - CreateRedirects(oldRoutes); - } - - private OldRoutesDictionary GetOldRoutes(IDictionary eventState) - { - if (! eventState.ContainsKey(_eventStateKey)) - { - eventState[_eventStateKey] = new OldRoutesDictionary(); + notification.State[NotificationStateKey] = new OldRoutesDictionary(); } - return eventState[_eventStateKey] as OldRoutesDictionary; + return (OldRoutesDictionary)notification.State[NotificationStateKey]; } private void StoreOldRoute(IContent entity, OldRoutesDictionary oldRoutes) { var contentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content; var entityContent = contentCache.GetById(entity.Id); - if (entityContent == null) return; + if (entityContent == null) + return; // get the default affected cultures by going up the tree until we find the first culture variant entity (default to no cultures) var defaultCultures = entityContent.AncestorsOrSelf()?.FirstOrDefault(a => a.Cultures.Any())?.Cultures.Keys.ToArray() @@ -130,7 +103,8 @@ namespace Umbraco.Cms.Core.Routing foreach (var culture in cultures) { var route = contentCache.GetRouteById(x.Id, culture); - if (IsNotRoute(route)) continue; + if (IsNotRoute(route)) + continue; oldRoutes[new ContentIdAndCulture(x.Id, culture)] = new ContentKeyAndOldRoute(x.Key, route); } } @@ -143,7 +117,8 @@ namespace Umbraco.Cms.Core.Routing foreach (var oldRoute in oldRoutes) { var newRoute = contentCache.GetRouteById(oldRoute.Key.ContentId, oldRoute.Key.Culture); - if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute) continue; + if (IsNotRoute(newRoute) || oldRoute.Value.OldRoute == newRoute) + continue; _redirectUrlService.Register(oldRoute.Value.OldRoute, oldRoute.Value.ContentKey, oldRoute.Key.Culture); } } @@ -176,6 +151,8 @@ namespace Umbraco.Cms.Core.Routing } private class OldRoutesDictionary : Dictionary - { } + { + + } } } diff --git a/src/Umbraco.Infrastructure/Scoping/IScope.cs b/src/Umbraco.Infrastructure/Scoping/IScope.cs index 4f55988d2f..26048f5ec2 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScope.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScope.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Infrastructure.Persistence; @@ -30,6 +30,11 @@ namespace Umbraco.Cms.Core.Scoping /// IEventDispatcher Events { get; } + /// + /// Gets the scope notification publisher + /// + IScopedNotificationPublisher Notifications { get; } + /// /// Gets the repositories cache mode. /// diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 66a4470645..d13c84da2e 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -20,6 +20,7 @@ namespace Umbraco.Cms.Core.Scoping private readonly ScopeProvider _scopeProvider; private readonly CoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; + private readonly IEventAggregator _eventAggregator; private readonly ILogger _logger; private readonly IsolationLevel _isolationLevel; @@ -36,12 +37,15 @@ namespace Umbraco.Cms.Core.Scoping private EventMessages _messages; private ICompletable _fscope; private IEventDispatcher _eventDispatcher; + // eventually this may need to be injectable - for now we'll create it explicitly and let future needs determine if it should be injectable + private IScopedNotificationPublisher _notificationPublisher; // initializes a new scope private Scope( ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, + IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, Scope parent, @@ -57,6 +61,7 @@ namespace Umbraco.Cms.Core.Scoping _scopeProvider = scopeProvider; _coreDebugSettings = coreDebugSettings; _mediaFileSystem = mediaFileSystem; + _eventAggregator = eventAggregator; _logger = logger; Context = scopeContext; @@ -70,6 +75,7 @@ namespace Umbraco.Cms.Core.Scoping Detachable = detachable; + #if DEBUG_SCOPES _scopeProvider.RegisterScope(this); #endif @@ -146,6 +152,7 @@ namespace Umbraco.Cms.Core.Scoping ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, + IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, bool detachable, @@ -156,7 +163,7 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, eventAggregator, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent @@ -164,6 +171,7 @@ namespace Umbraco.Cms.Core.Scoping ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, + IEventAggregator eventAggregator, ILogger logger, FileSystems fileSystems, Scope parent, @@ -173,7 +181,7 @@ namespace Umbraco.Cms.Core.Scoping bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, eventAggregator, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } public Guid InstanceId { get; } = Guid.NewGuid(); @@ -381,6 +389,16 @@ namespace Umbraco.Cms.Core.Scoping } } + public IScopedNotificationPublisher Notifications + { + get + { + EnsureNotDisposed(); + if (ParentScope != null) return ParentScope.Notifications; + return _notificationPublisher ?? (_notificationPublisher = new ScopedNotificationPublisher(_eventAggregator)); + } + } + /// public bool Complete() { @@ -556,6 +574,7 @@ namespace Umbraco.Cms.Core.Scoping if (onException == false) { _eventDispatcher?.ScopeExit(completed); + _notificationPublisher?.ScopeExit(completed); } }, () => { diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index ca5b96e0db..15ccacdb9f 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -34,8 +34,9 @@ namespace Umbraco.Cms.Core.Scoping private static readonly AsyncLocal> s_scopeContextStack = new AsyncLocal>(); private static readonly string s_scopeItemKey = typeof(Scope).FullName; private static readonly string s_contextItemKey = typeof(ScopeProvider).FullName; + private readonly IEventAggregator _eventAggregator; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache, IEventAggregator eventAggregator) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; @@ -44,6 +45,7 @@ namespace Umbraco.Cms.Core.Scoping _logger = logger; _loggerFactory = loggerFactory; _requestCache = requestCache; + _eventAggregator = eventAggregator; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; } @@ -379,7 +381,7 @@ namespace Umbraco.Cms.Core.Scoping RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) - => new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + => new Scope(this, _coreDebugSettings, _mediaFileSystem, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); /// public void AttachScope(IScope other, bool callContext = false) @@ -458,7 +460,7 @@ namespace Umbraco.Cms.Core.Scoping { IScopeContext ambientContext = AmbientContext; ScopeContext newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! PushAmbientScope(scope); if (newContext != null) @@ -468,7 +470,7 @@ namespace Umbraco.Cms.Core.Scoping return scope; } - var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _eventAggregator, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); PushAmbientScope(nested); return nested; } diff --git a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs b/src/Umbraco.Infrastructure/Search/ExamineNotificationHandler.cs similarity index 86% rename from src/Umbraco.Infrastructure/Search/ExamineComponent.cs rename to src/Umbraco.Infrastructure/Search/ExamineNotificationHandler.cs index 30dc01dc9a..b10bf70c10 100644 --- a/src/Umbraco.Infrastructure/Search/ExamineComponent.cs +++ b/src/Umbraco.Infrastructure/Search/ExamineNotificationHandler.cs @@ -7,7 +7,7 @@ using Examine; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Runtime; @@ -20,7 +20,13 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Search { - public sealed class ExamineComponent : IComponent + public sealed class ExamineNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly IExamineManager _examineManager; private readonly IContentValueSetBuilder _contentValueSetBuilder; @@ -33,18 +39,19 @@ namespace Umbraco.Cms.Infrastructure.Search private readonly ServiceContext _services; private readonly IMainDom _mainDom; private readonly IProfilingLogger _profilingLogger; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUmbracoIndexesCreator _indexCreator; + private static bool s_deactivate_handlers; // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" runs after us // but greater that SafeXmlReaderWriter priority which is 60 private const int EnlistPriority = 80; - public ExamineComponent(IMainDom mainDom, + public ExamineNotificationHandler(IMainDom mainDom, IExamineManager examineManager, IProfilingLogger profilingLogger, - ILoggerFactory loggerFactory, + ILogger logger, IScopeProvider scopeProvider, IUmbracoIndexesCreator indexCreator, ServiceContext services, @@ -66,16 +73,15 @@ namespace Umbraco.Cms.Infrastructure.Search _taskHelper = taskHelper; _mainDom = mainDom; _profilingLogger = profilingLogger; - _logger = loggerFactory.CreateLogger(); + _logger = logger; _indexCreator = indexCreator; } - - public void Initialize() + public void Handle(UmbracoApplicationStarting notification) { //let's deal with shutting down Examine with MainDom var examineShutdownRegistered = _mainDom.Register(release: () => { - using (_profilingLogger.TraceDuration("Examine shutting down")) + using (_profilingLogger.TraceDuration("Examine shutting down")) { _examineManager.Dispose(); } @@ -105,26 +111,12 @@ namespace Umbraco.Cms.Infrastructure.Search // don't bind event handlers if we're not suppose to listen if (registeredIndexers == 0) { - return; + s_deactivate_handlers = true; } - // bind to distributed cache events - this ensures that this logic occurs on ALL servers - // that are taking part in a load balanced environment. - ContentCacheRefresher.CacheUpdated += ContentCacheRefresherUpdated; - ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheRefresherUpdated; - MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated; - MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated; - LanguageCacheRefresher.CacheUpdated += LanguageCacheRefresherUpdated; + } - public void Terminate() - { - ContentCacheRefresher.CacheUpdated -= ContentCacheRefresherUpdated; - ContentTypeCacheRefresher.CacheUpdated -= ContentTypeCacheRefresherUpdated; - MediaCacheRefresher.CacheUpdated -= MediaCacheRefresherUpdated; - MemberCacheRefresher.CacheUpdated -= MemberCacheRefresherUpdated; - LanguageCacheRefresher.CacheUpdated -= LanguageCacheRefresherUpdated; - } #region Cache refresher updated event handlers @@ -133,8 +125,12 @@ namespace Umbraco.Cms.Infrastructure.Search /// /// /// - private void ContentCacheRefresherUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) + public void Handle(ContentCacheRefresherNotification args) { + if (s_deactivate_handlers) + { + return; + } if (Suspendable.ExamineEvents.CanIndex == false) { return; @@ -237,8 +233,13 @@ namespace Umbraco.Cms.Infrastructure.Search } } - private void MemberCacheRefresherUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs args) + public void Handle(MemberCacheRefresherNotification args) { + if (s_deactivate_handlers) + { + return; + } + if (Suspendable.ExamineEvents.CanIndex == false) { return; @@ -300,8 +301,13 @@ namespace Umbraco.Cms.Infrastructure.Search } } - private void MediaCacheRefresherUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs args) + public void Handle(MediaCacheRefresherNotification args) { + if (s_deactivate_handlers) + { + return; + } + if (Suspendable.ExamineEvents.CanIndex == false) { return; @@ -364,9 +370,14 @@ namespace Umbraco.Cms.Infrastructure.Search } } - private void LanguageCacheRefresherUpdated(LanguageCacheRefresher sender, CacheRefresherEventArgs e) + public void Handle(LanguageCacheRefresherNotification args) { - if (!(e.MessageObject is LanguageCacheRefresher.JsonPayload[] payloads)) + if (s_deactivate_handlers) + { + return; + } + + if (!(args.MessageObject is LanguageCacheRefresher.JsonPayload[] payloads)) { return; } @@ -393,8 +404,13 @@ namespace Umbraco.Cms.Infrastructure.Search /// /// /// - private void ContentTypeCacheRefresherUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs args) + public void Handle(ContentTypeCacheRefresherNotification args) { + if (s_deactivate_handlers) + { + return; + } + if (Suspendable.ExamineEvents.CanIndex == false) { return; @@ -668,34 +684,34 @@ namespace Umbraco.Cms.Infrastructure.Search private class DeferedReIndexForContent : DeferedAction { private readonly TaskHelper _taskHelper; - private readonly ExamineComponent _examineComponent; + private readonly ExamineNotificationHandler _ExamineNotificationHandler; private readonly IContent _content; private readonly bool _isPublished; - public DeferedReIndexForContent(TaskHelper taskHelper, ExamineComponent examineComponent, IContent content, bool isPublished) + public DeferedReIndexForContent(TaskHelper taskHelper, ExamineNotificationHandler ExamineNotificationHandler, IContent content, bool isPublished) { _taskHelper = taskHelper; - _examineComponent = examineComponent; + _ExamineNotificationHandler = ExamineNotificationHandler; _content = content; _isPublished = isPublished; } - public override void Execute() => Execute(_taskHelper, _examineComponent, _content, _isPublished); + public override void Execute() => Execute(_taskHelper, _ExamineNotificationHandler, _content, _isPublished); - public static void Execute(TaskHelper taskHelper, ExamineComponent examineComponent, IContent content, bool isPublished) + public static void Execute(TaskHelper taskHelper, ExamineNotificationHandler ExamineNotificationHandler, IContent content, bool isPublished) => taskHelper.RunBackgroundTask(() => { - using IScope scope = examineComponent._scopeProvider.CreateScope(autoComplete: true); + using IScope scope = ExamineNotificationHandler._scopeProvider.CreateScope(autoComplete: true); // for content we have a different builder for published vs unpublished // we don't want to build more value sets than is needed so we'll lazily build 2 one for published one for non-published var builders = new Dictionary>> { - [true] = new Lazy>(() => examineComponent._publishedContentValueSetBuilder.GetValueSets(content).ToList()), - [false] = new Lazy>(() => examineComponent._contentValueSetBuilder.GetValueSets(content).ToList()) + [true] = new Lazy>(() => ExamineNotificationHandler._publishedContentValueSetBuilder.GetValueSets(content).ToList()), + [false] = new Lazy>(() => ExamineNotificationHandler._contentValueSetBuilder.GetValueSets(content).ToList()) }; - foreach (IUmbracoIndex index in examineComponent._examineManager.Indexes.OfType() + foreach (IUmbracoIndex index in ExamineNotificationHandler._examineManager.Indexes.OfType() //filter the indexers .Where(x => isPublished || !x.PublishedValuesOnly) .Where(x => x.EnableDefaultEventHandler)) @@ -714,29 +730,29 @@ namespace Umbraco.Cms.Infrastructure.Search private class DeferedReIndexForMedia : DeferedAction { private readonly TaskHelper _taskHelper; - private readonly ExamineComponent _examineComponent; + private readonly ExamineNotificationHandler _ExamineNotificationHandler; private readonly IMedia _media; private readonly bool _isPublished; - public DeferedReIndexForMedia(TaskHelper taskHelper, ExamineComponent examineComponent, IMedia media, bool isPublished) + public DeferedReIndexForMedia(TaskHelper taskHelper, ExamineNotificationHandler ExamineNotificationHandler, IMedia media, bool isPublished) { _taskHelper = taskHelper; - _examineComponent = examineComponent; + _ExamineNotificationHandler = ExamineNotificationHandler; _media = media; _isPublished = isPublished; } - public override void Execute() => Execute(_taskHelper, _examineComponent, _media, _isPublished); + public override void Execute() => Execute(_taskHelper, _ExamineNotificationHandler, _media, _isPublished); - public static void Execute(TaskHelper taskHelper, ExamineComponent examineComponent, IMedia media, bool isPublished) => + public static void Execute(TaskHelper taskHelper, ExamineNotificationHandler ExamineNotificationHandler, IMedia media, bool isPublished) => // perform the ValueSet lookup on a background thread taskHelper.RunBackgroundTask(() => { - using IScope scope = examineComponent._scopeProvider.CreateScope(autoComplete: true); + using IScope scope = ExamineNotificationHandler._scopeProvider.CreateScope(autoComplete: true); - var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); + var valueSet = ExamineNotificationHandler._mediaValueSetBuilder.GetValueSets(media).ToList(); - foreach (IUmbracoIndex index in examineComponent._examineManager.Indexes.OfType() + foreach (IUmbracoIndex index in ExamineNotificationHandler._examineManager.Indexes.OfType() //filter the indexers .Where(x => isPublished || !x.PublishedValuesOnly) .Where(x => x.EnableDefaultEventHandler)) @@ -753,27 +769,27 @@ namespace Umbraco.Cms.Infrastructure.Search /// private class DeferedReIndexForMember : DeferedAction { - private readonly ExamineComponent _examineComponent; + private readonly ExamineNotificationHandler _ExamineNotificationHandler; private readonly IMember _member; private readonly TaskHelper _taskHelper; - public DeferedReIndexForMember(TaskHelper taskHelper, ExamineComponent examineComponent, IMember member) + public DeferedReIndexForMember(TaskHelper taskHelper, ExamineNotificationHandler ExamineNotificationHandler, IMember member) { - _examineComponent = examineComponent; + _ExamineNotificationHandler = ExamineNotificationHandler; _member = member; _taskHelper = taskHelper; } - public override void Execute() => Execute(_taskHelper, _examineComponent, _member); + public override void Execute() => Execute(_taskHelper, _ExamineNotificationHandler, _member); - public static void Execute(TaskHelper taskHelper, ExamineComponent examineComponent, IMember member) => + public static void Execute(TaskHelper taskHelper, ExamineNotificationHandler ExamineNotificationHandler, IMember member) => // perform the ValueSet lookup on a background thread taskHelper.RunBackgroundTask(() => { - using IScope scope = examineComponent._scopeProvider.CreateScope(autoComplete: true); + using IScope scope = ExamineNotificationHandler._scopeProvider.CreateScope(autoComplete: true); - var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); - foreach (IUmbracoIndex index in examineComponent._examineManager.Indexes.OfType() + var valueSet = ExamineNotificationHandler._memberValueSetBuilder.GetValueSets(member).ToList(); + foreach (IUmbracoIndex index in ExamineNotificationHandler._examineManager.Indexes.OfType() //filter the indexers .Where(x => x.EnableDefaultEventHandler)) { @@ -786,23 +802,23 @@ namespace Umbraco.Cms.Infrastructure.Search private class DeferedDeleteIndex : DeferedAction { - private readonly ExamineComponent _examineComponent; + private readonly ExamineNotificationHandler _ExamineNotificationHandler; private readonly int _id; private readonly bool _keepIfUnpublished; - public DeferedDeleteIndex(ExamineComponent examineComponent, int id, bool keepIfUnpublished) + public DeferedDeleteIndex(ExamineNotificationHandler ExamineNotificationHandler, int id, bool keepIfUnpublished) { - _examineComponent = examineComponent; + _ExamineNotificationHandler = ExamineNotificationHandler; _id = id; _keepIfUnpublished = keepIfUnpublished; } - public override void Execute() => Execute(_examineComponent, _id, _keepIfUnpublished); + public override void Execute() => Execute(_ExamineNotificationHandler, _id, _keepIfUnpublished); - public static void Execute(ExamineComponent examineComponent, int id, bool keepIfUnpublished) + public static void Execute(ExamineNotificationHandler ExamineNotificationHandler, int id, bool keepIfUnpublished) { var strId = id.ToString(CultureInfo.InvariantCulture); - foreach (var index in examineComponent._examineManager.Indexes.OfType() + foreach (var index in ExamineNotificationHandler._examineManager.Indexes.OfType() .Where(x => x.PublishedValuesOnly || !keepIfUnpublished) .Where(x => x.EnableDefaultEventHandler)) { @@ -811,7 +827,5 @@ namespace Umbraco.Cms.Infrastructure.Search } } #endregion - - } } diff --git a/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs b/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs index d47a471426..2a357430ed 100644 --- a/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IBackOfficeUserManager.cs @@ -1,3 +1,6 @@ +using System.Security.Principal; +using Umbraco.Cms.Infrastructure.Security; + namespace Umbraco.Cms.Core.Security { /// @@ -5,5 +8,8 @@ namespace Umbraco.Cms.Core.Security /// public interface IBackOfficeUserManager : IUmbracoUserManager { + void NotifyForgotPasswordRequested(IPrincipal currentUser, string userId); + void NotifyForgotPasswordChanged(IPrincipal currentUser, string userId); + SignOutSuccessResult NotifyLogoutSuccess(IPrincipal currentUser, string userId); } } diff --git a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs index 2cfc00d9b2..90f7f766f9 100644 --- a/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/IUmbracoUserManager.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.Security.Claims; -using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Identity; -using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Cms.Core.Security { @@ -379,16 +376,5 @@ namespace Umbraco.Cms.Core.Security /// A user can only support a phone number if the BackOfficeUserStore is replaced with another that implements IUserPhoneNumberStore /// Task GetPhoneNumberAsync(TUser user); - - // TODO: These are raised from outside the signinmanager and usermanager in the auth and user controllers, - // let's see if there's a way to avoid that and only have these called within signinmanager and usermanager - // which means we can remove these from the interface (things like invite seems like they cannot be moved) - // TODO: When we change to not having the crappy static events this will need to be revisited - void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId); - void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId); - SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId); - UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser); - bool HasSendingUserInviteEventHandler { get; } - } } diff --git a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs b/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs deleted file mode 100644 index 5e86d48d5d..0000000000 --- a/src/Umbraco.Infrastructure/Security/SignOutAuditEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Umbraco.Cms.Core.Security -{ - /// - /// Event args used when signing out - /// - public class SignOutAuditEventArgs : IdentityAuditEventArgs - { - public SignOutAuditEventArgs(AuditEvent action, string ipAddress, string comment = null, string performingUser = Cms.Core.Constants.Security.SuperUserIdAsString, string affectedUser = Cms.Core.Constants.Security.SuperUserIdAsString) - : base(action, ipAddress, performingUser, comment, affectedUser, null) - { - } - - /// - /// Allows event handlers to set a GET absolute URL to be redirected to after successful logout out of the back office. This - /// can be used for external login providers. - /// - public string SignOutRedirectUrl { get; set; } - } -} diff --git a/src/Umbraco.Infrastructure/Security/SignOutSuccessResult.cs b/src/Umbraco.Infrastructure/Security/SignOutSuccessResult.cs new file mode 100644 index 0000000000..f1b4688505 --- /dev/null +++ b/src/Umbraco.Infrastructure/Security/SignOutSuccessResult.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Infrastructure.Security +{ + public class SignOutSuccessResult + { + public string SignOutRedirectUrl { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs b/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs deleted file mode 100644 index 2d21b03187..0000000000 --- a/src/Umbraco.Infrastructure/Security/UserInviteEventArgs.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; - -namespace Umbraco.Cms.Core.Security -{ - public class UserInviteEventArgs : IdentityAuditEventArgs - { - public UserInviteEventArgs(string ipAddress, string performingUser, UserInvite invitedUser, IUser localUser, string comment = null) - : base(AuditEvent.SendingUserInvite, ipAddress, performingUser, comment, string.Intern(localUser.Id.ToString()), localUser.Name) - { - InvitedUser = invitedUser ?? throw new System.ArgumentNullException(nameof(invitedUser)); - User = localUser; - } - - /// - /// The model used to invite the user - /// - public UserInvite InvitedUser { get; } - - /// - /// If event handler sets this to true it indicates that Umbraco will no try to send the invite itself - /// - public bool InviteHandled { get; set; } - - /// - /// The local user that has been created that is pending the invite - /// - public IUser User { get; } - - /// - /// if set to true will show the edit user button in the UI, else it will not be shown - /// - public bool ShowUserResult { get; set; } - - } -} diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index b6e91c717d..7c3b3db346 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -747,8 +748,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(content, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -773,7 +774,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + scope.Notifications.Publish(new ContentSavedNotification(content, evtMsgs).WithStateFrom(savingNotification)); } var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); @@ -802,8 +803,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new ContentSavingEventArgs(contentsA, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(contentsA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -823,7 +824,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + scope.Notifications.Publish(new ContentSavedNotification(contentsA, evtMsgs).WithStateFrom(savingNotification)); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Saved multiple content"); @@ -867,9 +868,11 @@ namespace Umbraco.Cms.Core.Services.Implement var allLangs = _languageRepository.GetMany().ToList(); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } // if culture is specific, first publish the invariant values, then publish the culture itself. // if culture is '*', then publish them all (including variants) @@ -881,7 +884,7 @@ namespace Umbraco.Cms.Core.Services.Implement // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. content.PublishCulture(impact); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId, raiseEvents); scope.Complete(); return result; } @@ -905,9 +908,12 @@ namespace Umbraco.Cms.Core.Services.Implement var allLangs = _languageRepository.GetMany().ToList(); var evtMsgs = EventMessagesFactory.Get(); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + + var savingNotification = new ContentSavingNotification(content, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } var varies = content.ContentType.VariesByCulture(); @@ -927,7 +933,7 @@ namespace Umbraco.Cms.Core.Services.Implement foreach (var impact in impacts) content.PublishCulture(impact); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId, raiseEvents); scope.Complete(); return result; } @@ -969,9 +975,11 @@ namespace Umbraco.Cms.Core.Services.Implement var allLangs = _languageRepository.GetMany().ToList(); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } // all cultures = unpublish whole if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null)) @@ -982,7 +990,7 @@ namespace Umbraco.Cms.Core.Services.Implement // to be non-routable so that when it's re-published all variants were as they were. content.PublishedState = PublishedState.Unpublishing; - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); scope.Complete(); return result; } @@ -996,7 +1004,7 @@ namespace Umbraco.Cms.Core.Services.Implement var removed = content.UnpublishCulture(culture); //save and publish any changes - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); scope.Complete(); @@ -1039,13 +1047,15 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); - var saveEventArgs = new ContentSavingEventArgs(content, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); + } var allLangs = _languageRepository.GetMany().ToList(); - var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, allLangs, userId, raiseEvents); + var result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId, raiseEvents); scope.Complete(); return result; } @@ -1056,7 +1066,7 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - /// + /// /// /// /// @@ -1069,15 +1079,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// private PublishResult CommitDocumentChangesInternal(IScope scope, IContent content, - ContentSavingEventArgs saveEventArgs, IReadOnlyCollection allLangs, + EventMessages evtMsgs, IReadOnlyCollection allLangs, + IDictionary notificationState, int userId = Cms.Core.Constants.Security.SuperUserId, bool raiseEvents = true, bool branchOne = false, bool branchRoot = false) { if (scope == null) throw new ArgumentNullException(nameof(scope)); if (content == null) throw new ArgumentNullException(nameof(content)); - if (saveEventArgs == null) throw new ArgumentNullException(nameof(saveEventArgs)); - - var evtMsgs = saveEventArgs.Messages; + if (evtMsgs == null) throw new ArgumentNullException(nameof(evtMsgs)); PublishResult publishResult = null; PublishResult unpublishResult = null; @@ -1125,7 +1134,7 @@ namespace Umbraco.Cms.Core.Services.Implement : null; // ensure that the document can be published, and publish handling events, business rules, etc - publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, saveEventArgs, allLangs); + publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ (!branchOne || branchRoot), culturesPublishing, culturesUnpublishing, evtMsgs, allLangs, notificationState); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! @@ -1210,7 +1219,7 @@ namespace Umbraco.Cms.Core.Services.Implement // raise the Saved event, always if (raiseEvents) { - scope.Events.Dispatch(Saved, this, saveEventArgs.ToContentSavedEventArgs(), nameof(Saved)); + scope.Notifications.Publish(new ContentSavedNotification(content, evtMsgs).WithState(notificationState)); } if (unpublishing) // we have tried to unpublish - won't happen in a branch @@ -1218,7 +1227,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (unpublishResult.Success) // and succeeded, trigger events { // events and audit - scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), "Unpublished"); + scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs).WithState(notificationState)); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); if (culturesUnpublishing != null) @@ -1273,7 +1282,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (!branchOne) // for branches, handled by SaveAndPublishBranch { scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - scope.Events.Dispatch(Published, this, saveEventArgs.ToContentPublishedEventArgs(), nameof(Published)); + scope.Notifications.Publish(new ContentPublishedNotification(content, evtMsgs).WithState(notificationState)); } // it was not published and now is... descendants that were 'published' (but @@ -1282,7 +1291,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id)) { var descendants = GetPublishedDescendantsLocked(content).ToArray(); - scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(descendants, false, evtMsgs), "Published"); + scope.Notifications.Publish(new ContentPublishedNotification(descendants, evtMsgs).WithState(notificationState)); } switch (publishResult.Result) @@ -1375,8 +1384,8 @@ namespace Umbraco.Cms.Core.Services.Implement if (pendingCultures.Count == 0) continue; //shouldn't happen but no point in processing this document if there's nothing there - var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(d, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); continue; @@ -1390,7 +1399,7 @@ namespace Umbraco.Cms.Core.Services.Implement d.UnpublishCulture(c); } - var result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); + var result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId); if (result.Success == false) _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); results.Add(result); @@ -1436,11 +1445,11 @@ namespace Umbraco.Cms.Core.Services.Implement if (pendingCultures.Count == 0) continue; //shouldn't happen but no point in processing this document if there's nothing there - var saveEventArgs = new ContentSavingEventArgs(d, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(d, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d)); - continue; // this document is canceled move next + continue; } var publishing = true; @@ -1470,7 +1479,7 @@ namespace Umbraco.Cms.Core.Services.Implement else if (!publishing) result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d); else - result = CommitDocumentChangesInternal(scope, d, saveEventArgs, allLangs.Value, d.WriterId); + result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value, savingNotification.State, d.WriterId); if (result.Success == false) _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id, result.Result); @@ -1694,7 +1703,7 @@ namespace Umbraco.Cms.Core.Services.Implement // trigger events for the entire branch // (SaveAndPublishBranchOne does *not* do it) scope.Events.Dispatch(TreeChanged, this, new TreeChange(document, TreeChangeTypes.RefreshBranch).ToEventArgs()); - scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(publishedDocuments, false, evtMsgs), nameof(Published)); + scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, evtMsgs)); scope.Complete(); } @@ -1718,9 +1727,11 @@ namespace Umbraco.Cms.Core.Services.Implement if (culturesToPublish.Count == 0) // empty = already published return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document); - var saveEventArgs = new ContentSavingEventArgs(document, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving))) + var savingNotification = new ContentSavingNotification(document, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document); + } // publish & check if values are valid if (!publishCultures(document, culturesToPublish, allLangs)) @@ -1729,7 +1740,7 @@ namespace Umbraco.Cms.Core.Services.Implement return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - var result = CommitDocumentChangesInternal(scope, document, saveEventArgs, allLangs, userId, branchOne: true, branchRoot: isRoot); + var result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, branchOne: true, branchRoot: isRoot); if (result.Success) publishedDocuments.Add(document); return result; @@ -1746,8 +1757,7 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(content, evtMsgs); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs, nameof(Deleting))) + if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(content, evtMsgs))) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -1759,9 +1769,11 @@ namespace Umbraco.Cms.Core.Services.Implement // but... Unpublishing event makes no sense (not going to cancel?) and no need to save // just raise the event if (content.Trashed == false && content.Published) - scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), nameof(Unpublished)); + { + scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs)); + } - DeleteLocked(scope, content); + DeleteLocked(scope, content, evtMsgs); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.Remove).ToEventArgs()); Audit(AuditType.Delete, userId, content.Id); @@ -1772,13 +1784,12 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Succeed(evtMsgs); } - private void DeleteLocked(IScope scope, IContent content) + private void DeleteLocked(IScope scope, IContent content, EventMessages evtMsgs) { void DoDelete(IContent c) { _documentRepository.Delete(c); - var args = new DeleteEventArgs(c, false); // raise event & get flagged files - scope.Events.Dispatch(Deleted, this, args, nameof(Deleted)); + scope.Notifications.Publish(new ContentDeletedNotification(c, evtMsgs)); // media files deleted by QueuingEventDispatcher } @@ -1809,10 +1820,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User deleting versions of a Content object public void DeleteVersions(int id, DateTime versionDate, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); - if (scope.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs)) + var deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); return; @@ -1821,8 +1834,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); _documentRepository.DeleteVersions(id, versionDate); - deleteRevisionsEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs); + scope.Notifications.Publish(new ContentDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version date)"); scope.Complete(); @@ -1839,9 +1851,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User deleting versions of a Content object public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - if (scope.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId))) + var deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, specificVersion: versionId); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); return; @@ -1858,7 +1873,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (c.VersionId != versionId && c.PublishedVersionId != versionId) // don't delete the current or published version _documentRepository.DeleteVersion(versionId); - scope.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId)); + scope.Notifications.Publish(new ContentDeletedVersionsNotification(id, evtMsgs, specificVersion: versionId).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete (by version)"); scope.Complete(); @@ -1881,8 +1896,9 @@ namespace Umbraco.Cms.Core.Services.Implement var originalPath = content.Path; var moveEventInfo = new MoveEventInfo(content, originalPath, Cms.Core.Constants.System.RecycleBinContent); - var moveEventArgs = new MoveEventArgs(evtMsgs, moveEventInfo); - if (scope.Events.DispatchCancelable(Trashing, this, moveEventArgs, nameof(Trashing))) + + var movingToRecycleBinNotification = new ContentMovingToRecycleBinNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); // causes rollback @@ -1901,9 +1917,7 @@ namespace Umbraco.Cms.Core.Services.Implement .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - moveEventArgs.CanCancel = false; - moveEventArgs.MoveInfoCollection = moveInfo; - scope.Events.Dispatch(Trashed, this, moveEventArgs, nameof(Trashed)); + scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfo, evtMsgs).WithStateFrom(movingToRecycleBinNotification)); Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin"); scope.Complete(); @@ -1932,6 +1946,8 @@ namespace Umbraco.Cms.Core.Services.Implement return; } + var evtMsgs = EventMessagesFactory.Get(); + var moves = new List<(IContent, string)>(); using (var scope = ScopeProvider.CreateScope()) @@ -1943,8 +1959,9 @@ namespace Umbraco.Cms.Core.Services.Implement throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback var moveEventInfo = new MoveEventInfo(content, content.Path, parentId); - var moveEventArgs = new MoveEventArgs(moveEventInfo); - if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs, nameof(Moving))) + + var movingNotification = new ContentMovingNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingNotification)) { scope.Complete(); return; // causes rollback @@ -1973,9 +1990,8 @@ namespace Umbraco.Cms.Core.Services.Implement .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved)); + scope.Notifications.Publish(new ContentMovedNotification(moveInfo, evtMsgs).WithStateFrom(movingNotification)); + Audit(AuditType.Move, userId, content.Id); scope.Complete(); @@ -2047,7 +2063,6 @@ namespace Umbraco.Cms.Core.Services.Implement /// public OperationResult EmptyRecycleBin(int userId = Cms.Core.Constants.Security.SuperUserId) { - var nodeObjectType = Cms.Core.Constants.ObjectTypes.Document; var deleted = new List(); var evtMsgs = EventMessagesFactory.Get(); @@ -2060,8 +2075,8 @@ namespace Umbraco.Cms.Core.Services.Implement // are managed by Delete, and not here. // no idea what those events are for, keep a simplified version - var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType, evtMsgs); - if (scope.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs)) + var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(evtMsgs); + if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -2072,13 +2087,11 @@ namespace Umbraco.Cms.Core.Services.Implement var contents = _documentRepository.Get(query).ToArray(); foreach (var content in contents) { - DeleteLocked(scope, content); + DeleteLocked(scope, content, evtMsgs); deleted.Add(content); } - recycleBinEventArgs.CanCancel = false; - recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?! - scope.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); + scope.Notifications.Publish(new ContentEmptiedRecycleBinNotification(evtMsgs).WithStateFrom(emptyingRecycleBinNotification)); scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.RecycleBinContent, "Recycle bin emptied"); @@ -2118,13 +2131,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// The newly created object public IContent Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + var copy = content.DeepCloneWithResetIdentities(); copy.ParentId = parentId; using (var scope = ScopeProvider.CreateScope()) { - var copyEventArgs = new CopyEventArgs(content, copy, true, parentId, relateToOriginal); - if (scope.Events.DispatchCancelable(Copying, this, copyEventArgs)) + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(content, copy, parentId, evtMsgs))) { scope.Complete(); return null; @@ -2179,8 +2193,10 @@ namespace Umbraco.Cms.Core.Services.Implement var descendantCopy = descendant.DeepCloneWithResetIdentities(); descendantCopy.ParentId = parentId; - if (scope.Events.DispatchCancelable(Copying, this, new CopyEventArgs(descendant, descendantCopy, parentId))) + if (scope.Notifications.PublishCancelable(new ContentCopyingNotification(descendant, descendantCopy, parentId, evtMsgs))) + { continue; + } // a copy is not published (but not really unpublishing either) // update the create author and last edit author @@ -2204,7 +2220,9 @@ namespace Umbraco.Cms.Core.Services.Implement scope.Events.Dispatch(TreeChanged, this, new TreeChange(copy, TreeChangeTypes.RefreshBranch).ToEventArgs()); foreach (var x in copies) - scope.Events.Dispatch(Copied, this, new CopyEventArgs(x.Item1, x.Item2, false, x.Item2.ParentId, relateToOriginal)); + { + scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, evtMsgs)); + } Audit(AuditType.Copy, userId, content.Id); scope.Complete(); @@ -2221,10 +2239,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// True if sending publication was successful otherwise false public bool SendToPublication(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var sendToPublishEventArgs = new SendToPublishEventArgs(content); - if (scope.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs)) + var sendingToPublishNotification = new ContentSendingToPublishNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(sendingToPublishNotification)) { scope.Complete(); return false; @@ -2249,8 +2269,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (!saveResult.Success) return saveResult.Success; - sendToPublishEventArgs.CanCancel = false; - scope.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs); + scope.Notifications.Publish(new ContentSentToPublishNotification(content, evtMsgs).WithStateFrom(sendingToPublishNotification)); if (culturesChanging != null) Audit(AuditType.SendToPublishVariant, userId, content.Id, $"Send To Publish for cultures: {culturesChanging}", culturesChanging); @@ -2322,16 +2341,21 @@ namespace Umbraco.Cms.Core.Services.Implement private OperationResult Sort(IScope scope, IContent[] itemsA, int userId, EventMessages evtMsgs, bool raiseEvents) { - var saveEventArgs = new ContentSavingEventArgs(itemsA, evtMsgs); + var sortingNotification = new ContentSortingNotification(itemsA, evtMsgs); + var savingNotification = new ContentSavingNotification(itemsA, evtMsgs); if (raiseEvents) { - //raise cancelable sorting event - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Sorting))) + // raise cancelable sorting event + if (scope.Notifications.PublishCancelable(sortingNotification)) + { return OperationResult.Cancel(evtMsgs); + } - //raise saving event (this one cannot be canceled) - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saving, this, saveEventArgs, nameof(Saving)); + // raise cancelable saving event + if (scope.Notifications.PublishCancelable(savingNotification)) + { + return OperationResult.Cancel(evtMsgs); + } } var published = new List(); @@ -2364,16 +2388,17 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - var savedEventsArgs = saveEventArgs.ToContentSavedEventArgs(); //first saved, then sorted - scope.Events.Dispatch(Saved, this, savedEventsArgs, nameof(Saved)); - scope.Events.Dispatch(Sorted, this, savedEventsArgs, nameof(Sorted)); + scope.Notifications.Publish(new ContentSavedNotification(itemsA, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new ContentSortedNotification(itemsA, evtMsgs).WithStateFrom(sortingNotification)); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); if (raiseEvents && published.Any()) - scope.Events.Dispatch(Published, this, new ContentPublishedEventArgs(published, false, evtMsgs), "Published"); + { + scope.Notifications.Publish(new ContentPublishedNotification(published, evtMsgs)); + } Audit(AuditType.Sort, userId, 0, "Sorting content performed by user"); return OperationResult.Succeed(evtMsgs); @@ -2453,140 +2478,13 @@ namespace Umbraco.Cms.Core.Services.Implement #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Delete Versions - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete Versions - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Sorting - /// - public static event TypedEventHandler> Sorting; - - /// - /// Occurs after Sorting - /// - public static event TypedEventHandler> Sorted; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler Saved; - - /// - /// Occurs before Copy - /// - public static event TypedEventHandler> Copying; - - /// - /// Occurs after Copy - /// - public static event TypedEventHandler> Copied; - - /// - /// Occurs before Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Content is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before Rollback - /// - public static event TypedEventHandler> RollingBack; - - /// - /// Occurs after Rollback - /// - public static event TypedEventHandler> RolledBack; - - /// - /// Occurs before Send to Publish - /// - public static event TypedEventHandler> SendingToPublish; - - /// - /// Occurs after Send to Publish - /// - public static event TypedEventHandler> SentToPublish; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - - /// - /// Occurs before publish - /// - public static event TypedEventHandler Publishing; - - /// - /// Occurs after publish - /// - public static event TypedEventHandler Published; - - /// - /// Occurs before unpublish - /// - public static event TypedEventHandler> Unpublishing; - - /// - /// Occurs after unpublish - /// - public static event TypedEventHandler> Unpublished; - /// /// Occurs after change. /// - public static event TypedEventHandler.EventArgs> TreeChanged; - - /// - /// Occurs after a blueprint has been saved. - /// - public static event TypedEventHandler> SavedBlueprint; - - /// - /// Occurs after a blueprint has been deleted. - /// - public static event TypedEventHandler> DeletedBlueprint; + /// + /// This event needs to be rewritten using notifications instead + /// + internal static event TypedEventHandler.EventArgs> TreeChanged; #endregion @@ -2601,14 +2499,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// /// /// - /// + /// + /// /// private PublishResult StrategyCanPublish(IScope scope, IContent content, bool checkPath, IReadOnlyList culturesPublishing, - IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs, - IReadOnlyCollection allLangs) + IReadOnlyCollection culturesUnpublishing, EventMessages evtMsgs, IReadOnlyCollection allLangs, IDictionary notificationState) { - // raise Publishing event - if (scope.Events.DispatchCancelable(Publishing, this, savingEventArgs.ToContentPublishingEventArgs())) + // raise Publishing notification + if (scope.Notifications.PublishCancelable(new ContentPublishingNotification(content, evtMsgs).WithState(notificationState))) { _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); @@ -2767,8 +2665,8 @@ namespace Umbraco.Cms.Core.Services.Implement /// private PublishResult StrategyCanUnpublish(IScope scope, IContent content, EventMessages evtMsgs) { - // raise Unpublishing event - if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs(content, evtMsgs))) + // raise Unpublishing notification + if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs))) { _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id); return new PublishResult(PublishResultType.FailedUnpublishCancelledByEvent, evtMsgs, content); @@ -2837,6 +2735,7 @@ namespace Umbraco.Cms.Core.Services.Implement var changes = new List>(); var moves = new List<(IContent, string)>(); var contentTypeIdsA = contentTypeIds.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); // using an immediate uow here because we keep making changes with // PerformMoveLocked and DeleteLocked that must be applied immediately, @@ -2849,7 +2748,7 @@ namespace Umbraco.Cms.Core.Services.Implement var query = Query().WhereIn(x => x.ContentTypeId, contentTypeIdsA); var contents = _documentRepository.Get(query).ToArray(); - if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(contents), nameof(Deleting))) + if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(contents, evtMsgs))) { scope.Complete(); return; @@ -2863,7 +2762,9 @@ namespace Umbraco.Cms.Core.Services.Implement // but... Unpublishing event makes no sense (not going to cancel?) and no need to save // just raise the event if (content.Trashed == false && content.Published) - scope.Events.Dispatch(Unpublished, this, new PublishEventArgs(content, false, false), nameof(Unpublished)); + { + scope.Notifications.Publish(new ContentUnpublishedNotification(content, evtMsgs)); + } // if current content has children, move them to trash var c = content; @@ -2878,7 +2779,7 @@ namespace Umbraco.Cms.Core.Services.Implement // delete content // triggers the deleted event (and handles the files) - DeleteLocked(scope, content); + DeleteLocked(scope, content, evtMsgs); changes.Add(new TreeChange(content, TreeChangeTypes.Remove)); } @@ -2886,7 +2787,9 @@ namespace Umbraco.Cms.Core.Services.Implement .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); if (moveInfos.Length > 0) - scope.Events.Dispatch(Trashed, this, new MoveEventArgs(false, moveInfos), nameof(Trashed)); + { + scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfos, evtMsgs)); + } scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, $"Delete content of type {string.Join(",", contentTypeIdsA)}"); @@ -2963,6 +2866,8 @@ namespace Umbraco.Cms.Core.Services.Implement public void SaveBlueprint(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + //always ensure the blueprint is at the root if (content.ParentId != -1) content.ParentId = -1; @@ -2983,7 +2888,7 @@ namespace Umbraco.Cms.Core.Services.Implement Audit(AuditType.Save, Cms.Core.Constants.Security.SuperUserId, content.Id, $"Saved content template: {content.Name}"); - scope.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); + scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs)); scope.Complete(); } @@ -2991,11 +2896,13 @@ namespace Umbraco.Cms.Core.Services.Implement public void DeleteBlueprint(IContent content, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); _documentBlueprintRepository.Delete(content); - scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), nameof(DeletedBlueprint)); + scope.Notifications.Publish(new ContentDeletedBlueprintNotification(content, evtMsgs)); scope.Complete(); } } @@ -3065,6 +2972,8 @@ namespace Umbraco.Cms.Core.Services.Implement public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Cms.Core.Constants.Locks.ContentTree); @@ -3085,7 +2994,7 @@ namespace Umbraco.Cms.Core.Services.Implement _documentBlueprintRepository.Delete(blueprint); } - scope.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(blueprints), nameof(DeletedBlueprint)); + scope.Notifications.Publish(new ContentDeletedBlueprintNotification(blueprints, evtMsgs)); scope.Complete(); } } @@ -3120,10 +3029,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var rollbackEventArgs = new RollbackEventArgs(content); - - //Emit RollingBack event aka before - if (scope.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) + var rollingBackNotification = new ContentRollingBackNotification(content, evtMsgs); + if (scope.Notifications.PublishCancelable(rollingBackNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); @@ -3143,9 +3050,7 @@ namespace Umbraco.Cms.Core.Services.Implement } else { - //Emit RolledBack event aka after - rollbackEventArgs.CanCancel = false; - scope.Events.Dispatch(RolledBack, this, rollbackEventArgs); + scope.Notifications.Publish(new ContentRolledBackNotification(content, evtMsgs).WithStateFrom(rollingBackNotification)); //Logging & Audit message _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); diff --git a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs index 4b9d6f8e5c..38a7ad7b28 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MediaService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -289,19 +290,21 @@ namespace Umbraco.Cms.Core.Services.Implement private void CreateMedia(IScope scope, Core.Models.Media media, IMedia parent, int userId, bool withIdentity) { + var evtMsgs = EventMessagesFactory.Get(); + media.CreatorId = userId; if (withIdentity) { - // if saving is cancelled, media remains without an identity - var saveEventArgs = new SaveEventArgs(media); - if (Saving.IsRaisedEventCancelled(saveEventArgs, this)) + var savingNotification = new MediaSavingNotification(media, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { return; + } _mediaRepository.Save(media); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MediaSavedNotification(media, evtMsgs).WithStateFrom(savingNotification)); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.RefreshNode).ToEventArgs()); } @@ -659,8 +662,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(media, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotification = new MediaSavingNotification(media, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -682,8 +685,7 @@ namespace Umbraco.Cms.Core.Services.Implement _mediaRepository.Save(media); if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MediaSavedNotification(media, evtMsgs).WithStateFrom(savingNotification)); } var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, changeType).ToEventArgs()); @@ -708,8 +710,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(mediasA, evtMsgs); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, new SaveEventArgs(mediasA, evtMsgs))) + var savingNotification = new MediaSavingNotification(mediasA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -727,8 +729,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new MediaSavedNotification(mediasA, evtMsgs).WithStateFrom(savingNotification)); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Bulk save media"); @@ -754,7 +755,7 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(media, evtMsgs))) + if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(media, evtMsgs))) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -762,7 +763,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Cms.Core.Constants.Locks.MediaTree); - DeleteLocked(scope, media); + DeleteLocked(scope, media, evtMsgs); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.Remove).ToEventArgs()); Audit(AuditType.Delete, userId, media.Id); @@ -773,13 +774,12 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(evtMsgs); } - private void DeleteLocked(IScope scope, IMedia media) + private void DeleteLocked(IScope scope, IMedia media, EventMessages evtMsgs) { void DoDelete(IMedia c) { _mediaRepository.Delete(c); - var args = new DeleteEventArgs(c, false); // raise event & get flagged files - scope.Events.Dispatch(Deleted, this, args); + scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs)); // media files deleted by QueuingEventDispatcher } @@ -815,36 +815,24 @@ namespace Umbraco.Cms.Core.Services.Implement { DeleteVersions(scope, true, id, versionDate, userId); scope.Complete(); - - //if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, dateToRetain: versionDate))) - //{ - // uow.Complete(); - // return; - //} - - //uow.WriteLock(Constants.Locks.MediaTree); - //var repository = uow.CreateRepository(); - //repository.DeleteVersions(id, versionDate); - - //uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate)); - //Audit(uow, AuditType.Delete, "Delete Media by version date, userId, Constants.System.Root); - - //uow.Complete(); } } private void DeleteVersions(IScope scope, bool wlock, int id, DateTime versionDate, int userId = Cms.Core.Constants.Security.SuperUserId) { - var args = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate); - if (scope.Events.DispatchCancelable(DeletingVersions, this, args)) + var evtMsgs = EventMessagesFactory.Get(); + + var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) + { return; + } if (wlock) scope.WriteLock(Cms.Core.Constants.Locks.MediaTree); _mediaRepository.DeleteVersions(id, versionDate); - args.CanCancel = false; - scope.Events.Dispatch(DeletedVersions, this, args); + scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete Media by version date"); } @@ -858,10 +846,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User deleting versions of a Media object public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Cms.Core.Constants.Security.SuperUserId) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var args = new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId); - if (scope.Events.DispatchCancelable(DeletingVersions, this, args)) + var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, specificVersion: versionId); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); return; @@ -879,8 +869,7 @@ namespace Umbraco.Cms.Core.Services.Implement _mediaRepository.DeleteVersion(versionId); - args.CanCancel = false; - scope.Events.Dispatch(DeletedVersions, this, args); + scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, specificVersion: versionId).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete Media by version"); scope.Complete(); @@ -911,8 +900,9 @@ namespace Umbraco.Cms.Core.Services.Implement var originalPath = media.Path; var moveEventInfo = new MoveEventInfo(media, originalPath, Cms.Core.Constants.System.RecycleBinMedia); - var moveEventArgs = new MoveEventArgs(true, evtMsgs, moveEventInfo); - if (scope.Events.DispatchCancelable(Trashing, this, moveEventArgs, nameof(Trashing))) + + var movingToRecycleBinNotification = new MediaMovingToRecycleBinNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -921,11 +911,8 @@ namespace Umbraco.Cms.Core.Services.Implement PerformMoveLocked(media, Cms.Core.Constants.System.RecycleBinMedia, null, userId, moves, true); scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, TreeChangeTypes.RefreshBranch).ToEventArgs()); - var moveInfo = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) - .ToArray(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - scope.Events.Dispatch(Trashed, this, moveEventArgs, nameof(Trashed)); + var moveInfo = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)).ToArray(); + scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfo, evtMsgs).WithStateFrom(movingToRecycleBinNotification)); Audit(AuditType.Move, userId, media.Id, "Move Media to recycle bin"); scope.Complete(); @@ -962,8 +949,8 @@ namespace Umbraco.Cms.Core.Services.Implement throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback var moveEventInfo = new MoveEventInfo(media, media.Path, parentId); - var moveEventArgs = new MoveEventArgs(true, evtMsgs, moveEventInfo); - if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs, nameof(Moving))) + var movingNotification = new MediaMovingNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -979,9 +966,7 @@ namespace Umbraco.Cms.Core.Services.Implement var moveInfo = moves //changes .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); - moveEventArgs.MoveInfoCollection = moveInfo; - moveEventArgs.CanCancel = false; - scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved)); + scope.Notifications.Publish(new MediaMovedNotification(moveInfo, evtMsgs).WithStateFrom(movingNotification)); Audit(AuditType.Move, userId, media.Id); scope.Complete(); } @@ -1050,7 +1035,6 @@ namespace Umbraco.Cms.Core.Services.Implement /// Optional Id of the User emptying the Recycle Bin public OperationResult EmptyRecycleBin(int userId = Cms.Core.Constants.Security.SuperUserId) { - var nodeObjectType = Cms.Core.Constants.ObjectTypes.Media; var deleted = new List(); var evtMsgs = EventMessagesFactory.Get(); // TODO: and then? @@ -1063,23 +1047,22 @@ namespace Umbraco.Cms.Core.Services.Implement // v7 EmptyingRecycleBin and EmptiedRecycleBin events are greatly simplified since // each deleted items will have its own deleting/deleted events. so, files and such // are managed by Delete, and not here. - var args = new RecycleBinEventArgs(nodeObjectType, evtMsgs); - - if (scope.Events.DispatchCancelable(EmptyingRecycleBin, this, args)) + var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(evtMsgs); + if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) { scope.Complete(); return OperationResult.Cancel(evtMsgs); } + // emptying the recycle bin means deleting whatever is in there - do it properly! var query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.RecycleBinMedia); var medias = _mediaRepository.Get(query).ToArray(); foreach (var media in medias) { - DeleteLocked(scope, media); + DeleteLocked(scope, media, evtMsgs); deleted.Add(media); } - args.CanCancel = false; - scope.Events.Dispatch(EmptiedRecycleBin, this, args); + scope.Notifications.Publish(new MediaEmptiedRecycleBinNotification(new EventMessages()).WithStateFrom(emptyingRecycleBinNotification)); scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.RecycleBinMedia, "Empty Media recycle bin"); scope.Complete(); @@ -1105,10 +1088,12 @@ namespace Umbraco.Cms.Core.Services.Implement var itemsA = items.ToArray(); if (itemsA.Length == 0) return true; + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var args = new SaveEventArgs(itemsA); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, args)) + var savingNotification = new MediaSavingNotification(itemsA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return false; @@ -1137,8 +1122,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - args.CanCancel = false; - scope.Events.Dispatch(Saved, this, args); + scope.Notifications.Publish(new MediaSavedNotification(itemsA, evtMsgs).WithStateFrom(savingNotification)); } scope.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); Audit(AuditType.Sort, userId, 0); @@ -1216,70 +1200,13 @@ namespace Umbraco.Cms.Core.Services.Implement #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - /// - /// Occurs before Delete Versions - /// - public static event TypedEventHandler DeletingVersions; - - /// - /// Occurs after Delete Versions - /// - public static event TypedEventHandler DeletedVersions; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Media is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashing; - - /// - /// Occurs after Media is moved to Recycle Bin - /// - public static event TypedEventHandler> Trashed; - - /// - /// Occurs before Move - /// - public static event TypedEventHandler> Moving; - - /// - /// Occurs after Move - /// - public static event TypedEventHandler> Moved; - - /// - /// Occurs before the Recycle Bin is emptied - /// - public static event TypedEventHandler EmptyingRecycleBin; - - /// - /// Occurs after the Recycle Bin has been Emptied - /// - public static event TypedEventHandler EmptiedRecycleBin; - /// /// Occurs after change. /// - public static event TypedEventHandler.EventArgs> TreeChanged; + /// + /// This event needs to be rewritten using notifications instead + /// + internal static event TypedEventHandler.EventArgs> TreeChanged; #endregion @@ -1307,6 +1234,7 @@ namespace Umbraco.Cms.Core.Services.Implement var changes = new List>(); var moves = new List<(IMedia, string)>(); var mediaTypeIdsA = mediaTypeIds.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) { @@ -1315,7 +1243,7 @@ namespace Umbraco.Cms.Core.Services.Implement var query = Query().WhereIn(x => x.ContentTypeId, mediaTypeIdsA); var medias = _mediaRepository.Get(query).ToArray(); - if (scope.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(medias))) + if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(medias, evtMsgs))) { scope.Complete(); return; @@ -1338,14 +1266,16 @@ namespace Umbraco.Cms.Core.Services.Implement // delete media // triggers the deleted event (and handles the files) - DeleteLocked(scope, media); + DeleteLocked(scope, media, evtMsgs); changes.Add(new TreeChange(media, TreeChangeTypes.Remove)); } var moveInfos = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); if (moveInfos.Length > 0) - scope.Events.Dispatch(Trashed, this, new MoveEventArgs(false, moveInfos), nameof(Trashed)); + { + scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfos, evtMsgs)); + } scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs()); Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, $"Delete Media of types {string.Join(",", mediaTypeIdsA)}"); diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CancelableEnumerableObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CancelableEnumerableObjectNotification.cs new file mode 100644 index 0000000000..3875967530 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CancelableEnumerableObjectNotification.cs @@ -0,0 +1,18 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CancelableEnumerableObjectNotification : CancelableObjectNotification> + { + protected CancelableEnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) + { + } + protected CancelableEnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CancelableObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CancelableObjectNotification.cs new file mode 100644 index 0000000000..603ee9efbb --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CancelableObjectNotification.cs @@ -0,0 +1,23 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CancelableObjectNotification : ObjectNotification, ICancelableNotification 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); + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentCopiedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopiedNotification.cs new file mode 100644 index 0000000000..55946366e5 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopiedNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentCopiedNotification : CopiedNotification + { + public ContentCopiedNotification(IContent original, IContent copy, int parentId, bool relateToOriginal, EventMessages messages) + : base(original, copy, parentId, relateToOriginal, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentCopyingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopyingNotification.cs new file mode 100644 index 0000000000..85f5d1e189 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentCopyingNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentCopyingNotification : CopyingNotification + { + public ContentCopyingNotification(IContent original, IContent copy, int parentId, EventMessages messages) + : base(original, copy, parentId, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedBlueprintNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedBlueprintNotification.cs new file mode 100644 index 0000000000..55e61276d0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedBlueprintNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletedBlueprintNotification : EnumerableObjectNotification + { + public ContentDeletedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentDeletedBlueprintNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedBlueprints => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedNotification.cs new file mode 100644 index 0000000000..4c0d6c3e98 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletedNotification : DeletedNotification + { + public ContentDeletedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedVersionsNotification.cs new file mode 100644 index 0000000000..19a2ae45d3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletedVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletedVersionsNotification : DeletedVersionsNotification + { + public ContentDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingNotification.cs new file mode 100644 index 0000000000..d38eb13beb --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletingNotification : DeletingNotification + { + public ContentDeletingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingVersionsNotification.cs new file mode 100644 index 0000000000..7b791eafef --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentDeletingVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentDeletingVersionsNotification : DeletingVersionsNotification + { + public ContentDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptiedRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptiedRecycleBinNotification.cs new file mode 100644 index 0000000000..53bc62fe91 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptiedRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentEmptiedRecycleBinNotification : EmptiedRecycleBinNotification + { + public ContentEmptiedRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptyingRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptyingRecycleBinNotification.cs new file mode 100644 index 0000000000..5d0ab67e26 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentEmptyingRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentEmptyingRecycleBinNotification : EmptyingRecycleBinNotification + { + public ContentEmptyingRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedNotification.cs new file mode 100644 index 0000000000..50bf0d1417 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovedNotification : MovedNotification + { + public ContentMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedToRecycleBinNotification.cs new file mode 100644 index 0000000000..b9da66e9aa --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovedToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovedToRecycleBinNotification : MovedToRecycleBinNotification + { + public ContentMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingNotification.cs new file mode 100644 index 0000000000..b57c9e6cf0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovingNotification : MovingNotification + { + public ContentMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingToRecycleBinNotification.cs new file mode 100644 index 0000000000..fc5a5f401a --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentMovingToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentMovingToRecycleBinNotification : MovingToRecycleBinNotification + { + public ContentMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public ContentMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentNotificationExtensions.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentNotificationExtensions.cs new file mode 100644 index 0000000000..c04a04ef87 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentNotificationExtensions.cs @@ -0,0 +1,67 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public static class ContentNotificationExtensions + { + /// + /// Determines whether a culture is being saved, during a Saving notification + /// + public static bool IsSavingCulture(this SavingNotification notification, T content, string culture) where T : IContentBase + => content.CultureInfos.TryGetValue(culture, out ContentCultureInfos cultureInfo) && cultureInfo.IsDirty(); + + /// + /// Determines whether a culture has been saved, during a Saved notification + /// + public static bool HasSavedCulture(this SavedNotification notification, T content, string culture) where T : IContentBase + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UpdatedCulture + culture); + + /// + /// Determines whether a culture is being published, during a Publishing notification + /// + public static bool IsPublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) + => IsPublishingCulture(content, culture); + + /// + /// Determines whether a culture is being unpublished, during an Publishing notification + /// + public static bool IsUnpublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) + => IsUnpublishingCulture(content, culture); + + /// + /// Determines whether a culture is being unpublished, during a Unpublishing notification + /// + public static bool IsUnpublishingCulture(this ContentUnpublishingNotification notification, IContent content, string culture) + => IsUnpublishingCulture(content, culture); + + /// + /// Determines whether a culture has been published, during a Published notification + /// + public static bool HasPublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); + + /// + /// Determines whether a culture has been unpublished, during a Published notification + /// + public static bool HasUnpublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) + => HasUnpublishedCulture(content, culture); + + /// + /// Determines whether a culture has been unpublished, during an Unpublished notification + /// + public static bool HasUnpublishedCulture(this ContentUnpublishedNotification notification, IContent content, string culture) + => HasUnpublishedCulture(content, culture); + + private static bool IsUnpublishingCulture(IContent content, string culture) + => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); + + public static bool IsPublishingCulture(IContent content, string culture) + => content.PublishCultureInfos.TryGetValue(culture, out ContentCultureInfos cultureInfo) && cultureInfo.IsDirty(); + + public static bool HasUnpublishedCulture(IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishedNotification.cs new file mode 100644 index 0000000000..f19899136e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishedNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentPublishedNotification : EnumerableObjectNotification + { + public ContentPublishedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentPublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable PublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishingNotification.cs new file mode 100644 index 0000000000..b31b8fa391 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentPublishingNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentPublishingNotification : CancelableEnumerableObjectNotification + { + public ContentPublishingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentPublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable PublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentRolledBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentRolledBackNotification.cs new file mode 100644 index 0000000000..fbc64eebb2 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentRolledBackNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentRolledBackNotification : RolledBackNotification + { + public ContentRolledBackNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentRollingBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentRollingBackNotification.cs new file mode 100644 index 0000000000..ae3065ad58 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentRollingBackNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentRollingBackNotification : RollingBackNotification + { + public ContentRollingBackNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedBlueprintNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedBlueprintNotification.cs new file mode 100644 index 0000000000..a57f278ef0 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedBlueprintNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSavedBlueprintNotification : ObjectNotification + { + public ContentSavedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public IContent SavedBlueprint => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedNotification.cs new file mode 100644 index 0000000000..0a79bf1285 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSavedNotification : SavedNotification + { + public ContentSavedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavingNotification.cs new file mode 100644 index 0000000000..c32f584cc6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSavingNotification : SavingNotification + { + public ContentSavingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSendingToPublishNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSendingToPublishNotification.cs new file mode 100644 index 0000000000..9719580c62 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSendingToPublishNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSendingToPublishNotification : CancelableObjectNotification + { + public ContentSendingToPublishNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public IContent Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSentToPublishNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSentToPublishNotification.cs new file mode 100644 index 0000000000..733fbfbb93 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSentToPublishNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSentToPublishNotification : ObjectNotification + { + public ContentSentToPublishNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public IContent Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSortedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortedNotification.cs new file mode 100644 index 0000000000..fa51155f96 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortedNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSortedNotification : SortedNotification + { + public ContentSortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentSortingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortingNotification.cs new file mode 100644 index 0000000000..e5b011276d --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentSortingNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentSortingNotification : SortingNotification + { + public ContentSortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishedNotification.cs new file mode 100644 index 0000000000..2beb8f2d55 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishedNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentUnpublishedNotification : EnumerableObjectNotification + { + public ContentUnpublishedNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentUnpublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable UnpublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishingNotification.cs new file mode 100644 index 0000000000..85db0015c3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ContentUnpublishingNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class ContentUnpublishingNotification : CancelableEnumerableObjectNotification + { + public ContentUnpublishingNotification(IContent target, EventMessages messages) : base(target, messages) + { + } + + public ContentUnpublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable UnpublishedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CopiedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CopiedNotification.cs new file mode 100644 index 0000000000..653f3913aa --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CopiedNotification.cs @@ -0,0 +1,24 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CopiedNotification : ObjectNotification where T : class + { + protected CopiedNotification(T original, T copy, int parentId, bool relateToOriginal, EventMessages messages) : base(original, messages) + { + Copy = copy; + ParentId = parentId; + RelateToOriginal = relateToOriginal; + } + + public T Original => Target; + + public T Copy { get; } + + public int ParentId { get; } + public bool RelateToOriginal { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/CopyingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/CopyingNotification.cs new file mode 100644 index 0000000000..ae658577b6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/CopyingNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class CopyingNotification : CancelableObjectNotification where T : class + { + protected CopyingNotification(T original, T copy, int parentId, EventMessages messages) : base(original, messages) + { + Copy = copy; + ParentId = parentId; + } + + public T Original => Target; + + public T Copy { get; } + + public int ParentId { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletedNotification.cs new file mode 100644 index 0000000000..99016ca073 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletedNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletedNotification : EnumerableObjectNotification + { + protected DeletedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotification.cs new file mode 100644 index 0000000000..4e8e4d5ba4 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletedVersionsNotification : DeletedVersionsNotificationBase where T : class + { + protected DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotificationBase.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotificationBase.cs new file mode 100644 index 0000000000..938a174fbe --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletedVersionsNotificationBase.cs @@ -0,0 +1,30 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletedVersionsNotificationBase : StatefulNotification where T : class + { + protected DeletedVersionsNotificationBase(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + { + Id = id; + Messages = messages; + SpecificVersion = specificVersion; + DeletePriorVersions = deletePriorVersions; + DateToRetain = dateToRetain; + } + + public int Id { get; } + + public EventMessages Messages { get; } + + public int SpecificVersion { get; } + + public bool DeletePriorVersions { get; } + + public DateTime DateToRetain { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs new file mode 100644 index 0000000000..2dd8e09c6b --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletingNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletingNotification : CancelableEnumerableObjectNotification + { + protected DeletingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected DeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable DeletedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/DeletingVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/DeletingVersionsNotification.cs new file mode 100644 index 0000000000..4c6d8423fd --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/DeletingVersionsNotification.cs @@ -0,0 +1,18 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class DeletingVersionsNotification : DeletedVersionsNotificationBase, ICancelableNotification where T : class + { + protected DeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + + public bool Cancel { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/EmptiedRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/EmptiedRecycleBinNotification.cs new file mode 100644 index 0000000000..1859687170 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/EmptiedRecycleBinNotification.cs @@ -0,0 +1,14 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class EmptiedRecycleBinNotification : StatefulNotification where T : class + { + protected EmptiedRecycleBinNotification(EventMessages messages) => Messages = messages; + + public EventMessages Messages { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/EmptyingRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/EmptyingRecycleBinNotification.cs new file mode 100644 index 0000000000..86ee7f303d --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/EmptyingRecycleBinNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class EmptyingRecycleBinNotification : StatefulNotification, ICancelableNotification where T : class + { + protected EmptyingRecycleBinNotification(EventMessages messages) => Messages = messages; + + public EventMessages Messages { get; } + + public bool Cancel { get; set; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/EnumerableObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/EnumerableObjectNotification.cs new file mode 100644 index 0000000000..0201040ecf --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/EnumerableObjectNotification.cs @@ -0,0 +1,19 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class EnumerableObjectNotification : ObjectNotification> + { + protected EnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) + { + } + + protected EnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedNotification.cs new file mode 100644 index 0000000000..c78bfa863d --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletedNotification : DeletedNotification + { + public MediaDeletedNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedVersionsNotification.cs new file mode 100644 index 0000000000..789079bd5e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletedVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletedVersionsNotification : DeletedVersionsNotification + { + public MediaDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingNotification.cs new file mode 100644 index 0000000000..524865bbfb --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletingNotification : DeletingNotification + { + public MediaDeletingNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + + public MediaDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingVersionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingVersionsNotification.cs new file mode 100644 index 0000000000..fd979b5641 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaDeletingVersionsNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaDeletingVersionsNotification : DeletingVersionsNotification + { + public MediaDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptiedRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptiedRecycleBinNotification.cs new file mode 100644 index 0000000000..8d984eceab --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptiedRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaEmptiedRecycleBinNotification : EmptiedRecycleBinNotification + { + public MediaEmptiedRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptyingRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptyingRecycleBinNotification.cs new file mode 100644 index 0000000000..1fd72c7d99 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaEmptyingRecycleBinNotification.cs @@ -0,0 +1,15 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaEmptyingRecycleBinNotification : EmptyingRecycleBinNotification + { + public MediaEmptyingRecycleBinNotification(EventMessages messages) : base(messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedNotification.cs new file mode 100644 index 0000000000..43f8f8a3ff --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovedNotification : MovedNotification + { + public MediaMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedToRecycleBinNotification.cs new file mode 100644 index 0000000000..bb9274f941 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovedToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovedToRecycleBinNotification : MovedToRecycleBinNotification + { + public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingNotification.cs new file mode 100644 index 0000000000..407c95918a --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovingNotification : MovingNotification + { + public MediaMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingToRecycleBinNotification.cs new file mode 100644 index 0000000000..72e20ca424 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaMovingToRecycleBinNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaMovingToRecycleBinNotification : MovingToRecycleBinNotification + { + public MediaMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + { + } + + public MediaMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaSavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavedNotification.cs new file mode 100644 index 0000000000..4337cd1eb3 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavedNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaSavedNotification : SavedNotification + { + public MediaSavedNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + + public MediaSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MediaSavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavingNotification.cs new file mode 100644 index 0000000000..cca0de8d19 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MediaSavingNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public sealed class MediaSavingNotification : SavingNotification + { + public MediaSavingNotification(IMedia target, EventMessages messages) : base(target, messages) + { + } + + public MediaSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovedNotification.cs new file mode 100644 index 0000000000..35b587f73c --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovedNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovedNotification : ObjectNotification>> + { + protected MovedNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + protected MovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovedToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovedToRecycleBinNotification.cs new file mode 100644 index 0000000000..4562cdab30 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovedToRecycleBinNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovedToRecycleBinNotification : ObjectNotification>> + { + protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + protected MovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovingNotification.cs new file mode 100644 index 0000000000..2781c0ba57 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovingNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovingNotification : CancelableObjectNotification>> + { + protected MovingNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) + { + } + + protected MovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MovingToRecycleBinNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MovingToRecycleBinNotification.cs new file mode 100644 index 0000000000..38e989a418 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MovingToRecycleBinNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MovingToRecycleBinNotification : CancelableObjectNotification>> + { + protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) + { + } + + protected MovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable> MoveInfoCollection => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ObjectNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ObjectNotification.cs new file mode 100644 index 0000000000..16c57f210e --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ObjectNotification.cs @@ -0,0 +1,20 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class ObjectNotification : StatefulNotification where T : class + { + protected ObjectNotification(T target, EventMessages messages) + { + Messages = messages; + Target = target; + } + + public EventMessages Messages { get; } + + protected T Target { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/RolledBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/RolledBackNotification.cs new file mode 100644 index 0000000000..326b224478 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/RolledBackNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class RolledBackNotification : ObjectNotification where T : class + { + protected RolledBackNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/RollingBackNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/RollingBackNotification.cs new file mode 100644 index 0000000000..6afa46d6f1 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/RollingBackNotification.cs @@ -0,0 +1,16 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class RollingBackNotification : CancelableObjectNotification where T : class + { + protected RollingBackNotification(T target, EventMessages messages) : base(target, messages) + { + } + + public T Entity => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SavedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SavedNotification.cs new file mode 100644 index 0000000000..b6a363019f --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SavedNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SavedNotification : EnumerableObjectNotification + { + protected SavedNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected SavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SavedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SavingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SavingNotification.cs new file mode 100644 index 0000000000..df84628b93 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SavingNotification.cs @@ -0,0 +1,21 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SavingNotification : CancelableEnumerableObjectNotification + { + protected SavingNotification(T target, EventMessages messages) : base(target, messages) + { + } + + protected SavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SavedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SortedNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SortedNotification.cs new file mode 100644 index 0000000000..88b0e11cf6 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SortedNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SortedNotification : EnumerableObjectNotification + { + protected SortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SortedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/SortingNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/SortingNotification.cs new file mode 100644 index 0000000000..7d040a8b28 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/SortingNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class SortingNotification : CancelableEnumerableObjectNotification + { + protected SortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable SortedEntities => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/StatefulNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/StatefulNotification.cs new file mode 100644 index 0000000000..07831611fe --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/StatefulNotification.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. + +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class StatefulNotification : IStatefulNotification + { + private IDictionary _state; + + /// + /// This can be used by event subscribers to store state in the notification so they easily deal with custom state data between + /// a starting ("ing") and an ending ("ed") notification + /// + public IDictionary State + { + get => _state ??= new Dictionary(); + set => _state = value; + } + } +} diff --git a/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs index 73b5c5beea..9b8a1e9c98 100644 --- a/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using NUnit.Framework; @@ -82,25 +82,10 @@ namespace Umbraco.Cms.Tests.Integration.Cache new EventDefinition>(null, MemberGroupService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, MemberGroupService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MediaService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MediaService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Moved"), - new EventDefinition>(null, MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), - new EventDefinition(null, MediaService, new RecycleBinEventArgs(Guid.NewGuid())), - - new EventDefinition>(null, ContentService, new SaveEventArgs(Enumerable.Empty()), "Saved"), - new EventDefinition>(null, ContentService, new DeleteEventArgs(Enumerable.Empty()), "Deleted"), - // not managed //new EventDefinition>(null, ContentService, new SaveEventArgs(Enumerable.Empty()), "SavedBlueprint"), //new EventDefinition>(null, ContentService, new DeleteEventArgs(Enumerable.Empty()), "DeletedBlueprint"), - new EventDefinition>(null, ContentService, new CopyEventArgs(null, null, -1)), - new EventDefinition>(null, ContentService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), - new EventDefinition(null, ContentService, new RecycleBinEventArgs(Guid.NewGuid())), - new EventDefinition>(null, ContentService, new PublishEventArgs(Enumerable.Empty()), "Published"), - new EventDefinition>(null, ContentService, new PublishEventArgs(Enumerable.Empty()), "Unpublished"), - new EventDefinition>(null, PublicAccessService, new SaveEventArgs(Enumerable.Empty())), new EventDefinition>(null, PublicAccessService, new DeleteEventArgs(Enumerable.Empty())), diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 94d8be6b4d..6b61307bb3 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -135,9 +135,10 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest public override void ConfigureServices(IServiceCollection services) { services.AddTransient(); + IWebHostEnvironment webHostEnvironment = TestHelper.GetWebHostEnvironment(); TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, - TestHelper.GetWebHostEnvironment(), + webHostEnvironment, TestHelper.GetHostingEnvironment(), TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, @@ -150,7 +151,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest .AddConfiguration() .AddUmbracoCore() .AddWebComponents() - .AddRuntimeMinifier() + .AddRuntimeMinifier(webHostEnvironment) .AddBackOfficeCore() .AddBackOfficeAuthentication() .AddBackOfficeIdentity() diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index e73c0a5c5f..16a8a392cf 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -25,7 +25,6 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Web; @@ -213,10 +212,11 @@ namespace Umbraco.Cms.Tests.Integration.Testing builder.AddConfiguration() .AddUmbracoCore() .AddWebComponents() - .AddRuntimeMinifier() + .AddRuntimeMinifier(webHostEnvironment) .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddMembersIdentity() + .AddExamine() .AddTestServices(TestHelper, GetAppCaches()); if (TestOptions.Mapper) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs index c26d2e0e7b..0cf091ac65 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs @@ -8,7 +8,10 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NUnit.Framework; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; @@ -18,7 +21,6 @@ using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; using Umbraco.Extensions; -using Constants = Umbraco.Cms.Core.Constants; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { @@ -34,6 +36,40 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services #region Setup + private class TestNotificationHandler : INotificationHandler + { + public void Handle(ContentCacheRefresherNotification args) + { + // reports the event as: "ContentCache/,.../X + // where + // is(are) the action(s) + // X is the event content ID + if (args.MessageType != MessageType.RefreshByPayload) + { + throw new NotSupportedException(); + } + + foreach (ContentCacheRefresher.JsonPayload payload in (ContentCacheRefresher.JsonPayload[])args.MessageObject) + { + var e = new EventInstance + { + Message = _msgCount, + Sender = "ContentCacheRefresher", + EventArgs = payload, + Name = payload.ChangeTypes.ToString().Replace(" ", string.Empty), + Args = payload.Id.ToInvariantString() + }; + _events.Add(e); + } + + _msgCount++; + } + } + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } + [SetUp] public void SetUp() { @@ -45,7 +81,6 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services DocumentRepository.ScopedEntityRefresh += ContentRepositoryRefreshed; DocumentRepository.ScopeEntityRemove += ContentRepositoryRemoved; DocumentRepository.ScopeVersionRemove += ContentRepositoryRemovedVersion; - ContentCacheRefresher.CacheUpdated += ContentCacheUpdated; // prepare content type Template template = TemplateBuilder.CreateTextPageTemplate(); @@ -66,12 +101,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services DocumentRepository.ScopedEntityRefresh -= ContentRepositoryRefreshed; DocumentRepository.ScopeEntityRemove -= ContentRepositoryRemoved; DocumentRepository.ScopeVersionRemove -= ContentRepositoryRemovedVersion; - ContentCacheRefresher.CacheUpdated -= ContentCacheUpdated; } private DistributedCacheBinder _distributedCacheBinder; - private IList _events; - private int _msgCount; + private static IList _events; + private static int _msgCount; private IContentType _contentType; private void ResetEvents() @@ -324,32 +358,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services _events.Add(e); } - private void ContentCacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) - { - // reports the event as: "ContentCache/,.../X - // where - // is(are) the action(s) - // X is the event content ID - if (args.MessageType != MessageType.RefreshByPayload) - { - throw new NotSupportedException(); - } - foreach (ContentCacheRefresher.JsonPayload payload in (ContentCacheRefresher.JsonPayload[])args.MessageObject) - { - var e = new EventInstance - { - Message = _msgCount, - Sender = sender.Name, - EventArgs = payload, - Name = payload.ChangeTypes.ToString().Replace(" ", string.Empty), - Args = payload.Id.ToInvariantString() - }; - _events.Add(e); - } - - _msgCount++; - } private void WriteEvents() { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceEventTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs similarity index 53% rename from src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceEventTests.cs rename to src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs index 24bb2b3a84..db9c2634dc 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceEventTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceNotificationTests.cs @@ -1,14 +1,17 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System; using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -22,7 +25,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services PublishedRepositoryEvents = true, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)] - public class ContentServiceEventTests : UmbracoIntegrationTest + public class ContentServiceNotificationTests : UmbracoIntegrationTest { private IContentTypeService ContentTypeService => GetRequiredService(); @@ -46,6 +49,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services CreateTestData(); } + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + private void CreateTestData() { Template template = TemplateBuilder.CreateTextPageTemplate(); @@ -82,36 +93,43 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // properties: title, bodyText, keywords, description document.SetValue("title", "title-en", "en-US"); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + var savedWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame(document, saved); - Assert.IsTrue(e.IsSavingCulture(saved, "en-US")); - Assert.IsFalse(e.IsSavingCulture(saved, "fr-FR")); - } + Assert.IsTrue(notification.IsSavingCulture(saved, "en-US")); + Assert.IsFalse(notification.IsSavingCulture(saved, "fr-FR")); - void OnSaved(IContentService sender, ContentSavedEventArgs e) + savingWasCalled = true; + }; + + ContentNotificationHandler.SavedContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame(document, saved); - Assert.IsTrue(e.HasSavedCulture(saved, "en-US")); - Assert.IsFalse(e.HasSavedCulture(saved, "fr-FR")); - } + Assert.IsTrue(notification.HasSavedCulture(saved, "en-US")); + Assert.IsFalse(notification.HasSavedCulture(saved, "fr-FR")); + + savedWasCalled = true; + }; - ContentService.Saving += OnSaving; - ContentService.Saved += OnSaved; try { ContentService.Save(document); + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); } finally { - ContentService.Saving -= OnSaving; - ContentService.Saved -= OnSaved; + ContentNotificationHandler.SavingContent = null; + ContentNotificationHandler.SavedContent = null; } } @@ -120,18 +138,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { IContent document = new Content("content", -1, _contentType); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + var savedWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); saved.SetValue("title", "title"); - } - void OnSaved(IContentService sender, ContentSavedEventArgs e) + savingWasCalled = true; + }; + + ContentNotificationHandler.SavedContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame("title", document.GetValue("title")); @@ -140,18 +163,20 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual("title", propValue.EditedValue); Assert.IsNull(propValue.PublishedValue); - } - ContentService.Saving += OnSaving; - ContentService.Saved += OnSaved; + savedWasCalled = true; + }; + try { ContentService.Save(document); + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); } finally { - ContentService.Saving -= OnSaving; - ContentService.Saved -= OnSaved; + ContentNotificationHandler.SavingContent = null; + ContentNotificationHandler.SavedContent = null; } } @@ -179,36 +204,43 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // re-get - dirty properties need resetting document = ContentService.GetById(document.Id); - void OnPublishing(IContentService sender, ContentPublishingEventArgs e) + var publishingWasCalled = false; + var publishedWasCalled = false; + + ContentNotificationHandler.PublishingContent += notification => { - IContent publishing = e.PublishedEntities.First(); + IContent publishing = notification.PublishedEntities.First(); Assert.AreSame(document, publishing); - Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US")); - Assert.IsTrue(e.IsPublishingCulture(publishing, "fr-FR")); - } + Assert.IsFalse(notification.IsPublishingCulture(publishing, "en-US")); + Assert.IsTrue(notification.IsPublishingCulture(publishing, "fr-FR")); - void OnPublished(IContentService sender, ContentPublishedEventArgs e) + publishingWasCalled = true; + }; + + ContentNotificationHandler.PublishedContent += notification => { - IContent published = e.PublishedEntities.First(); + IContent published = notification.PublishedEntities.First(); Assert.AreSame(document, published); - Assert.IsFalse(e.HasPublishedCulture(published, "en-US")); - Assert.IsTrue(e.HasPublishedCulture(published, "fr-FR")); - } + Assert.IsFalse(notification.HasPublishedCulture(published, "en-US")); + Assert.IsTrue(notification.HasPublishedCulture(published, "fr-FR")); + + publishedWasCalled = true; + }; - ContentService.Publishing += OnPublishing; - ContentService.Published += OnPublished; try { ContentService.SaveAndPublish(document, "fr-FR"); + Assert.IsTrue(publishingWasCalled); + Assert.IsTrue(publishedWasCalled); } finally { - ContentService.Publishing -= OnPublishing; - ContentService.Published -= OnPublished; + ContentNotificationHandler.PublishingContent = null; + ContentNotificationHandler.PublishedContent = null; } document = ContentService.GetById(document.Id); @@ -223,18 +255,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services { IContent document = new Content("content", -1, _contentType); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + var savedWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); saved.SetValue("title", "title"); - } - void OnSaved(IContentService sender, ContentSavedEventArgs e) + savingWasCalled = true; + }; + + ContentNotificationHandler.SavedContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.AreSame("title", document.GetValue("title")); @@ -243,21 +280,20 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual("title", propValue.EditedValue); Assert.AreEqual("title", propValue.PublishedValue); - } - // We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be - // used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model - // are published, but Saving is raised BEFORE. - ContentService.Saving += OnSaving; - ContentService.Saved += OnSaved; + savedWasCalled = true; + }; + try { ContentService.SaveAndPublish(document); + Assert.IsTrue(savingWasCalled); + Assert.IsTrue(savedWasCalled); } finally { - ContentService.Saving -= OnSaving; - ContentService.Saved -= OnSaved; + ContentNotificationHandler.SavingContent = null; + ContentNotificationHandler.SavedContent = null; } } @@ -278,27 +314,28 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // re-create it document = new Content("content", -1, _contentType); - void OnSaving(IContentService sender, ContentSavingEventArgs e) + var savingWasCalled = false; + + ContentNotificationHandler.SavingContent = notification => { - IContent saved = e.SavedEntities.First(); + IContent saved = notification.SavedEntities.First(); Assert.IsTrue(document.GetValue("title").IsNullOrWhiteSpace()); saved.SetValue("title", "title"); - } - // We are binding to Saving (not Publishing), because the Publishing event is really just used for cancelling, it should not be - // used for setting values and it won't actually work! This is because the Publishing event is raised AFTER the values on the model - // are published, but Saving is raised BEFORE. - ContentService.Saving += OnSaving; + savingWasCalled = true; + }; + try { result = ContentService.SaveAndPublish(document); Assert.IsTrue(result.Success); // will succeed now because we were able to specify the required value in the Saving event + Assert.IsTrue(savingWasCalled); } finally { - ContentService.Saving -= OnSaving; + ContentNotificationHandler.SavingContent = null; } } @@ -328,44 +365,53 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services // re-get - dirty properties need resetting document = contentService.GetById(document.Id); - void OnPublishing(IContentService sender, ContentPublishingEventArgs e) + document.UnpublishCulture("fr-FR"); + + var publishingWasCalled = false; + var publishedWasCalled = false; + + // TODO: revisit this - it was migrated when removing static events, but the expected result seems illogic - why does this test bind to Published and not Unpublished? + + ContentNotificationHandler.PublishingContent += notification => { - IContent publishing = e.PublishedEntities.First(); - - Assert.AreSame(document, publishing); - - Assert.IsFalse(e.IsPublishingCulture(publishing, "en-US")); - Assert.IsFalse(e.IsPublishingCulture(publishing, "fr-FR")); - - Assert.IsFalse(e.IsUnpublishingCulture(publishing, "en-US")); - Assert.IsTrue(e.IsUnpublishingCulture(publishing, "fr-FR")); - } - - void OnPublished(IContentService sender, ContentPublishedEventArgs e) - { - IContent published = e.PublishedEntities.First(); + IContent published = notification.PublishedEntities.First(); Assert.AreSame(document, published); - Assert.IsFalse(e.HasPublishedCulture(published, "en-US")); - Assert.IsFalse(e.HasPublishedCulture(published, "fr-FR")); + Assert.IsFalse(notification.IsPublishingCulture(published, "en-US")); + Assert.IsFalse(notification.IsPublishingCulture(published, "fr-FR")); - Assert.IsFalse(e.HasUnpublishedCulture(published, "en-US")); - Assert.IsTrue(e.HasUnpublishedCulture(published, "fr-FR")); - } + Assert.IsFalse(notification.IsUnpublishingCulture(published, "en-US")); + Assert.IsTrue(notification.IsUnpublishingCulture(published, "fr-FR")); - document.UnpublishCulture("fr-FR"); + publishingWasCalled = true; + }; + + ContentNotificationHandler.PublishedContent += notification => + { + IContent published = notification.PublishedEntities.First(); + + Assert.AreSame(document, published); + + Assert.IsFalse(notification.HasPublishedCulture(published, "en-US")); + Assert.IsFalse(notification.HasPublishedCulture(published, "fr-FR")); + + Assert.IsFalse(notification.HasUnpublishedCulture(published, "en-US")); + Assert.IsTrue(notification.HasUnpublishedCulture(published, "fr-FR")); + + publishedWasCalled = true; + }; - ContentService.Publishing += OnPublishing; - ContentService.Published += OnPublished; try { contentService.CommitDocumentChanges(document); + Assert.IsTrue(publishingWasCalled); + Assert.IsTrue(publishedWasCalled); } finally { - ContentService.Publishing -= OnPublishing; - ContentService.Published -= OnPublished; + ContentNotificationHandler.PublishingContent = null; + ContentNotificationHandler.PublishedContent = null; } document = contentService.GetById(document.Id); @@ -373,5 +419,38 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.IsFalse(document.IsCulturePublished("fr-FR")); Assert.IsTrue(document.IsCulturePublished("en-US")); } + + public class ContentNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler + { + public void Handle(ContentSavingNotification notification) => SavingContent?.Invoke(notification); + + public void Handle(ContentSavedNotification notification) => SavedContent?.Invoke(notification); + + public void Handle(ContentPublishingNotification notification) => PublishingContent?.Invoke(notification); + + public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); + + public void Handle(ContentUnpublishingNotification notification) => UnpublishingContent?.Invoke(notification); + + public void Handle(ContentUnpublishedNotification notification) => UnpublishedContent?.Invoke(notification); + + public static Action SavingContent { get; set; } + + public static Action SavedContent { get; set; } + + public static Action PublishingContent { get; set; } + + public static Action PublishedContent { get; set; } + + public static Action UnpublishingContent { get; set; } + + public static Action UnpublishedContent { get; set; } + } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index 5421add491..819e41c257 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -9,6 +9,7 @@ using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; @@ -20,6 +21,7 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Extensions; @@ -73,6 +75,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [SetUp] public void Setup() => ContentRepositoryBase.ThrowOnWarning = true; + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler(); + [TearDown] public void Teardown() => ContentRepositoryBase.ThrowOnWarning = false; @@ -1076,7 +1083,19 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [Test] public void Can_Publish_Content_WithEvents() { - ContentService.Publishing += ContentServiceOnPublishing; + bool publishingWasCalled = false; + + ContentNotificationHandler.PublishingContent = notification => + { + Assert.AreEqual(1, notification.PublishedEntities.Count()); + IContent entity = notification.PublishedEntities.First(); + Assert.AreEqual("foo", entity.Name); + + IContent e = ContentService.GetById(entity.Id); + Assert.AreEqual("Home", e.Name); + + publishingWasCalled = true; + }; // tests that during 'publishing' event, what we get from the repo is the 'old' content, // because 'publishing' fires before the 'saved' event ie before the content is actually @@ -1094,23 +1113,15 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services IContent e = ContentService.GetById(content.Id); Assert.AreEqual("foo", e.Name); + + Assert.IsTrue(publishingWasCalled); } finally { - ContentService.Publishing -= ContentServiceOnPublishing; + ContentNotificationHandler.PublishingContent = null; } } - private void ContentServiceOnPublishing(IContentService sender, PublishEventArgs args) - { - Assert.AreEqual(1, args.PublishedEntities.Count()); - IContent entity = args.PublishedEntities.First(); - Assert.AreEqual("foo", entity.Name); - - IContent e = ContentService.GetById(entity.Id); - Assert.AreEqual("Home", e.Name); - } - [Test] public void Can_Not_Publish_Invalid_Cultures() { @@ -1899,26 +1910,31 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services public void Can_Copy_And_Modify_Content_With_Events() { // see https://github.com/umbraco/Umbraco-CMS/issues/5513 - static void Copying(IContentService sender, CopyEventArgs args) - { - args.Copy.SetValue("title", "1"); - args.Original.SetValue("title", "2"); - } - static void Copied(IContentService sender, CopyEventArgs args) + bool copyingWasCalled = false; + bool copiedWasCalled = false; + + ContentNotificationHandler.CopyingContent = notification => { - string copyVal = args.Copy.GetValue("title"); - string origVal = args.Original.GetValue("title"); + notification.Copy.SetValue("title", "1"); + notification.Original.SetValue("title", "2"); + + copyingWasCalled = true; + }; + + ContentNotificationHandler.CopiedContent = notification => + { + string copyVal = notification.Copy.GetValue("title"); + string origVal = notification.Original.GetValue("title"); Assert.AreEqual("1", copyVal); Assert.AreEqual("2", origVal); - } + + copiedWasCalled = true; + }; try { - ContentService.Copying += Copying; - ContentService.Copied += Copied; - Template template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); @@ -1930,11 +1946,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services IContent copy = ContentService.Copy(content, content.ParentId, false, Constants.Security.SuperUserId); Assert.AreEqual("1", copy.GetValue("title")); + + Assert.IsTrue(copyingWasCalled); + Assert.IsTrue(copiedWasCalled); } finally { - ContentService.Copying -= Copying; - ContentService.Copied -= Copied; + ContentNotificationHandler.CopyingContent = null; + ContentNotificationHandler.CopiedContent = null; } } @@ -3275,5 +3294,23 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services return content; } + + public class ContentNotificationHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler + { + public void Handle(ContentPublishingNotification notification) => PublishingContent?.Invoke(notification); + + public void Handle(ContentCopyingNotification notification) => CopyingContent?.Invoke(notification); + + public void Handle(ContentCopiedNotification notification) => CopiedContent?.Invoke(notification); + + public static Action PublishingContent { get; set; } + + public static Action CopyingContent { get; set; } + + public static Action CopiedContent { get; set; } + } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs index 556a909255..dff4e80e4c 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceTests.cs @@ -6,11 +6,13 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -30,6 +32,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services private ContentTypeService ContentTypeService => (ContentTypeService)GetRequiredService(); + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler(); + [Test] public void CanSaveAndGetIsElement() { @@ -112,7 +117,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [Test] public void Deleting_Content_Types_With_Hierarchy_Of_Content_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items_1() { - ContentService.Trashed += ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = MovedContentToRecycleBin; try { @@ -151,14 +156,14 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services } finally { - ContentService.Trashed -= ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = null; } } [Test] public void Deleting_Content_Types_With_Hierarchy_Of_Content_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items_2() { - ContentService.Trashed += ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = MovedContentToRecycleBin; try { @@ -194,13 +199,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services } finally { - ContentService.Trashed -= ContentServiceOnTrashed; + ContentNotificationHandler.MovedContentToRecycleBin = null; } } - private void ContentServiceOnTrashed(IContentService sender, MoveEventArgs e) + private void MovedContentToRecycleBin(ContentMovedToRecycleBinNotification notification) { - foreach (MoveEventInfo item in e.MoveInfoCollection) + foreach (MoveEventInfo item in notification.MoveInfoCollection) { // if this item doesn't exist then Fail! IContent exists = ContentService.GetById(item.Entity.Id); @@ -1729,5 +1734,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services return list.ToArray(); } + + public class ContentNotificationHandler : + INotificationHandler + { + public void Handle(ContentMovedToRecycleBinNotification notification) => MovedContentToRecycleBin?.Invoke(notification); + + public static Action MovedContentToRecycleBin { get; set; } + } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs index ad1d353039..b28e22e50a 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -6,10 +6,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using NUnit.Framework; +using Umbraco.Cms.Core.DependencyInjection; 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.Notifications; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; @@ -25,6 +27,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services private IMediaTypeService MediaTypeService => GetRequiredService(); + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder + .AddNotificationHandler(); + [Test] public void Get_With_Missing_Guid() { @@ -94,7 +99,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services [Test] public void Deleting_Media_Types_With_Hierarchy_Of_Media_Items_Doesnt_Raise_Trashed_Event_For_Deleted_Items() { - MediaService.Trashed += MediaServiceOnTrashed; + ContentNotificationHandler.MovedMediaToRecycleBin = MovedMediaToRecycleBin; try { @@ -130,13 +135,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services } finally { - MediaService.Trashed -= MediaServiceOnTrashed; + ContentNotificationHandler.MovedMediaToRecycleBin = null; } } - private void MediaServiceOnTrashed(IMediaService sender, MoveEventArgs e) + private void MovedMediaToRecycleBin(MediaMovedToRecycleBinNotification notification) { - foreach (MoveEventInfo item in e.MoveInfoCollection) + foreach (MoveEventInfo item in notification.MoveInfoCollection) { // if this item doesn't exist then Fail! IMedia exists = MediaService.GetById(item.Entity.Id); @@ -213,5 +218,13 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreNotEqual(clonedMediaType.PropertyTypes.First(x => x.Alias.StartsWith("umbracoFile")).Id, originalMediaType.PropertyTypes.First(x => x.Alias.StartsWith("umbracoFile")).Id); Assert.AreNotEqual(clonedMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id, originalMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id); } + + public class ContentNotificationHandler : + INotificationHandler + { + public void Handle(MediaMovedToRecycleBinNotification notification) => MovedMediaToRecycleBin?.Invoke(notification); + + public static Action MovedMediaToRecycleBin { get; set; } + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index b06144d555..745e8309d7 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -17,6 +17,7 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; @@ -50,7 +51,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components var fs = new FileSystems(mock.Object, loggerFactory.CreateLogger(), loggerFactory, IOHelper, Options.Create(globalSettings), Mock.Of()); var coreDebug = new CoreDebugSettings(); IMediaFileSystem mediaFileSystem = Mock.Of(); - var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); + IEventAggregator eventAggregator = Mock.Of(); + var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs index e1b9ec9264..590ff58222 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs @@ -7,7 +7,7 @@ using System.Linq; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Compose; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -32,7 +32,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Cannot_Have_Null_Udi() { - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var json = GetBlockListJson(null, string.Empty); Assert.Throws(() => component.ReplaceBlockListUdis(json)); } @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors var expected = ReplaceGuids(json, guids, ContentGuid1, ContentGuid2, SettingsGuid1); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); @@ -75,7 +75,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors // get the json with the subFeatures as escaped var json = GetBlockListJson(innerJsonEscaped); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); // the expected result is that the subFeatures data is no longer escaped @@ -119,7 +119,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors SubContentGuid2, SubSettingsGuid1); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); @@ -147,7 +147,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors var json = GetBlockListJson(complexEditorJsonEscaped); - var component = new BlockEditorComponent(); + var component = new BlockEditorPropertyHandler(); var result = component.ReplaceBlockListUdis(json, GuidFactory); // the expected result is that the subFeatures data is no longer escaped diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs index d132f346b4..0ada6a20dd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -4,7 +4,7 @@ using System; using Newtonsoft.Json; using NUnit.Framework; -using Umbraco.Cms.Core.Compose; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors { @@ -14,7 +14,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Invalid_Json() { - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); Assert.DoesNotThrow(() => component.CreateNestedContentKeys("this is not json", true)); } @@ -34,7 +34,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString()) .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -78,7 +78,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -126,7 +126,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -230,7 +230,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString()) .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); @@ -248,7 +248,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors {""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""} ]"; - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON @@ -291,7 +291,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors } ]"; - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON for each item @@ -390,7 +390,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors } ]"; - var component = new NestedContentPropertyComponent(); + var component = new NestedContentPropertyHandler(); var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON for each item diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs index 17bc26dc23..43d14677fa 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs @@ -87,7 +87,8 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Scoping Mock.Of(), Mock.Of>(), instance, - Mock.Of() + Mock.Of(), + Mock.Of() ); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs index a319d33b34..ffa778765c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberIdentityUserManagerTests.cs @@ -70,7 +70,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.Security pwdValidators, new BackOfficeIdentityErrorDescriber(), _mockServiceProviders.Object, - new Mock().Object, new Mock>>().Object, _mockPasswordConfiguration.Object); diff --git a/src/Umbraco.Tests/Models/MediaXmlTest.cs b/src/Umbraco.Tests/Models/MediaXmlTest.cs index 6b6be30dab..bb26b2e70e 100644 --- a/src/Umbraco.Tests/Models/MediaXmlTest.cs +++ b/src/Umbraco.Tests/Models/MediaXmlTest.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Xml.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Models var contentSettings = new ContentSettings(); var mediaFileSystem = new MediaFileSystem(Mock.Of(), scheme, loggerFactory.CreateLogger(), ShortStringHelper); - var ignored = new FileUploadPropertyEditor(loggerFactory, mediaFileSystem, Microsoft.Extensions.Options.Options.Create(contentSettings), DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer); + var ignored = new FileUploadPropertyEditor(loggerFactory, mediaFileSystem, Microsoft.Extensions.Options.Options.Create(contentSettings), DataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer, ContentService); var media = MockedMedia.CreateMediaImage(mediaType, -1); media.WriterId = -1; // else it's zero and that's not a user and it breaks the tests diff --git a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs index a3a54a5fcf..7149938b39 100644 --- a/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/MediaUrlProviderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -38,8 +38,8 @@ namespace Umbraco.Tests.Routing var dataTypeService = Mock.Of(); var propertyEditors = new MediaUrlGeneratorCollection(new IMediaUrlGenerator[] { - new FileUploadPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer), - new ImageCropperPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, IOHelper, ShortStringHelper, LocalizedTextService, UploadAutoFillProperties, JsonNetSerializer), + new FileUploadPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, LocalizedTextService, ShortStringHelper, UploadAutoFillProperties, JsonNetSerializer, ContentService), + new ImageCropperPropertyEditor(loggerFactory, mediaFileSystemMock, Microsoft.Extensions.Options.Options.Create(contentSettings), dataTypeService, LocalizationService, IOHelper, ShortStringHelper, LocalizedTextService, UploadAutoFillProperties, JsonNetSerializer, ContentService), }); _mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors, UriUtility); } diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index d8d86c3162..718b54a939 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Models; @@ -16,12 +17,12 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.PublishedCache; using Umbraco.Cms.Infrastructure.PublishedCache.Persistence; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Tests.Common; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Extensions; @@ -49,6 +50,14 @@ namespace Umbraco.Tests.Scoping Builder.Services.AddUnique(f => Mock.Of()); Builder.WithCollectionBuilder() .Add(() => Builder.TypeLoader.GetCacheRefreshers()); + Builder.AddNotificationHandler(); + } + + public class NotificationHandler : INotificationHandler + { + public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); + + public static Action PublishedContent { get; set; } } public override void TearDown() @@ -58,17 +67,9 @@ namespace Umbraco.Tests.Scoping _distributedCacheBinder?.UnbindEvents(); _distributedCacheBinder = null; - _onPublishedAssertAction = null; - ContentService.Published -= OnPublishedAssert; + NotificationHandler.PublishedContent = null; } - private void OnPublishedAssert(IContentService sender, PublishEventArgs args) - { - _onPublishedAssertAction?.Invoke(); - } - - private Action _onPublishedAssertAction; - protected override IPublishedSnapshotService CreatePublishedSnapshotService(GlobalSettings globalSettings = null) { var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; @@ -149,7 +150,7 @@ namespace Umbraco.Tests.Scoping // event handler var evented = 0; - _onPublishedAssertAction = () => + NotificationHandler.PublishedContent = notification => { evented++; @@ -171,8 +172,6 @@ namespace Umbraco.Tests.Scoping Assert.IsNotNull(x); Assert.AreEqual("name", x.Name(VariationContextAccessor)); - ContentService.Published += OnPublishedAssert; - using (var scope = ScopeProvider.CreateScope()) { item.Name = "changed"; diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 4a47f3689e..f943eada80 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -7,13 +7,13 @@ using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PublishedCache; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Cms.Infrastructure.Sync; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Extensions; @@ -41,6 +41,7 @@ namespace Umbraco.Tests.Scoping Builder.Services.AddUnique(f => Mock.Of()); Builder.WithCollectionBuilder() .Add(() => Builder.TypeLoader.GetCacheRefreshers()); + Builder.AddNotificationHandler(); } protected override void ComposeSettings() @@ -54,24 +55,24 @@ namespace Umbraco.Tests.Scoping Builder.Services.AddTransient(x => Microsoft.Extensions.Options.Options.Create(userPasswordConfigurationSettings)); } + + public class NotificationHandler : INotificationHandler + { + public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification); + + public static Action PublishedContent { get; set; } + } + [TearDown] public void Teardown() { _distributedCacheBinder?.UnbindEvents(); _distributedCacheBinder = null; - _onPublishedAssertAction = null; - ContentService.Published -= OnPublishedAssert; + NotificationHandler.PublishedContent = null; SafeXmlReaderWriter.Cloning = null; } - private void OnPublishedAssert(IContentService sender, PublishEventArgs args) - { - _onPublishedAssertAction?.Invoke(); - } - - private Action _onPublishedAssertAction; - // in 7.6, content.Instance // .XmlContent - comes from .XmlContentInternal and is cached in context items for current request // .XmlContentInternal - the actual main xml document @@ -111,7 +112,7 @@ namespace Umbraco.Tests.Scoping // event handler var evented = 0; - _onPublishedAssertAction = () => + NotificationHandler.PublishedContent = notification => { evented++; @@ -128,8 +129,6 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(beforeOuterXml, xml.OuterXml); }; - ContentService.Published += OnPublishedAssert; - using (var scope = ScopeProvider.CreateScope()) { ServiceContext.ContentService.SaveAndPublish(item); // should create an xml clone diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 6f3ac08219..c8be036d05 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -8,6 +8,7 @@ using NPoco; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Migrations.Install; @@ -86,7 +87,8 @@ namespace Umbraco.Tests.TestHelpers fileSystems ??= new FileSystems(Current.Factory, loggerFactory.CreateLogger(), loggerFactory, TestHelper.IOHelper, globalSettings, TestHelper.GetHostingEnvironment()); var coreDebug = TestHelper.CoreDebugSettings; var mediaFileSystem = Mock.Of(); - return new ScopeProvider(databaseFactory, fileSystems, Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); + var eventAggregator = Mock.Of(); + return new ScopeProvider(databaseFactory, fileSystems, Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance, eventAggregator); } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 6150bf43e1..15634c6083 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -160,6 +160,7 @@ namespace Umbraco.Tests.Testing protected UmbracoMapper Mapper => Factory.GetRequiredService(); protected IHttpContextAccessor HttpContextAccessor => Factory.GetRequiredService(); + protected IContentService ContentService => Factory.GetRequiredService(); protected IRuntimeState RuntimeState => MockRuntimeState(RuntimeLevel.Run); private ILoggerFactory _loggerFactory; diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 1ffc4ab996..ff95ea1ec0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -390,7 +390,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers await _emailSender.SendAsync(mailMessage); - _userManager.RaiseForgotPasswordRequestedEvent(User, user.Id.ToString()); + _userManager.NotifyForgotPasswordRequested(User, user.Id.ToString()); } } @@ -554,7 +554,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers } } - _userManager.RaiseForgotPasswordChangedSuccessEvent(User, model.UserId.ToString()); + _userManager.NotifyForgotPasswordChanged(User, model.UserId.ToString()); return Ok(); } @@ -578,7 +578,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _logger.LogInformation("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress); var userId = result.Principal.Identity.GetUserId(); - var args = _userManager.RaiseLogoutSuccessEvent(User, userId); + var args = _userManager.NotifyLogoutSuccess(User, userId); if (!args.SignOutRedirectUrl.IsNullOrWhiteSpace()) { return new ObjectResult(new diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index da19fa473a..77bb026d69 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -447,7 +447,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } - if (!_emailSender.CanSendRequiredEmail() && !_userManager.HasSendingUserInviteEventHandler) + if (!_emailSender.CanSendRequiredEmail()) { return new ValidationErrorResult("No Email server is configured"); } @@ -486,39 +486,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _userService.Save(user); var display = _umbracoMapper.Map(user); - UserInviteEventArgs inviteArgs; - - try - { - inviteArgs = _userManager.RaiseSendingUserInvite(User, userSave, user); - } - catch (Exception ex) - { - _logger.LogError(ex, "An error occurred in a custom event handler while inviting the user"); - return ValidationErrorResult.CreateNotificationValidationErrorResult($"An error occurred inviting the user (check logs for more info): {ex.Message}"); - } - - // If the event is handled then no need to send the email - if (inviteArgs.InviteHandled) - { - // if no user result was created then map the minimum args manually for the UI - if (!inviteArgs.ShowUserResult) - { - display = new UserDisplay - { - Name = userSave.Name, - Email = userSave.Email, - Username = userSave.Username - }; - } - } - else - { - //send the email - - await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message); - - } + //send the email + await SendUserInviteEmailAsync(display, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Email, user, userSave.Message); display.AddSuccessNotification(_localizedTextService.Localize("speechBubbles/resendInviteHeader"), _localizedTextService.Localize("speechBubbles/resendInviteSuccess", new[] { user.Name })); return display; diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs index 3218897eee..9e887d6a86 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs @@ -61,7 +61,6 @@ namespace Umbraco.Extensions services.TryAddScoped(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); /* * IdentityBuilderExtensions.AddUserManager adds UserManager to service collection diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index b3fce3fa8c..0c5bcc1177 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -5,13 +5,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models.Identity; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.WebAssets; using Umbraco.Cms.Infrastructure.DependencyInjection; using Umbraco.Cms.Infrastructure.WebAssets; using Umbraco.Cms.Web.BackOffice.Controllers; @@ -21,9 +21,11 @@ using Umbraco.Cms.Web.BackOffice.ModelsBuilder; using Umbraco.Cms.Web.BackOffice.Routing; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.BackOffice.Services; +using Umbraco.Cms.Web.BackOffice.SignalR; using Umbraco.Cms.Web.BackOffice.Trees; using Umbraco.Cms.Web.Common.Authorization; using Umbraco.Cms.Web.Common.Security; +using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IWebHostEnvironment; namespace Umbraco.Extensions { @@ -35,11 +37,11 @@ namespace Umbraco.Extensions /// /// Adds all required components to run the Umbraco back office /// - public static IUmbracoBuilder AddBackOffice(this IUmbracoBuilder builder) => builder + public static IUmbracoBuilder AddBackOffice(this IUmbracoBuilder builder, IWebHostEnvironment webHostEnvironment) => builder .AddConfiguration() .AddUmbracoCore() .AddWebComponents() - .AddRuntimeMinifier() + .AddRuntimeMinifier(webHostEnvironment) .AddBackOfficeCore() .AddBackOfficeAuthentication() .AddBackOfficeIdentity() @@ -52,7 +54,8 @@ namespace Umbraco.Extensions .AddHostedServices() .AddDistributedCache() .AddModelsBuilderDashboard() - .AddUnattedInstallCreateUser(); + .AddUnattedInstallCreateUser() + .AddExamine(); /// /// Adds Umbraco back office authentication requirements @@ -89,6 +92,14 @@ namespace Umbraco.Extensions builder.Services.AddUnique, PasswordChanger>(); builder.Services.AddUnique, PasswordChanger>(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + return builder; } @@ -158,6 +169,7 @@ namespace Umbraco.Extensions builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.AddNotificationAsyncHandler(); builder.Services.AddUnique(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index 35f41e0af7..813d5cd096 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -20,8 +20,6 @@ namespace Umbraco.Extensions throw new ArgumentNullException(nameof(app)); } - app.UseBackOfficeUserManagerAuditing(); - if (!app.UmbracoCanBoot()) { return app; @@ -52,12 +50,5 @@ namespace Umbraco.Extensions return app; } - - private static IApplicationBuilder UseBackOfficeUserManagerAuditing(this IApplicationBuilder app) - { - var auditer = app.ApplicationServices.GetRequiredService(); - auditer.Start(); - return app; - } } } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs index 5219a7bfb0..03ebb1aa45 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSignInManager.cs @@ -429,17 +429,17 @@ namespace Umbraco.Cms.Web.BackOffice.Security Logger.LogInformation("User: {UserName} logged in from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress); if (user != null) { - _userManager.RaiseLoginSuccessEvent(Context.User, user.Id); + _userManager.NotifyLoginSuccess(Context.User, user.Id); } } else if (result.IsLockedOut) { - _userManager.RaiseAccountLockedEvent(Context.User, user.Id); + _userManager.NotifyAccountLocked(Context.User, user.Id); Logger.LogInformation("Login attempt failed for username {UserName} from IP address {IpAddress}, the user is locked", username, Context.Connection.RemoteIpAddress); } else if (result.RequiresTwoFactor) { - _userManager.RaiseLoginRequiresVerificationEvent(Context.User, user.Id); + _userManager.NotifyLoginRequiresVerification(Context.User, user.Id); Logger.LogInformation("Login attempt requires verification for username {UserName} from IP address {IpAddress}", username, Context.Connection.RemoteIpAddress); } else if (!result.Succeeded || result.IsNotAllowed) diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs index 6e67ee84ba..c74d445a89 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeUserManagerAuditer.cs @@ -1,9 +1,6 @@ using System; -using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Compose; -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.Security; using Umbraco.Extensions; @@ -11,73 +8,49 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Security { /// - /// Binds to events to write audit logs for the + /// Binds to notifications to write audit logs for the /// - internal class BackOfficeUserManagerAuditer : IDisposable + internal class BackOfficeUserManagerAuditer : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { private readonly IAuditService _auditService; private readonly IUserService _userService; - private readonly GlobalSettings _globalSettings; - private bool _disposedValue; - public BackOfficeUserManagerAuditer(IAuditService auditService, IUserService userService, IOptions globalSettings) + public BackOfficeUserManagerAuditer(IAuditService auditService, IUserService userService) { _auditService = auditService; _userService = userService; - _globalSettings = globalSettings.Value; } - /// - /// Binds to events to start auditing - /// - public void Start() - { - // NOTE: This was migrated as-is from v8 including these missing entries - // TODO: See note about static events in BackOfficeUserManager - BackOfficeUserManager.ForgotPasswordRequested += OnForgotPasswordRequest; - BackOfficeUserManager.ForgotPasswordChangedSuccess += OnForgotPasswordChange; - BackOfficeUserManager.LoginFailed += OnLoginFailed; - BackOfficeUserManager.LoginSuccess += OnLoginSuccess; - BackOfficeUserManager.LogoutSuccess += OnLogoutSuccess; - BackOfficeUserManager.PasswordChanged += OnPasswordChanged; - BackOfficeUserManager.PasswordReset += OnPasswordReset; - } + public void Handle(UserLoginSuccessNotification notification) + => WriteAudit(notification.PerformingUserId, notification.AffectedUserId, notification.IpAddress, "umbraco/user/sign-in/login", "login success"); - private IUser GetPerformingUser(string userId) - { - if (!int.TryParse(userId, out int asInt)) - { - return AuditEventsComponent.UnknownUser(_globalSettings); - } + public void Handle(UserLogoutSuccessNotification notification) + => WriteAudit(notification.PerformingUserId, notification.AffectedUserId, notification.IpAddress, "umbraco/user/sign-in/logout", "logout success"); - IUser found = asInt >= 0 ? _userService.GetUserById(asInt) : null; - return found ?? AuditEventsComponent.UnknownUser(_globalSettings); - } + public void Handle(UserLoginFailedNotification notification) => + WriteAudit(notification.PerformingUserId, "0", notification.IpAddress, "umbraco/user/sign-in/failed", "login failed", ""); + + public void Handle(UserForgotPasswordRequestedNotification notification) => + WriteAudit(notification.PerformingUserId, notification.AffectedUserId, notification.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); + + public void Handle(UserForgotPasswordChangedNotification notification) => + WriteAudit(notification.PerformingUserId, notification.AffectedUserId, notification.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); + + public void Handle(UserPasswordChangedNotification notification) => + WriteAudit(notification.PerformingUserId, notification.AffectedUserId, notification.IpAddress, "umbraco/user/password/change", "password change"); + + public void Handle(UserPasswordResetNotification notification) => + WriteAudit(notification.PerformingUserId, notification.AffectedUserId, notification.IpAddress, "umbraco/user/password/reset", "password reset"); private static string FormatEmail(IMembershipUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - private void OnLoginSuccess(object sender, IdentityAuditEventArgs args) - { - var performingUser = GetPerformingUser(args.PerformingUser); - WriteAudit(performingUser, args.AffectedUser, args.IpAddress, "umbraco/user/sign-in/login", "login success"); - } - - private void OnLogoutSuccess(object sender, IdentityAuditEventArgs args) - { - IUser performingUser = GetPerformingUser(args.PerformingUser); - WriteAudit(performingUser, args.AffectedUser, args.IpAddress, "umbraco/user/sign-in/logout", "logout success"); - } - - private void OnPasswordReset(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/reset", "password reset"); - - private void OnPasswordChanged(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/change", "password change"); - - private void OnLoginFailed(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, "0", args.IpAddress, "umbraco/user/sign-in/failed", "login failed", affectedDetails: ""); - - private void OnForgotPasswordChange(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/change", "password forgot/change"); - - private void OnForgotPasswordRequest(object sender, IdentityAuditEventArgs args) => WriteAudit(args.PerformingUser, args.AffectedUser, args.IpAddress, "umbraco/user/password/forgot/request", "password forgot/request"); - private void WriteAudit(string performingId, string affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) { IUser performingUser = null; @@ -103,20 +76,6 @@ namespace Umbraco.Cms.Web.BackOffice.Security WriteAudit(performingIdAsInt, performingDetails, affectedIdAsInt, ipAddress, eventType, eventDetails, affectedDetails); } - private void WriteAudit(IUser performingUser, string affectedId, string ipAddress, string eventType, string eventDetails) - { - var performingDetails = performingUser == null - ? $"User UNKNOWN" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - if (!int.TryParse(affectedId, out int affectedIdInt)) - { - affectedIdInt = 0; - } - - WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedIdInt, ipAddress, eventType, eventDetails); - } - private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) { if (affectedDetails == null) @@ -137,30 +96,5 @@ namespace Umbraco.Cms.Web.BackOffice.Security eventType, eventDetails); } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - BackOfficeUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; - BackOfficeUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; - BackOfficeUserManager.LoginFailed -= OnLoginFailed; - BackOfficeUserManager.LoginSuccess -= OnLoginSuccess; - BackOfficeUserManager.LogoutSuccess -= OnLogoutSuccess; - BackOfficeUserManager.PasswordChanged -= OnPasswordChanged; - BackOfficeUserManager.PasswordReset -= OnPasswordReset; - } - _disposedValue = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/src/Umbraco.Web.BackOffice/SignalR/PreviewHubComposer.cs b/src/Umbraco.Web.BackOffice/SignalR/PreviewHubComposer.cs deleted file mode 100644 index 18b8f90825..0000000000 --- a/src/Umbraco.Web.BackOffice/SignalR/PreviewHubComposer.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.DependencyInjection; - -namespace Umbraco.Cms.Web.BackOffice.SignalR -{ - public class PreviewHubComposer : ComponentComposer, ICoreComposer - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - } - } -} diff --git a/src/Umbraco.Web.BackOffice/SignalR/PreviewHubComponent.cs b/src/Umbraco.Web.BackOffice/SignalR/PreviewHubUpdater.cs similarity index 54% rename from src/Umbraco.Web.BackOffice/SignalR/PreviewHubComponent.cs rename to src/Umbraco.Web.BackOffice/SignalR/PreviewHubUpdater.cs index 00d3dc8013..a71b5439d4 100644 --- a/src/Umbraco.Web.BackOffice/SignalR/PreviewHubComponent.cs +++ b/src/Umbraco.Web.BackOffice/SignalR/PreviewHubUpdater.cs @@ -1,38 +1,26 @@ using System; +using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Web.BackOffice.SignalR { - public class PreviewHubComponent : IComponent + public class PreviewHubUpdater :INotificationAsyncHandler { private readonly Lazy> _hubContext; // using a lazy arg here means that we won't create the hub until necessary // and therefore we won't have too bad an impact on boot time - public PreviewHubComponent(Lazy> hubContext) + public PreviewHubUpdater(Lazy> hubContext) { _hubContext = hubContext; } - public void Initialize() - { - // ContentService.Saved is too soon - the content cache is not ready yet, - // so use the content cache refresher event, because when it triggers - // the cache has already been notified of the changes - ContentCacheRefresher.CacheUpdated += HandleCacheUpdated; - } - - public void Terminate() - { - ContentCacheRefresher.CacheUpdated -= HandleCacheUpdated; - } - - private async void HandleCacheUpdated(ContentCacheRefresher sender, CacheRefresherEventArgs args) - { + public async Task HandleAsync(ContentCacheRefresherNotification args, CancellationToken cancellationToken) { if (args.MessageType != MessageType.RefreshByPayload) return; var payloads = (ContentCacheRefresher.JsonPayload[])args.MessageObject; var hubContextInstance = _hubContext.Value; diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 4b3715cd52..9a52b0b72a 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -12,7 +12,9 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using Serilog; using Smidge; using Smidge.Nuglify; @@ -217,9 +219,11 @@ namespace Umbraco.Extensions /// /// Add runtime minifier support for Umbraco /// - public static IUmbracoBuilder AddRuntimeMinifier(this IUmbracoBuilder builder) + public static IUmbracoBuilder AddRuntimeMinifier(this IUmbracoBuilder builder, IWebHostEnvironment webHostEnvironment) { - builder.Services.AddSmidge(builder.Config.GetSection(Cms.Core.Constants.Configuration.ConfigRuntimeMinification)); + var smidgePhysicalFileProvider = new SmidgePhysicalFileProvider(webHostEnvironment.ContentRootFileProvider, webHostEnvironment.WebRootFileProvider); + + builder.Services.AddSmidge(builder.Config.GetSection(Cms.Core.Constants.Configuration.ConfigRuntimeMinification), smidgePhysicalFileProvider); builder.Services.AddSmidgeNuglify(); return builder; @@ -412,5 +416,29 @@ namespace Umbraco.Extensions return new AspNetCoreHostingEnvironment(wrappedHostingSettings,wrappedWebRoutingSettings, webHostEnvironment); } + + /// + /// This file provider lets us serve physical files to Smidge for minification from both wwwroot and App_Plugins (which is outside wwwroot). + /// This file provider is NOT intended for use anywhere else, as it exposes files from the content root. + /// + private class SmidgePhysicalFileProvider : IFileProvider + { + private readonly IFileProvider _contentRootFileProvider; + private readonly IFileProvider _webRooFileProvider; + + public SmidgePhysicalFileProvider(IFileProvider contentRootFileProvider, IFileProvider webRooFileProvider) + { + _contentRootFileProvider = contentRootFileProvider; + _webRooFileProvider = webRooFileProvider; + } + + public IFileInfo GetFileInfo(string subpath) => subpath.InvariantStartsWith(Constants.SystemDirectories.AppPlugins) + ? _contentRootFileProvider.GetFileInfo(subpath) + : _webRooFileProvider.GetFileInfo(subpath); + + public IDirectoryContents GetDirectoryContents(string subpath) => throw new NotSupportedException(); + + public IChangeToken Watch(string filter) => throw new NotSupportedException(); + } } } diff --git a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs index 5ce05e62cd..8762dc6cc3 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; @@ -102,10 +103,14 @@ namespace Umbraco.Extensions builder.AddNotificationHandler(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); - builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs index ca7acb7a4e..459ed57138 100644 --- a/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Web.Common/Security/BackOfficeUserManager.cs @@ -8,10 +8,10 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Infrastructure.Security; using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Security @@ -19,6 +19,7 @@ namespace Umbraco.Cms.Web.Common.Security public class BackOfficeUserManager : UmbracoUserManager, IBackOfficeUserManager { private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IEventAggregator _eventAggregator; public BackOfficeUserManager( IIpResolver ipResolver, @@ -31,10 +32,12 @@ namespace Umbraco.Cms.Web.Common.Security IServiceProvider services, IHttpContextAccessor httpContextAccessor, ILogger> logger, - IOptions passwordConfiguration) + IOptions passwordConfiguration, + IEventAggregator eventAggregator) : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, errors, services, logger, passwordConfiguration) { _httpContextAccessor = httpContextAccessor; + _eventAggregator = eventAggregator; } /// @@ -109,7 +112,7 @@ namespace Umbraco.Cms.Web.Common.Security // Slightly confusing: this will return a Success if we successfully update the AccessFailed count if (result.Succeeded) { - RaiseLoginFailedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + NotifyLoginFailed(_httpContextAccessor.HttpContext?.User, user.Id); } return result; @@ -120,7 +123,7 @@ namespace Umbraco.Cms.Web.Common.Security IdentityResult result = await base.ChangePasswordWithResetAsync(userId, token, newPassword); if (result.Succeeded) { - RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, userId); + NotifyPasswordReset(_httpContextAccessor.HttpContext?.User, userId); } return result; @@ -131,7 +134,7 @@ namespace Umbraco.Cms.Web.Common.Security IdentityResult result = await base.ChangePasswordAsync(user, currentPassword, newPassword); if (result.Succeeded) { - RaisePasswordChangedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + NotifyPasswordChanged(_httpContextAccessor.HttpContext?.User, user.Id); } return result; @@ -150,11 +153,11 @@ namespace Umbraco.Cms.Web.Common.Security // The way we unlock is by setting the lockoutEnd date to the current datetime if (result.Succeeded && lockoutEnd >= DateTimeOffset.UtcNow) { - RaiseAccountLockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + NotifyAccountLocked(_httpContextAccessor.HttpContext?.User, user.Id); } else { - RaiseAccountUnlockedEvent(_httpContextAccessor.HttpContext?.User, user.Id); + NotifyAccountUnlocked(_httpContextAccessor.HttpContext?.User, user.Id); // Resets the login attempt fails back to 0 when unlock is clicked await ResetAccessFailedCountAsync(user); @@ -168,8 +171,8 @@ namespace Umbraco.Cms.Web.Common.Security { IdentityResult result = await base.ResetAccessFailedCountAsync(user); - // raise the event now that it's reset - RaiseResetAccessFailedCountEvent(_httpContextAccessor.HttpContext?.User, user.Id); + // notify now that it's reset + NotifyResetAccessFailedCount(_httpContextAccessor.HttpContext?.User, user.Id); return result; } @@ -181,101 +184,63 @@ namespace Umbraco.Cms.Web.Common.Security return currentUserId; } - private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, string affectedUserId, string affectedUsername) + public void NotifyAccountLocked(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserLockedNotification(ip, userId, currentUserId) + ); + + public void NotifyAccountUnlocked(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserUnlockedNotification(ip, userId, currentUserId) + ); + + public void NotifyForgotPasswordRequested(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserForgotPasswordRequestedNotification(ip, userId, currentUserId) + ); + + public void NotifyForgotPasswordChanged(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserForgotPasswordChangedNotification(ip, userId, currentUserId) + ); + + public void NotifyLoginFailed(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserLoginFailedNotification(ip, userId, currentUserId) + ); + + public void NotifyLoginRequiresVerification(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserLoginRequiresVerificationNotification(ip, userId, currentUserId) + ); + + public void NotifyLoginSuccess(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserLoginSuccessNotification(ip, userId, currentUserId) + ); + + public SignOutSuccessResult NotifyLogoutSuccess(IPrincipal currentUser, string userId) + { + var notification = Notify(currentUser, + (currentUserId, ip) => new UserLogoutSuccessNotification(ip, userId, currentUserId) + ); + + return new SignOutSuccessResult {SignOutRedirectUrl = notification.SignOutRedirectUrl}; + } + + public void NotifyPasswordChanged(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserPasswordChangedNotification(ip, userId, currentUserId) + ); + + public void NotifyPasswordReset(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserPasswordResetNotification(ip, userId, currentUserId) + ); + + public void NotifyResetAccessFailedCount(IPrincipal currentUser, string userId) => Notify(currentUser, + (currentUserId, ip) => new UserResetAccessFailedCountNotification(ip, userId, currentUserId) + ); + + private T Notify(IPrincipal currentUser, Func createNotification) where T : INotification { var currentUserId = GetCurrentUserId(currentUser); var ip = IpResolver.GetCurrentRequestIpAddress(); - return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); + + var notification = createNotification(currentUserId, ip); + _eventAggregator.Publish(notification); + return notification; } - - private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, BackOfficeIdentityUser currentUser, string affectedUserId, string affectedUsername) - { - var currentUserId = currentUser.Id; - var ip = IpResolver.GetCurrentRequestIpAddress(); - return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); - } - - // TODO: Review where these are raised and see if they can be simplified and either done in the this usermanager or the signin manager, - // lastly we'll resort to the authentication controller but we should try to remove all instances of that occuring - public void RaiseAccountLockedEvent(IPrincipal currentUser, string userId) => OnAccountLocked(CreateArgs(AuditEvent.AccountLocked, currentUser, userId, string.Empty)); - - public void RaiseAccountUnlockedEvent(IPrincipal currentUser, string userId) => OnAccountUnlocked(CreateArgs(AuditEvent.AccountUnlocked, currentUser, userId, string.Empty)); - - public void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId) => OnForgotPasswordRequested(CreateArgs(AuditEvent.ForgotPasswordRequested, currentUser, userId, string.Empty)); - - public void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId) => OnForgotPasswordChangedSuccess(CreateArgs(AuditEvent.ForgotPasswordChangedSuccess, currentUser, userId, string.Empty)); - - public void RaiseLoginFailedEvent(IPrincipal currentUser, string userId) => OnLoginFailed(CreateArgs(AuditEvent.LoginFailed, currentUser, userId, string.Empty)); - - public void RaiseLoginRequiresVerificationEvent(IPrincipal currentUser, string userId) => OnLoginRequiresVerification(CreateArgs(AuditEvent.LoginRequiresVerification, currentUser, userId, string.Empty)); - - public void RaiseLoginSuccessEvent(IPrincipal currentUser, string userId) => OnLoginSuccess(CreateArgs(AuditEvent.LoginSucces, currentUser, userId, string.Empty)); - - public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId) - { - var currentUserId = GetCurrentUserId(currentUser); - var args = new SignOutAuditEventArgs(AuditEvent.LogoutSuccess, IpResolver.GetCurrentRequestIpAddress(), performingUser: currentUserId, affectedUser: userId); - OnLogoutSuccess(args); - return args; - } - - public void RaisePasswordChangedEvent(IPrincipal currentUser, string userId) => OnPasswordChanged(CreateArgs(AuditEvent.LogoutSuccess, currentUser, userId, string.Empty)); - - public void RaiseResetAccessFailedCountEvent(IPrincipal currentUser, string userId) => OnResetAccessFailedCount(CreateArgs(AuditEvent.ResetAccessFailedCount, currentUser, userId, string.Empty)); - - public UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser) - { - var currentUserId = GetCurrentUserId(currentUser); - var ip = IpResolver.GetCurrentRequestIpAddress(); - var args = new UserInviteEventArgs(ip, currentUserId, invite, createdUser); - OnSendingUserInvite(args); - return args; - } - - public bool HasSendingUserInviteEventHandler => SendingUserInvite != null; - - // TODO: These static events are problematic. Moving forward we don't want static events at all but we cannot - // have non-static events here because the user manager is a Scoped instance not a singleton - // so we'll have to deal with this a diff way i.e. refactoring how events are done entirely - public static event EventHandler AccountLocked; - public static event EventHandler AccountUnlocked; - public static event EventHandler ForgotPasswordRequested; - public static event EventHandler ForgotPasswordChangedSuccess; - public static event EventHandler LoginFailed; - public static event EventHandler LoginRequiresVerification; - public static event EventHandler LoginSuccess; - public static event EventHandler LogoutSuccess; - public static event EventHandler PasswordChanged; - public static event EventHandler PasswordReset; - public static event EventHandler ResetAccessFailedCount; - - /// - /// Raised when a user is invited - /// - public static event EventHandler SendingUserInvite; // this event really has nothing to do with the user manager but was the most convenient place to put it - - protected virtual void OnAccountLocked(IdentityAuditEventArgs e) => AccountLocked?.Invoke(this, e); - - protected virtual void OnSendingUserInvite(UserInviteEventArgs e) => SendingUserInvite?.Invoke(this, e); - - protected virtual void OnAccountUnlocked(IdentityAuditEventArgs e) => AccountUnlocked?.Invoke(this, e); - - protected virtual void OnForgotPasswordRequested(IdentityAuditEventArgs e) => ForgotPasswordRequested?.Invoke(this, e); - - protected virtual void OnForgotPasswordChangedSuccess(IdentityAuditEventArgs e) => ForgotPasswordChangedSuccess?.Invoke(this, e); - - protected virtual void OnLoginFailed(IdentityAuditEventArgs e) => LoginFailed?.Invoke(this, e); - - protected virtual void OnLoginRequiresVerification(IdentityAuditEventArgs e) => LoginRequiresVerification?.Invoke(this, e); - - protected virtual void OnLoginSuccess(IdentityAuditEventArgs e) => LoginSuccess?.Invoke(this, e); - - protected virtual void OnLogoutSuccess(SignOutAuditEventArgs e) => LogoutSuccess?.Invoke(this, e); - - protected virtual void OnPasswordChanged(IdentityAuditEventArgs e) => PasswordChanged?.Invoke(this, e); - - protected virtual void OnPasswordReset(IdentityAuditEventArgs e) => PasswordReset?.Invoke(this, e); - - protected virtual void OnResetAccessFailedCount(IdentityAuditEventArgs e) => ResetAccessFailedCount?.Invoke(this, e); } } diff --git a/src/Umbraco.Web.Common/Security/MemberManager.cs b/src/Umbraco.Web.Common/Security/MemberManager.cs index ae91852f46..887dfa5b92 100644 --- a/src/Umbraco.Web.Common/Security/MemberManager.cs +++ b/src/Umbraco.Web.Common/Security/MemberManager.cs @@ -1,24 +1,17 @@ using System; using System.Collections.Generic; -using System.Security.Claims; -using System.Security.Principal; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; -using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; -using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.Security { public class MemberManager : UmbracoUserManager, IMemberManager { - private readonly IHttpContextAccessor _httpContextAccessor; public MemberManager( IIpResolver ipResolver, @@ -29,38 +22,10 @@ namespace Umbraco.Cms.Web.Common.Security IEnumerable> passwordValidators, BackOfficeIdentityErrorDescriber errors, IServiceProvider services, - IHttpContextAccessor httpContextAccessor, ILogger> logger, IOptions passwordConfiguration) : base(ipResolver, store, optionsAccessor, passwordHasher, userValidators, passwordValidators, errors, services, logger, passwordConfiguration) { - _httpContextAccessor = httpContextAccessor; } - - private string GetCurrentUserId(IPrincipal currentUser) - { - ClaimsIdentity umbIdentity = currentUser?.GetUmbracoIdentity(); - var currentUserId = umbIdentity?.GetUserId() ?? Cms.Core.Constants.Security.SuperUserIdAsString; - return currentUserId; - } - - private IdentityAuditEventArgs CreateArgs(AuditEvent auditEvent, IPrincipal currentUser, string affectedUserId, string affectedUsername) - { - var currentUserId = GetCurrentUserId(currentUser); - var ip = IpResolver.GetCurrentRequestIpAddress(); - return new IdentityAuditEventArgs(auditEvent, ip, currentUserId, string.Empty, affectedUserId, affectedUsername); - } - - //TODO: have removed all other member audit events - can revisit if we need member auditing on a user level in future - - public void RaiseForgotPasswordRequestedEvent(IPrincipal currentUser, string userId) => throw new NotImplementedException(); - - public void RaiseForgotPasswordChangedSuccessEvent(IPrincipal currentUser, string userId) => throw new NotImplementedException(); - - public SignOutAuditEventArgs RaiseLogoutSuccessEvent(IPrincipal currentUser, string userId) => throw new NotImplementedException(); - - public UserInviteEventArgs RaiseSendingUserInvite(IPrincipal currentUser, UserInvite invite, IUser createdUser) => throw new NotImplementedException(); - - public bool HasSendingUserInviteEventHandler { get; } } } diff --git a/src/Umbraco.Web.Common/Security/UserForgotPasswordChangedNotification.cs b/src/Umbraco.Web.Common/Security/UserForgotPasswordChangedNotification.cs new file mode 100644 index 0000000000..04fb74a1ca --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserForgotPasswordChangedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserForgotPasswordChangedNotification : UserNotification + { + public UserForgotPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserForgotPasswordRequestedNotification.cs b/src/Umbraco.Web.Common/Security/UserForgotPasswordRequestedNotification.cs new file mode 100644 index 0000000000..766e1c61ca --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserForgotPasswordRequestedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserForgotPasswordRequestedNotification : UserNotification + { + public UserForgotPasswordRequestedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserLockedNotification.cs b/src/Umbraco.Web.Common/Security/UserLockedNotification.cs new file mode 100644 index 0000000000..6c4737fb49 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserLockedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserLockedNotification : UserNotification + { + public UserLockedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserLoginFailedNotification.cs b/src/Umbraco.Web.Common/Security/UserLoginFailedNotification.cs new file mode 100644 index 0000000000..de8578015e --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserLoginFailedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserLoginFailedNotification : UserNotification + { + public UserLoginFailedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserLoginRequiresVerificationNotification.cs b/src/Umbraco.Web.Common/Security/UserLoginRequiresVerificationNotification.cs new file mode 100644 index 0000000000..cff2d9f45e --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserLoginRequiresVerificationNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserLoginRequiresVerificationNotification : UserNotification + { + public UserLoginRequiresVerificationNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserLoginSuccessNotification.cs b/src/Umbraco.Web.Common/Security/UserLoginSuccessNotification.cs new file mode 100644 index 0000000000..bb9f3ccecb --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserLoginSuccessNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserLoginSuccessNotification : UserNotification + { + public UserLoginSuccessNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserLogoutSuccessNotification.cs b/src/Umbraco.Web.Common/Security/UserLogoutSuccessNotification.cs new file mode 100644 index 0000000000..9ef9d5d0e7 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserLogoutSuccessNotification.cs @@ -0,0 +1,11 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserLogoutSuccessNotification : UserNotification + { + public UserLogoutSuccessNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + + public string SignOutRedirectUrl { get; set; } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserNotification.cs b/src/Umbraco.Web.Common/Security/UserNotification.cs new file mode 100644 index 0000000000..40d37ec82a --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserNotification.cs @@ -0,0 +1,36 @@ +using System; +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Web.Common.Security +{ + public abstract class UserNotification : INotification + { + protected UserNotification(string ipAddress, string affectedUserId, string performingUserId) + { + DateTimeUtc = DateTime.UtcNow; + IpAddress = ipAddress; + AffectedUserId = affectedUserId; + PerformingUserId = performingUserId; + } + + /// + /// Current date/time in UTC format + /// + public DateTime DateTimeUtc { get; } + + /// + /// The source IP address of the user performing the action + /// + public string IpAddress { get; } + + /// + /// The user affected by the event raised + /// + public string AffectedUserId { get; } + + /// + /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 + /// + public string PerformingUserId { get; } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserPasswordChangedNotification.cs b/src/Umbraco.Web.Common/Security/UserPasswordChangedNotification.cs new file mode 100644 index 0000000000..68115d411c --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserPasswordChangedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserPasswordChangedNotification : UserNotification + { + public UserPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserPasswordResetNotification.cs b/src/Umbraco.Web.Common/Security/UserPasswordResetNotification.cs new file mode 100644 index 0000000000..618fa04f4c --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserPasswordResetNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserPasswordResetNotification : UserNotification + { + public UserPasswordResetNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserResetAccessFailedCountNotification.cs b/src/Umbraco.Web.Common/Security/UserResetAccessFailedCountNotification.cs new file mode 100644 index 0000000000..9335bd76a0 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserResetAccessFailedCountNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserResetAccessFailedCountNotification : UserNotification + { + public UserResetAccessFailedCountNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Security/UserUnlockedNotification.cs b/src/Umbraco.Web.Common/Security/UserUnlockedNotification.cs new file mode 100644 index 0000000000..0ecba4d597 --- /dev/null +++ b/src/Umbraco.Web.Common/Security/UserUnlockedNotification.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Web.Common.Security +{ + public class UserUnlockedNotification : UserNotification + { + public UserUnlockedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) + { + } + } +} diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index 66d9b3c3f2..f8627dc2e9 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -118,31 +118,29 @@ namespace Umbraco.Cms.Web.Common.Views // ASP.NET default value is text/html if (Context.Response.ContentType.InvariantContains("text/html")) { - if (UmbracoContext.IsDebug || UmbracoContext.InPreviewMode) + if ((UmbracoContext.IsDebug || UmbracoContext.InPreviewMode) + && tagHelperOutput.TagName != null + && tagHelperOutput.TagName.Equals("body", StringComparison.InvariantCultureIgnoreCase)) { + string markupToInject; - if (tagHelperOutput.TagName.Equals("body", StringComparison.InvariantCultureIgnoreCase)) + if (UmbracoContext.InPreviewMode) { - string markupToInject; - - if (UmbracoContext.InPreviewMode) - { - // creating previewBadge markup - markupToInject = - string.Format( - ContentSettings.PreviewBadge, - IOHelper.ResolveUrl(GlobalSettings.UmbracoPath), - Context.Request.GetEncodedUrl(), - UmbracoContext.PublishedRequest.PublishedContent.Id); - } - else - { - // creating mini-profiler markup - markupToInject = ProfilerHtml.Render(); - } - - tagHelperOutput.Content.AppendHtml(markupToInject); + // creating previewBadge markup + markupToInject = + string.Format( + ContentSettings.PreviewBadge, + IOHelper.ResolveUrl(GlobalSettings.UmbracoPath), + Context.Request.GetEncodedUrl(), + UmbracoContext.PublishedRequest.PublishedContent.Id); } + else + { + // creating mini-profiler markup + markupToInject = ProfilerHtml.Render(); + } + + tagHelperOutput.Content.AppendHtml(markupToInject); } } } diff --git a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js index 86a65d1698..ff05ee6a57 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -34,12 +34,12 @@ module.exports = function (config) { 'test/config/app.unit.js', //application files - '../Umbraco.Web.UI/Umbraco/js/*.controllers.min.js', - '../Umbraco.Web.UI/Umbraco/js/*.directives.min.js', - '../Umbraco.Web.UI/Umbraco/js/*.filters.min.js', - '../Umbraco.Web.UI/Umbraco/js/*.services.min.js', - '../Umbraco.Web.UI/Umbraco/js/*.interceptors.min.js', - '../Umbraco.Web.UI/Umbraco/js/*.resources.min.js', + '../Umbraco.Web.UI/umbraco/js/*.controllers.min.js', + '../Umbraco.Web.UI/umbraco/js/*.directives.min.js', + '../Umbraco.Web.UI/umbraco/js/*.filters.min.js', + '../Umbraco.Web.UI/umbraco/js/*.services.min.js', + '../Umbraco.Web.UI/umbraco/js/*.interceptors.min.js', + '../Umbraco.Web.UI/umbraco/js/*.resources.min.js', //mocked data and routing 'src/common/mocks/umbraco.servervariables.js', diff --git a/src/Umbraco.Web.UI.NetCore/Program.cs b/src/Umbraco.Web.UI.NetCore/Program.cs index 89ce7d92bc..7a6ee4dcfd 100644 --- a/src/Umbraco.Web.UI.NetCore/Program.cs +++ b/src/Umbraco.Web.UI.NetCore/Program.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -7,18 +8,20 @@ namespace Umbraco.Cms.Web.UI.NetCore public class Program { public static void Main(string[] args) - { - CreateHostBuilder(args) + => CreateHostBuilder(args) .Build() .Run(); - } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureLogging(x => - { - x.ClearProviders(); - }) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); +#if DEBUG + .ConfigureAppConfiguration(config + => config.AddJsonFile( + "appsettings.Local.json", + optional: true, + reloadOnChange: true)) +#endif + .ConfigureLogging(x => x.ClearProviders()) + .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); } } diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index dcb7580eca..676c168906 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -40,7 +40,7 @@ namespace Umbraco.Cms.Web.UI.NetCore { #pragma warning disable IDE0022 // Use expression body for methods services.AddUmbraco(_env, _config) - .AddBackOffice() + .AddBackOffice(_env) .AddWebsite() .AddComposers() .Build(); diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index 5aeb0e8bb1..2324c4c252 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -81,6 +81,7 @@ false + false diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index a3e57978da..53a30cbcea 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -38,8 +38,8 @@ "ConvertUrlsToAscii": "try" }, "RuntimeMinification": { - "dataFolder": "App_Data/TEMP/Smidge", - "version": "637432008251409860" + "dataFolder": "umbraco/Data/TEMP/Smidge", + "version": "1" }, "Security": { "KeepUserLoggedIn": false,