using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Factories; using Umbraco.Cms.Infrastructure.Persistence.Querying; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { internal class MemberGroupRepository : EntityRepositoryBase, IMemberGroupRepository { private readonly IEventMessagesFactory _eventMessagesFactory; public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IEventMessagesFactory eventMessagesFactory) : base(scopeAccessor, cache, logger) => _eventMessagesFactory = eventMessagesFactory; protected override IMemberGroup? PerformGet(int id) { var sql = GetBaseQuery(false); sql.Where(GetBaseWhereClause(), new { id = id }); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : MemberGroupFactory.BuildEntity(dto); } protected override IEnumerable PerformGetAll(params int[]? ids) { var sql = Sql() .SelectAll() .From() .Where(dto => dto.NodeObjectType == NodeObjectTypeId); if (ids?.Any() ?? false) sql.WhereIn(x => x.NodeId, ids); return Database.Fetch(sql).Select(x => MemberGroupFactory.BuildEntity(x)); } protected override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); return Database.Fetch(sql).Select(x => MemberGroupFactory.BuildEntity(x)); } protected override Sql GetBaseQuery(bool isCount) { var sql = Sql(); sql = isCount ? sql.SelectCount() : sql.Select(); sql .From() .Where(x => x.NodeObjectType == NodeObjectTypeId); return sql; } protected override string GetBaseWhereClause() { return $"{Cms.Core.Constants.DatabaseSchema.Tables.Node}.id = @id"; } protected override IEnumerable GetDeleteClauses() { var list = new[] { "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @id", "DELETE FROM umbracoUserGroup2Node WHERE nodeId = @id", "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @id", "DELETE FROM umbracoRelation WHERE parentId = @id", "DELETE FROM umbracoRelation WHERE childId = @id", "DELETE FROM cmsTagRelationship WHERE nodeId = @id", "DELETE FROM cmsMember2MemberGroup WHERE MemberGroup = @id", "DELETE FROM umbracoNode WHERE id = @id" }; return list; } protected Guid NodeObjectTypeId => Cms.Core.Constants.ObjectTypes.MemberGroup; protected override void PersistNewItem(IMemberGroup entity) { //Save to db entity.AddingEntity(); var group = (MemberGroup)entity; var dto = MemberGroupFactory.BuildDto(group); var o = Database.IsNew(dto) ? Convert.ToInt32(Database.Insert(dto)) : Database.Update(dto); group.Id = dto.NodeId; //Set Id on entity to ensure an Id is set //Update with new correct path and id dto.Path = string.Concat("-1,", dto.NodeId); Database.Update(dto); //assign to entity group.Id = o; group.ResetDirtyProperties(); } protected override void PersistUpdatedItem(IMemberGroup entity) { var dto = MemberGroupFactory.BuildDto(entity); Database.Update(dto); entity.ResetDirtyProperties(); } public IMemberGroup? Get(Guid uniqueId) { var sql = GetBaseQuery(false); sql.Where(x => x.UniqueId == uniqueId); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); return dto == null ? null : MemberGroupFactory.BuildEntity(dto); } public IMemberGroup? GetByName(string? name) { return IsolatedCache.GetCacheItem( typeof(IMemberGroup).FullName + "." + name, () => { var qry = Query().Where(group => group.Name!.Equals(name)); var result = Get(qry); return result?.FirstOrDefault(); }, //cache for 5 mins since that is the default in the Runtime app cache TimeSpan.FromMinutes(5), //sliding is true true); } public IMemberGroup? CreateIfNotExists(string roleName) { var qry = Query().Where(group => group.Name!.Equals(roleName)); var result = Get(qry); if (result?.Any() ?? false) return null; var grp = new MemberGroup { Name = roleName }; PersistNewItem(grp); var evtMsgs = _eventMessagesFactory.Get(); if (AmbientScope.Notifications.PublishCancelable(new MemberGroupSavingNotification(grp, evtMsgs))) { return null; } AmbientScope.Notifications.Publish(new MemberGroupSavedNotification(grp, evtMsgs)); return grp; } public IEnumerable GetMemberGroupsForMember(int memberId) { var sql = Sql() .Select("umbracoNode.*") .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.MemberGroup) .Where(x => x.NodeObjectType == NodeObjectTypeId) .Where(x => x.Member == memberId); return Database.Fetch(sql) .DistinctBy(dto => dto.NodeId) .Select(x => MemberGroupFactory.BuildEntity(x)); } public IEnumerable GetMemberGroupsForMember(string? username) { var sql = Sql() .Select("un.*") .From("umbracoNode AS un") .InnerJoin("cmsMember2MemberGroup") .On("cmsMember2MemberGroup.MemberGroup = un.id") .InnerJoin("cmsMember") .On("cmsMember.nodeId = cmsMember2MemberGroup.Member") .Where("un.nodeObjectType=@objectType", new { objectType = NodeObjectTypeId }) .Where("cmsMember.LoginName=@loginName", new { loginName = username }); return Database.Fetch(sql) .DistinctBy(dto => dto.NodeId) .Select(x => MemberGroupFactory.BuildEntity(x)); } public void ReplaceRoles(int[] memberIds, string[] roleNames) => AssignRolesInternal(memberIds, roleNames, true); public void AssignRoles(int[] memberIds, string[] roleNames) => AssignRolesInternal(memberIds, roleNames); private void AssignRolesInternal(int[] memberIds, string[] roleNames, bool replace = false) { //ensure they're unique memberIds = memberIds.Distinct().ToArray(); //create the missing roles first Sql existingSql = Sql() .SelectAll() .From() .Where(dto => dto.NodeObjectType == NodeObjectTypeId) .Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " in (@names)", new { names = roleNames }); IEnumerable existingRoles = Database.Fetch(existingSql).Select(x => x.Text); IEnumerable missingRoles = roleNames.Except(existingRoles, StringComparer.CurrentCultureIgnoreCase); MemberGroup[] missingGroups = missingRoles.Select(x => new MemberGroup { Name = x }).ToArray(); var evtMsgs = _eventMessagesFactory.Get(); if (AmbientScope.Notifications.PublishCancelable(new MemberGroupSavingNotification(missingGroups, evtMsgs))) { return; } foreach (MemberGroup m in missingGroups) { PersistNewItem(m); } AmbientScope.Notifications.Publish(new MemberGroupSavedNotification(missingGroups, evtMsgs)); //now go get all the dto's for roles with these role names var rolesForNames = Database.Fetch(existingSql) .ToDictionary(x => x.Text!, StringComparer.InvariantCultureIgnoreCase); AssignedRolesDto[] currentlyAssigned; if (replace) { // delete all assigned groups first Database.Execute("DELETE FROM cmsMember2MemberGroup WHERE Member IN (@memberIds)", new { memberIds }); currentlyAssigned = Array.Empty(); } else { //get the groups that are currently assigned to any of these members Sql assignedSql = Sql() .Select($"{SqlSyntax.GetQuotedColumnName("text")},{SqlSyntax.GetQuotedColumnName("Member")},{SqlSyntax.GetQuotedColumnName("MemberGroup")}") .From() .InnerJoin() .On(dto => dto.NodeId, dto => dto.MemberGroup) .Where(x => x.NodeObjectType == NodeObjectTypeId) .WhereIn(x => x.Member, memberIds); currentlyAssigned = Database.Fetch(assignedSql).ToArray(); } //assign the roles for each member id foreach (var memberId in memberIds) { //find any roles for the current member that are currently assigned that //exist in the roleNames list, then determine which ones are not currently assigned. var mId = memberId; AssignedRolesDto[] found = currentlyAssigned.Where(x => x.MemberId == mId).ToArray(); IEnumerable assignedRoles = found.Where(x => roleNames.Contains(x.RoleName, StringComparer.CurrentCultureIgnoreCase)).Select(x => x.RoleName); IEnumerable nonAssignedRoles = roleNames.Except(assignedRoles, StringComparer.CurrentCultureIgnoreCase); IEnumerable dtos = nonAssignedRoles .Select(x => new Member2MemberGroupDto { Member = mId, MemberGroup = rolesForNames[x!].NodeId }); Database.InsertBulk(dtos); } } public void DissociateRoles(int[] memberIds, string[] roleNames) { DissociateRolesInternal(memberIds, roleNames); } private void DissociateRolesInternal(int[] memberIds, string[] roleNames) { var existingSql = Sql() .SelectAll() .From() .Where(dto => dto.NodeObjectType == NodeObjectTypeId) .Where("umbracoNode." + SqlSyntax.GetQuotedColumnName("text") + " in (@names)", new { names = roleNames }); var existingRolesIds = Database.Fetch(existingSql).Select(x => x.NodeId).ToArray(); Database.Execute("DELETE FROM cmsMember2MemberGroup WHERE Member IN (@memberIds) AND MemberGroup IN (@memberGroups)", new { /*memberIds =*/ memberIds, memberGroups = existingRolesIds }); } private class AssignedRolesDto { [Column("text")] public string? RoleName { get; set; } [Column("Member")] public int MemberId { get; set; } [Column("MemberGroup")] public int MemberGroupId { get; set; } } } }