Replace static events with notifications in MemberService, PublicAccessService and UserService, and clean up unused components etc.

This commit is contained in:
Kenn Jacobsen
2021-03-09 07:52:32 +01:00
parent 78bb319c79
commit eb74f74c91
19 changed files with 390 additions and 699 deletions

View File

@@ -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<PublicAccessEntry> e)
{
_distributedCache.RefreshPublicAccess();
}
private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs<PublicAccessEntry> e)
{
_distributedCache.RefreshPublicAccess();
}
#endregion
#region ContentService
/// <summary>
@@ -296,45 +259,6 @@ namespace Umbraco.Cms.Core.Cache
#endregion
#region UserService
private void UserService_UserGroupPermissionsAssigned(IUserService sender, SaveEventArgs<EntityPermission> 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<IUser> e)
{
foreach (var entity in e.SavedEntities)
_distributedCache.RefreshUserCache(entity.Id);
}
private void UserService_DeletedUser(IUserService sender, DeleteEventArgs<IUser> e)
{
foreach (var entity in e.DeletedEntities)
_distributedCache.RemoveUserCache(entity.Id);
}
private void UserService_SavedUserGroup(IUserService sender, SaveEventArgs<UserGroupWithUsers> e)
{
foreach (var entity in e.SavedEntities)
_distributedCache.RefreshUserGroupCache(entity.UserGroup.Id);
}
private void UserService_DeletedUserGroup(IUserService sender, DeleteEventArgs<IUserGroup> e)
{
foreach (var entity in e.DeletedEntities)
_distributedCache.RemoveUserGroupCache(entity.Id);
}
#endregion
#region FileService
/// <summary>
@@ -390,20 +314,6 @@ namespace Umbraco.Cms.Core.Cache
#endregion
#region MemberService
private void MemberService_Deleted(IMemberService sender, DeleteEventArgs<IMember> e)
{
_distributedCache.RemoveMemberCache(e.DeletedEntities.ToArray());
}
private void MemberService_Saved(IMemberService sender, SaveEventArgs<IMember> e)
{
_distributedCache.RefreshMemberCache(e.SavedEntities.ToArray());
}
#endregion
#region MemberGroupService
private void MemberGroupService_Deleted(IMemberGroupService sender, DeleteEventArgs<IMemberGroup> e)

View File

@@ -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<SavedNotification<IMember>>,
INotificationHandler<DeletedNotification<IMember>>,
INotificationHandler<SavedNotification<IUser>>,
INotificationHandler<DeletedNotification<IUser>>,
INotificationHandler<SavedNotification<IUserGroup>>,
INotificationHandler<DeletedNotification<IUserGroup>>,
INotificationHandler<SavedNotification<PublicAccessEntry>>,
INotificationHandler<DeletedNotification<PublicAccessEntry>>
{
private readonly DistributedCache _distributedCache;
public DistributedCacheHandler(DistributedCache distributedCache) => _distributedCache = distributedCache;
public void Handle(SavedNotification<IMember> notification) => _distributedCache.RefreshMemberCache(notification.SavedEntities.ToArray());
public void Handle(DeletedNotification<IMember> notification) => _distributedCache.RemoveMemberCache(notification.DeletedEntities.ToArray());
public void Handle(SavedNotification<IUser> notification)
{
foreach (var entity in notification.SavedEntities)
{
_distributedCache.RefreshUserCache(entity.Id);
}
}
public void Handle(DeletedNotification<IUser> notification)
{
foreach (var entity in notification.DeletedEntities)
{
_distributedCache.RemoveUserCache(entity.Id);
}
}
public void Handle(SavedNotification<IUserGroup> notification)
{
foreach (var entity in notification.SavedEntities)
{
_distributedCache.RefreshUserGroupCache(entity.Id);
}
}
public void Handle(DeletedNotification<IUserGroup> notification)
{
foreach (var entity in notification.DeletedEntities)
{
_distributedCache.RemoveUserGroupCache(entity.Id);
}
}
public void Handle(SavedNotification<PublicAccessEntry> notification) => _distributedCache.RefreshPublicAccess();
public void Handle(DeletedNotification<PublicAccessEntry> notification) => _distributedCache.RefreshPublicAccess();
}
}

View File

