From 4dedd52e371c15d3bf3d540af9f8885d71fa8cf2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Sat, 26 Aug 2017 17:39:23 +0200 Subject: [PATCH] Port 7.7 - WIP --- .../ServicesCompositionRoot.cs | 3 + src/Umbraco.Core/Models/Rdbms/TemplateDto.cs | 1 - src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs | 1 + .../Factories/UmbracoEntityFactory.cs | 2 +- .../Persistence/Factories/UserFactory.cs | 107 ++-- .../Persistence/Factories/UserGroupFactory.cs | 80 +++ .../Persistence/Factories/UserTypeFactory.cs | 54 -- .../Mappers/ExternalLoginMapper.cs | 35 ++ .../Persistence/Mappers/UserGroupMapper.cs | 43 ++ .../Persistence/Mappers/UserMapper.cs | 30 +- .../Persistence/Querying/IQuery.cs | 7 + .../Persistence/Querying/Query.cs | 45 ++ .../ContentBlueprintRepository.cs | 28 + .../Repositories/ContentRepository.cs | 79 +-- .../Repositories/ContentTypeRepository.cs | 14 + .../Repositories/ContentTypeRepositoryBase.cs | 2 +- .../DataTypeDefinitionRepository.cs | 4 +- .../Repositories/EntityRepository.cs | 57 +- .../Interfaces/IContentRepository.cs | 23 +- .../Interfaces/IContentTypeRepository.cs | 2 + .../Interfaces/IEntityRepository.cs | 2 + .../Interfaces/IUserGroupRepository.cs | 60 +++ .../Interfaces/IUserRepository.cs | 68 ++- .../Interfaces/IUserTypeRepository.cs | 10 - .../Repositories/MediaRepository.cs | 2 +- .../Repositories/MemberGroupRepository.cs | 2 +- .../Repositories/MemberRepository.cs | 11 +- .../Repositories/MemberTypeRepository.cs | 2 +- .../Repositories/PermissionRepository.cs | 338 +++++++----- .../Repositories/SimpleGetRepository.cs | 10 +- .../Repositories/TemplateRepository.cs | 2 +- .../Repositories/UserGroupRepository.cs | 489 ++++++++++++++++++ .../Repositories/UserRepository.cs | 14 +- .../Repositories/UserTypeRepository.cs | 121 ----- .../Repositories/VersionableRepositoryBase.cs | 17 +- src/Umbraco.Core/Services/ContentService.cs | 199 +++++-- .../Services/ContentServiceExtensions.cs | 2 +- .../Services/ContentTypeService.cs | 20 +- src/Umbraco.Core/Services/EntityService.cs | 144 +++--- src/Umbraco.Core/Services/MediaService.cs | 18 +- src/Umbraco.Core/Umbraco.Core.csproj | 33 +- 41 files changed, 1556 insertions(+), 625 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs delete mode 100644 src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs create mode 100644 src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs delete mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserTypeRepository.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs delete mode 100644 src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs index b8e6f69781..3b51a380c7 100644 --- a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs +++ b/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs @@ -21,6 +21,9 @@ namespace Umbraco.Core.Composing.CompositionRoots // register the service context container.RegisterSingleton(); + // register the special idk map + container.RegisterSingleton(); + // register the services container.RegisterSingleton(); container.RegisterSingleton(); diff --git a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs b/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs index 9da7ab9462..32e9720a91 100644 --- a/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/TemplateDto.cs @@ -1,5 +1,4 @@ using NPoco; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Models.Rdbms diff --git a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs index c52a4542b0..032b9d85f6 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserGroupDto.cs @@ -58,6 +58,7 @@ namespace Umbraco.Core.Models.Rdbms public int? StartMediaId { get; set; } [ResultColumn] + [Reference(ReferenceType.Many, ReferenceMemberName = "id")] public List UserGroup2AppDtos { get; set; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 07d4171b7d..5a1befa875 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence.Factories entity.DisableChangeTracking(); entity.CreateDate = d.createDate; - entity.CreatorId = d.nodeUser; + entity.CreatorId = d.nodeUser == null ? 0 : d.nodeUser; entity.Id = d.id; entity.Key = d.uniqueID; entity.Level = d.level; diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 670530768f..01237c3066 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -7,47 +7,35 @@ using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Factories { - internal class UserFactory + internal static class UserFactory { - private readonly IUserType _userType; - - public UserFactory(IUserType userType) - { - _userType = userType; - } - - #region Implementation of IEntityFactory - - public IUser BuildEntity(UserDto dto) + public static IUser BuildEntity(UserDto dto) { var guidId = dto.Id.ToGuid(); - var user = new User(_userType); + + var user = new User(dto.Id, dto.UserName, dto.Email, dto.Login,dto.Password, + dto.UserGroupDtos.Select(x => x.ToReadOnlyGroup()).ToArray(), + dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Content).Select(x => x.StartNode).ToArray(), + dto.UserStartNodeDtos.Where(x => x.StartNodeType == (int)UserStartNodeDto.StartNodeTypeValue.Media).Select(x => x.StartNode).ToArray()); try { user.DisableChangeTracking(); - - user.Id = dto.Id; + user.Key = guidId; - user.StartContentId = dto.ContentStartId; - user.StartMediaId = dto.MediaStartId.HasValue ? dto.MediaStartId.Value : -1; - user.RawPasswordValue = dto.Password; - user.Username = dto.Login; - user.Name = dto.UserName; user.IsLockedOut = dto.NoConsole; user.IsApproved = dto.Disabled == false; - user.Email = dto.Email; user.Language = dto.UserLanguage; user.SecurityStamp = dto.SecurityStampToken; user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0; user.LastLockoutDate = dto.LastLockoutDate ?? DateTime.MinValue; user.LastLoginDate = dto.LastLoginDate ?? DateTime.MinValue; user.LastPasswordChangeDate = dto.LastPasswordChangeDate ?? DateTime.MinValue; - - foreach (var app in dto.User2AppDtos.EmptyNull()) - { - user.AddAllowedSection(app.AppAlias); - } + user.CreateDate = dto.CreateDate; + user.UpdateDate = dto.UpdateDate; + user.Avatar = dto.Avatar; + user.EmailConfirmedDate = dto.EmailConfirmedDate; + user.InvitedDate = dto.InvitedDate; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -61,48 +49,55 @@ namespace Umbraco.Core.Persistence.Factories } } - public UserDto BuildDto(IUser entity) + public static UserDto BuildDto(IUser entity) { var dto = new UserDto - { - ContentStartId = entity.StartContentId, - MediaStartId = entity.StartMediaId, - Disabled = entity.IsApproved == false, - Email = entity.Email, - Login = entity.Username, - NoConsole = entity.IsLockedOut, - Password = entity.RawPasswordValue, - UserLanguage = entity.Language, - UserName = entity.Name, - Type = short.Parse(entity.UserType.Id.ToString(CultureInfo.InvariantCulture)), - User2AppDtos = new List(), - SecurityStampToken = entity.SecurityStamp, - FailedLoginAttempts = entity.FailedPasswordAttempts, - LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, - LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, - LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, - }; + { + Disabled = entity.IsApproved == false, + Email = entity.Email, + Login = entity.Username, + NoConsole = entity.IsLockedOut, + Password = entity.RawPasswordValue, + UserLanguage = entity.Language, + UserName = entity.Name, + SecurityStampToken = entity.SecurityStamp, + FailedLoginAttempts = entity.FailedPasswordAttempts, + LastLockoutDate = entity.LastLockoutDate == DateTime.MinValue ? (DateTime?)null : entity.LastLockoutDate, + LastLoginDate = entity.LastLoginDate == DateTime.MinValue ? (DateTime?)null : entity.LastLoginDate, + LastPasswordChangeDate = entity.LastPasswordChangeDate == DateTime.MinValue ? (DateTime?)null : entity.LastPasswordChangeDate, + CreateDate = entity.CreateDate, + UpdateDate = entity.UpdateDate, + Avatar = entity.Avatar, + EmailConfirmedDate = entity.EmailConfirmedDate, + InvitedDate = entity.InvitedDate + }; - foreach (var app in entity.AllowedSections) + foreach (var startNodeId in entity.StartContentIds) { - var appDto = new User2AppDto - { - AppAlias = app - }; - if (entity.HasIdentity) + dto.UserStartNodeDtos.Add(new UserStartNodeDto { - appDto.UserId = (int) entity.Id; - } + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Content, + UserId = entity.Id + }); + } - dto.User2AppDtos.Add(appDto); + foreach (var startNodeId in entity.StartMediaIds) + { + dto.UserStartNodeDtos.Add(new UserStartNodeDto + { + StartNode = startNodeId, + StartNodeType = (int)UserStartNodeDto.StartNodeTypeValue.Media, + UserId = entity.Id + }); } if (entity.HasIdentity) + { dto.Id = entity.Id.SafeCast(); + } return dto; - } - - #endregion + } } } diff --git a/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs new file mode 100644 index 0000000000..f4ecffca54 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Factories/UserGroupFactory.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Factories +{ + internal static class UserGroupFactory + { + public static IUserGroup BuildEntity(UserGroupDto dto) + { + var userGroup = new UserGroup(dto.UserCount, dto.Alias, dto.Name, + dto.DefaultPermissions.IsNullOrWhiteSpace() + ? Enumerable.Empty() + : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)).ToList(), + dto.Icon); + + try + { + userGroup.DisableChangeTracking(); + userGroup.Id = dto.Id; + userGroup.CreateDate = dto.CreateDate; + userGroup.UpdateDate = dto.UpdateDate; + userGroup.StartContentId = dto.StartContentId; + userGroup.StartMediaId = dto.StartMediaId; + if (dto.UserGroup2AppDtos != null) + { + foreach (var app in dto.UserGroup2AppDtos) + { + userGroup.AddAllowedSection(app.AppAlias); + } + } + + userGroup.ResetDirtyProperties(false); + return userGroup; + } + finally + { + userGroup.EnableChangeTracking(); + } + } + + public static UserGroupDto BuildDto(IUserGroup entity) + { + var dto = new UserGroupDto + { + Alias = entity.Alias, + DefaultPermissions = entity.Permissions == null ? "" : string.Join("", entity.Permissions), + Name = entity.Name, + UserGroup2AppDtos = new List(), + CreateDate = entity.CreateDate, + UpdateDate = entity.UpdateDate, + Icon = entity.Icon, + StartMediaId = entity.StartMediaId, + StartContentId = entity.StartContentId + }; + + foreach (var app in entity.AllowedSections) + { + var appDto = new UserGroup2AppDto + { + AppAlias = app + }; + if (entity.HasIdentity) + { + appDto.UserGroupId = entity.Id; + } + + dto.UserGroup2AppDtos.Add(appDto); + } + + if (entity.HasIdentity) + dto.Id = short.Parse(entity.Id.ToString()); + + return dto; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs deleted file mode 100644 index 33272b7352..0000000000 --- a/src/Umbraco.Core/Persistence/Factories/UserTypeFactory.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Globalization; -using System.Linq; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; - -namespace Umbraco.Core.Persistence.Factories -{ - internal class UserTypeFactory - { - #region Implementation of IEntityFactory - - public IUserType BuildEntity(UserTypeDto dto) - { - var userType = new UserType(); - - try - { - userType.DisableChangeTracking(); - - userType.Alias = dto.Alias; - userType.Id = dto.Id; - userType.Name = dto.Name; - userType.Permissions = dto.DefaultPermissions.IsNullOrWhiteSpace() - ? Enumerable.Empty() - : dto.DefaultPermissions.ToCharArray().Select(x => x.ToString(CultureInfo.InvariantCulture)); - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - userType.ResetDirtyProperties(false); - return userType; - } - finally - { - userType.EnableChangeTracking(); - } - } - - public UserTypeDto BuildDto(IUserType entity) - { - var userType = new UserTypeDto - { - Alias = entity.Alias, - DefaultPermissions = entity.Permissions == null ? "" : string.Join("", entity.Permissions), - Name = entity.Name - }; - - if(entity.HasIdentity) - userType.Id = short.Parse(entity.Id.ToString()); - - return userType; - } - - #endregion - } -} diff --git a/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs b/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs new file mode 100644 index 0000000000..c4226797e2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/ExternalLoginMapper.cs @@ -0,0 +1,35 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof(IIdentityUserLogin))] + [MapperFor(typeof(IdentityUserLogin))] + public sealed class ExternalLoginMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + public ExternalLoginMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.LoginProvider, dto => dto.LoginProvider); + CacheMap(src => src.ProviderKey, dto => dto.ProviderKey); + CacheMap(src => src.UserId, dto => dto.UserId); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs new file mode 100644 index 0000000000..cd376e79a9 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/UserGroupMapper.cs @@ -0,0 +1,43 @@ +using System.Collections.Concurrent; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + /// + /// Represents a to DTO mapper used to translate the properties of the public api + /// implementation to that of the database's DTO as sql: [tableName].[columnName]. + /// + [MapperFor(typeof(IUserGroup))] + [MapperFor(typeof(UserGroup))] + public sealed class UserGroupMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); + + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // otherwise that would fail because there is no public constructor. + public UserGroupMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override ConcurrentDictionary PropertyInfoCache + { + get { return PropertyInfoCacheInstance; } + } + + internal override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.Id); + CacheMap(src => src.Alias, dto => dto.Alias); + CacheMap(src => src.Name, dto => dto.Name); + CacheMap(src => src.Icon, dto => dto.Icon); + CacheMap(src => src.StartContentId, dto => dto.StartContentId); + CacheMap(src => src.StartMediaId, dto => dto.StartMediaId); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs index 5964499f18..cda559633d 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UserMapper.cs @@ -1,28 +1,9 @@ using System.Collections.Concurrent; -using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; namespace Umbraco.Core.Persistence.Mappers { - [MapperFor(typeof(IIdentityUserLogin))] - [MapperFor(typeof(IdentityUserLogin))] - public sealed class ExternalLoginMapper : BaseMapper - { - private static readonly ConcurrentDictionary PropertyInfoCacheInstance = new ConcurrentDictionary(); - - internal override ConcurrentDictionary PropertyInfoCache => PropertyInfoCacheInstance; - - protected override void BuildMap() - { - CacheMap(src => src.Id, dto => dto.Id); - CacheMap(src => src.CreateDate, dto => dto.CreateDate); - CacheMap(src => src.LoginProvider, dto => dto.LoginProvider); - CacheMap(src => src.ProviderKey, dto => dto.ProviderKey); - CacheMap(src => src.UserId, dto => dto.UserId); - } - } - [MapperFor(typeof(IUser))] [MapperFor(typeof(User))] public sealed class UserMapper : BaseMapper @@ -39,13 +20,16 @@ namespace Umbraco.Core.Persistence.Mappers CacheMap(src => src.RawPasswordValue, dto => dto.Password); CacheMap(src => src.Name, dto => dto.UserName); //NOTE: This column in the db is *not* used! - //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); - CacheMap(src => src.StartMediaId, dto => dto.MediaStartId); - CacheMap(src => src.StartContentId, dto => dto.ContentStartId); + //CacheMap(src => src.DefaultPermissions, dto => dto.DefaultPermissions); CacheMap(src => src.IsApproved, dto => dto.Disabled); CacheMap(src => src.IsLockedOut, dto => dto.NoConsole); - CacheMap(src => src.UserType, dto => dto.Type); CacheMap(src => src.Language, dto => dto.UserLanguage); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.UpdateDate, dto => dto.UpdateDate); + CacheMap(src => src.LastLockoutDate, dto => dto.LastLockoutDate); + CacheMap(src => src.LastLoginDate, dto => dto.LastLoginDate); + CacheMap(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate); + CacheMap(src => src.SecurityStamp, dto => dto.SecurityStampToken); } } } diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs index 9528d25c09..6b48ad5313 100644 --- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -31,5 +31,12 @@ namespace Umbraco.Core.Persistence.Querying /// /// This instance so calls to this method are chainable IQuery WhereIn(Expression> fieldSelector, IEnumerable values); + + /// + /// Adds a set of OR-ed where clauses to the query. + /// + /// + /// This instance so calls to this method are chainable. + IQuery WhereAny(IEnumerable>> predicates); } } diff --git a/src/Umbraco.Core/Persistence/Querying/Query.cs b/src/Umbraco.Core/Persistence/Querying/Query.cs index a68df57aa3..aca69bccae 100644 --- a/src/Umbraco.Core/Persistence/Querying/Query.cs +++ b/src/Umbraco.Core/Persistence/Querying/Query.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq.Expressions; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; +using System.Text; namespace Umbraco.Core.Persistence.Querying { @@ -54,6 +55,50 @@ namespace Umbraco.Core.Persistence.Querying return this; } + /// + /// Adds a set of OR-ed where clauses to the query. + /// + /// + /// This instance so calls to this method are chainable. + public virtual IQuery WhereAny(IEnumerable>> predicates) + { + if (predicates == null) return this; + + StringBuilder sb = null; + List parameters = null; + Sql sql = null; + foreach (var predicate in predicates) + { + // see notes in Where() + var expressionHelper = new ModelToSqlExpressionVisitor(); + var whereExpression = expressionHelper.Visit(predicate); + + if (sb == null) + { + sb = new StringBuilder("("); + parameters = new List(); + sql = new Sql(); + } + else + { + sb.Append(" OR "); + sql.Append(" OR "); + } + + sb.Append(whereExpression); + parameters.AddRange(expressionHelper.GetSqlParameters()); + sql.Append(whereExpression, expressionHelper.GetSqlParameters()); + } + + if (sb == null) return this; + + sb.Append(")"); + //_wheres.Add(Tuple.Create(sb.ToString(), parameters.ToArray())); + _wheres.Add(Tuple.Create("(" + sql.SQL + ")", sql.Arguments)); + + return this; + } + /// /// Returns all translated where clauses and their sql parameters /// diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs new file mode 100644 index 0000000000..547d59a528 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/ContentBlueprintRepository.cs @@ -0,0 +1,28 @@ +using System; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Override the base content repository so we can change the node object type + /// + /// + /// It would be nicer if we could separate most of this down into a smaller version of the ContentRepository class, however to do that + /// requires quite a lot of work since we'd need to re-organize the interhitance quite a lot or create a helper class to perform a lot of the underlying logic. + /// + /// TODO: Create a helper method to contain most of the underlying logic for the ContentRepository + /// + internal class ContentBlueprintRepository : ContentRepository + { + public ContentBlueprintRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection settings) + : base(work, cacheHelper, logger, contentTypeRepository, templateRepository, tagRepository, settings) + { + EnsureUniqueNaming = false; // duplicates are allowed + } + + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.DocumentBlueprintGuid; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 541fdd8aff..30b176ca30 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,10 +1,10 @@ -using System; +using NPoco; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Xml; -using NPoco; -using Umbraco.Core.IO; +using Umbraco.Core.Cache; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -13,9 +13,6 @@ using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -32,17 +29,17 @@ namespace Umbraco.Core.Persistence.Repositories private readonly CacheHelper _cacheHelper; private PermissionRepository _permissionRepository; - public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository /*, IContentSection contentSection*/) - : base(work, cacheHelper, logger /*, contentSection*/) + public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection settings) + : base(work, cacheHelper, logger) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); _cacheHelper = cacheHelper; - _publishedQuery = work.Query().Where(x => x.Published); + _publishedQuery = work.Query().Where(x => x.Published); // fixme not used? - EnsureUniqueNaming = true; + EnsureUniqueNaming = settings.EnsureUniqueNaming; } protected override ContentRepository This => this; @@ -51,7 +48,7 @@ namespace Umbraco.Core.Persistence.Repositories // note: is ok to 'new' the repo here as it's a sub-repo really private PermissionRepository PermissionRepository => _permissionRepository - ?? (_permissionRepository = new PermissionRepository(UnitOfWork, _cacheHelper)); + ?? (_permissionRepository = new PermissionRepository(UnitOfWork, _cacheHelper, Logger)); #region Overrides of RepositoryBase @@ -196,7 +193,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM umbracoRedirectUrl WHERE contentKey IN (SELECT uniqueID FROM umbracoNode WHERE id = @Id)", "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission 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", @@ -213,12 +210,21 @@ namespace Umbraco.Core.Persistence.Repositories return list; } - protected override Guid NodeObjectTypeId => new Guid(Constants.ObjectTypes.Document); + protected override Guid NodeObjectTypeId => Constants.ObjectTypes.DocumentGuid; #endregion #region Overrides of VersionableRepositoryBase + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate); + + return MapQueryDtos(Database.Fetch(sql), true); + } + public override IContent GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); @@ -345,24 +351,6 @@ namespace Umbraco.Core.Persistence.Repositories entity.SortOrder = sortOrder; entity.Level = level; - //Assign the same permissions to it as the parent node - // http://issues.umbraco.org/issue/U4-2161 - var parentPermissions = PermissionRepository.GetPermissionsForEntity(entity.ParentId).ToArray(); - //if there are parent permissions then assign them, otherwise leave null and permissions will become the - // user's default permissions. - if (parentPermissions.Any()) - { - var userPermissions = ( - from perm in parentPermissions - from p in perm.AssignedPermissions - select new EntityPermissionSet.UserPermission(perm.UserId, p)).ToList(); - - PermissionRepository.ReplaceEntityPermissions(new EntityPermissionSet(entity.Id, userPermissions)); - //flag the entity's permissions changed flag so we can track those changes. - //Currently only used for the cache refreshers to detect if we should refresh all user permissions cache. - ((Content)entity).PermissionsChanged = true; - } - //Create the Content specific data - cmsContent var contentDto = dto.ContentVersionDto.ContentDto; contentDto.NodeId = nodeDto.NodeId; @@ -506,9 +494,13 @@ namespace Umbraco.Core.Persistence.Repositories { //In order to update the ContentVersion we need to retrieve its primary key id var contentVerDto = Database.SingleOrDefault("WHERE VersionId = @Version", new { /*Version =*/ entity.Version }); - contentVersionDto.Id = contentVerDto.Id; - Database.Update(contentVersionDto); + if (contentVerDto != null) + { + contentVersionDto.Id = contentVerDto.Id; + Database.Update(contentVersionDto); + } + Database.Update(dto); } @@ -682,21 +674,30 @@ namespace Umbraco.Core.Persistence.Repositories } /// - /// Assigns a single permission to the current content item for the specified user ids + /// Assigns a single permission to the current content item for the specified group ids /// /// /// - /// - public void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds) + /// + public void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds) { - PermissionRepository.AssignEntityPermission(entity, permission, userIds); + PermissionRepository.AssignEntityPermission(entity, permission, groupIds); } - public IEnumerable GetPermissionsForEntity(int entityId) + public EntityPermissionCollection GetPermissionsForEntity(int entityId) { return PermissionRepository.GetPermissionsForEntity(entityId); } + /// + /// Used to add/update a permission for a content item + /// + /// + public void AddOrUpdatePermissions(ContentPermissionSet permission) + { + PermissionRepository.AddOrUpdate(permission); + } + /// /// Gets paged content results /// diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index fab16d3b9a..55f7f333d3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -137,6 +137,20 @@ namespace Umbraco.Core.Persistence.Repositories return Database.Fetch(sql); } + public IEnumerable GetAllContentTypeIds(string[] aliases) + { + if (aliases.Length == 0) return Enumerable.Empty(); + + var sql = Sql() + .Select("cmsContentType.nodeId") + .From() + .InnerJoin() + .On(dto => dto.NodeId, dto => dto.NodeId) + .Where(dto => aliases.Contains(dto.Alias)); + + return Database.Fetch(sql); + } + protected override Sql GetBaseQuery(bool isCount) { var sql = Sql(); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs index 90c60f0f1f..ea1b5f476b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepositoryBase.cs @@ -1252,7 +1252,7 @@ WHERE cmsContent.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer", var list = new List { "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", "DELETE FROM cmsContentTypeAllowedContentType WHERE Id = @Id", "DELETE FROM cmsContentTypeAllowedContentType WHERE AllowedId = @Id", diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 8106384673..4444050efb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -223,7 +223,7 @@ AND umbracoNode.id <> @id", throw new DuplicateNameException("A data type with the name " + entity.Name + " already exists"); } - //Updates Modified date and Version Guid + //Updates Modified date ((DataTypeDefinition)entity).UpdatingEntity(); //Look up parent to get and set the correct Path if ParentId has changed @@ -263,7 +263,7 @@ AND umbracoNode.id <> @id", Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); //Remove Permissions - Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); + Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); //Remove associated tags Database.Delete("WHERE nodeId = @Id", new { Id = entity.Id }); diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index 989f6cc6d3..35fbcc4a11 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -41,17 +41,17 @@ namespace Umbraco.Core.Persistence.Repositories public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, IQuery filter = null) { - var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; var factory = new UmbracoEntityFactory(); var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, sql => { if (filter != null) { - foreach (var filterClaus in filter.GetWhereClauses()) + foreach (var filterClause in filter.GetWhereClauses()) { - sql.Where(filterClaus.Item1, filterClaus.Item2); + sql.Where(filterClause.Item1, filterClause.Item2); } } }, objectTypeId); @@ -142,9 +142,9 @@ namespace Umbraco.Core.Persistence.Repositories { if (filter != null) { - foreach (var filterClaus in filter.GetWhereClauses()) + foreach (var filterClause in filter.GetWhereClauses()) { - sql.Where(filterClaus.Item1, filterClaus.Item2); + sql.Where(filterClause.Item1, filterClause.Item2); } } }, objectTypeId); @@ -171,8 +171,8 @@ namespace Umbraco.Core.Persistence.Repositories public IUmbracoEntity GetByKey(Guid key, Guid objectTypeId) { - var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId); @@ -223,8 +223,8 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id, Guid objectTypeId) { - var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId); @@ -253,22 +253,22 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) { - return ids.Any() - ? PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.id in (@ids)", new { /*ids =*/ ids })) + return ids.Any() + ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.id in (@ids)", new { ids })) : PerformGetAll(objectTypeId); } public virtual IEnumerable GetAll(Guid objectTypeId, params Guid[] keys) { - return keys.Any() - ? PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.uniqueID in (@keys)", new { /*keys =*/ keys })) + return keys.Any() + ? PerformGetAll(objectTypeId, sql => sql.Where(" umbracoNode.uniqueID in (@keys)", new { keys })) : PerformGetAll(objectTypeId); } private IEnumerable PerformGetAll(Guid objectTypeId, Action filter = null) { - var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter); if (isMedia) @@ -290,6 +290,27 @@ namespace Umbraco.Core.Persistence.Repositories return collection.Select(x => x.BuildFromDynamic()).ToList(); } + public virtual IEnumerable GetAllPaths(Guid objectTypeId, params int[] ids) + { + return ids.Any() + ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.id in (@ids)", new { ids })) + : PerformGetAllPaths(objectTypeId); + } + + public virtual IEnumerable GetAllPaths(Guid objectTypeId, params Guid[] keys) + { + return keys.Any() + ? PerformGetAllPaths(objectTypeId, sql => sql.Append(" AND umbracoNode.uniqueID in (@keys)", new { keys })) + : PerformGetAllPaths(objectTypeId); + } + + private IEnumerable PerformGetAllPaths(Guid objectTypeId, Action filter = null) + { + var sql = new Sql("SELECT id, path FROM umbracoNode WHERE umbracoNode.nodeObjectType=@type", new { type = objectTypeId }); + if (filter != null) filter(sql); + return _work.Database.Fetch(sql); + } + public virtual IEnumerable GetByQuery(IQuery query) { var sqlClause = GetBase(false, false, null); @@ -306,8 +327,8 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { - var isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); - var isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); + var isContent = objectTypeId == Constants.ObjectTypes.DocumentGuid || objectTypeId == Constants.ObjectTypes.DocumentBlueprintGuid; + var isMedia = objectTypeId == Constants.ObjectTypes.MediaGuid; var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index ff94aacb45..6641fe69b9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -39,18 +39,31 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetByPublishedVersion(IQuery query); /// - /// Assigns a single permission to the current content item for the specified user ids + /// Assigns a single permission to the current content item for the specified user group ids /// /// /// - /// - void AssignEntityPermission(IContent entity, char permission, IEnumerable userIds); + /// + void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds); /// - /// Gets the list of permissions for the content item + /// Gets the explicit list of permissions for the content item /// /// /// - IEnumerable GetPermissionsForEntity(int entityId); + EntityPermissionCollection GetPermissionsForEntity(int entityId); + + ///// + ///// Gets the implicit/inherited list of permissions for the content item + ///// + ///// + ///// + //IEnumerable GetPermissionsForPath(string path); + + /// + /// Used to add/update a permission for a content item + /// + /// + void AddOrUpdatePermissions(ContentPermissionSet permission); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index 07ca9248fe..8a2060ec01 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -29,5 +29,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes); + + IEnumerable GetAllContentTypeIds(string[] aliases); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs index 02e0816745..1de535486e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IEntityRepository.cs @@ -23,6 +23,8 @@ namespace Umbraco.Core.Persistence.Repositories UmbracoObjectTypes GetObjectType(int id); UmbracoObjectTypes GetObjectType(Guid key); + IEnumerable GetAllPaths(Guid objectTypeId, params int[] ids); + IEnumerable GetAllPaths(Guid objectTypeId, params Guid[] keys); /// /// Gets paged results diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs new file mode 100644 index 0000000000..dd6188e31c --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserGroupRepository.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IUserGroupRepository : IRepositoryQueryable + { + /// + /// Gets a group by it's alias + /// + /// + /// + IUserGroup Get(string alias); + + /// + /// This is useful when an entire section is removed from config + /// + /// + IEnumerable GetGroupsAssignedToSection(string sectionAlias); + + /// + /// Used to add or update a user group and assign users to it + /// + /// + /// + void AddOrUpdateGroupWithUsers(IUserGroup userGroup, int[] userIds); + + /// + /// Gets explicilty defined permissions for the group for specified entities + /// + /// + /// Array of entity Ids, if empty will return permissions for the group for all entities + EntityPermissionCollection GetPermissions(int[] groupIds, params int[] entityIds); + + /// + /// Gets explicilt and default permissions (if requested) permissions for the group for specified entities + /// + /// + /// If true will include the group's default permissions if no permissions are explicitly assigned + /// Array of entity Ids, if empty will return permissions for the group for all entities + EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds); + + /// + /// Replaces the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. + void ReplaceGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds); + + /// + /// Assigns the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for + void AssignGroupPermission(int groupId, char permission, params int[] entityIds); + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 114c667be1..053235de7a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq.Expressions; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; @@ -22,47 +24,65 @@ namespace Umbraco.Core.Persistence.Repositories /// /// bool Exists(string username); - + /// - /// This is useful when an entire section is removed from config + /// Gets a list of objects associated with a given group /// - /// - IEnumerable GetUsersAssignedToSection(string sectionAlias); + /// Id of group + IEnumerable GetAllInGroup(int groupId); /// - /// Gets paged member results + /// Gets a list of objects not associated with a given group + /// + /// Id of group + IEnumerable GetAllNotInGroup(int groupId); + + [Obsolete("Use the overload with long operators instead")] + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetPagedResultsByQuery(IQuery query, int pageIndex, int pageSize, out int totalRecords, Expression> orderBy); + + /// + /// Gets paged user results /// /// /// /// /// /// + /// + /// + /// Optional parameter to filter by specified user groups + /// Optional parameter to filter by specfied user state /// - IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Expression> orderBy); - + IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Expression> orderBy, Direction orderDirection, string[] userGroups = null, UserState[] userState = null, IQuery filter = null); /// - /// Gets the user permissions for the specified entities + /// Returns a user by username /// - /// - /// - /// - IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds); + /// + /// + /// This is only used for a shim in order to upgrade to 7.7 + /// + /// + /// A non cached instance + /// + IUser GetByUsername(string username, bool includeSecurityData); /// - /// Replaces the same permission set for a single user to any number of entities + /// Returns a user by id /// - /// - /// - /// - void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds); + /// + /// + /// This is only used for a shim in order to upgrade to 7.7 + /// + /// + /// A non cached instance + /// + IUser Get(int id, bool includeSecurityData); - /// - /// Assigns the same permission set for a single user to any number of entities - /// - /// - /// - /// - void AssignUserPermission(int userId, char permission, params int[] entityIds); + IProfile GetProfile(string username); + IProfile GetProfile(int id); + IDictionary GetUserStates(); + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserTypeRepository.cs deleted file mode 100644 index 1ee2d8bb98..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserTypeRepository.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - public interface IUserTypeRepository : IUnitOfWorkRepository, IQueryRepository - { - - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index d7ffc1ec38..2b47f05e87 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -127,7 +127,7 @@ namespace Umbraco.Core.Persistence.Repositories { "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission 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", diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 879c33d06c..d87504b514 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -78,7 +78,7 @@ namespace Umbraco.Core.Persistence.Repositories var list = new[] { "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission 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", diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 06fbd1f168..d1d9a22d95 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -170,7 +170,7 @@ namespace Umbraco.Core.Persistence.Repositories { "DELETE FROM cmsTask WHERE nodeId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission 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", @@ -398,6 +398,15 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate); + + return MapQueryDtos(Database.Fetch(sql), true); + } + public override IMember GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 386a5f445a..d21633f32e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -185,7 +185,7 @@ namespace Umbraco.Core.Persistence.Repositories protected override IEnumerable GetDeleteClauses() { - var l = (List)base.GetDeleteClauses(); // we know it's a list + var l = (List) base.GetDeleteClauses(); // we know it's a list l.Add("DELETE FROM cmsMemberType WHERE NodeId = @Id"); l.Add("DELETE FROM cmsContentType WHERE nodeId = @Id"); l.Add("DELETE FROM umbracoNode WHERE id = @Id"); diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs index ed459fb2b8..ba2d0ad83a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs @@ -2,16 +2,15 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; -using System.Web.Caching; -using Umbraco.Core.Events; +using NPoco; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; -using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using CacheKeys = Umbraco.Core.Cache.CacheKeys; using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { @@ -19,202 +18,207 @@ namespace Umbraco.Core.Persistence.Repositories /// A (sub) repository that exposes functionality to modify assigned permissions to a node /// /// - internal class PermissionRepository + /// + /// This repo implements the base class so that permissions can be queued to be persisted + /// like the normal repository pattern but the standard repository Get commands don't apply and will throw + /// + internal class PermissionRepository : NPocoRepositoryBase where TEntity : class, IAggregateRoot { - private readonly IScopeUnitOfWork _unitOfWork; - private readonly IRuntimeCacheProvider _runtimeCache; - internal PermissionRepository(IScopeUnitOfWork unitOfWork, CacheHelper cache) - { - _unitOfWork = unitOfWork; - //Make this repository use an isolated cache - _runtimeCache = cache.IsolatedRuntimeCache.GetOrCreateCache(); - } - - private ISqlSyntaxProvider SqlSyntax => _unitOfWork.SqlSyntax; + public PermissionRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger) + : base(work, cache, logger) + { } /// - /// Returns permissions for a given user for any number of nodes + /// Returns explicitly defined permissions for a user group for any number of nodes /// - /// + /// + /// The group ids to lookup permissions for + /// /// /// - public IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds) - { - var entityIdKey = string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture))); - return _runtimeCache.GetCacheItem>( - string.Format("{0}{1}{2}", CacheKeys.UserPermissionsCacheKey, userId, entityIdKey), - () => + /// + /// This method will not support passing in more than 2000 group Ids + /// + public EntityPermissionCollection GetPermissionsForEntities(int[] groupIds, params int[] entityIds) { + var result = new EntityPermissionCollection(); - var whereBuilder = new StringBuilder(); + foreach (var groupOfGroupIds in groupIds.InGroupsOf(2000)) + { + //copy local + var localIds = groupOfGroupIds.ToArray(); - //where userId = @userId AND - whereBuilder.Append(SqlSyntax.GetQuotedColumnName("userId")); - whereBuilder.Append("="); - whereBuilder.Append(userId); - - if (entityIds.Any()) - { - whereBuilder.Append(" AND "); - - //where nodeId = @nodeId1 OR nodeId = @nodeId2, etc... - whereBuilder.Append("("); - for (var index = 0; index < entityIds.Length; index++) - { - var entityId = entityIds[index]; - whereBuilder.Append(SqlSyntax.GetQuotedColumnName("nodeId")); - whereBuilder.Append("="); - whereBuilder.Append(entityId); - if (index < entityIds.Length - 1) - { - whereBuilder.Append(" OR "); - } - } - whereBuilder.Append(")"); - } - - var sql = _unitOfWork.Sql() + if (entityIds.Length == 0) + { + var sql = Sql() .SelectAll() - .From() - .Where(whereBuilder.ToString()); - - //ToArray() to ensure it's all fetched from the db once. - var result = _unitOfWork.Database.Fetch(sql).ToArray(); - return ConvertToPermissionList(result); - - }, - //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will only have this exist in cache for 20 minutes, - // then it will refresh from the database. - new TimeSpan(0, 20, 0), - //Since this cache can be quite large (http://issues.umbraco.org/issue/U4-2161) we will make this priority below average - priority: CacheItemPriority.BelowNormal); + .From() + .Where(dto => localIds.Contains(dto.UserGroupId)); + var permissions = UnitOfWork.Database.Fetch(sql); + foreach (var permission in ConvertToPermissionList(permissions)) + { + result.Add(permission); + } + } + else + { + //iterate in groups of 2000 since we don't want to exceed the max SQL param count + foreach (var groupOfEntityIds in entityIds.InGroupsOf(2000)) + { + var ids = groupOfEntityIds; + var sql = Sql() + .SelectAll() + .From() + .Where(dto => localIds.Contains(dto.UserGroupId) && ids.Contains(dto.NodeId)); + var permissions = UnitOfWork.Database.Fetch(sql); + foreach (var permission in ConvertToPermissionList(permissions)) + { + result.Add(permission); + } + } + } + } + return result; } /// - /// Returns permissions for all users for a given entity + /// Returns permissions directly assigned to the content items for all user groups /// - /// + /// /// - public IEnumerable GetPermissionsForEntity(int entityId) + public IEnumerable GetPermissionsForEntities(int[] entityIds) { - var sql = _unitOfWork.Sql() + var sql = Sql() .SelectAll() - .From() - .Where(dto => dto.NodeId == entityId) - .OrderBy(dto => dto.NodeId); + .From() + .Where(dto => entityIds.Contains(dto.NodeId)) + .OrderBy(dto => dto.NodeId); - //ToArray() to ensure it's all fetched from the db once. - var result = _unitOfWork.Database.Fetch(sql).ToArray(); + var result = UnitOfWork.Database.Fetch(sql); return ConvertToPermissionList(result); } /// - /// Assigns the same permission set for a single user to any number of entities + /// Returns permissions directly assigned to the content item for all user groups /// - /// + /// + /// + public EntityPermissionCollection GetPermissionsForEntity(int entityId) + { + var sql = Sql() + .SelectAll() + .From() + .Where(dto => dto.NodeId == entityId) + .OrderBy(dto => dto.NodeId); + + var result = UnitOfWork.Database.Fetch(sql); + return ConvertToPermissionList(result); + } + + /// + /// Assigns the same permission set for a single group to any number of entities + /// + /// /// /// /// /// This will first clear the permissions for this user and entities and recreate them /// - public void ReplaceUserPermissions(int userId, IEnumerable permissions, params int[] entityIds) + public void ReplacePermissions(int groupId, IEnumerable permissions, params int[] entityIds) { - var db = _unitOfWork.Database; + if (entityIds.Length == 0) + return; + + var db = UnitOfWork.Database; //we need to batch these in groups of 2000 so we don't exceed the max 2100 limit + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND nodeId in (@nodeIds)"; foreach (var idGroup in entityIds.InGroupsOf(2000)) { - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND nodeId in (@nodeIds)", - new { userId = userId, nodeIds = idGroup }); + db.Execute(sql, new { groupId, nodeIds = idGroup }); } - var toInsert = new List(); + var toInsert = new List(); foreach (var p in permissions) { foreach (var e in entityIds) { - toInsert.Add(new User2NodePermissionDto + toInsert.Add(new UserGroup2NodePermissionDto { NodeId = e, Permission = p.ToString(CultureInfo.InvariantCulture), - UserId = userId + UserGroupId = groupId }); } } db.BulkInsertRecords(toInsert); - - //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(toInsert), false)); } /// /// Assigns one permission for a user to many entities /// - /// + /// /// /// - public void AssignUserPermission(int userId, char permission, params int[] entityIds) + public void AssignPermission(int groupId, char permission, params int[] entityIds) { - var db = _unitOfWork.Database; + var db = UnitOfWork.Database; - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE userId=@userId AND permission=@permission AND nodeId in (@entityIds)", - new - { - userId = userId, - permission = permission.ToString(CultureInfo.InvariantCulture), - entityIds = entityIds - }); + var sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @groupId AND permission=@permission AND nodeId in (@entityIds)"; + db.Execute(sql, + new + { + groupId, + permission = permission.ToString(CultureInfo.InvariantCulture), + entityIds + }); - var actions = entityIds.Select(id => new User2NodePermissionDto + var actions = entityIds.Select(id => new UserGroup2NodePermissionDto { NodeId = id, Permission = permission.ToString(CultureInfo.InvariantCulture), - UserId = userId + UserGroupId = groupId }).ToArray(); db.BulkInsertRecords(actions); - - //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } /// - /// Assigns one permission to an entity for multiple users + /// Assigns one permission to an entity for multiple groups /// /// /// - /// - public void AssignEntityPermission(TEntity entity, char permission, IEnumerable userIds) + /// + public void AssignEntityPermission(TEntity entity, char permission, IEnumerable groupIds) { - var db = _unitOfWork.Database; + var db = UnitOfWork.Database; + var groupIdsA = groupIds.ToArray(); - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId AND permission=@permission AND userId in (@userIds)", + const string sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId AND permission = @permission AND userGroupId in (@groupIds)"; + db.Execute(sql, new { nodeId = entity.Id, permission = permission.ToString(CultureInfo.InvariantCulture), - userIds = userIds + groupIdsA }); - var actions = userIds.Select(id => new User2NodePermissionDto + var actions = groupIdsA.Select(id => new UserGroup2NodePermissionDto { NodeId = entity.Id, Permission = permission.ToString(CultureInfo.InvariantCulture), - UserId = id + UserGroupId = id }).ToArray(); db.BulkInsertRecords(actions); - - //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); } /// - /// Assigns permissions to an entity for multiple users/permission entries + /// Assigns permissions to an entity for multiple group/permission entries /// /// /// @@ -223,41 +227,109 @@ namespace Umbraco.Core.Persistence.Repositories /// public void ReplaceEntityPermissions(EntityPermissionSet permissionSet) { - var db = _unitOfWork.Database; + var db = UnitOfWork.Database; - db.Execute("DELETE FROM umbracoUser2NodePermission WHERE nodeId=@nodeId", new { nodeId = permissionSet.EntityId }); + const string sql = "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @nodeId"; + db.Execute(sql, new { nodeId = permissionSet.EntityId }); - var actions = permissionSet.UserPermissionsSet.Select(p => new User2NodePermissionDto + var toInsert = new List(); + foreach (var entityPermission in permissionSet.PermissionsSet) { - NodeId = permissionSet.EntityId, - Permission = p.Permission, - UserId = p.UserId - }).ToArray(); + foreach (var permission in entityPermission.AssignedPermissions) + { + toInsert.Add(new UserGroup2NodePermissionDto + { + NodeId = permissionSet.EntityId, + Permission = permission, + UserGroupId = entityPermission.UserGroupId + }); + } + } - db.BulkInsertRecords(actions); - - //Raise the event - _unitOfWork.Events.Dispatch(AssignedPermissions, this, new SaveEventArgs(ConvertToPermissionList(actions), false)); + db.BulkInsertRecords(toInsert); } - private static IEnumerable ConvertToPermissionList(IEnumerable result) + #region Not implemented (don't need to for the purposes of this repo) + + protected override ContentPermissionSet PerformGet(int id) { - var permissions = new List(); + throw new WontImplementException(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new WontImplementException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new WontImplementException(); + } + + protected override Sql GetBaseQuery(bool isCount) + { + throw new WontImplementException(); + } + + protected override string GetBaseWhereClause() + { + throw new WontImplementException(); + } + + protected override IEnumerable GetDeleteClauses() + { + return new List(); + } + + protected override Guid NodeObjectTypeId => throw new WontImplementException(); + + protected override void PersistDeletedItem(ContentPermissionSet entity) + { + throw new WontImplementException(); + } + + #endregion + + /// + /// Used to add or update entity permissions during a content item being updated + /// + /// + protected override void PersistNewItem(ContentPermissionSet entity) + { + //does the same thing as update + PersistUpdatedItem(entity); + } + + /// + /// Used to add or update entity permissions during a content item being updated + /// + /// + protected override void PersistUpdatedItem(ContentPermissionSet entity) + { + var asAggregateRoot = (IAggregateRoot)entity; + if (asAggregateRoot.HasIdentity == false) + { + throw new InvalidOperationException("Cannot create permissions for an entity without an Id"); + } + + ReplaceEntityPermissions(entity); + } + + private static EntityPermissionCollection ConvertToPermissionList(IEnumerable result) + { + var permissions = new EntityPermissionCollection(); var nodePermissions = result.GroupBy(x => x.NodeId); foreach (var np in nodePermissions) { - var userPermissions = np.GroupBy(x => x.UserId); - foreach (var up in userPermissions) + var userGroupPermissions = np.GroupBy(x => x.UserGroupId); + foreach (var permission in userGroupPermissions) { - var perms = up.Select(x => x.Permission).ToArray(); - permissions.Add(new EntityPermission(up.Key, up.First().NodeId, perms)); + var perms = permission.Select(x => x.Permission).Distinct().ToArray(); + permissions.Add(new EntityPermission(permission.Key, np.Key, perms)); } } + return permissions; } - - // todo - understand why we need this repository-level event, move it back to service - - public static event TypedEventHandler, SaveEventArgs> AssignedPermissions; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs index aed784f7ca..18b92c3cb4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/SimpleGetRepository.cs @@ -66,7 +66,7 @@ namespace Umbraco.Core.Persistence.Repositories return Database.Fetch(sql).Select(ConvertToEntity); } - protected override sealed IEnumerable PerformGetByQuery(IQuery query) + protected sealed override IEnumerable PerformGetByQuery(IQuery query) { var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); @@ -76,22 +76,22 @@ namespace Umbraco.Core.Persistence.Repositories #region Not implemented and not required - protected override sealed IEnumerable GetDeleteClauses() + protected sealed override IEnumerable GetDeleteClauses() { throw new NotImplementedException(); } - protected override sealed Guid NodeObjectTypeId + protected sealed override Guid NodeObjectTypeId { get { throw new NotImplementedException(); } } - protected override sealed void PersistNewItem(TEntity entity) + protected sealed override void PersistNewItem(TEntity entity) { throw new NotImplementedException(); } - protected override sealed void PersistUpdatedItem(TEntity entity) + protected sealed override void PersistUpdatedItem(TEntity entity) { throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index df731f0480..7ba1f1eaa9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories var list = new List { "DELETE FROM umbracoUser2NodeNotify WHERE nodeId = @Id", - "DELETE FROM umbracoUser2NodePermission WHERE nodeId = @Id", + "DELETE FROM umbracoUserGroup2NodePermission WHERE nodeId = @Id", "UPDATE cmsDocument SET templateId = NULL WHERE templateId = @Id", "DELETE FROM cmsDocumentType WHERE templateNodeId = @Id", "DELETE FROM cmsTemplate WHERE nodeId = @Id", diff --git a/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs new file mode 100644 index 0000000000..c5926d8db0 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/UserGroupRepository.cs @@ -0,0 +1,489 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NPoco; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Cache; +using Umbraco.Core.Exceptions; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Factories; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.UnitOfWork; + +namespace Umbraco.Core.Persistence.Repositories +{ + /// + /// Represents the UserGroupRepository for doing CRUD operations for + /// + internal class UserGroupRepository : NPocoRepositoryBase, IUserGroupRepository + { + private readonly UserGroupWithUsersRepository _userGroupWithUsersRepository; + private readonly PermissionRepository _permissionRepository; + + public UserGroupRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger) + : base(work, cacheHelper, logger) + { + _userGroupWithUsersRepository = new UserGroupWithUsersRepository(this, work, cacheHelper, logger); + _permissionRepository = new PermissionRepository(work, cacheHelper, logger); + } + + public const string GetByAliasCacheKeyPrefix = "UserGroupRepository_GetByAlias_"; + public static string GetByAliasCacheKey(string alias) + { + return GetByAliasCacheKeyPrefix + alias; + } + + public IUserGroup Get(string alias) + { + try + { + //need to do a simple query to get the id - put this cache + var id = IsolatedCache.GetCacheItem(GetByAliasCacheKey(alias), () => + { + var groupId = Database.ExecuteScalar("SELECT id FROM umbracoUserGroup WHERE userGroupAlias=@alias", new { alias }); + if (groupId.HasValue == false) throw new InvalidOperationException("No group found with alias " + alias); + return groupId.Value; + }); + + //return from the normal method which will cache + return Get(id); + } + catch (InvalidOperationException) + { + //if this is caught it's because we threw this in the caching method + return null; + } + } + + public IEnumerable GetGroupsAssignedToSection(string sectionAlias) + { + //Here we're building up a query that looks like this, a sub query is required because the resulting structure + // needs to still contain all of the section rows per user group. + + //SELECT * + //FROM [umbracoUserGroup] + //LEFT JOIN [umbracoUserGroup2App] + //ON [umbracoUserGroup].[id] = [umbracoUserGroup2App].[user] + //WHERE umbracoUserGroup.id IN (SELECT umbracoUserGroup.id + // FROM [umbracoUserGroup] + // LEFT JOIN [umbracoUserGroup2App] + // ON [umbracoUserGroup].[id] = [umbracoUserGroup2App].[user] + // WHERE umbracoUserGroup2App.app = 'content') + + var sql = GetBaseQuery(QueryType.Many); + var innerSql = GetBaseQuery(QueryType.Ids); + innerSql.Where("umbracoUserGroup2App.app = " + SqlSyntax.GetQuotedValue(sectionAlias)); + sql.Where($"umbracoUserGroup.id IN ({innerSql.SQL})"); + AppendGroupBy(sql); + + return ConvertFromDtos(Database.Fetch(sql)); + } + + public void AddOrUpdateGroupWithUsers(IUserGroup userGroup, int[] userIds) + { + _userGroupWithUsersRepository.AddOrUpdate(new UserGroupWithUsers(userGroup, userIds)); + } + + + /// + /// Gets explicilty defined permissions for the group for specified entities + /// + /// + /// Array of entity Ids, if empty will return permissions for the group for all entities + public EntityPermissionCollection GetPermissions(int[] groupIds, params int[] entityIds) + { + return _permissionRepository.GetPermissionsForEntities(groupIds, entityIds); + } + + /// + /// Gets explicilt and default permissions (if requested) permissions for the group for specified entities + /// + /// + /// If true will include the group's default permissions if no permissions are explicitly assigned + /// Array of entity Ids, if empty will return permissions for the group for all entities + public EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) + { + if (groups == null) throw new ArgumentNullException(nameof(groups)); + + var groupIds = groups.Select(x => x.Id).ToArray(); + var explicitPermissions = GetPermissions(groupIds, nodeIds); + var result = new EntityPermissionCollection(explicitPermissions); + + // If requested, and no permissions are assigned to a particular node, then we will fill in those permissions with the group's defaults + if (fallbackToDefaultPermissions) + { + //if no node ids are passed in, then we need to determine the node ids for the explicit permissions set + nodeIds = nodeIds.Length == 0 + ? explicitPermissions.Select(x => x.EntityId).Distinct().ToArray() + : nodeIds; + + //if there are still no nodeids we can just exit + if (nodeIds.Length == 0) + return result; + + foreach (var group in groups) + { + foreach (var nodeId in nodeIds) + { + //TODO: We could/should change the EntityPermissionsCollection into a KeyedCollection and they key could be + // a struct of the nodeid + groupid so then we don't actually allocate this class just to check if it's not + // going to be included in the result! + + var defaultPermission = new EntityPermission(group.Id, nodeId, group.Permissions.ToArray(), isDefaultPermissions: true); + //Since this is a hashset, this will not add anything that already exists by group/node combination + result.Add(defaultPermission); + } + } + } + + return result; + } + + /// + /// Replaces the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of If nothing is specified all permissions are removed. + /// Specify the nodes to replace permissions for. + public void ReplaceGroupPermissions(int groupId, IEnumerable permissions, params int[] entityIds) + { + _permissionRepository.ReplacePermissions(groupId, permissions, entityIds); + } + + /// + /// Assigns the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for + public void AssignGroupPermission(int groupId, char permission, params int[] entityIds) + { + _permissionRepository.AssignPermission(groupId, permission, entityIds); + } + + #region Overrides of RepositoryBase + + protected override IUserGroup PerformGet(int id) + { + var sql = GetBaseQuery(QueryType.Single); + sql.Where(GetBaseWhereClause(), new { Id = id }); + AppendGroupBy(sql); + + var dto = Database.Fetch(sql).FirstOrDefault(); + + if (dto == null) + return null; + + var userGroup = UserGroupFactory.BuildEntity(dto); + return userGroup; + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + var sql = GetBaseQuery(QueryType.Many); + + if (ids.Any()) + sql.WhereIn(x => x.Id, ids); + else + sql.Where(x => x.Id >= 0); + + AppendGroupBy(sql); + + var dtos = Database.Fetch(sql); + return ConvertFromDtos(dtos); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + var sqlClause = GetBaseQuery(QueryType.Many); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + AppendGroupBy(sql); + + var dtos = Database.Fetch(sql); + return ConvertFromDtos(dtos); + } + + #endregion + + #region Overrides of NPocoRepositoryBase + + protected Sql GetBaseQuery(QueryType type) + { + var sql = Sql(); + var addFrom = false; + + switch (type) + { + case QueryType.Count: + sql + .SelectCount() + .From(); + break; + case QueryType.Ids: + sql + .Select("umbracoUserGroup.id"); + addFrom = true; + break; + case QueryType.Single: + case QueryType.Many: + sql + .Select(r => r.Select()) + .Append(", COUNT(umbracoUser2UserGroup.UserId)"); // vs COUNT(umbracoUser.Id) - removes a JOIN + addFrom = true; + break; + default: + throw new NotSupportedException(type.ToString()); + } + + if (addFrom) + sql + .From() + .LeftJoin() + .On(left => left.Id, right => right.UserGroupId) + .LeftJoin() + .On(left => left.UserGroupId, right => right.Id) + /*.LeftJoin() + .On(left => left.Id, right => right.UserId)*/; + + return sql; + } + + protected override Sql GetBaseQuery(bool isCount) + { + return GetBaseQuery(isCount ? QueryType.Count : QueryType.Many); + +// var sql = Sql(); + +// if (isCount) +// { +// sql.Select("COUNT(*)").From(); +// } +// else +// { +// return GetBaseQuery(@"umbracoUserGroup.createDate, umbracoUserGroup.icon, umbracoUserGroup.id, umbracoUserGroup.startContentId, +//umbracoUserGroup.startMediaId, umbracoUserGroup.updateDate, umbracoUserGroup.userGroupAlias, umbracoUserGroup.userGroupDefaultPermissions, +//umbracoUserGroup.userGroupName, COUNT(umbracoUser.id) AS UserCount, umbracoUserGroup2App.app, umbracoUserGroup2App.userGroupId"); +// } +// return sql; + } + + //protected Sql GetBaseQuery(string columns) + //{ + // var sql = Sql() + // .Select(columns) + // .From() + // .LeftJoin() + // .On(left => left.Id, right => right.UserGroupId) + // .LeftJoin() + // .On(left => left.UserGroupId, right => right.Id) + // .LeftJoin() + // .On(left => left.Id, right => right.UserId); + + // return sql; + //} + + private static void AppendGroupBy(Sql sql) + { + sql.GroupBy(@"umbracoUserGroup.createDate, umbracoUserGroup.icon, umbracoUserGroup.id, umbracoUserGroup.startContentId, +umbracoUserGroup.startMediaId, umbracoUserGroup.updateDate, umbracoUserGroup.userGroupAlias, umbracoUserGroup.userGroupDefaultPermissions, +umbracoUserGroup.userGroupName, umbracoUserGroup2App.app, umbracoUserGroup2App.userGroupId"); + } + + protected override string GetBaseWhereClause() + { + return "umbracoUserGroup.id = @Id"; + } + + protected override IEnumerable GetDeleteClauses() + { + var list = new List + { + "DELETE FROM umbracoUser2UserGroup WHERE userGroupId = @Id", + "DELETE FROM umbracoUserGroup2App WHERE userGroupId = @Id", + "DELETE FROM umbracoUserGroup2NodePermission WHERE userGroupId = @Id", + "DELETE FROM umbracoUserGroup WHERE id = @Id" + }; + return list; + } + + protected override Guid NodeObjectTypeId => throw new WontImplementException(); + + protected override void PersistNewItem(IUserGroup entity) + { + ((UserGroup) entity).AddingEntity(); + + var userGroupDto = UserGroupFactory.BuildDto(entity); + + var id = Convert.ToInt32(Database.Insert(userGroupDto)); + entity.Id = id; + + PersistAllowedSections(entity); + } + + protected override void PersistUpdatedItem(IUserGroup entity) + { + ((UserGroup) entity).UpdatingEntity(); + + var userGroupDto = UserGroupFactory.BuildDto(entity); + + Database.Update(userGroupDto); + + PersistAllowedSections(entity); + } + + private void PersistAllowedSections(IUserGroup entity) + { + var userGroup = (UserGroup) entity; + + // First delete all + Database.Delete("WHERE UserGroupId = @UserGroupId", new { UserGroupId = userGroup.Id }); + + // Then re-add any associated with the group + foreach (var app in userGroup.AllowedSections) + { + var dto = new UserGroup2AppDto + { + UserGroupId = userGroup.Id, + AppAlias = app + }; + Database.Insert(dto); + } + } + + #endregion + + private static IEnumerable ConvertFromDtos(IEnumerable dtos) + { + return dtos.Select(UserGroupFactory.BuildEntity); + } + + /// + /// used to persist a user group with associated users at once + /// + private class UserGroupWithUsers : Entity, IAggregateRoot + { + public UserGroupWithUsers(IUserGroup userGroup, int[] userIds) + { + UserGroup = userGroup; + UserIds = userIds; + } + + public override bool HasIdentity + { + get => UserGroup.HasIdentity; + protected set => throw new NotSupportedException(); + } + + public IUserGroup UserGroup { get; } + public int[] UserIds { get; } + } + + /// + /// used to persist a user group with associated users at once + /// + private class UserGroupWithUsersRepository : NPocoRepositoryBase + { + private readonly UserGroupRepository _userGroupRepo; + + public UserGroupWithUsersRepository(UserGroupRepository userGroupRepo, IScopeUnitOfWork work, CacheHelper cache, ILogger logger) + : base(work, cache, logger) + { + _userGroupRepo = userGroupRepo; + } + + #region Not implemented (don't need to for the purposes of this repo) + + protected override UserGroupWithUsers PerformGet(int id) + { + throw new WontImplementException(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new WontImplementException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new WontImplementException(); + } + + protected override Sql GetBaseQuery(bool isCount) + { + throw new WontImplementException(); + } + + protected override string GetBaseWhereClause() + { + throw new WontImplementException(); + } + + protected override IEnumerable GetDeleteClauses() + { + throw new WontImplementException(); + } + + protected override Guid NodeObjectTypeId => throw new WontImplementException(); + + #endregion + + protected override void PersistNewItem(UserGroupWithUsers entity) + { + //save the user group + _userGroupRepo.PersistNewItem(entity.UserGroup); + + if (entity.UserIds == null) + return; + + //now the user association + RemoveAllUsersFromGroup(entity.UserGroup.Id); + AddUsersToGroup(entity.UserGroup.Id, entity.UserIds); + } + + protected override void PersistUpdatedItem(UserGroupWithUsers entity) + { + //save the user group + _userGroupRepo.PersistUpdatedItem(entity.UserGroup); + + if (entity.UserIds == null) + return; + + //now the user association + RemoveAllUsersFromGroup(entity.UserGroup.Id); + AddUsersToGroup(entity.UserGroup.Id, entity.UserIds); + } + + /// + /// Removes all users from a group + /// + /// Id of group + private void RemoveAllUsersFromGroup(int groupId) + { + Database.Delete("WHERE userGroupId = @groupId", new { groupId }); + } + + /// + /// Adds a set of users to a group + /// + /// Id of group + /// Ids of users + private void AddUsersToGroup(int groupId, int[] userIds) + { + //TODO: Check if the user exists? + foreach (var userId in userIds) + { + var dto = new User2UserGroupDto + { + UserGroupId = groupId, + UserId = userId, + }; + Database.Insert(dto); + } + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 0e1789961b..b262cf56df 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Web.Security; using NPoco; using Umbraco.Core; using Umbraco.Core.Cache; @@ -17,6 +18,7 @@ using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Security; namespace Umbraco.Core.Persistence.Repositories { @@ -28,14 +30,24 @@ namespace Umbraco.Core.Persistence.Repositories private readonly IUserTypeRepository _userTypeRepository; private readonly CacheHelper _cacheHelper; private readonly IMapperCollection _mapperCollection; + private readonly IDictionary _passwordConfig; private PermissionRepository _permissionRepository; - public UserRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IUserTypeRepository userTypeRepository, IMapperCollection mapperCollection) + public UserRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, IUserTypeRepository userTypeRepository, IMapperCollection mapperCollection, IDictionary passwordConfig = null) : base(work, cacheHelper, logger) { _userTypeRepository = userTypeRepository; _mapperCollection = mapperCollection; _cacheHelper = cacheHelper; + + if (passwordConfig == null) + { + var userMembershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider(); + passwordConfig = userMembershipProvider == null || userMembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed + ? null + : new Dictionary { { "hashAlgorithm", Membership.HashAlgorithmType } }; + } + _passwordConfig = passwordConfig; } // note: is ok to 'new' the repo here as it's a sub-repo really diff --git a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs deleted file mode 100644 index 2549ac17e6..0000000000 --- a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NPoco; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.Rdbms; - -using Umbraco.Core.Persistence.Factories; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.UnitOfWork; - -namespace Umbraco.Core.Persistence.Repositories -{ - /// - /// Represents the UserTypeRepository for doing CRUD operations for - /// - internal class UserTypeRepository : NPocoRepositoryBase, IUserTypeRepository - { - public UserTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger) - : base(work, cache, logger) - { - } - - #region Overrides of RepositoryBase - - protected override IUserType PerformGet(int id) - { - return GetAll(new[] {id}).FirstOrDefault(); - } - - protected override IEnumerable PerformGetAll(params int[] ids) - { - var userTypeFactory = new UserTypeFactory(); - - var sql = GetBaseQuery(false); - - if (ids.Any()) - { - sql.Where("umbracoUserType.id in (@ids)", new { ids = ids }); - } - else - { - sql.Where(x => x.Id >= 0); - } - - var dtos = Database.Fetch(sql); - return dtos.Select(userTypeFactory.BuildEntity).ToArray(); - } - - protected override IEnumerable PerformGetByQuery(IQuery query) - { - var userTypeFactory = new UserTypeFactory(); - var sqlClause = GetBaseQuery(false); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - var dtos = Database.Fetch(sql); - - return dtos.Select(userTypeFactory.BuildEntity).ToArray(); - } - - #endregion - - #region Overrides of NPocoRepositoryBase - - protected override Sql GetBaseQuery(bool isCount) - { - var sql = Sql(); - - sql = isCount - ? sql.SelectCount() - : sql.Select(); - - sql - .From(); - - return sql; - } - - protected override string GetBaseWhereClause() - { - return "umbracoUserType.id = @Id"; - } - - protected override IEnumerable GetDeleteClauses() - { - var list = new List - { - "DELETE FROM umbracoUser WHERE userType = @Id", - "DELETE FROM umbracoUserType WHERE id = @Id" - }; - return list; - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistNewItem(IUserType entity) - { - var userTypeFactory = new UserTypeFactory(); - var userTypeDto = userTypeFactory.BuildDto(entity); - - var id = Convert.ToInt32(Database.Insert(userTypeDto)); - entity.Id = id; - } - - protected override void PersistUpdatedItem(IUserType entity) - { - var userTypeFactory = new UserTypeFactory(); - var userTypeDto = userTypeFactory.BuildDto(entity); - - Database.Update(userTypeDto); - } - - #endregion - } -} diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 955a578486..d7f480cf76 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -50,22 +50,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Id of the to retrieve versions from /// An enumerable list of the same object with different versions - public virtual IEnumerable GetAllVersions(int id) - { - var sql = Sql() - .SelectAll() - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId) - .Where(x => x.NodeId == id) - .OrderByDescending(x => x.VersionDate); - - var dtos = Database.Fetch(sql); - return dtos.Select(x => GetByVersion(x.VersionId)); - } + public abstract IEnumerable GetAllVersions(int id); /// /// Gets a list of all version Ids for the given content item ordered so latest is first diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 985e43110b..0255f5bbb4 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -23,13 +23,15 @@ namespace Umbraco.Core.Services { private readonly MediaFileSystem _mediaFileSystem; private IQuery _queryNotTrashed; + private readonly IdkMap _idkMap; #region Constructors - public ContentService(IScopeUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem) + public ContentService(IScopeUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem, IdkMap idkMap) : base(provider, logger, eventMessagesFactory) { _mediaFileSystem = mediaFileSystem; + _idkMap = idkMap; } #endregion @@ -105,36 +107,34 @@ namespace Umbraco.Core.Services } /// - /// Assigns a single permission to the current content item for the specified user ids + /// Assigns a single permission to the current content item for the specified group ids /// /// /// - /// - public void AssignContentPermission(IContent entity, char permission, IEnumerable userIds) + /// + public void AssignContentPermission(IContent entity, char permission, IEnumerable groupIds) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.ContentTree); var repo = uow.CreateRepository(); - repo.AssignEntityPermission(entity, permission, userIds); + repo.AssignEntityPermission(entity, permission, groupIds); uow.Complete(); } - } - + } + /// - /// Gets the list of permissions for the content item + /// Returns implicit/inherited permissions assigned to the content item for all user groups /// /// /// - public IEnumerable GetPermissionsForEntity(IContent content) + public EntityPermissionCollection GetPermissionsForEntity(IContent content) { - using (var uow = UowProvider.CreateUnitOfWork()) + using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { uow.ReadLock(Constants.Locks.ContentTree); var repo = uow.CreateRepository(); - var perms = repo.GetPermissionsForEntity(content.Id); - uow.Complete(); - return perms; + return repo.GetPermissionsForEntity(content.Id); } } @@ -315,7 +315,7 @@ namespace Umbraco.Core.Services if (withIdentity) { - if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content))) + if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content), "Saving")) { content.WasCancelled = true; return; @@ -326,16 +326,16 @@ namespace Umbraco.Core.Services uow.Flush(); // need everything so we can serialize - uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false)); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false), "Saved"); uow.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshNode).ToEventArgs()); } uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, content.ContentType.Alias, parent)); - var msg = withIdentity - ? "Content '{0}' was created with Id {1}" - : "Content '{0}' was created"; - Audit(uow, AuditType.New, string.Format(msg, content.Name, content.Id), content.CreatorId, content.Id); + if (withIdentity == false) + return; + + Audit(uow, AuditType.New, $"Content '{content.Name}' was created with Id {content.Id}", content.CreatorId, content.Id); } #endregion @@ -386,13 +386,13 @@ namespace Umbraco.Core.Services /// public IContent GetById(Guid key) { - using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) - { - uow.ReadLock(Constants.Locks.ContentTree); - var repository = uow.CreateRepository(); - var query = uow.Query().Where(x => x.Key == key); - return repository.GetByQuery(query).SingleOrDefault(); - } + // the repository implements a cache policy on int identifiers, not guids, + // and we are not changing it now, but we still would like to rely on caching + // instead of running a full query against the database, so relying on the + // id-key map, which is fast. + + var a = _idkMap.GetIdForKey(key, UmbracoObjectTypes.Document); + return a.Success ? GetById(a.Result) : null; } /// @@ -942,7 +942,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content, evtMsgs))) + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content, evtMsgs), "Saving")) { uow.Complete(); return OperationStatus.Attempt.Cancel(evtMsgs); @@ -970,7 +970,7 @@ namespace Umbraco.Core.Services repository.AddOrUpdate(content); if (raiseEvents) - uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs)); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs), "Saved"); var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode; uow.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); Audit(uow, AuditType.Save, "Save Content performed by user", userId, content.Id); @@ -1009,7 +1009,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(contentsA, evtMsgs))) + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(contentsA, evtMsgs), "Saving")) { uow.Complete(); return OperationStatus.Attempt.Cancel(evtMsgs); @@ -1035,7 +1035,7 @@ namespace Umbraco.Core.Services } if (raiseEvents) - uow.Events.Dispatch(Saved, this, new SaveEventArgs(contentsA, false, evtMsgs)); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(contentsA, false, evtMsgs), "Saved"); uow.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); Audit(uow, AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); @@ -1670,8 +1670,20 @@ namespace Umbraco.Core.Services copy.CreatorId = userId; copy.WriterId = userId; + //get the current permissions, if there are any explicit ones they need to be copied + var currentPermissions = GetPermissionsForEntity(content); + currentPermissions.RemoveWhere(p => p.IsDefaultPermissions); + // save and flush because we need the ID for the recursive Copying events repository.AddOrUpdate(copy); + + //add permissions + if (currentPermissions.Count > 0) + { + var permissionSet = new ContentPermissionSet(copy, currentPermissions); + repository.AddOrUpdatePermissions(permissionSet); + } + uow.Flush(); // keep track of copies @@ -1818,7 +1830,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(itemsA))) + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(itemsA), "Saving")) return false; var published = new List(); @@ -1855,7 +1867,7 @@ namespace Umbraco.Core.Services } if (raiseEvents) - uow.Events.Dispatch(Saved, this, new SaveEventArgs(saved, false)); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(saved, false), "Saved"); uow.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)).ToEventArgs()); @@ -2038,7 +2050,7 @@ namespace Umbraco.Core.Services using (var uow = UowProvider.CreateUnitOfWork()) { - if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content))) + if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content), "Saving")) { uow.Complete(); return Attempt.Fail(new PublishStatus(PublishStatusType.FailedCancelledByEvent, evtMsgs, content)); @@ -2067,7 +2079,7 @@ namespace Umbraco.Core.Services repository.AddOrUpdate(content); if (raiseEvents) // always - uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs)); + uow.Events.Dispatch(Saved, this, new SaveEventArgs(content, false, evtMsgs), "Saved"); if (status.Success == false) { @@ -2234,6 +2246,16 @@ namespace Umbraco.Core.Services /// internal static event TypedEventHandler.EventArgs> TreeChanged; + /// + /// Occurs after a blueprint has been saved. + /// + public static event TypedEventHandler> SavedBlueprint; + + /// + /// Occurs after a blueprint has been deleted. + /// + public static event TypedEventHandler> DeletedBlueprint; + #endregion #region Publishing Strategies @@ -2599,6 +2621,115 @@ namespace Umbraco.Core.Services { return GetContentType(uow, contentTypeAlias); } + } + + #endregion + + #region Blueprints + + public IContent GetBlueprintById(int id) + { + using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) + { + uow.ReadLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + var blueprint = repository.Get(id); + if (blueprint != null) + ((Content) blueprint).IsBlueprint = true; + return blueprint; + } + } + + public IContent GetBlueprintById(Guid id) + { + // the repository implements a cache policy on int identifiers, not guids, + // and we are not changing it now, but we still would like to rely on caching + // instead of running a full query against the database, so relying on the + // id-key map, which is fast. + + var a = _idkMap.GetIdForKey(id, UmbracoObjectTypes.DocumentBlueprint); + return a.Success ? GetBlueprintById(a.Result) : null; + } + + public void SaveBlueprint(IContent content, int userId = 0) + { + //always ensure the blueprint is at the root + if (content.ParentId != -1) + content.ParentId = -1; + + ((Content) content).IsBlueprint = true; + + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + + if (string.IsNullOrWhiteSpace(content.Name)) + { + throw new ArgumentException("Cannot save content blueprint with empty name."); + } + + var repository = uow.CreateRepository(); + + if (content.HasIdentity == false) + { + content.CreatorId = userId; + } + content.WriterId = userId; + + repository.AddOrUpdate(content); + + uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs(content), "SavedBlueprint"); + + uow.Complete(); + } + } + + public void DeleteBlueprint(IContent content, int userId = 0) + { + using (var uow = UowProvider.CreateUnitOfWork()) + { + uow.WriteLock(Constants.Locks.ContentTree); + var repository = uow.CreateRepository(); + repository.Delete(content); + uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs(content), "DeletedBlueprint"); + uow.Complete(); + } + } + + public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = 0) + { + if (blueprint == null) throw new ArgumentNullException(nameof(blueprint)); + + var contentType = blueprint.ContentType; + var content = new Content(name, -1, contentType); + content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id); + + content.CreatorId = userId; + content.WriterId = userId; + + foreach (var property in blueprint.Properties) + content.SetValue(property.Alias, property.Value); + + return content; + } + + public IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeIds) + { + using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) + { + var repository = uow.CreateRepository(); + + var query = uow.Query(); + if (documentTypeIds.Length > 0) + { + query.Where(x => documentTypeIds.Contains(x.ContentTypeId)); + } + return repository.GetByQuery(query).Select(x => + { + ((Content) x).IsBlueprint = true; + return x; + }); + } } #endregion diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 11a68adacc..077a9611a5 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services /// public static void RemoveContentPermissions(this IContentService contentService, int contentId) { - contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, Enumerable.Empty())); + contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection())); } /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index bf9ba115bb..29ee4a9fdb 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -54,7 +54,7 @@ namespace Umbraco.Core.Services /// Gets all content type aliases accross content, media and member types. /// /// Optional object types guid to restrict to content, and/or media, and/or member types. - /// All property type aliases. + /// All content type aliases. /// Beware! Works accross content, media and member types. public IEnumerable GetAllContentTypeAliases(params Guid[] guids) { @@ -66,5 +66,23 @@ namespace Umbraco.Core.Services return repo.GetAllContentTypeAliases(guids); } } + + /// + /// Gets all content type id for aliases accross content, media and member types. + /// + /// Aliases to look for. + /// All content type ids. + /// Beware! Works accross content, media and member types. + public IEnumerable GetAllContentTypeIds(string[] aliases) + { + using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) + { + // that one is special because it works accross content, media and member types + uow.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes); + var repo = uow.CreateRepository(); + return repo.GetAllContentTypeIds(aliases); + } + } + } } diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 8b4a15dcea..1a6eadb3c8 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using NPoco; +using System.Linq.Expressions; +using System.Text; using Umbraco.Core.Cache; using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Events; @@ -19,19 +21,19 @@ namespace Umbraco.Core.Services { public class EntityService : ScopeRepositoryService, IEntityService { - private readonly IRuntimeCacheProvider _runtimeCache; private readonly Dictionary>> _supportedObjectTypes; private IQuery _queryRootEntity; + private readonly IdkMap _idkMap; public EntityService(IScopeUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IMediaTypeService mediaTypeService, IDataTypeService dataTypeService, - IMemberService memberService, IMemberTypeService memberTypeService, + IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap) IRuntimeCacheProvider runtimeCache) : base(provider, logger, eventMessagesFactory) { - _runtimeCache = runtimeCache; + _idkMap = idkMap; _supportedObjectTypes = new Dictionary>> { @@ -42,27 +44,6 @@ namespace Umbraco.Core.Services {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, mediaTypeService.Get)}, {typeof (IMember).FullName, new Tuple>(UmbracoObjectTypes.Member, memberService.GetById)}, {typeof (IMemberType).FullName, new Tuple>(UmbracoObjectTypes.MemberType, memberTypeService.Get)}, - //{typeof (IUmbracoEntity).FullName, new Tuple>(UmbracoObjectTypes.EntityContainer, id => - //{ - // using (var uow = UowProvider.GetUnitOfWork()) - // { - // var found = uow.Database.FirstOrDefault("SELECT * FROM umbracoNode WHERE id=@id", new { id = id }); - // return found == null ? null : new UmbracoEntity(found.Trashed) - // { - // Id = found.NodeId, - // Name = found.Text, - // Key = found.UniqueId, - // SortOrder = found.SortOrder, - // Path = found.Path, - // NodeObjectTypeId = found.NodeObjectType.Value, - // CreateDate = found.CreateDate, - // CreatorId = found.UserId.Value, - // Level = found.Level, - // ParentId = found.ParentId - // }; - // } - - //})} }; } @@ -83,21 +64,12 @@ namespace Umbraco.Core.Services /// public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) { - var result = _runtimeCache.GetCacheItem(CacheKeys.IdToKeyCacheKey + key, () => - { - using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) - { - var nodeObjectType = GetNodeObjectTypeGuid(umbracoObjectType); + return _idkMap.GetIdForKey(key, umbracoObjectType); + } - var sql = uow.Sql() - .Select("id") - .From() - .Where(x => x.UniqueId == key && x.NodeObjectType == nodeObjectType); - - return uow.Database.ExecuteScalar(sql); - } - }); - return result.HasValue ? Attempt.Succeed(result.Value) : Attempt.Fail(); + public Attempt GetIdForUdi(Udi udi) + { + return _idkMap.GetIdForUdi(udi); } /// @@ -108,29 +80,7 @@ namespace Umbraco.Core.Services /// public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) { - var result = _runtimeCache.GetCacheItem(CacheKeys.KeyToIdCacheKey + id, () => - { - using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) - { - var nodeObjectType = GetNodeObjectTypeGuid(umbracoObjectType); - - var sql = uow.Sql() - .Select("uniqueID") - .From() - .Where(x => x.NodeId == id && x.NodeObjectType == nodeObjectType); - return uow.Database.ExecuteScalar(sql); - } - }); - return result.HasValue ? Attempt.Succeed(result.Value) : Attempt.Fail(); - } - - private static Guid GetNodeObjectTypeGuid(UmbracoObjectTypes umbracoObjectType) - { - var guid = umbracoObjectType.GetGuid(); - if (guid == Guid.Empty) - throw new NotSupportedException("Unsupported object type (" + umbracoObjectType + ")."); - - return guid; + return _idkMap.GetKeyForId(id, umbracoObjectType); } public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) @@ -433,7 +383,47 @@ namespace Umbraco.Core.Services IQuery filterQuery = null; if (filter.IsNullOrWhiteSpace() == false) { - filterQuery = repository.Query.Where(x => x.Name.Contains(filter)); + filterQuery = uow.Query().Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return contents; + } + } + /// + /// Returns a paged collection of descendants. + /// + public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + totalRecords = 0; + + var idsA = ids.ToArray(); + if (idsA.Length == 0) + return Enumerable.Empty(); + + var objectTypeId = umbracoObjectType.GetGuid(); + + using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) + { + var repository = uow.CreateRepository(); + + var query = uow.Query(); + if (idsA.All(x => x != Constants.System.Root)) + { + var clauses = new List>>(); + foreach (var id in idsA) + { + var qid = id; + clauses.Add(x => x.Path.SqlContains(string.Format(",{0},", qid), TextColumnType.NVarchar) || x.Path.SqlEndsWith(string.Format(",{0}", qid), TextColumnType.NVarchar)); + } + query.WhereAny(clauses); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = uow.Query().Where(x => x.Name.Contains(filter)); } var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); @@ -547,6 +537,36 @@ namespace Umbraco.Core.Services } } + public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + if (_supportedObjectTypes.ContainsKey(typeFullName) == false) + throw new NotSupportedException("The passed in type is not supported.); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) + { + var repository = uow.CreateRepository(); + return repository.GetAllPaths(objectTypeId, ids); + } + } + + public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys) + { + var entityType = GetEntityType(umbracoObjectType); + var typeFullName = entityType.FullName; + if (_supportedObjectTypes.ContainsKey(typeFullName) == false) + throw new NotSupportedException("The passed in type is not supported.); + + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) + { + var repository = uow.CreateRepository(); + return repository.GetAllPaths(objectTypeId, keys); + } + } + /// /// Gets a collection of /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 78f80791b3..c2f9f86330 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -22,13 +22,15 @@ namespace Umbraco.Core.Services public class MediaService : ScopeRepositoryService, IMediaService, IMediaServiceOperations { private readonly MediaFileSystem _mediaFileSystem; + private readonly IdkMap _idkMap; #region Constructors - public MediaService(IScopeUnitOfWorkProvider provider, MediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory) + public MediaService(IScopeUnitOfWorkProvider provider, MediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory, IdkMap idkMap) : base(provider, logger, eventMessagesFactory) { _mediaFileSystem = mediaFileSystem; + _idkMap = idkMap; } #endregion @@ -330,13 +332,13 @@ namespace Umbraco.Core.Services /// public IMedia GetById(Guid key) { - using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) - { - uow.ReadLock(Constants.Locks.MediaTree); - var repository = uow.CreateRepository(); - var query = uow.Query().Where(x => x.Key == key); - return repository.GetByQuery(query).SingleOrDefault(); - } + // the repository implements a cache policy on int identifiers, not guids, + // and we are not changing it now, but we still would like to rely on caching + // instead of running a full query against the database, so relying on the + // id-key map, which is fast. + + var a = _idkMap.GetIdForKey(key, UmbracoObjectTypes.Media); + return a.Success ? GetById(a.Result) : null; } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index c9ea4a11b5..d8a86e250e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -582,17 +582,27 @@ + + + + + + + + + + @@ -691,10 +701,13 @@ - - + + + + + @@ -798,7 +811,7 @@ - + @@ -824,6 +837,7 @@ + @@ -847,6 +861,7 @@ + @@ -1079,6 +1094,7 @@ + @@ -1285,17 +1301,23 @@ + + + - + + + + @@ -1344,6 +1366,7 @@ + @@ -1494,6 +1517,8 @@ + + \ No newline at end of file