diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs index 1aa4906029..ae6b6f135b 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder_Handlers.cs @@ -49,18 +49,6 @@ namespace Umbraco.Cms.Core.Cache _logger.LogInformation("Initializing Umbraco internal event handlers for cache refreshing."); - // bind to user and user group events - Bind(() => UserService.SavedUserGroup += UserService_SavedUserGroup, - () => UserService.SavedUserGroup -= UserService_SavedUserGroup); - Bind(() => UserService.DeletedUserGroup += UserService_DeletedUserGroup, - () => UserService.DeletedUserGroup -= UserService_DeletedUserGroup); - Bind(() => UserService.SavedUser += UserService_SavedUser, - () => UserService.SavedUser -= UserService_SavedUser); - Bind(() => UserService.DeletedUser += UserService_DeletedUser, - () => UserService.DeletedUser -= UserService_DeletedUser); - Bind(() => UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned, - () => UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned); - // bind to dictionary events Bind(() => LocalizationService.DeletedDictionaryItem += LocalizationService_DeletedDictionaryItem, () => LocalizationService.DeletedDictionaryItem -= LocalizationService_DeletedDictionaryItem); @@ -112,10 +100,6 @@ namespace Umbraco.Cms.Core.Cache () => MacroService.Deleted -= MacroService_Deleted); // bind to member events - Bind(() => MemberService.Saved += MemberService_Saved, - () => MemberService.Saved -= MemberService_Saved); - Bind(() => MemberService.Deleted += MemberService_Deleted, - () => MemberService.Deleted -= MemberService_Deleted); Bind(() => MemberGroupService.Saved += MemberGroupService_Saved, () => MemberGroupService.Saved -= MemberGroupService_Saved); Bind(() => MemberGroupService.Deleted += MemberGroupService_Deleted, @@ -135,12 +119,6 @@ namespace Umbraco.Cms.Core.Cache //Bind(() => ContentService.DeletedBlueprint += ContentService_DeletedBlueprint, // () => ContentService.DeletedBlueprint -= ContentService_DeletedBlueprint); - // bind to public access events - Bind(() => PublicAccessService.Saved += PublicAccessService_Saved, - () => PublicAccessService.Saved -= PublicAccessService_Saved); - Bind(() => PublicAccessService.Deleted += PublicAccessService_Deleted, - () => PublicAccessService.Deleted -= PublicAccessService_Deleted); - // bind to relation type events Bind(() => RelationService.SavedRelationType += RelationService_SavedRelationType, () => RelationService.SavedRelationType -= RelationService_SavedRelationType); @@ -148,21 +126,6 @@ namespace Umbraco.Cms.Core.Cache () => RelationService.DeletedRelationType -= RelationService_DeletedRelationType); } - #region PublicAccessService - - private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs e) - { - - _distributedCache.RefreshPublicAccess(); - } - - private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) - { - _distributedCache.RefreshPublicAccess(); - } - - #endregion - #region ContentService /// @@ -296,45 +259,6 @@ namespace Umbraco.Cms.Core.Cache #endregion - #region UserService - - private void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs e) - { - // TODO: Not sure if we need this yet depends if we start caching permissions - //var groupIds = e.SavedEntities.Select(x => x.UserGroupId).Distinct(); - //foreach (var groupId in groupIds) - //{ - // DistributedCache.Instance.RefreshUserGroupPermissionsCache(groupId); - //} - } - - private void UserService_SavedUser(IUserService sender, SaveEventArgs e) - { - foreach (var entity in e.SavedEntities) - _distributedCache.RefreshUserCache(entity.Id); - } - - private void UserService_DeletedUser(IUserService sender, DeleteEventArgs e) - { - foreach (var entity in e.DeletedEntities) - _distributedCache.RemoveUserCache(entity.Id); - } - - private void UserService_SavedUserGroup(IUserService sender, SaveEventArgs e) - { - foreach (var entity in e.SavedEntities) - _distributedCache.RefreshUserGroupCache(entity.UserGroup.Id); - } - - private void UserService_DeletedUserGroup(IUserService sender, DeleteEventArgs e) - { - - foreach (var entity in e.DeletedEntities) - _distributedCache.RemoveUserGroupCache(entity.Id); - } - - #endregion - #region FileService /// @@ -390,20 +314,6 @@ namespace Umbraco.Cms.Core.Cache #endregion - #region MemberService - - private void MemberService_Deleted(IMemberService sender, DeleteEventArgs e) - { - _distributedCache.RemoveMemberCache(e.DeletedEntities.ToArray()); - } - - private void MemberService_Saved(IMemberService sender, SaveEventArgs e) - { - _distributedCache.RefreshMemberCache(e.SavedEntities.ToArray()); - } - - #endregion - #region MemberGroupService private void MemberGroupService_Deleted(IMemberGroupService sender, DeleteEventArgs e) diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheHandler.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheHandler.cs new file mode 100644 index 0000000000..648edaca68 --- /dev/null +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheHandler.cs @@ -0,0 +1,64 @@ +using System.Linq; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Infrastructure.Services.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Cache +{ + internal class DistributedCacheHandler : + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler> + { + private readonly DistributedCache _distributedCache; + + public DistributedCacheHandler(DistributedCache distributedCache) => _distributedCache = distributedCache; + + public void Handle(SavedNotification notification) => _distributedCache.RefreshMemberCache(notification.SavedEntities.ToArray()); + + public void Handle(DeletedNotification notification) => _distributedCache.RemoveMemberCache(notification.DeletedEntities.ToArray()); + + public void Handle(SavedNotification notification) + { + foreach (var entity in notification.SavedEntities) + { + _distributedCache.RefreshUserCache(entity.Id); + } + } + + public void Handle(DeletedNotification notification) + { + foreach (var entity in notification.DeletedEntities) + { + _distributedCache.RemoveUserCache(entity.Id); + } + } + + public void Handle(SavedNotification notification) + { + foreach (var entity in notification.SavedEntities) + { + _distributedCache.RefreshUserGroupCache(entity.Id); + } + } + + public void Handle(DeletedNotification notification) + { + foreach (var entity in notification.DeletedEntities) + { + _distributedCache.RemoveUserGroupCache(entity.Id); + } + } + + public void Handle(SavedNotification notification) => _distributedCache.RefreshPublicAccess(); + + public void Handle(DeletedNotification notification) => _distributedCache.RefreshPublicAccess(); + } +} diff --git a/src/Umbraco.Infrastructure/Compose/AuditEventsComposer.cs b/src/Umbraco.Infrastructure/Compose/AuditEventsComposer.cs deleted file mode 100644 index b6d5f60765..0000000000 --- a/src/Umbraco.Infrastructure/Compose/AuditEventsComposer.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.Compose -{ - public sealed class AuditEventsComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs b/src/Umbraco.Infrastructure/Compose/AuditNotificationsHandler.cs similarity index 61% rename from src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs rename to src/Umbraco.Infrastructure/Compose/AuditNotificationsHandler.cs index 7c526d330c..f152dd16cf 100644 --- a/src/Umbraco.Infrastructure/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Infrastructure/Compose/AuditNotificationsHandler.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Text; 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; @@ -10,12 +9,21 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Security; 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 { - public sealed class AuditEventsComponent : IComponent + public sealed class AuditNotificationsHandler : + INotificationHandler>, + INotificationHandler>, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler>, + INotificationHandler>, + INotificationHandler>, + INotificationHandler { private readonly IAuditService _auditService; private readonly IUserService _userService; @@ -23,55 +31,26 @@ namespace Umbraco.Cms.Core.Compose private readonly IIpResolver _ipResolver; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly GlobalSettings _globalSettings; + private readonly IMemberService _memberService; - public AuditEventsComponent( + public AuditNotificationsHandler( IAuditService auditService, IUserService userService, IEntityService entityService, IIpResolver ipResolver, IOptions globalSettings, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMemberService memberService) { _auditService = auditService; _userService = userService; _entityService = entityService; _ipResolver = ipResolver; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _memberService = memberService; _globalSettings = globalSettings.Value; } - public void Initialize() - { - UserService.SavedUserGroup += OnSavedUserGroupWithUsers; - - UserService.SavedUser += OnSavedUser; - UserService.DeletedUser += OnDeletedUser; - UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned; - - MemberService.Saved += OnSavedMember; - MemberService.Deleted += OnDeletedMember; - MemberService.AssignedRoles += OnAssignedRoles; - MemberService.RemovedRoles += OnRemovedRoles; - MemberService.Exported += OnMemberExported; - } - - public void Terminate() - { - UserService.SavedUserGroup -= OnSavedUserGroupWithUsers; - - UserService.SavedUser -= OnSavedUser; - UserService.DeletedUser -= OnDeletedUser; - UserService.UserGroupPermissionsAssigned -= UserGroupPermissionAssigned; - - MemberService.Saved -= OnSavedMember; - MemberService.Deleted -= OnDeletedMember; - MemberService.AssignedRoles -= OnAssignedRoles; - MemberService.RemovedRoles -= OnRemovedRoles; - MemberService.Exported -= OnMemberExported; - } - - public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) { Id = Cms.Core.Constants.Security.UnknownUserId, Name = Cms.Core.Constants.Security.UnknownUserName, Email = "" }; - private IUser CurrentPerformingUser { get @@ -82,45 +61,48 @@ namespace Umbraco.Cms.Core.Compose } } - private IUser GetPerformingUser(int userId) - { - var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? UnknownUser(_globalSettings); - } + public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) { Id = Cms.Core.Constants.Security.UnknownUserId, Name = Cms.Core.Constants.Security.UnknownUserName, Email = "" }; private string PerformingIp => _ipResolver.GetCurrentRequestIpAddress(); - private string FormatEmail(IMember member) - { - return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; - } + private string FormatEmail(IMember member) => member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; - private string FormatEmail(IUser user) - { - return user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - } + private string FormatEmail(IUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; - private void OnRemovedRoles(IMemberService sender, RolesEventArgs args) + public void Handle(SavedNotification notification) { var performingUser = CurrentPerformingUser; - var roles = string.Join(", ", args.Roles); - var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x); - foreach (var id in args.MemberIds) + var members = notification.SavedEntities; + foreach (var member in members) { - members.TryGetValue(id, out var member); + var dp = string.Join(", ", ((Member)member).GetWereDirtyProperties()); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", - "umbraco/member/roles/removed", $"roles modified, removed {roles}"); + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); } } - private void OnAssignedRoles(IMemberService sender, RolesEventArgs args) + public void Handle(DeletedNotification notification) { var performingUser = CurrentPerformingUser; - var roles = string.Join(", ", args.Roles); - var members = sender.GetAllMembers(args.MemberIds).ToDictionary(x => x.Id, x => x); - foreach (var id in args.MemberIds) + var members = notification.DeletedEntities; + foreach (var member in members) + { + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); + } + } + + public void Handle(AssignedMemberRolesNotification notification) + { + var performingUser = CurrentPerformingUser; + var roles = string.Join(", ", notification.Roles); + var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in notification.MemberIds) { members.TryGetValue(id, out var member); _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, @@ -130,10 +112,25 @@ namespace Umbraco.Cms.Core.Compose } } - private void OnMemberExported(IMemberService sender, ExportedMemberEventArgs exportedMemberEventArgs) + public void Handle(RemovedMemberRolesNotification notification) { var performingUser = CurrentPerformingUser; - var member = exportedMemberEventArgs.Member; + var roles = string.Join(", ", notification.Roles); + var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in notification.MemberIds) + { + members.TryGetValue(id, out var member); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/removed", $"roles modified, removed {roles}"); + } + } + + public void Handle(ExportedMemberNotification notification) + { + var performingUser = CurrentPerformingUser; + var member = notification.Member; _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, @@ -141,10 +138,40 @@ namespace Umbraco.Cms.Core.Compose "umbraco/member/exported", "exported member data"); } - private void OnSavedUserGroupWithUsers(IUserService sender, SaveEventArgs saveEventArgs) + public void Handle(SavedNotification notification) { var performingUser = CurrentPerformingUser; - foreach (var groupWithUser in saveEventArgs.SavedEntities) + var affectedUsers = notification.SavedEntities; + foreach (var affectedUser in affectedUsers) + { + var groups = affectedUser.WasPropertyDirty("Groups") + ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) + : null; + + var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); + } + } + + public void Handle(DeletedNotification notification) + { + var performingUser = CurrentPerformingUser; + var affectedUsers = notification.DeletedEntities; + foreach (var affectedUser in affectedUsers) + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + DateTime.UtcNow, + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/delete", "delete user"); + } + + public void Handle(SavedNotification notification) + { + var performingUser = CurrentPerformingUser; + foreach (var groupWithUser in notification.SavedEntities) { var group = groupWithUser.UserGroup; @@ -192,13 +219,13 @@ namespace Umbraco.Cms.Core.Compose } } - private void UserGroupPermissionAssigned(IUserService sender, SaveEventArgs saveEventArgs) + public void Handle(AssignedUserGroupPermissionsNotification notification) { var performingUser = CurrentPerformingUser; - var perms = saveEventArgs.SavedEntities; + var perms = notification.EntityPermissions; foreach (var perm in perms) { - var group = sender.GetUserGroupById(perm.UserGroupId); + var group = _userService.GetUserGroupById(perm.UserGroupId); var assigned = string.Join(", ", perm.AssignedPermissions); var entity = _entityService.Get(perm.EntityId); @@ -208,100 +235,5 @@ namespace Umbraco.Cms.Core.Compose "umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity.Name}\""); } } - - private void OnSavedMember(IMemberService sender, SaveEventArgs saveEventArgs) - { - var performingUser = CurrentPerformingUser; - var members = saveEventArgs.SavedEntities; - foreach (var member in members) - { - var dp = string.Join(", ", ((Member) member).GetWereDirtyProperties()); - - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); - } - } - - private void OnDeletedMember(IMemberService sender, DeleteEventArgs deleteEventArgs) - { - var performingUser = CurrentPerformingUser; - var members = deleteEventArgs.DeletedEntities; - foreach (var member in members) - { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); - } - } - - private void OnSavedUser(IUserService sender, SaveEventArgs saveEventArgs) - { - var performingUser = CurrentPerformingUser; - var affectedUsers = saveEventArgs.SavedEntities; - foreach (var affectedUser in affectedUsers) - { - var groups = affectedUser.WasPropertyDirty("Groups") - ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) - : null; - - var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); - - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", - "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); - } - } - - private void OnDeletedUser(IUserService sender, DeleteEventArgs deleteEventArgs) - { - var performingUser = CurrentPerformingUser; - var affectedUsers = deleteEventArgs.DeletedEntities; - foreach (var affectedUser in affectedUsers) - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", - "umbraco/user/delete", "delete user"); - } - - private void WriteAudit(int performingId, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) - { - var performingUser = _userService.GetUserById(performingId); - - var performingDetails = performingUser == null - ? $"User UNKNOWN:{performingId}" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - WriteAudit(performingId, performingDetails, affectedId, ipAddress, eventType, eventDetails, affectedDetails); - } - - private void WriteAudit(IUser performingUser, int affectedId, string ipAddress, string eventType, string eventDetails) - { - var performingDetails = performingUser == null - ? $"User UNKNOWN" - : $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}"; - - WriteAudit(performingUser?.Id ?? 0, performingDetails, affectedId, ipAddress, eventType, eventDetails); - } - - private void WriteAudit(int performingId, string performingDetails, int affectedId, string ipAddress, string eventType, string eventDetails, string affectedDetails = null) - { - if (affectedDetails == null) - { - var affectedUser = _userService.GetUserById(affectedId); - affectedDetails = affectedUser == null - ? $"User UNKNOWN:{affectedId}" - : $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}"; - } - - _auditService.Write(performingId, performingDetails, - ipAddress, - DateTime.UtcNow, - affectedId, affectedDetails, - eventType, eventDetails); - } } } diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs deleted file mode 100644 index 687fdbf294..0000000000 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComponent.cs +++ /dev/null @@ -1,181 +0,0 @@ -// 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.Composing; -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.Membership; -using Umbraco.Cms.Core.Security; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Implement; -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; - private readonly ActionCollection _actions; - private readonly IContentService _contentService; - - public NotificationsComponent(Notifier notifier, ActionCollection actions, IContentService contentService) - { - _notifier = notifier; - _actions = actions; - _contentService = contentService; - } - - public void Initialize() - { - //Send notifications for the public access changed action - PublicAccessService.Saved += PublicAccessService_Saved; - - UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned; - } - - public void Terminate() - { - PublicAccessService.Saved -= PublicAccessService_Saved; - UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned; - } - - private void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs args) - => UserServiceUserGroupPermissionsAssigned(args, _contentService); - - private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs args) - => PublicAccessServiceSaved(args, _contentService); - - private void UserServiceUserGroupPermissionsAssigned(SaveEventArgs args, IContentService contentService) - { - var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray(); - if (entities.Any() == false) - { - return; - } - _notifier.Notify(_actions.GetAction(), entities); - } - - private void PublicAccessServiceSaved(SaveEventArgs args, IContentService contentService) - { - var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); - if (entities.Any() == false) - { - return; - } - _notifier.Notify(_actions.GetAction(), entities); - } - - /// - /// This class is used to send the notifications - /// - public sealed class Notifier - { - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly INotificationService _notificationService; - private readonly IUserService _userService; - private readonly ILocalizedTextService _textService; - private readonly GlobalSettings _globalSettings; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - public Notifier( - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IHostingEnvironment hostingEnvironment, - INotificationService notificationService, - IUserService userService, - ILocalizedTextService textService, - IOptions globalSettings, - ILogger logger) - { - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _hostingEnvironment = hostingEnvironment; - _notificationService = notificationService; - _userService = userService; - _textService = textService; - _globalSettings = globalSettings.Value; - _logger = logger; - } - - public void Notify(IAction action, params IContent[] entities) - { - var user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - - //if there is no current user, then use the admin - if (user == null) - { - _logger.LogDebug("There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = _userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - _logger.LogWarning("Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } - - SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); - } - - private void SendNotification(IUser sender, IEnumerable entities, IAction action, Uri siteUri) - { - if (sender == null) throw new ArgumentNullException(nameof(sender)); - if (siteUri == null) - { - _logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)"); - return; - } - - //group by the content type variation since the emails will be different - foreach(var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) - { - _notificationService.SendNotifications( - sender, - contentVariantGroup, - action.Letter.ToString(CultureInfo.InvariantCulture), - _textService.Localize("actions", action.Alias), - siteUri, - ((IUser user, NotificationEmailSubjectParams subject) x) - => _textService.Localize( - "notifications/mailSubject", - x.user.GetUserCulture(_textService, _globalSettings), - new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), - ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) - => _textService.Localize( - x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody", - x.user.GetUserCulture(_textService, _globalSettings), - new[] - { - x.body.RecipientName, - x.body.Action, - x.body.ItemName, - x.body.EditedUser, - x.body.SiteUrl, - x.body.ItemId, - //format the summary depending on if it's variant or not - contentVariantGroup.Key == ContentVariation.Culture - ? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary })) - : x.body.Summary, - x.body.ItemUrl - })); - } - } - - } - } - - -} diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs index 906f10c524..9b9253cfbf 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs @@ -1,10 +1,12 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Infrastructure.Services.Notifications; @@ -12,14 +14,10 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Compose { - public sealed class NotificationsComposer : ComponentComposer, ICoreComposer + public sealed class NotificationsComposer : ICoreComposer { - public override void Compose(IUmbracoBuilder builder) + public void Compose(IUmbracoBuilder builder) { - base.Compose(builder); - - builder.Services.AddUnique(); - // add handlers for sending user notifications (i.e. emails) builder.Services.AddUnique(); builder @@ -31,7 +29,9 @@ namespace Umbraco.Cms.Core.Compose .AddNotificationHandler, UserNotificationsHandler>() .AddNotificationHandler, UserNotificationsHandler>() .AddNotificationHandler, UserNotificationsHandler>() - .AddNotificationHandler, UserNotificationsHandler>(); + .AddNotificationHandler, UserNotificationsHandler>() + .AddNotificationHandler() + .AddNotificationHandler, UserNotificationsHandler>(); // add handlers for building content relations builder @@ -51,10 +51,12 @@ namespace Umbraco.Cms.Core.Compose .AddNotificationHandler, FileUploadPropertyEditor>() .AddNotificationHandler, FileUploadPropertyEditor>() .AddNotificationHandler, FileUploadPropertyEditor>() + .AddNotificationHandler, FileUploadPropertyEditor>() .AddNotificationHandler, ImageCropperPropertyEditor>() .AddNotificationHandler, ImageCropperPropertyEditor>() .AddNotificationHandler, ImageCropperPropertyEditor>() - .AddNotificationHandler, ImageCropperPropertyEditor>(); + .AddNotificationHandler, ImageCropperPropertyEditor>() + .AddNotificationHandler, ImageCropperPropertyEditor>(); // add notification handlers for redirect tracking builder @@ -62,6 +64,29 @@ namespace Umbraco.Cms.Core.Compose .AddNotificationHandler, RedirectTrackingHandler>() .AddNotificationHandler, RedirectTrackingHandler>() .AddNotificationHandler, RedirectTrackingHandler>(); + + // add notification handlers for auditing + builder + .AddNotificationHandler, AuditNotificationsHandler>() + .AddNotificationHandler, AuditNotificationsHandler>() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler() + .AddNotificationHandler, AuditNotificationsHandler>() + .AddNotificationHandler, AuditNotificationsHandler>() + .AddNotificationHandler, AuditNotificationsHandler>() + .AddNotificationHandler(); + + // add notifications handlers for distributed cache + builder + .AddNotificationHandler, DistributedCacheHandler>() + .AddNotificationHandler, DistributedCacheHandler>() + .AddNotificationHandler, DistributedCacheHandler>() + .AddNotificationHandler, DistributedCacheHandler>() + .AddNotificationHandler, DistributedCacheHandler>() + .AddNotificationHandler, DistributedCacheHandler>() + .AddNotificationHandler, DistributedCacheHandler>() + .AddNotificationHandler, DistributedCacheHandler>(); } } } diff --git a/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs index a099a66d8e..ef5d9b660b 100644 --- a/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs +++ b/src/Umbraco.Infrastructure/Events/UserNotificationsHandler.cs @@ -29,7 +29,9 @@ namespace Umbraco.Cms.Core.Events INotificationHandler>, INotificationHandler>, INotificationHandler>, - INotificationHandler> + INotificationHandler>, + INotificationHandler, + INotificationHandler> { private readonly Notifier _notifier; private readonly ActionCollection _actions; @@ -211,5 +213,26 @@ namespace Umbraco.Cms.Core.Events } } + + public void Handle(AssignedUserGroupPermissionsNotification notification) + { + var entities = _contentService.GetByIds(notification.EntityPermissions.Select(e => e.EntityId)).ToArray(); + if (entities.Any() == false) + { + return; + } + _notifier.Notify(_actions.GetAction(), entities); + + } + + public void Handle(SavedNotification notification) + { + var entities = _contentService.GetByIds(notification.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); + if (entities.Any() == false) + { + return; + } + _notifier.Notify(_actions.GetAction(), entities); + } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs index a43531bb83..6b75c54a12 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyEditor.cs @@ -27,7 +27,8 @@ namespace Umbraco.Cms.Core.PropertyEditors Icon = "icon-download-alt")] public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler>, INotificationHandler>, - INotificationHandler>, INotificationHandler> + INotificationHandler>, INotificationHandler>, + INotificationHandler> { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -95,10 +96,7 @@ namespace Umbraco.Cms.Core.PropertyEditors /// The paths to all file upload property files contained within a collection of content entities /// /// - /// - /// This method must be made private once MemberService events have been replaced by notifications - /// - internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + private IEnumerable ContainedFilePaths(IEnumerable entities) => entities .SelectMany(x => x.Properties) .Where(IsUploadField) .SelectMany(GetFilePathsFromPropertyValues) @@ -162,6 +160,8 @@ namespace Umbraco.Cms.Core.PropertyEditors public void Handle(DeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + public void Handle(DeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + private void DeleteContainedFiles(IEnumerable deletedEntities) { var filePathsToDelete = ContainedFilePaths(deletedEntities); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs index d4c0c94054..886812d5a2 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ImageCropperPropertyEditor.cs @@ -34,7 +34,8 @@ namespace Umbraco.Cms.Core.PropertyEditors Icon = "icon-crop")] public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator, INotificationHandler>, INotificationHandler>, - INotificationHandler>, INotificationHandler> + INotificationHandler>, INotificationHandler>, + INotificationHandler> { private readonly IMediaFileSystem _mediaFileSystem; private readonly ContentSettings _contentSettings; @@ -131,10 +132,7 @@ namespace Umbraco.Cms.Core.PropertyEditors /// The paths to all image cropper property files contained within a collection of content entities /// /// - /// - /// This method must be made private once MemberService events have been replaced by notifications - /// - internal IEnumerable ContainedFilePaths(IEnumerable entities) => entities + private IEnumerable ContainedFilePaths(IEnumerable entities) => entities .SelectMany(x => x.Properties) .Where(IsCropperField) .SelectMany(GetFilePathsFromPropertyValues) @@ -218,6 +216,8 @@ namespace Umbraco.Cms.Core.PropertyEditors public void Handle(DeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + public void Handle(DeletedNotification notification) => DeleteContainedFiles(notification.DeletedEntities); + private void DeleteContainedFiles(IEnumerable deletedEntities) { var filePathsToDelete = ContainedFilePaths(deletedEntities); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs deleted file mode 100644 index b9e9e33889..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComponent.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using System; -using System.Collections.Generic; -using System.Linq; -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.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; - private readonly List _terminate = new List(); - - public PropertyEditorsComponent(PropertyEditorCollection propertyEditors) - { - _propertyEditors = propertyEditors; - } - - public void Initialize() - { - var fileUpload = _propertyEditors.OfType().FirstOrDefault(); - if (fileUpload != null) Initialize(fileUpload); - - var imageCropper = _propertyEditors.OfType().FirstOrDefault(); - if (imageCropper != null) Initialize(imageCropper); - - // grid/examine moved to ExamineComponent - } - - public void Terminate() - { - foreach (var t in _terminate) t(); - } - - private void Initialize(FileUploadPropertyEditor fileUpload) - { - 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) - { - 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/PropertyEditors/PropertyEditorsComposer.cs b/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComposer.cs deleted file mode 100644 index 4e876ad554..0000000000 --- a/src/Umbraco.Infrastructure/PropertyEditors/PropertyEditorsComposer.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using Umbraco.Cms.Core.Composing; - -namespace Umbraco.Cms.Core.PropertyEditors -{ - public sealed class PropertyEditorsComposer : ComponentComposer, ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs index 96ba494790..026052fa67 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/MemberService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Querying; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -22,7 +23,6 @@ namespace Umbraco.Cms.Core.Services.Implement private readonly IMemberTypeRepository _memberTypeRepository; private readonly IMemberGroupRepository _memberGroupRepository; private readonly IAuditRepository _auditRepository; - private readonly IMemberTypeService _memberTypeService; private readonly IMemberGroupService _memberGroupService; @@ -773,10 +773,12 @@ namespace Umbraco.Cms.Core.Services.Implement member.Username = member.Username.Trim(); member.Email = member.Email.Trim(); + var evtMsgs = EventMessagesFactory.Get(); + using (IScope scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(member); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotification = new SavingNotification(member, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -793,8 +795,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(member, evtMsgs).WithStateFrom(savingNotification)); } Audit(AuditType.Save, 0, member.Id); @@ -808,10 +809,12 @@ namespace Umbraco.Cms.Core.Services.Implement { var membersA = members.ToArray(); + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(membersA); - if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotification = new SavingNotification(membersA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -830,8 +833,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(membersA, evtMsgs).WithStateFrom(savingNotification)); } Audit(AuditType.Save, 0, -1, "Save multiple Members"); @@ -849,32 +851,30 @@ namespace Umbraco.Cms.Core.Services.Implement /// to Delete public void Delete(IMember member) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(member); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + var deletingNotification = new DeletingNotification(member, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; } scope.WriteLock(Constants.Locks.MemberTree); - DeleteLocked(scope, member, deleteEventArgs); + DeleteLocked(scope, member, evtMsgs, deletingNotification.State); Audit(AuditType.Delete, 0, member.Id); scope.Complete(); } } - private void DeleteLocked(IScope scope, IMember member, DeleteEventArgs args = null) + private void DeleteLocked(IScope scope, IMember member, EventMessages evtMsgs, IDictionary notificationState = null) { // a member has no descendants _memberRepository.Delete(member); - if (args == null) - args = new DeleteEventArgs(member, false); // raise event & get flagged files - else - args.CanCancel = false; - scope.Events.Dispatch(Deleted, this, args); + scope.Notifications.Publish(new DeletedNotification(member, evtMsgs).WithState(notificationState)); // media files deleted by QueuingEventDispatcher } @@ -1017,8 +1017,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Constants.Locks.MemberTree); int[] ids = _memberGroupRepository.GetMemberIds(usernames); _memberGroupRepository.AssignRoles(ids, roleNames); - scope.Events.Dispatch(AssignedRoles, this, new RolesEventArgs(ids, roleNames), nameof(AssignedRoles)); - scope.Complete(); + scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames)); } } @@ -1031,8 +1030,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.WriteLock(Constants.Locks.MemberTree); int[] ids = _memberGroupRepository.GetMemberIds(usernames); _memberGroupRepository.DissociateRoles(ids, roleNames); - scope.Events.Dispatch(RemovedRoles, this, new RolesEventArgs(ids, roleNames), nameof(RemovedRoles)); - scope.Complete(); + scope.Notifications.Publish(new RemovedMemberRolesNotification(ids, roleNames)); } } @@ -1044,8 +1042,7 @@ namespace Umbraco.Cms.Core.Services.Implement { scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.AssignRoles(memberIds, roleNames); - scope.Events.Dispatch(AssignedRoles, this, new RolesEventArgs(memberIds, roleNames), nameof(AssignedRoles)); - scope.Complete(); + scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames)); } } @@ -1057,8 +1054,7 @@ namespace Umbraco.Cms.Core.Services.Implement { scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.DissociateRoles(memberIds, roleNames); - scope.Events.Dispatch(RemovedRoles, this, new RolesEventArgs(memberIds, roleNames), nameof(RemovedRoles)); - scope.Complete(); + scope.Notifications.Publish(new RemovedMemberRolesNotification(memberIds, roleNames)); } } @@ -1072,36 +1068,6 @@ 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 Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs after roles have been assigned. - /// - public static event TypedEventHandler AssignedRoles; - - /// - /// Occurs after roles have been removed. - /// - public static event TypedEventHandler RemovedRoles; - /// /// Occurs after members have been exported. /// @@ -1145,7 +1111,7 @@ namespace Umbraco.Cms.Core.Services.Implement Properties = new List(GetPropertyExportItems(member)) }; - scope.Events.Dispatch(Exported, this, new ExportedMemberEventArgs(member, model)); + scope.Notifications.Publish(new ExportedMemberNotification(member, model)); return model; } @@ -1187,6 +1153,8 @@ namespace Umbraco.Cms.Core.Services.Implement /// Id of the MemberType public void DeleteMembersOfType(int memberTypeId) { + var evtMsgs = EventMessagesFactory.Get(); + // note: no tree to manage here using (IScope scope = ScopeProvider.CreateScope()) { @@ -1196,9 +1164,8 @@ namespace Umbraco.Cms.Core.Services.Implement IQuery query = Query().Where(x => x.ContentTypeId == memberTypeId); IMember[] members = _memberRepository.Get(query).ToArray(); - var deleteEventArgs = new DeleteEventArgs(members); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + if (scope.Notifications.PublishCancelable(new DeletingNotification(members, evtMsgs))) { scope.Complete(); return; @@ -1208,7 +1175,7 @@ namespace Umbraco.Cms.Core.Services.Implement { // delete media // triggers the deleted event (and handles the files) - DeleteLocked(scope, member); + DeleteLocked(scope, member, evtMsgs); } scope.Complete(); diff --git a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs index 4c8615f442..b82f09d1b1 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/PublicAccessService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -6,6 +6,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -129,8 +130,8 @@ namespace Umbraco.Cms.Core.Services.Implement return OperationResult.Attempt.Succeed(evtMsgs, entry); } - var saveEventArgs = new SaveEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotifiation = new SavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs, entry); @@ -140,8 +141,7 @@ namespace Umbraco.Cms.Core.Services.Implement scope.Complete(); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs, entry); @@ -167,8 +167,8 @@ namespace Umbraco.Cms.Core.Services.Implement entry.RemoveRule(existingRule); - var saveEventArgs = new SaveEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotifiation = new SavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -177,8 +177,7 @@ namespace Umbraco.Cms.Core.Services.Implement _publicAccessRepository.Save(entry); scope.Complete(); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs); @@ -194,8 +193,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs)) + var savingNotifiation = new SavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -204,8 +203,7 @@ namespace Umbraco.Cms.Core.Services.Implement _publicAccessRepository.Save(entry); scope.Complete(); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(Saved, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } return OperationResult.Attempt.Succeed(evtMsgs); @@ -221,8 +219,8 @@ namespace Umbraco.Cms.Core.Services.Implement using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(entry, evtMsgs); - if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs)) + var deletingNotification = new DeletingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return OperationResult.Attempt.Cancel(evtMsgs); @@ -231,33 +229,10 @@ namespace Umbraco.Cms.Core.Services.Implement _publicAccessRepository.Delete(entry); scope.Complete(); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(Deleted, this, deleteEventArgs); + scope.Notifications.Publish(new DeletedNotification(entry, evtMsgs).WithStateFrom(deletingNotification)); } return OperationResult.Attempt.Succeed(evtMsgs); } - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> Saving; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> Saved; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> Deleting; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> Deleted; - - } } diff --git a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs index 751581e068..3336ba37a3 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/UserService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/UserService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data.Common; using System.Globalization; @@ -14,6 +14,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; +using Umbraco.Cms.Infrastructure.Services.Notifications; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services.Implement @@ -109,6 +110,8 @@ namespace Umbraco.Cms.Core.Services.Implement if (username == null) throw new ArgumentNullException(nameof(username)); if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); + var evtMsgs = EventMessagesFactory.Get(); + // TODO: PUT lock here!! User user; @@ -129,8 +132,8 @@ namespace Umbraco.Cms.Core.Services.Implement IsApproved = isApproved }; - var saveEventArgs = new SaveEventArgs(user); - if (scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) + var savingNotification = new SavingNotification(user, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return user; @@ -138,8 +141,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userRepository.Save(user); - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUser, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(user, evtMsgs).WithStateFrom(savingNotification)); scope.Complete(); } @@ -239,10 +241,12 @@ namespace Umbraco.Cms.Core.Services.Implement } else { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(user); - if (scope.Events.DispatchCancelable(DeletingUser, this, deleteEventArgs)) + var deletingNotification = new DeletingNotification(user, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; @@ -250,8 +254,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userRepository.Delete(user); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedUser, this, deleteEventArgs); + scope.Notifications.Publish(new DeletedNotification(user, evtMsgs).WithStateFrom(deletingNotification)); scope.Complete(); } } @@ -272,10 +275,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// Default is True otherwise set to False to not raise events public void Save(IUser entity, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(entity); - if (raiseEvents && scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) + var savingNotification = new SavingNotification(entity, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -292,8 +297,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userRepository.Save(entity); if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUser, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(entity, evtMsgs).WithStateFrom(savingNotification)); } scope.Complete(); @@ -319,12 +323,14 @@ namespace Umbraco.Cms.Core.Services.Implement /// Default is True otherwise set to False to not raise events public void Save(IEnumerable entities, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + var entitiesA = entities.ToArray(); using (var scope = ScopeProvider.CreateScope()) { - var saveEventArgs = new SaveEventArgs(entitiesA); - if (raiseEvents && scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs)) + var savingNotification = new SavingNotification(entitiesA, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) { scope.Complete(); return; @@ -344,8 +350,7 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUser, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(entitiesA, evtMsgs).WithStateFrom(savingNotification)); } //commit the whole lot in one go @@ -709,14 +714,16 @@ namespace Umbraco.Cms.Core.Services.Implement if (entityIds.Length == 0) return; + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { _userGroupRepository.ReplaceGroupPermissions(groupId, permissions, entityIds); scope.Complete(); var assigned = permissions.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray(); - scope.Events.Dispatch(UserGroupPermissionsAssigned, this, - new SaveEventArgs(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false)); + var entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); + scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); } } @@ -731,14 +738,16 @@ namespace Umbraco.Cms.Core.Services.Implement if (entityIds.Length == 0) return; + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { _userGroupRepository.AssignGroupPermission(groupId, permission, entityIds); scope.Complete(); var assigned = new[] { permission.ToString(CultureInfo.InvariantCulture) }; - scope.Events.Dispatch(UserGroupPermissionsAssigned, this, - new SaveEventArgs(entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(), false)); + var entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); + scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); } } @@ -809,6 +818,8 @@ namespace Umbraco.Cms.Core.Services.Implement /// Default is True otherwise set to False to not raise events public void Save(IUserGroup userGroup, int[] userIds = null, bool raiseEvents = true) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { // we need to figure out which users have been added / removed, for audit purposes @@ -826,9 +837,19 @@ namespace Umbraco.Cms.Core.Services.Implement removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray(); } - var saveEventArgs = new SaveEventArgs(new UserGroupWithUsers(userGroup, addedUsers, removedUsers)); + var userGroupWithUsers = new UserGroupWithUsers(userGroup, addedUsers, removedUsers); - if (raiseEvents && scope.Events.DispatchCancelable(SavingUserGroup, this, saveEventArgs)) + // this is the default/expected notification for the IUserGroup entity being saved + var savingNotification = new SavingNotification(userGroup, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification)) + { + scope.Complete(); + return; + } + + // this is an additional notification for special auditing + var savingUserGroupWithUsersNotification = new SavingNotification(userGroupWithUsers, evtMsgs); + if (raiseEvents && scope.Notifications.PublishCancelable(savingUserGroupWithUsersNotification)) { scope.Complete(); return; @@ -838,8 +859,8 @@ namespace Umbraco.Cms.Core.Services.Implement if (raiseEvents) { - saveEventArgs.CanCancel = false; - scope.Events.Dispatch(SavedUserGroup, this, saveEventArgs); + scope.Notifications.Publish(new SavedNotification(userGroup, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new SavedNotification(userGroupWithUsers, evtMsgs).WithStateFrom(savingUserGroupWithUsersNotification)); } scope.Complete(); @@ -852,10 +873,12 @@ namespace Umbraco.Cms.Core.Services.Implement /// UserGroup to delete public void DeleteUserGroup(IUserGroup userGroup) { + var evtMsgs = EventMessagesFactory.Get(); + using (var scope = ScopeProvider.CreateScope()) { - var deleteEventArgs = new DeleteEventArgs(userGroup); - if (scope.Events.DispatchCancelable(DeletingUserGroup, this, deleteEventArgs)) + var deletingNotification = new DeletingNotification(userGroup, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; @@ -863,8 +886,7 @@ namespace Umbraco.Cms.Core.Services.Implement _userGroupRepository.Delete(userGroup); - deleteEventArgs.CanCancel = false; - scope.Events.Dispatch(DeletedUserGroup, this, deleteEventArgs); + scope.Notifications.Publish(new DeletedNotification(userGroup, evtMsgs).WithStateFrom(deletingNotification)); scope.Complete(); } @@ -1144,49 +1166,5 @@ namespace Umbraco.Cms.Core.Services.Implement } #endregion - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingUser; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedUser; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingUser; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> DeletedUser; - - /// - /// Occurs before Save - /// - public static event TypedEventHandler> SavingUserGroup; - - /// - /// Occurs after Save - /// - public static event TypedEventHandler> SavedUserGroup; - - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingUserGroup; - - /// - /// Occurs after Delete - /// - public static event TypedEventHandler> DeletedUserGroup; - - // TODO: still don't know if we need this yet unless we start caching permissions, but that also means we'll need another - // event on the ContentService since there's a method there to modify node permissions too, or we can proxy events if needed. - public static event TypedEventHandler> UserGroupPermissionsAssigned; } } diff --git a/src/Umbraco.Infrastructure/Services/Notifications/AssignedMemberRolesNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/AssignedMemberRolesNotification.cs new file mode 100644 index 0000000000..9d5d707f64 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/AssignedMemberRolesNotification.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class AssignedMemberRolesNotification : MemberRolesNotification + { + public AssignedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) + { + + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/AssignedUserGroupPermissionsNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/AssignedUserGroupPermissionsNotification.cs new file mode 100644 index 0000000000..e0838f8c33 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/AssignedUserGroupPermissionsNotification.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class AssignedUserGroupPermissionsNotification : EnumerableObjectNotification + { + public AssignedUserGroupPermissionsNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } + + public IEnumerable EntityPermissions => Target; + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/ExportedMemberNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/ExportedMemberNotification.cs new file mode 100644 index 0000000000..449244242f --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/ExportedMemberNotification.cs @@ -0,0 +1,19 @@ +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class ExportedMemberNotification : INotification + { + public ExportedMemberNotification(IMember member, MemberExportModel exported) + { + Member = member; + Exported = exported; + } + + public IMember Member { get; } + + public MemberExportModel Exported { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/MemberRolesNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/MemberRolesNotification.cs new file mode 100644 index 0000000000..2b7c9bd828 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/MemberRolesNotification.cs @@ -0,0 +1,17 @@ +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public abstract class MemberRolesNotification : INotification + { + protected MemberRolesNotification(int[] memberIds, string[] roles) + { + MemberIds = memberIds; + Roles = roles; + } + + public int[] MemberIds { get; } + + public string[] Roles { get; } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Notifications/RemovedMemberRolesNotification.cs b/src/Umbraco.Infrastructure/Services/Notifications/RemovedMemberRolesNotification.cs new file mode 100644 index 0000000000..bd902cb078 --- /dev/null +++ b/src/Umbraco.Infrastructure/Services/Notifications/RemovedMemberRolesNotification.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Infrastructure.Services.Notifications +{ + public class RemovedMemberRolesNotification : MemberRolesNotification + { + public RemovedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) + { + + } + } +}