@@ -1,7 +0,0 @@
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Core.Compose
{
public sealed class AuditEventsComposer : ComponentComposer<AuditEventsComponent>, ICoreComposer
{ }
}

View File

@@ -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<SavedNotification<IMember>>,
INotificationHandler<DeletedNotification<IMember>>,
INotificationHandler<AssignedMemberRolesNotification>,
INotificationHandler<RemovedMemberRolesNotification>,
INotificationHandler<ExportedMemberNotification>,
INotificationHandler<SavedNotification<IUser>>,
INotificationHandler<DeletedNotification<IUser>>,
INotificationHandler<SavedNotification<UserGroupWithUsers>>,
INotificationHandler<AssignedUserGroupPermissionsNotification>
{
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> 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<IMember> 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<IMember> 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<UserGroupWithUsers> saveEventArgs)
public void Handle(SavedNotification<IUser> 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<IUser> 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<UserGroupWithUsers> 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<EntityPermission> 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<IMember> 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<IMember> 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<IUser> 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<IUser> 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);
}
}
}

View File

@@ -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
{
/// <remarks>
/// TODO: this component must be removed entirely - there is some code duplication in <see cref="UserNotificationsHandler"/> in anticipation of this component being deleted
/// </remarks>
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<EntityPermission> args)
=> UserServiceUserGroupPermissionsAssigned(args, _contentService);
private void PublicAccessService_Saved(IPublicAccessService sender, SaveEventArgs<PublicAccessEntry> args)
=> PublicAccessServiceSaved(args, _contentService);
private void UserServiceUserGroupPermissionsAssigned(SaveEventArgs<EntityPermission> args, IContentService contentService)
{
var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray();
if (entities.Any() == false)
{
return;
}
_notifier.Notify(_actions.GetAction<ActionRights>(), entities);
}
private void PublicAccessServiceSaved(SaveEventArgs<PublicAccessEntry> args, IContentService contentService)
{
var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray();
if (entities.Any() == false)
{
return;
}
_notifier.Notify(_actions.GetAction<ActionProtect>(), entities);
}
/// <summary>
/// This class is used to send the notifications
/// </summary>
public sealed class Notifier
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly INotificationService _notificationService;
private readonly IUserService _userService;
private readonly ILocalizedTextService _textService;
private readonly GlobalSettings _globalSettings;
private readonly ILogger<Notifier> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="Notifier"/> class.
/// </summary>
public Notifier(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IHostingEnvironment hostingEnvironment,
INotificationService notificationService,
IUserService userService,
ILocalizedTextService textService,
IOptions<GlobalSettings> globalSettings,
ILogger<Notifier> logger)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_hostingEnvironment = hostingEnvironment;
_notificationService = notificationService;
_userService = userService;
_textService = textService;
_globalSettings = globalSettings.Value;
_logger = logger;
}
public void Notify(IAction action, params IContent[] entities)
{
var user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser;
//if there is no current user, then use the admin
if (user == null)
{
_logger.LogDebug("There is no current Umbraco user logged in, the notifications will be sent from the administrator");
user = _userService.GetUserById(Constants.Security.SuperUserId);
if (user == null)
{
_logger.LogWarning("Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId);
return;
}
}
SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl);
}
private void SendNotification(IUser sender, IEnumerable<IContent> entities, IAction action, Uri siteUri)
{
if (sender == null) throw new ArgumentNullException(nameof(sender));
if (siteUri == null)
{
_logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)");
return;
}
//group by the content type variation since the emails will be different
foreach(var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations))
{
_notificationService.SendNotifications(
sender,
contentVariantGroup,
action.Letter.ToString(CultureInfo.InvariantCulture),
_textService.Localize("actions", action.Alias),
siteUri,
((IUser user, NotificationEmailSubjectParams subject) x)
=> _textService.Localize(
"notifications/mailSubject",
x.user.GetUserCulture(_textService, _globalSettings),
new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }),
((IUser user, NotificationEmailBodyParams body, bool isHtml) x)
=> _textService.Localize(
x.isHtml ? "notifications/mailBodyHtml" : "notifications/mailBody",
x.user.GetUserCulture(_textService, _globalSettings),
new[]
{
x.body.RecipientName,
x.body.Action,
x.body.ItemName,
x.body.EditedUser,
x.body.SiteUrl,
x.body.ItemId,
//format the summary depending on if it's variant or not
contentVariantGroup.Key == ContentVariation.Culture
? (x.isHtml ? _textService.Localize("notifications/mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications/mailBodyVariantSummary", new []{ x.body.Summary }))
: x.body.Summary,
x.body.ItemUrl
}));
}
}
}
}
}

View File

@@ -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<NotificationsComponent>, ICoreComposer
public sealed class NotificationsComposer : ICoreComposer
{
public override void Compose(IUmbracoBuilder builder)
public void Compose(IUmbracoBuilder builder)
{
base.Compose(builder);
builder.Services.AddUnique<NotificationsComponent.Notifier>();
// add handlers for sending user notifications (i.e. emails)
builder.Services.AddUnique<UserNotificationsHandler.Notifier>();
builder
@@ -31,7 +29,9 @@ namespace Umbraco.Cms.Core.Compose
.AddNotificationHandler<CopiedNotification<IContent>, UserNotificationsHandler>()
.AddNotificationHandler<RolledBackNotification<IContent>, UserNotificationsHandler>()
.AddNotificationHandler<SentToPublishNotification<IContent>, UserNotificationsHandler>()
.AddNotificationHandler<UnpublishedNotification<IContent>, UserNotificationsHandler>();
.AddNotificationHandler<UnpublishedNotification<IContent>, UserNotificationsHandler>()
.AddNotificationHandler<AssignedUserGroupPermissionsNotification, UserNotificationsHandler>()
.AddNotificationHandler<SavedNotification<PublicAccessEntry>, UserNotificationsHandler>();
// add handlers for building content relations
builder
@@ -51,10 +51,12 @@ namespace Umbraco.Cms.Core.Compose
.AddNotificationHandler<DeletedNotification<IContent>, FileUploadPropertyEditor>()
.AddNotificationHandler<DeletedNotification<IMedia>, FileUploadPropertyEditor>()
.AddNotificationHandler<SavingNotification<IMedia>, FileUploadPropertyEditor>()
.AddNotificationHandler<DeletedNotification<IMember>, FileUploadPropertyEditor>()
.AddNotificationHandler<CopiedNotification<IContent>, ImageCropperPropertyEditor>()
.AddNotificationHandler<DeletedNotification<IContent>, ImageCropperPropertyEditor>()
.AddNotificationHandler<DeletedNotification<IMedia>, ImageCropperPropertyEditor>()
.AddNotificationHandler<SavingNotification<IMedia>, ImageCropperPropertyEditor>();
.AddNotificationHandler<SavingNotification<IMedia>, ImageCropperPropertyEditor>()
.AddNotificationHandler<DeletedNotification<IMember>, ImageCropperPropertyEditor>();
// add notification handlers for redirect tracking
builder
@@ -62,6 +64,29 @@ namespace Umbraco.Cms.Core.Compose
.AddNotificationHandler<PublishedNotification<IContent>, RedirectTrackingHandler>()
.AddNotificationHandler<MovingNotification<IContent>, RedirectTrackingHandler>()
.AddNotificationHandler<MovedNotification<IContent>, RedirectTrackingHandler>();
// add notification handlers for auditing
builder
.AddNotificationHandler<SavedNotification<IMember>, AuditNotificationsHandler>()
.AddNotificationHandler<DeletedNotification<IMember>, AuditNotificationsHandler>()
.AddNotificationHandler<AssignedMemberRolesNotification, AuditNotificationsHandler>()
.AddNotificationHandler<RemovedMemberRolesNotification, AuditNotificationsHandler>()
.AddNotificationHandler<ExportedMemberNotification, AuditNotificationsHandler>()
.AddNotificationHandler<SavedNotification<IUser>, AuditNotificationsHandler>()
.AddNotificationHandler<DeletedNotification<IUser>, AuditNotificationsHandler>()
.AddNotificationHandler<SavedNotification<UserGroupWithUsers>, AuditNotificationsHandler>()
.AddNotificationHandler<AssignedUserGroupPermissionsNotification, AuditNotificationsHandler>();
// add notifications handlers for distributed cache
builder
.AddNotificationHandler<SavedNotification<IMember>, DistributedCacheHandler>()
.AddNotificationHandler<DeletedNotification<IMember>, DistributedCacheHandler>()
.AddNotificationHandler<SavedNotification<IUser>, DistributedCacheHandler>()
.AddNotificationHandler<DeletedNotification<IUser>, DistributedCacheHandler>()
.AddNotificationHandler<SavedNotification<IUserGroup>, DistributedCacheHandler>()
.AddNotificationHandler<DeletedNotification<IUserGroup>, DistributedCacheHandler>()
.AddNotificationHandler<SavedNotification<PublicAccessEntry>, DistributedCacheHandler>()
.AddNotificationHandler<DeletedNotification<PublicAccessEntry>, DistributedCacheHandler>();
}
}
}

View File

@@ -29,7 +29,9 @@ namespace Umbraco.Cms.Core.Events
INotificationHandler<CopiedNotification<IContent>>,
INotificationHandler<RolledBackNotification<IContent>>,
INotificationHandler<SentToPublishNotification<IContent>>,
INotificationHandler<UnpublishedNotification<IContent>>
INotificationHandler<UnpublishedNotification<IContent>>,
INotificationHandler<AssignedUserGroupPermissionsNotification>,
INotificationHandler<SavedNotification<PublicAccessEntry>>
{
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<ActionRights>(), entities);
}
public void Handle(SavedNotification<PublicAccessEntry> notification)
{
var entities = _contentService.GetByIds(notification.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray();
if (entities.Any() == false)
{
return;
}
_notifier.Notify(_actions.GetAction<ActionProtect>(), entities);
}
}
}

View File

@@ -27,7 +27,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
Icon = "icon-download-alt")]
public class FileUploadPropertyEditor : DataEditor, IMediaUrlGenerator,
INotificationHandler<CopiedNotification<IContent>>, INotificationHandler<DeletedNotification<IContent>>,
INotificationHandler<DeletedNotification<IMedia>>, INotificationHandler<SavingNotification<IMedia>>
INotificationHandler<DeletedNotification<IMedia>>, INotificationHandler<SavingNotification<IMedia>>,
INotificationHandler<DeletedNotification<IMember>>
{
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
/// </summary>
/// <param name="entities"></param>
/// <remarks>
/// This method must be made private once MemberService events have been replaced by notifications
/// </remarks>
internal IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> entities) => entities
private IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> entities) => entities
.SelectMany(x => x.Properties)
.Where(IsUploadField)
.SelectMany(GetFilePathsFromPropertyValues)
@@ -162,6 +160,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
public void Handle(DeletedNotification<IMedia> notification) => DeleteContainedFiles(notification.DeletedEntities);
public void Handle(DeletedNotification<IMember> notification) => DeleteContainedFiles(notification.DeletedEntities);
private void DeleteContainedFiles(IEnumerable<IContentBase> deletedEntities)
{
var filePathsToDelete = ContainedFilePaths(deletedEntities);

View File

@@ -34,7 +34,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
Icon = "icon-crop")]
public class ImageCropperPropertyEditor : DataEditor, IMediaUrlGenerator,
INotificationHandler<CopiedNotification<IContent>>, INotificationHandler<DeletedNotification<IContent>>,
INotificationHandler<DeletedNotification<IMedia>>, INotificationHandler<SavingNotification<IMedia>>
INotificationHandler<DeletedNotification<IMedia>>, INotificationHandler<SavingNotification<IMedia>>,
INotificationHandler<DeletedNotification<IMember>>
{
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
/// </summary>
/// <param name="entities"></param>
/// <remarks>
/// This method must be made private once MemberService events have been replaced by notifications
/// </remarks>
internal IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> entities) => entities
private IEnumerable<string> ContainedFilePaths(IEnumerable<IContentBase> entities) => entities
.SelectMany(x => x.Properties)
.Where(IsCropperField)
.SelectMany(GetFilePathsFromPropertyValues)
@@ -218,6 +216,8 @@ namespace Umbraco.Cms.Core.PropertyEditors
public void Handle(DeletedNotification<IMedia> notification) => DeleteContainedFiles(notification.DeletedEntities);
public void Handle(DeletedNotification<IMember> notification) => DeleteContainedFiles(notification.DeletedEntities);
private void DeleteContainedFiles(IEnumerable<IContentBase> deletedEntities)
{
var filePathsToDelete = ContainedFilePaths(deletedEntities);

View File

@@ -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<Action> _terminate = new List<Action>();
public PropertyEditorsComponent(PropertyEditorCollection propertyEditors)
{
_propertyEditors = propertyEditors;
}
public void Initialize()
{
var fileUpload = _propertyEditors.OfType<FileUploadPropertyEditor>().FirstOrDefault();
if (fileUpload != null) Initialize(fileUpload);
var imageCropper = _propertyEditors.OfType<ImageCropperPropertyEditor>().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<IMember> args) => args.MediaFilesToDelete.AddRange(fileUpload.ContainedFilePaths(args.DeletedEntities.Cast<ContentBase>()));
MemberService.Deleted += memberServiceDeleted;
_terminate.Add(() => MemberService.Deleted -= memberServiceDeleted);
}
private void Initialize(ImageCropperPropertyEditor imageCropper)
{
void memberServiceDeleted(IMemberService sender, DeleteEventArgs<IMember> args) => args.MediaFilesToDelete.AddRange(imageCropper.ContainedFilePaths(args.DeletedEntities.Cast<ContentBase>()));
MemberService.Deleted += memberServiceDeleted;
_terminate.Add(() => MemberService.Deleted -= memberServiceDeleted);
}
}
}

View File

@@ -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<PropertyEditorsComponent>, ICoreComposer
{ }
}

View File

@@ -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<IMember>(member);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
var savingNotification = new SavingNotification<IMember>(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<IMember>(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<IMember>(membersA);
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
var savingNotification = new SavingNotification<IMember>(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<IMember>(membersA, evtMsgs).WithStateFrom(savingNotification));
}
Audit(AuditType.Save, 0, -1, "Save multiple Members");
@@ -849,32 +851,30 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <param name="member"><see cref="IMember"/> to Delete</param>
public void Delete(IMember member)
{
var evtMsgs = EventMessagesFactory.Get();
using (var scope = ScopeProvider.CreateScope())
{
var deleteEventArgs = new DeleteEventArgs<IMember>(member);
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
var deletingNotification = new DeletingNotification<IMember>(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<IMember> args = null)
private void DeleteLocked(IScope scope, IMember member, EventMessages evtMsgs, IDictionary<string, object> notificationState = null)
{
// a member has no descendants
_memberRepository.Delete(member);
if (args == null)
args = new DeleteEventArgs<IMember>(member, false); // raise event & get flagged files
else
args.CanCancel = false;
scope.Events.Dispatch(Deleted, this, args);
scope.Notifications.Publish(new DeletedNotification<IMember>(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
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IMemberService, DeleteEventArgs<IMember>> Deleting;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IMemberService, DeleteEventArgs<IMember>> Deleted;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IMemberService, SaveEventArgs<IMember>> Saving;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IMemberService, SaveEventArgs<IMember>> Saved;
/// <summary>
/// Occurs after roles have been assigned.
/// </summary>
public static event TypedEventHandler<IMemberService, RolesEventArgs> AssignedRoles;
/// <summary>
/// Occurs after roles have been removed.
/// </summary>
public static event TypedEventHandler<IMemberService, RolesEventArgs> RemovedRoles;
/// <summary>
/// Occurs after members have been exported.
/// </summary>
@@ -1145,7 +1111,7 @@ namespace Umbraco.Cms.Core.Services.Implement
Properties = new List<MemberExportProperty>(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
/// <param name="memberTypeId">Id of the MemberType</param>
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<IMember> query = Query<IMember>().Where(x => x.ContentTypeId == memberTypeId);
IMember[] members = _memberRepository.Get(query).ToArray();
var deleteEventArgs = new DeleteEventArgs<IMember>(members);
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
if (scope.Notifications.PublishCancelable(new DeletingNotification<IMember>(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();

View File

@@ -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<PublicAccessEntry>(entry, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
var savingNotifiation = new SavingNotification<PublicAccessEntry>(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<PublicAccessEntry>(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<PublicAccessEntry>(entry, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
var savingNotifiation = new SavingNotification<PublicAccessEntry>(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<PublicAccessEntry>(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<PublicAccessEntry>(entry, evtMsgs);
if (scope.Events.DispatchCancelable(Saving, this, saveEventArgs))
var savingNotifiation = new SavingNotification<PublicAccessEntry>(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<PublicAccessEntry>(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<PublicAccessEntry>(entry, evtMsgs);
if (scope.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
var deletingNotification = new DeletingNotification<PublicAccessEntry>(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<PublicAccessEntry>(entry, evtMsgs).WithStateFrom(deletingNotification));
}
return OperationResult.Attempt.Succeed(evtMsgs);
}
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saving;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IPublicAccessService, SaveEventArgs<PublicAccessEntry>> Saved;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleting;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>> Deleted;
}
}

View File

@@ -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<IUser>(user);
if (scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs))
var savingNotification = new SavingNotification<IUser>(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<IUser>(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<IUser>(user);
if (scope.Events.DispatchCancelable(DeletingUser, this, deleteEventArgs))
var deletingNotification = new DeletingNotification<IUser>(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<IUser>(user, evtMsgs).WithStateFrom(deletingNotification));
scope.Complete();
}
}
@@ -272,10 +275,12 @@ namespace Umbraco.Cms.Core.Services.Implement
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IUser entity, bool raiseEvents = true)
{
var evtMsgs = EventMessagesFactory.Get();
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IUser>(entity);
if (raiseEvents && scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs))
var savingNotification = new SavingNotification<IUser>(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<IUser>(entity, evtMsgs).WithStateFrom(savingNotification));
}
scope.Complete();
@@ -319,12 +323,14 @@ namespace Umbraco.Cms.Core.Services.Implement
/// Default is <c>True</c> otherwise set to <c>False</c> to not raise events</param>
public void Save(IEnumerable<IUser> entities, bool raiseEvents = true)
{
var evtMsgs = EventMessagesFactory.Get();
var entitiesA = entities.ToArray();
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IUser>(entitiesA);
if (raiseEvents && scope.Events.DispatchCancelable(SavingUser, this, saveEventArgs))
var savingNotification = new SavingNotification<IUser>(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<IUser>(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<EntityPermission>(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<EntityPermission>(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 <c>True</c> otherwise set to <c>False</c> to not raise events</param>
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<UserGroupWithUsers>(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<IUserGroup>(userGroup, evtMsgs);
if (raiseEvents && scope.Notifications.PublishCancelable(savingNotification))
{
scope.Complete();
return;
}
// this is an additional notification for special auditing
var savingUserGroupWithUsersNotification = new SavingNotification<UserGroupWithUsers>(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<IUserGroup>(userGroup, evtMsgs).WithStateFrom(savingNotification));
scope.Notifications.Publish(new SavedNotification<UserGroupWithUsers>(userGroupWithUsers, evtMsgs).WithStateFrom(savingUserGroupWithUsersNotification));
}
scope.Complete();
@@ -852,10 +873,12 @@ namespace Umbraco.Cms.Core.Services.Implement
/// <param name="userGroup">UserGroup to delete</param>
public void DeleteUserGroup(IUserGroup userGroup)
{
var evtMsgs = EventMessagesFactory.Get();
using (var scope = ScopeProvider.CreateScope())
{
var deleteEventArgs = new DeleteEventArgs<IUserGroup>(userGroup);
if (scope.Events.DispatchCancelable(DeletingUserGroup, this, deleteEventArgs))
var deletingNotification = new DeletingNotification<IUserGroup>(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<IUserGroup>(userGroup, evtMsgs).WithStateFrom(deletingNotification));
scope.Complete();
}
@@ -1144,49 +1166,5 @@ namespace Umbraco.Cms.Core.Services.Implement
}
#endregion
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<IUser>> SavingUser;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<IUser>> SavedUser;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUser>> DeletingUser;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUser>> DeletedUser;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavingUserGroup;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IUserService, SaveEventArgs<UserGroupWithUsers>> SavedUserGroup;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUserGroup>> DeletingUserGroup;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IUserService, DeleteEventArgs<IUserGroup>> 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<IUserService, SaveEventArgs<EntityPermission>> UserGroupPermissionsAssigned;
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Cms.Infrastructure.Services.Notifications
{
public class AssignedMemberRolesNotification : MemberRolesNotification
{
public AssignedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles)
{
}
}
}

View File

@@ -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<EntityPermission>
{
public AssignedUserGroupPermissionsNotification(IEnumerable<EntityPermission> target, EventMessages messages) : base(target, messages)
{
}
public IEnumerable<EntityPermission> EntityPermissions => Target;
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Cms.Infrastructure.Services.Notifications
{
public class RemovedMemberRolesNotification : MemberRolesNotification
{
public RemovedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles)
{
}
}
}