Port 7.7 - WIP

This commit is contained in:
Stephan
2017-08-26 17:39:23 +02:00
parent ac1e4bdfe3
commit 4dedd52e37
41 changed files with 1556 additions and 625 deletions
@@ -21,6 +21,9 @@ namespace Umbraco.Core.Composing.CompositionRoots
// register the service context
container.RegisterSingleton<ServiceContext>();
// register the special idk map
container.RegisterSingleton<IdkMap>();
// register the services
container.RegisterSingleton<IMigrationEntryService, MigrationEntryService>();
container.RegisterSingleton<IPublicAccessService, PublicAccessService>();
@@ -1,5 +1,4 @@
using NPoco;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
@@ -58,6 +58,7 @@ namespace Umbraco.Core.Models.Rdbms
public int? StartMediaId { get; set; }
[ResultColumn]
[Reference(ReferenceType.Many, ReferenceMemberName = "id")]
public List<UserGroup2AppDto> UserGroup2AppDtos { get; set; }
/// <summary>
@@ -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;
@@ -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<IUser,UserDto>
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<User2AppDto>(),
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<int>();
}
return dto;
}
#endregion
}
}
}
@@ -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<string>()
: 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<UserGroup2AppDto>(),
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;
}
}
}
@@ -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<IUserType,UserTypeDto>
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<string>()
: 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
}
}
@@ -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<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
public ExternalLoginMapper()
{
BuildMap();
}
#region Overrides of BaseMapper
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache
{
get { return PropertyInfoCacheInstance; }
}
internal override void BuildMap()
{
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.Id, dto => dto.Id);
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.CreateDate, dto => dto.CreateDate);
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.LoginProvider, dto => dto.LoginProvider);
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.ProviderKey, dto => dto.ProviderKey);
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.UserId, dto => dto.UserId);
}
#endregion
}
}
@@ -0,0 +1,43 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Mappers
{
/// <summary>
/// Represents a <see cref="UserGroup"/> to DTO mapper used to translate the properties of the public api
/// implementation to that of the database's DTO as sql: [tableName].[columnName].
/// </summary>
[MapperFor(typeof(IUserGroup))]
[MapperFor(typeof(UserGroup))]
public sealed class UserGroupMapper : BaseMapper
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
//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<string, DtoMapModel> PropertyInfoCache
{
get { return PropertyInfoCacheInstance; }
}
internal override void BuildMap()
{
CacheMap<UserGroup, UserGroupDto>(src => src.Id, dto => dto.Id);
CacheMap<UserGroup, UserGroupDto>(src => src.Alias, dto => dto.Alias);
CacheMap<UserGroup, UserGroupDto>(src => src.Name, dto => dto.Name);
CacheMap<UserGroup, UserGroupDto>(src => src.Icon, dto => dto.Icon);
CacheMap<UserGroup, UserGroupDto>(src => src.StartContentId, dto => dto.StartContentId);
CacheMap<UserGroup, UserGroupDto>(src => src.StartMediaId, dto => dto.StartMediaId);
}
#endregion
}
}
@@ -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<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache => PropertyInfoCacheInstance;
protected override void BuildMap()
{
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.Id, dto => dto.Id);
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.CreateDate, dto => dto.CreateDate);
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.LoginProvider, dto => dto.LoginProvider);
CacheMap<IdentityUserLogin, ExternalLoginDto>(src => src.ProviderKey, dto => dto.ProviderKey);
CacheMap<IdentityUserLogin, ExternalLoginDto>(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<User, UserDto>(src => src.RawPasswordValue, dto => dto.Password);
CacheMap<User, UserDto>(src => src.Name, dto => dto.UserName);
//NOTE: This column in the db is *not* used!
//CacheMap<User, UserDto>(src => src.DefaultPermissions, dto => dto.DefaultPermissions);
CacheMap<User, UserDto>(src => src.StartMediaId, dto => dto.MediaStartId);
CacheMap<User, UserDto>(src => src.StartContentId, dto => dto.ContentStartId);
//CacheMap<User, UserDto>(src => src.DefaultPermissions, dto => dto.DefaultPermissions);
CacheMap<User, UserDto>(src => src.IsApproved, dto => dto.Disabled);
CacheMap<User, UserDto>(src => src.IsLockedOut, dto => dto.NoConsole);
CacheMap<User, UserDto>(src => src.UserType, dto => dto.Type);
CacheMap<User, UserDto>(src => src.Language, dto => dto.UserLanguage);
CacheMap<User, UserDto>(src => src.CreateDate, dto => dto.CreateDate);
CacheMap<User, UserDto>(src => src.UpdateDate, dto => dto.UpdateDate);
CacheMap<User, UserDto>(src => src.LastLockoutDate, dto => dto.LastLockoutDate);
CacheMap<User, UserDto>(src => src.LastLoginDate, dto => dto.LastLoginDate);
CacheMap<User, UserDto>(src => src.LastPasswordChangeDate, dto => dto.LastPasswordChangeDate);
CacheMap<User, UserDto>(src => src.SecurityStamp, dto => dto.SecurityStampToken);
}
}
}
@@ -31,5 +31,12 @@ namespace Umbraco.Core.Persistence.Querying
/// <param name="values"></param>
/// <returns>This instance so calls to this method are chainable</returns>
IQuery<T> WhereIn(Expression<Func<T, object>> fieldSelector, IEnumerable values);
/// <summary>
/// Adds a set of OR-ed where clauses to the query.
/// </summary>
/// <param name="predicates"></param>
/// <returns>This instance so calls to this method are chainable.</returns>
IQuery<T> WhereAny(IEnumerable<Expression<Func<T, bool>>> predicates);
}
}
@@ -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;
}
/// <summary>
/// Adds a set of OR-ed where clauses to the query.
/// </summary>
/// <param name="predicates"></param>
/// <returns>This instance so calls to this method are chainable.</returns>
public virtual IQuery<T> WhereAny(IEnumerable<Expression<Func<T, bool>>> predicates)
{
if (predicates == null) return this;
StringBuilder sb = null;
List<object> parameters = null;
Sql sql = null;
foreach (var predicate in predicates)
{
// see notes in Where()
var expressionHelper = new ModelToSqlExpressionVisitor<T>();
var whereExpression = expressionHelper.Visit(predicate);
if (sb == null)
{
sb = new StringBuilder("(");
parameters = new List<object>();
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;
}
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
@@ -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
{
/// <summary>
/// Override the base content repository so we can change the node object type
/// </summary>
/// <remarks>
/// 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
/// </remarks>
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;
}
}
@@ -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<IContent> _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<IContent>().Where(x => x.Published);
_publishedQuery = work.Query<IContent>().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<IContent> PermissionRepository => _permissionRepository
?? (_permissionRepository = new PermissionRepository<IContent>(UnitOfWork, _cacheHelper));
?? (_permissionRepository = new PermissionRepository<IContent>(UnitOfWork, _cacheHelper, Logger));
#region Overrides of RepositoryBase<IContent>
@@ -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<IContent>
public override IEnumerable<IContent> GetAllVersions(int id)
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
return MapQueryDtos(Database.Fetch<DocumentDto>(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<ContentVersionDto>("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
}
/// <summary>
/// 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
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
public void AssignEntityPermission(IContent entity, char permission, IEnumerable<int> userIds)
/// <param name="groupIds"></param>
public void AssignEntityPermission(IContent entity, char permission, IEnumerable<int> groupIds)
{
PermissionRepository.AssignEntityPermission(entity, permission, userIds);
PermissionRepository.AssignEntityPermission(entity, permission, groupIds);
}
public IEnumerable<EntityPermission> GetPermissionsForEntity(int entityId)
public EntityPermissionCollection GetPermissionsForEntity(int entityId)
{
return PermissionRepository.GetPermissionsForEntity(entityId);
}
/// <summary>
/// Used to add/update a permission for a content item
/// </summary>
/// <param name="permission"></param>
public void AddOrUpdatePermissions(ContentPermissionSet permission)
{
PermissionRepository.AddOrUpdate(permission);
}
/// <summary>
/// Gets paged content results
/// </summary>
@@ -137,6 +137,20 @@ namespace Umbraco.Core.Persistence.Repositories
return Database.Fetch<string>(sql);
}
public IEnumerable<int> GetAllContentTypeIds(string[] aliases)
{
if (aliases.Length == 0) return Enumerable.Empty<int>();
var sql = Sql()
.Select("cmsContentType.nodeId")
.From<ContentTypeDto>()
.InnerJoin<NodeDto>()
.On<ContentTypeDto, NodeDto>(dto => dto.NodeId, dto => dto.NodeId)
.Where<ContentTypeDto>(dto => aliases.Contains(dto.Alias));
return Database.Fetch<int>(sql);
}
protected override Sql<SqlContext> GetBaseQuery(bool isCount)
{
var sql = Sql();
@@ -1252,7 +1252,7 @@ WHERE cmsContent.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer",
var list = new List<string>
{
"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",
@@ -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<User2NodeNotifyDto>("WHERE nodeId = @Id", new { Id = entity.Id });
//Remove Permissions
Database.Delete<User2NodePermissionDto>("WHERE nodeId = @Id", new { Id = entity.Id });
Database.Delete<UserGroup2NodePermissionDto>("WHERE nodeId = @Id", new { Id = entity.Id });
//Remove associated tags
Database.Delete<TagRelationshipDto>("WHERE nodeId = @Id", new { Id = entity.Id });
@@ -41,17 +41,17 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable<IUmbracoEntity> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectTypeId, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, IQuery<IUmbracoEntity> 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<IUmbracoEntity> 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<IUmbracoEntity> 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<IUmbracoEntity> PerformGetAll(Guid objectTypeId, Action<Sql> 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<EntityPath> 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<EntityPath> 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<EntityPath> PerformGetAllPaths(Guid objectTypeId, Action<Sql> 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<EntityPath>(sql);
}
public virtual IEnumerable<IUmbracoEntity> GetByQuery(IQuery<IUmbracoEntity> query)
{
var sqlClause = GetBase(false, false, null);
@@ -306,8 +327,8 @@ namespace Umbraco.Core.Persistence.Repositories
public virtual IEnumerable<IUmbracoEntity> GetByQuery(IQuery<IUmbracoEntity> 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);
@@ -39,18 +39,31 @@ namespace Umbraco.Core.Persistence.Repositories
IEnumerable<IContent> GetByPublishedVersion(IQuery<IContent> query);
/// <summary>
/// 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
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
void AssignEntityPermission(IContent entity, char permission, IEnumerable<int> userIds);
/// <param name="groupIds"></param>
void AssignEntityPermission(IContent entity, char permission, IEnumerable<int> groupIds);
/// <summary>
/// Gets the list of permissions for the content item
/// Gets the explicit list of permissions for the content item
/// </summary>
/// <param name="entityId"></param>
/// <returns></returns>
IEnumerable<EntityPermission> GetPermissionsForEntity(int entityId);
EntityPermissionCollection GetPermissionsForEntity(int entityId);
///// <summary>
///// Gets the implicit/inherited list of permissions for the content item
///// </summary>
///// <param name="path"></param>
///// <returns></returns>
//IEnumerable<EntityPermission> GetPermissionsForPath(string path);
/// <summary>
/// Used to add/update a permission for a content item
/// </summary>
/// <param name="permission"></param>
void AddOrUpdatePermissions(ContentPermissionSet permission);
}
}
@@ -29,5 +29,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// </param>
/// <returns></returns>
IEnumerable<string> GetAllContentTypeAliases(params Guid[] objectTypes);
IEnumerable<int> GetAllContentTypeIds(string[] aliases);
}
}
@@ -23,6 +23,8 @@ namespace Umbraco.Core.Persistence.Repositories
UmbracoObjectTypes GetObjectType(int id);
UmbracoObjectTypes GetObjectType(Guid key);
IEnumerable<EntityPath> GetAllPaths(Guid objectTypeId, params int[] ids);
IEnumerable<EntityPath> GetAllPaths(Guid objectTypeId, params Guid[] keys);
/// <summary>
/// Gets paged results
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IUserGroupRepository : IRepositoryQueryable<int, IUserGroup>
{
/// <summary>
/// Gets a group by it's alias
/// </summary>
/// <param name="alias"></param>
/// <returns></returns>
IUserGroup Get(string alias);
/// <summary>
/// This is useful when an entire section is removed from config
/// </summary>
/// <param name="sectionAlias"></param>
IEnumerable<IUserGroup> GetGroupsAssignedToSection(string sectionAlias);
/// <summary>
/// Used to add or update a user group and assign users to it
/// </summary>
/// <param name="userGroup"></param>
/// <param name="userIds"></param>
void AddOrUpdateGroupWithUsers(IUserGroup userGroup, int[] userIds);
/// <summary>
/// Gets explicilty defined permissions for the group for specified entities
/// </summary>
/// <param name="groupIds"></param>
/// <param name="entityIds">Array of entity Ids, if empty will return permissions for the group for all entities</param>
EntityPermissionCollection GetPermissions(int[] groupIds, params int[] entityIds);
/// <summary>
/// Gets explicilt and default permissions (if requested) permissions for the group for specified entities
/// </summary>
/// <param name="groups"></param>
/// <param name="fallbackToDefaultPermissions">If true will include the group's default permissions if no permissions are explicitly assigned</param>
/// <param name="nodeIds">Array of entity Ids, if empty will return permissions for the group for all entities</param>
EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds);
/// <summary>
/// Replaces the same permission set for a single group to any number of entities
/// </summary>
/// <param name="groupId">Id of group</param>
/// <param name="permissions">Permissions as enumerable list of <see cref="char"/></param>
/// <param name="entityIds">Specify the nodes to replace permissions for. If nothing is specified all permissions are removed.</param>
void ReplaceGroupPermissions(int groupId, IEnumerable<char> permissions, params int[] entityIds);
/// <summary>
/// Assigns the same permission set for a single group to any number of entities
/// </summary>
/// <param name="groupId">Id of group</param>
/// <param name="permission">Permissions as enumerable list of <see cref="char"/></param>
/// <param name="entityIds">Specify the nodes to replace permissions for</param>
void AssignGroupPermission(int groupId, char permission, params int[] entityIds);
}
}
@@ -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
/// <param name="username"></param>
/// <returns></returns>
bool Exists(string username);
/// <summary>
/// This is useful when an entire section is removed from config
/// Gets a list of <see cref="IUser"/> objects associated with a given group
/// </summary>
/// <param name="sectionAlias"></param>
IEnumerable<IUser> GetUsersAssignedToSection(string sectionAlias);
/// <param name="groupId">Id of group</param>
IEnumerable<IUser> GetAllInGroup(int groupId);
/// <summary>
/// Gets paged member results
/// Gets a list of <see cref="IUser"/> objects not associated with a given group
/// </summary>
/// <param name="groupId">Id of group</param>
IEnumerable<IUser> GetAllNotInGroup(int groupId);
[Obsolete("Use the overload with long operators instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, int pageIndex, int pageSize, out int totalRecords, Expression<Func<IUser, string>> orderBy);
/// <summary>
/// Gets paged user results
/// </summary>
/// <param name="query"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderBy"></param>
/// <param name="filter"></param>
/// <param name="orderDirection"></param>
/// <param name="userGroups">Optional parameter to filter by specified user groups</param>
/// <param name="userState">Optional parameter to filter by specfied user state</param>
/// <returns></returns>
IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, long pageIndex, int pageSize, out long totalRecords, Expression<Func<IUser, string>> orderBy);
IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, long pageIndex, int pageSize, out long totalRecords, Expression<Func<IUser, object>> orderBy, Direction orderDirection, string[] userGroups = null, UserState[] userState = null, IQuery<IUser> filter = null);
/// <summary>
/// Gets the user permissions for the specified entities
/// Returns a user by username
/// </summary>
/// <param name="userId"></param>
/// <param name="entityIds"></param>
/// <returns></returns>
IEnumerable<EntityPermission> GetUserPermissionsForEntities(int userId, params int[] entityIds);
/// <param name="username"></param>
/// <param name="includeSecurityData">
/// This is only used for a shim in order to upgrade to 7.7
/// </param>
/// <returns>
/// A non cached <see cref="IUser"/> instance
/// </returns>
IUser GetByUsername(string username, bool includeSecurityData);
/// <summary>
/// Replaces the same permission set for a single user to any number of entities
/// Returns a user by id
/// </summary>
/// <param name="userId"></param>
/// <param name="permissions"></param>
/// <param name="entityIds"></param>
void ReplaceUserPermissions(int userId, IEnumerable<char> permissions, params int[] entityIds);
/// <param name="id"></param>
/// <param name="includeSecurityData">
/// This is only used for a shim in order to upgrade to 7.7
/// </param>
/// <returns>
/// A non cached <see cref="IUser"/> instance
/// </returns>
IUser Get(int id, bool includeSecurityData);
/// <summary>
/// Assigns the same permission set for a single user to any number of entities
/// </summary>
/// <param name="userId"></param>
/// <param name="permission"></param>
/// <param name="entityIds"></param>
void AssignUserPermission(int userId, char permission, params int[] entityIds);
IProfile GetProfile(string username);
IProfile GetProfile(int id);
IDictionary<UserState, int> GetUserStates();
}
}
@@ -1,10 +0,0 @@
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IUserTypeRepository : IUnitOfWorkRepository, IQueryRepository<int, IUserType>
{
}
}
@@ -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",
@@ -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",
@@ -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<IMembershipUser>
public override IEnumerable<IMember> GetAllVersions(int id)
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
return MapQueryDtos(Database.Fetch<MemberDto>(sql), true);
}
public override IMember GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
@@ -185,7 +185,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable<string> GetDeleteClauses()
{
var l = (List<string>)base.GetDeleteClauses(); // we know it's a list
var l = (List<string>) 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");
@@ -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
/// </summary>
/// <typeparam name="TEntity"></typeparam>
internal class PermissionRepository<TEntity>
/// <remarks>
/// This repo implements the base <see cref="NPocoRepositoryBase{TId,TEntity}"/> 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 <see cref="NotImplementedException"/>
/// </remarks>
internal class PermissionRepository<TEntity> : NPocoRepositoryBase<int, ContentPermissionSet>
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<EntityPermission>();
}
private ISqlSyntaxProvider SqlSyntax => _unitOfWork.SqlSyntax;
public PermissionRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger)
: base(work, cache, logger)
{ }
/// <summary>
/// Returns permissions for a given user for any number of nodes
/// Returns explicitly defined permissions for a user group for any number of nodes
/// </summary>
/// <param name="userId"></param>
/// <param name="groupIds">
/// The group ids to lookup permissions for
/// </param>
/// <param name="entityIds"></param>
/// <returns></returns>
public IEnumerable<EntityPermission> GetUserPermissionsForEntities(int userId, params int[] entityIds)
{
var entityIdKey = string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture)));
return _runtimeCache.GetCacheItem<IEnumerable<EntityPermission>>(
string.Format("{0}{1}{2}", CacheKeys.UserPermissionsCacheKey, userId, entityIdKey),
() =>
/// <remarks>
/// This method will not support passing in more than 2000 group Ids
/// </remarks>
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<User2NodePermissionDto>()
.Where(whereBuilder.ToString());
//ToArray() to ensure it's all fetched from the db once.
var result = _unitOfWork.Database.Fetch<User2NodePermissionDto>(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<UserGroup2NodePermissionDto>()
.Where<UserGroup2NodePermissionDto>(dto => localIds.Contains(dto.UserGroupId));
var permissions = UnitOfWork.Database.Fetch<UserGroup2NodePermissionDto>(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<UserGroup2NodePermissionDto>()
.Where<UserGroup2NodePermissionDto>(dto => localIds.Contains(dto.UserGroupId) && ids.Contains(dto.NodeId));
var permissions = UnitOfWork.Database.Fetch<UserGroup2NodePermissionDto>(sql);
foreach (var permission in ConvertToPermissionList(permissions))
{
result.Add(permission);
}
}
}
}
return result;
}
/// <summary>
/// Returns permissions for all users for a given entity
/// Returns permissions directly assigned to the content items for all user groups
/// </summary>
/// <param name="entityId"></param>
/// <param name="entityIds"></param>
/// <returns></returns>
public IEnumerable<EntityPermission> GetPermissionsForEntity(int entityId)
public IEnumerable<EntityPermission> GetPermissionsForEntities(int[] entityIds)
{
var sql = _unitOfWork.Sql()
var sql = Sql()
.SelectAll()
.From<User2NodePermissionDto>()
.Where<User2NodePermissionDto>(dto => dto.NodeId == entityId)
.OrderBy<User2NodePermissionDto>(dto => dto.NodeId);
.From<UserGroup2NodePermissionDto>()
.Where<UserGroup2NodePermissionDto>(dto => entityIds.Contains(dto.NodeId))
.OrderBy<UserGroup2NodePermissionDto>(dto => dto.NodeId);
//ToArray() to ensure it's all fetched from the db once.
var result = _unitOfWork.Database.Fetch<User2NodePermissionDto>(sql).ToArray();
var result = UnitOfWork.Database.Fetch<UserGroup2NodePermissionDto>(sql);
return ConvertToPermissionList(result);
}
/// <summary>
/// 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
/// </summary>
/// <param name="userId"></param>
/// <param name="entityId"></param>
/// <returns></returns>
public EntityPermissionCollection GetPermissionsForEntity(int entityId)
{
var sql = Sql()
.SelectAll()
.From<UserGroup2NodePermissionDto>()
.Where<UserGroup2NodePermissionDto>(dto => dto.NodeId == entityId)
.OrderBy<UserGroup2NodePermissionDto>(dto => dto.NodeId);
var result = UnitOfWork.Database.Fetch<UserGroup2NodePermissionDto>(sql);
return ConvertToPermissionList(result);
}
/// <summary>
/// Assigns the same permission set for a single group to any number of entities
/// </summary>
/// <param name="groupId"></param>
/// <param name="permissions"></param>
/// <param name="entityIds"></param>
/// <remarks>
/// This will first clear the permissions for this user and entities and recreate them
/// </remarks>
public void ReplaceUserPermissions(int userId, IEnumerable<char> permissions, params int[] entityIds)
public void ReplacePermissions(int groupId, IEnumerable<char> 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<User2NodePermissionDto>();
var toInsert = new List<UserGroup2NodePermissionDto>();
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<EntityPermission>(ConvertToPermissionList(toInsert), false));
}
/// <summary>
/// Assigns one permission for a user to many entities
/// </summary>
/// <param name="userId"></param>
/// <param name="groupId"></param>
/// <param name="permission"></param>
/// <param name="entityIds"></param>
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<EntityPermission>(ConvertToPermissionList(actions), false));
}
/// <summary>
/// Assigns one permission to an entity for multiple users
/// Assigns one permission to an entity for multiple groups
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
public void AssignEntityPermission(TEntity entity, char permission, IEnumerable<int> userIds)
/// <param name="groupIds"></param>
public void AssignEntityPermission(TEntity entity, char permission, IEnumerable<int> 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<EntityPermission>(ConvertToPermissionList(actions), false));
}
/// <summary>
/// Assigns permissions to an entity for multiple users/permission entries
/// Assigns permissions to an entity for multiple group/permission entries
/// </summary>
/// <param name="permissionSet">
/// </param>
@@ -223,41 +227,109 @@ namespace Umbraco.Core.Persistence.Repositories
/// </remarks>
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<UserGroup2NodePermissionDto>();
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<EntityPermission>(ConvertToPermissionList(actions), false));
db.BulkInsertRecords(toInsert);
}
private static IEnumerable<EntityPermission> ConvertToPermissionList(IEnumerable<User2NodePermissionDto> result)
#region Not implemented (don't need to for the purposes of this repo)
protected override ContentPermissionSet PerformGet(int id)
{
var permissions = new List<EntityPermission>();
throw new WontImplementException();
}
protected override IEnumerable<ContentPermissionSet> PerformGetAll(params int[] ids)
{
throw new WontImplementException();
}
protected override IEnumerable<ContentPermissionSet> PerformGetByQuery(IQuery<ContentPermissionSet> query)
{
throw new WontImplementException();
}
protected override Sql<SqlContext> GetBaseQuery(bool isCount)
{
throw new WontImplementException();
}
protected override string GetBaseWhereClause()
{
throw new WontImplementException();
}
protected override IEnumerable<string> GetDeleteClauses()
{
return new List<string>();
}
protected override Guid NodeObjectTypeId => throw new WontImplementException();
protected override void PersistDeletedItem(ContentPermissionSet entity)
{
throw new WontImplementException();
}
#endregion
/// <summary>
/// Used to add or update entity permissions during a content item being updated
/// </summary>
/// <param name="entity"></param>
protected override void PersistNewItem(ContentPermissionSet entity)
{
//does the same thing as update
PersistUpdatedItem(entity);
}
/// <summary>
/// Used to add or update entity permissions during a content item being updated
/// </summary>
/// <param name="entity"></param>
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<UserGroup2NodePermissionDto> 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<PermissionRepository<TEntity>, SaveEventArgs<EntityPermission>> AssignedPermissions;
}
}
@@ -66,7 +66,7 @@ namespace Umbraco.Core.Persistence.Repositories
return Database.Fetch<TDto>(sql).Select(ConvertToEntity);
}
protected override sealed IEnumerable<TEntity> PerformGetByQuery(IQuery<TEntity> query)
protected sealed override IEnumerable<TEntity> PerformGetByQuery(IQuery<TEntity> query)
{
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<TEntity>(sqlClause, query);
@@ -76,22 +76,22 @@ namespace Umbraco.Core.Persistence.Repositories
#region Not implemented and not required
protected override sealed IEnumerable<string> GetDeleteClauses()
protected sealed override IEnumerable<string> 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();
}
@@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories
var list = new List<string>
{
"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",
@@ -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
{
/// <summary>
/// Represents the UserGroupRepository for doing CRUD operations for <see cref="IUserGroup"/>
/// </summary>
internal class UserGroupRepository : NPocoRepositoryBase<int, IUserGroup>, IUserGroupRepository
{
private readonly UserGroupWithUsersRepository _userGroupWithUsersRepository;
private readonly PermissionRepository<IContent> _permissionRepository;
public UserGroupRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger)
: base(work, cacheHelper, logger)
{
_userGroupWithUsersRepository = new UserGroupWithUsersRepository(this, work, cacheHelper, logger);
_permissionRepository = new PermissionRepository<IContent>(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<int>(GetByAliasCacheKey(alias), () =>
{
var groupId = Database.ExecuteScalar<int?>("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<IUserGroup> 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<UserGroupDto>(sql));
}
public void AddOrUpdateGroupWithUsers(IUserGroup userGroup, int[] userIds)
{
_userGroupWithUsersRepository.AddOrUpdate(new UserGroupWithUsers(userGroup, userIds));
}
/// <summary>
/// Gets explicilty defined permissions for the group for specified entities
/// </summary>
/// <param name="groupIds"></param>
/// <param name="entityIds">Array of entity Ids, if empty will return permissions for the group for all entities</param>
public EntityPermissionCollection GetPermissions(int[] groupIds, params int[] entityIds)
{
return _permissionRepository.GetPermissionsForEntities(groupIds, entityIds);
}
/// <summary>
/// Gets explicilt and default permissions (if requested) permissions for the group for specified entities
/// </summary>
/// <param name="groups"></param>
/// <param name="fallbackToDefaultPermissions">If true will include the group's default permissions if no permissions are explicitly assigned</param>
/// <param name="nodeIds">Array of entity Ids, if empty will return permissions for the group for all entities</param>
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;
}
/// <summary>
/// Replaces the same permission set for a single group to any number of entities
/// </summary>
/// <param name="groupId">Id of group</param>
/// <param name="permissions">Permissions as enumerable list of <see cref="char"/> If nothing is specified all permissions are removed.</param>
/// <param name="entityIds">Specify the nodes to replace permissions for. </param>
public void ReplaceGroupPermissions(int groupId, IEnumerable<char> permissions, params int[] entityIds)
{
_permissionRepository.ReplacePermissions(groupId, permissions, entityIds);
}
/// <summary>
/// Assigns the same permission set for a single group to any number of entities
/// </summary>
/// <param name="groupId">Id of group</param>
/// <param name="permission">Permissions as enumerable list of <see cref="char"/></param>
/// <param name="entityIds">Specify the nodes to replace permissions for</param>
public void AssignGroupPermission(int groupId, char permission, params int[] entityIds)
{
_permissionRepository.AssignPermission(groupId, permission, entityIds);
}
#region Overrides of RepositoryBase<int,IUserGroup>
protected override IUserGroup PerformGet(int id)
{
var sql = GetBaseQuery(QueryType.Single);
sql.Where(GetBaseWhereClause(), new { Id = id });
AppendGroupBy(sql);
var dto = Database.Fetch<UserGroupDto>(sql).FirstOrDefault();
if (dto == null)
return null;
var userGroup = UserGroupFactory.BuildEntity(dto);
return userGroup;
}
protected override IEnumerable<IUserGroup> PerformGetAll(params int[] ids)
{
var sql = GetBaseQuery(QueryType.Many);
if (ids.Any())
sql.WhereIn<UserDto>(x => x.Id, ids);
else
sql.Where<UserGroupDto>(x => x.Id >= 0);
AppendGroupBy(sql);
var dtos = Database.Fetch<UserGroupDto>(sql);
return ConvertFromDtos(dtos);
}
protected override IEnumerable<IUserGroup> PerformGetByQuery(IQuery<IUserGroup> query)
{
var sqlClause = GetBaseQuery(QueryType.Many);
var translator = new SqlTranslator<IUserGroup>(sqlClause, query);
var sql = translator.Translate();
AppendGroupBy(sql);
var dtos = Database.Fetch<UserGroupDto>(sql);
return ConvertFromDtos(dtos);
}
#endregion
#region Overrides of NPocoRepositoryBase<int,IUserGroup>
protected Sql<SqlContext> GetBaseQuery(QueryType type)
{
var sql = Sql();
var addFrom = false;
switch (type)
{
case QueryType.Count:
sql
.SelectCount()
.From<UserGroupDto>();
break;
case QueryType.Ids:
sql
.Select("umbracoUserGroup.id");
addFrom = true;
break;
case QueryType.Single:
case QueryType.Many:
sql
.Select<UserGroupDto>(r => r.Select<UserGroup2AppDto>())
.Append(", COUNT(umbracoUser2UserGroup.UserId)"); // vs COUNT(umbracoUser.Id) - removes a JOIN
addFrom = true;
break;
default:
throw new NotSupportedException(type.ToString());
}
if (addFrom)
sql
.From<UserGroupDto>()
.LeftJoin<UserGroup2AppDto>()
.On<UserGroupDto, UserGroup2AppDto>(left => left.Id, right => right.UserGroupId)
.LeftJoin<User2UserGroupDto>()
.On<User2UserGroupDto, UserGroupDto>(left => left.UserGroupId, right => right.Id)
/*.LeftJoin<UserDto>()
.On<UserDto, User2UserGroupDto>(left => left.Id, right => right.UserId)*/;
return sql;
}
protected override Sql<SqlContext> GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? QueryType.Count : QueryType.Many);
// var sql = Sql();
// if (isCount)
// {
// sql.Select("COUNT(*)").From<UserGroupDto>();
// }
// 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<SqlContext> GetBaseQuery(string columns)
//{
// var sql = Sql()
// .Select(columns)
// .From<UserGroupDto>()
// .LeftJoin<UserGroup2AppDto>()
// .On<UserGroupDto, UserGroup2AppDto>(left => left.Id, right => right.UserGroupId)
// .LeftJoin<User2UserGroupDto>()
// .On<User2UserGroupDto, UserGroupDto>(left => left.UserGroupId, right => right.Id)
// .LeftJoin<UserDto>()
// .On<UserDto, User2UserGroupDto>(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<string> GetDeleteClauses()
{
var list = new List<string>
{
"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<UserGroup2AppDto>("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<IUserGroup> ConvertFromDtos(IEnumerable<UserGroupDto> dtos)
{
return dtos.Select(UserGroupFactory.BuildEntity);
}
/// <summary>
/// used to persist a user group with associated users at once
/// </summary>
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; }
}
/// <summary>
/// used to persist a user group with associated users at once
/// </summary>
private class UserGroupWithUsersRepository : NPocoRepositoryBase<int, UserGroupWithUsers>
{
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<UserGroupWithUsers> PerformGetAll(params int[] ids)
{
throw new WontImplementException();
}
protected override IEnumerable<UserGroupWithUsers> PerformGetByQuery(IQuery<UserGroupWithUsers> query)
{
throw new WontImplementException();
}
protected override Sql<SqlContext> GetBaseQuery(bool isCount)
{
throw new WontImplementException();
}
protected override string GetBaseWhereClause()
{
throw new WontImplementException();
}
protected override IEnumerable<string> 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);
}
/// <summary>
/// Removes all users from a group
/// </summary>
/// <param name="groupId">Id of group</param>
private void RemoveAllUsersFromGroup(int groupId)
{
Database.Delete<User2UserGroupDto>("WHERE userGroupId = @groupId", new { groupId });
}
/// <summary>
/// Adds a set of users to a group
/// </summary>
/// <param name="groupId">Id of group</param>
/// <param name="userIds">Ids of users</param>
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);
}
}
}
}
}
@@ -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<string, string> _passwordConfig;
private PermissionRepository<IContent> _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<string, string> 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<string, string> { { "hashAlgorithm", Membership.HashAlgorithmType } };
}
_passwordConfig = passwordConfig;
}
// note: is ok to 'new' the repo here as it's a sub-repo really
@@ -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
{
/// <summary>
/// Represents the UserTypeRepository for doing CRUD operations for <see cref="IUserType"/>
/// </summary>
internal class UserTypeRepository : NPocoRepositoryBase<int, IUserType>, IUserTypeRepository
{
public UserTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger)
: base(work, cache, logger)
{
}
#region Overrides of RepositoryBase<int,IUserType>
protected override IUserType PerformGet(int id)
{
return GetAll(new[] {id}).FirstOrDefault();
}
protected override IEnumerable<IUserType> 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<UserTypeDto>(x => x.Id >= 0);
}
var dtos = Database.Fetch<UserTypeDto>(sql);
return dtos.Select(userTypeFactory.BuildEntity).ToArray();
}
protected override IEnumerable<IUserType> PerformGetByQuery(IQuery<IUserType> query)
{
var userTypeFactory = new UserTypeFactory();
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IUserType>(sqlClause, query);
var sql = translator.Translate();
var dtos = Database.Fetch<UserTypeDto>(sql);
return dtos.Select(userTypeFactory.BuildEntity).ToArray();
}
#endregion
#region Overrides of NPocoRepositoryBase<int,IUserType>
protected override Sql<SqlContext> GetBaseQuery(bool isCount)
{
var sql = Sql();
sql = isCount
? sql.SelectCount()
: sql.Select<UserTypeDto>();
sql
.From<UserTypeDto>();
return sql;
}
protected override string GetBaseWhereClause()
{
return "umbracoUserType.id = @Id";
}
protected override IEnumerable<string> GetDeleteClauses()
{
var list = new List<string>
{
"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
}
}
@@ -50,22 +50,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
/// <param name="id">Id of the <see cref="TEntity"/> to retrieve versions from</param>
/// <returns>An enumerable list of the same <see cref="TEntity"/> object with different versions</returns>
public virtual IEnumerable<TEntity> GetAllVersions(int id)
{
var sql = Sql()
.SelectAll()
.From<ContentVersionDto>()
.InnerJoin<ContentDto>()
.On<ContentVersionDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
.Where<NodeDto>(x => x.NodeId == id)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
var dtos = Database.Fetch<ContentVersionDto>(sql);
return dtos.Select(x => GetByVersion(x.VersionId));
}
public abstract IEnumerable<TEntity> GetAllVersions(int id);
/// <summary>
/// Gets a list of all version Ids for the given content item ordered so latest is first
+165 -34
View File
@@ -23,13 +23,15 @@ namespace Umbraco.Core.Services
{
private readonly MediaFileSystem _mediaFileSystem;
private IQuery<IContent> _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
}
/// <summary>
/// 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
/// </summary>
/// <param name="entity"></param>
/// <param name="permission"></param>
/// <param name="userIds"></param>
public void AssignContentPermission(IContent entity, char permission, IEnumerable<int> userIds)
/// <param name="groupIds"></param>
public void AssignContentPermission(IContent entity, char permission, IEnumerable<int> groupIds)
{
using (var uow = UowProvider.CreateUnitOfWork())
{
uow.WriteLock(Constants.Locks.ContentTree);
var repo = uow.CreateRepository<IContentRepository>();
repo.AssignEntityPermission(entity, permission, userIds);
repo.AssignEntityPermission(entity, permission, groupIds);
uow.Complete();
}
}
}
/// <summary>
/// Gets the list of permissions for the content item
/// Returns implicit/inherited permissions assigned to the content item for all user groups
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public IEnumerable<EntityPermission> 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<IContentRepository>();
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<IContent>(content)))
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(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<IContent>(content, false));
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(content, false), "Saved");
uow.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshNode).ToEventArgs());
}
uow.Events.Dispatch(Created, this, new NewEventArgs<IContent>(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
/// <returns><see cref="IContent"/></returns>
public IContent GetById(Guid key)
{
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.ContentTree);
var repository = uow.CreateRepository<IContentRepository>();
var query = uow.Query<IContent>().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;
}
/// <summary>
@@ -942,7 +942,7 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(content, evtMsgs)))
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(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<IContent>(content, false, evtMsgs));
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(content, false, evtMsgs), "Saved");
var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode;
uow.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(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<IContent>(contentsA, evtMsgs)))
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(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<IContent>(contentsA, false, evtMsgs));
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(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<IContent>(itemsA)))
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(itemsA), "Saving"))
return false;
var published = new List<IContent>();
@@ -1855,7 +1867,7 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(saved, false));
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(saved, false), "Saved");
uow.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange<IContent>(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<IContent>(content)))
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(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<IContent>(content, false, evtMsgs));
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(content, false, evtMsgs), "Saved");
if (status.Success == false)
{
@@ -2234,6 +2246,16 @@ namespace Umbraco.Core.Services
/// </summary>
internal static event TypedEventHandler<IContentService, TreeChange<IContent>.EventArgs> TreeChanged;
/// <summary>
/// Occurs after a blueprint has been saved.
/// </summary>
public static event TypedEventHandler<IContentService, SaveEventArgs<IContent>> SavedBlueprint;
/// <summary>
/// Occurs after a blueprint has been deleted.
/// </summary>
public static event TypedEventHandler<IContentService, DeleteEventArgs<IContent>> 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<IContentBlueprintRepository>();
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<IContentBlueprintRepository>();
if (content.HasIdentity == false)
{
content.CreatorId = userId;
}
content.WriterId = userId;
repository.AddOrUpdate(content);
uow.Events.Dispatch(SavedBlueprint, this, new SaveEventArgs<IContent>(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<IContentBlueprintRepository>();
repository.Delete(content);
uow.Events.Dispatch(DeletedBlueprint, this, new DeleteEventArgs<IContent>(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<IContent> GetBlueprintsForContentTypes(params int[] documentTypeIds)
{
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
var repository = uow.CreateRepository<IContentBlueprintRepository>();
var query = uow.Query<IContent>();
if (documentTypeIds.Length > 0)
{
query.Where(x => documentTypeIds.Contains(x.ContentTypeId));
}
return repository.GetByQuery(query).Select(x =>
{
((Content) x).IsBlueprint = true;
return x;
});
}
}
#endregion
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Services
/// <param name="contentId"></param>
public static void RemoveContentPermissions(this IContentService contentService, int contentId)
{
contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, Enumerable.Empty<EntityPermissionSet.UserPermission>()));
contentService.ReplaceContentPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection()));
}
/// <summary>
@@ -54,7 +54,7 @@ namespace Umbraco.Core.Services
/// Gets all content type aliases accross content, media and member types.
/// </summary>
/// <param name="guids">Optional object types guid to restrict to content, and/or media, and/or member types.</param>
/// <returns>All property type aliases.</returns>
/// <returns>All content type aliases.</returns>
/// <remarks>Beware! Works accross content, media and member types.</remarks>
public IEnumerable<string> GetAllContentTypeAliases(params Guid[] guids)
{
@@ -66,5 +66,23 @@ namespace Umbraco.Core.Services
return repo.GetAllContentTypeAliases(guids);
}
}
/// <summary>
/// Gets all content type id for aliases accross content, media and member types.
/// </summary>
/// <param name="aliases">Aliases to look for.</param>
/// <returns>All content type ids.</returns>
/// <remarks>Beware! Works accross content, media and member types.</remarks>
public IEnumerable<int> 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<IContentTypeRepository>();
return repo.GetAllContentTypeIds(aliases);
}
}
}
}
+82 -62
View File
@@ -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<string, Tuple<UmbracoObjectTypes, Func<int, IUmbracoEntity>>> _supportedObjectTypes;
private IQuery<IUmbracoEntity> _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<string, Tuple<UmbracoObjectTypes, Func<int, IUmbracoEntity>>>
{
@@ -42,27 +44,6 @@ namespace Umbraco.Core.Services
{typeof (IMediaType).FullName, new Tuple<UmbracoObjectTypes, Func<int, IUmbracoEntity>>(UmbracoObjectTypes.MediaType, mediaTypeService.Get)},
{typeof (IMember).FullName, new Tuple<UmbracoObjectTypes, Func<int, IUmbracoEntity>>(UmbracoObjectTypes.Member, memberService.GetById)},
{typeof (IMemberType).FullName, new Tuple<UmbracoObjectTypes, Func<int, IUmbracoEntity>>(UmbracoObjectTypes.MemberType, memberTypeService.Get)},
//{typeof (IUmbracoEntity).FullName, new Tuple<UmbracoObjectTypes, Func<int, IUmbracoEntity>>(UmbracoObjectTypes.EntityContainer, id =>
//{
// using (var uow = UowProvider.GetUnitOfWork())
// {
// var found = uow.Database.FirstOrDefault<NodeDto>("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
/// <returns></returns>
public Attempt<int> GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
{
var result = _runtimeCache.GetCacheItem<int?>(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<NodeDto>()
.Where<NodeDto>(x => x.UniqueId == key && x.NodeObjectType == nodeObjectType);
return uow.Database.ExecuteScalar<int?>(sql);
}
});
return result.HasValue ? Attempt.Succeed(result.Value) : Attempt<int>.Fail();
public Attempt<int> GetIdForUdi(Udi udi)
{
return _idkMap.GetIdForUdi(udi);
}
/// <summary>
@@ -108,29 +80,7 @@ namespace Umbraco.Core.Services
/// <returns></returns>
public Attempt<Guid> GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
{
var result = _runtimeCache.GetCacheItem<Guid?>(CacheKeys.KeyToIdCacheKey + id, () =>
{
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
var nodeObjectType = GetNodeObjectTypeGuid(umbracoObjectType);
var sql = uow.Sql()
.Select("uniqueID")
.From<NodeDto>()
.Where<NodeDto>(x => x.NodeId == id && x.NodeObjectType == nodeObjectType);
return uow.Database.ExecuteScalar<Guid?>(sql);
}
});
return result.HasValue ? Attempt.Succeed(result.Value) : Attempt<Guid>.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<IUmbracoEntity> filterQuery = null;
if (filter.IsNullOrWhiteSpace() == false)
{
filterQuery = repository.Query.Where(x => x.Name.Contains(filter));
filterQuery = uow.Query<IUmbracoEntity>().Where(x => x.Name.Contains(filter));
}
var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery);
return contents;
}
}
/// <summary>
/// Returns a paged collection of descendants.
/// </summary>
public IEnumerable<IUmbracoEntity> GetPagedDescendants(IEnumerable<int> 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<IUmbracoEntity>();
var objectTypeId = umbracoObjectType.GetGuid();
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
var repository = uow.CreateRepository<IEntityRepository>();
var query = uow.Query<IUmbracoEntity>();
if (idsA.All(x => x != Constants.System.Root))
{
var clauses = new List<Expression<Func<IUmbracoEntity, bool>>>();
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<IUmbracoEntity> filterQuery = null;
if (filter.IsNullOrWhiteSpace() == false)
{
filterQuery = uow.Query<IUmbracoEntity>().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<EntityPath> 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<IEntityRepository>();
return repository.GetAllPaths(objectTypeId, ids);
}
}
public virtual IEnumerable<EntityPath> 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<IEntityRepository>();
return repository.GetAllPaths(objectTypeId, keys);
}
}
/// <summary>
/// Gets a collection of <see cref="IUmbracoEntity"/>
/// </summary>
+10 -8
View File
@@ -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
/// <returns><see cref="IMedia"/></returns>
public IMedia GetById(Guid key)
{
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.MediaTree);
var repository = uow.CreateRepository<IMediaRepository>();
var query = uow.Query<IMedia>().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;
}
/// <summary>
+29 -4
View File
@@ -582,17 +582,27 @@
<Compile Include="Models\MediaType.cs" />
<Compile Include="Models\Member.cs" />
<Compile Include="Models\MemberGroup.cs" />
<Compile Include="Models\Membership\ContentPermissionSet.cs" />
<Compile Include="Models\Membership\EntityPermission.cs" />
<Compile Include="Models\Membership\EntityPermissionCollection.cs" />
<Compile Include="Models\Membership\EntityPermissionSet.cs" />
<Compile Include="Models\Membership\IMembershipUser.cs" />
<Compile Include="Models\Membership\IProfile.cs" />
<Compile Include="Models\Membership\IReadOnlyUserGroup.cs" />
<Compile Include="Models\Membership\IUser.cs" />
<Compile Include="Models\Membership\IUserGroup.cs" />
<Compile Include="Models\Membership\IUserType.cs" />
<Compile Include="Models\Membership\MemberCountType.cs" />
<Compile Include="Models\Membership\MembershipScenario.cs" />
<Compile Include="Models\Membership\MembershipUserExtensions.cs" />
<Compile Include="Models\Membership\ReadOnlyUserGroup.cs" />
<Compile Include="Models\Membership\UmbracoMembershipMember.cs" />
<Compile Include="Models\Membership\UmbracoMembershipUser.cs" />
<Compile Include="Models\Membership\User.cs" />
<Compile Include="Models\Membership\UserGroup.cs" />
<Compile Include="Models\Membership\UserGroupExtensions.cs" />
<Compile Include="Models\Membership\UserProfile.cs" />
<Compile Include="Models\Membership\UserState.cs" />
<Compile Include="Models\Membership\UserType.cs" />
<Compile Include="Models\MemberType.cs" />
<Compile Include="Models\MemberTypePropertyProfileAccess.cs" />
@@ -691,10 +701,13 @@
<Compile Include="Models\Rdbms\TaskDto.cs" />
<Compile Include="Models\Rdbms\TaskTypeDto.cs" />
<Compile Include="Models\Rdbms\TemplateDto.cs" />
<Compile Include="Models\Rdbms\User2AppDto.cs" />
<Compile Include="Models\Rdbms\User2NodeNotifyDto.cs" />
<Compile Include="Models\Rdbms\User2NodePermissionDto.cs" />
<Compile Include="Models\Rdbms\User2UserGroupDto.cs" />
<Compile Include="Models\Rdbms\UserDto.cs" />
<Compile Include="Models\Rdbms\UserGroup2AppDto.cs" />
<Compile Include="Models\Rdbms\UserGroup2NodePermissionDto.cs" />
<Compile Include="Models\Rdbms\UserGroupDto.cs" />
<Compile Include="Models\Rdbms\UserStartNodeDto.cs" />
<Compile Include="Models\Rdbms\UserTypeDto.cs" />
<Compile Include="Models\RedirectUrl.cs" />
<Compile Include="Models\Relation.cs" />
@@ -798,7 +811,7 @@
<Compile Include="Persistence\Factories\TemplateFactory.cs" />
<Compile Include="Persistence\Factories\UmbracoEntityFactory.cs" />
<Compile Include="Persistence\Factories\UserFactory.cs" />
<Compile Include="Persistence\Factories\UserTypeFactory.cs" />
<Compile Include="Persistence\Factories\UserGroupFactory.cs" />
<Compile Include="Persistence\FaultHandling\ITransientErrorDetectionStrategy.cs" />
<Compile Include="Persistence\FaultHandling\RetryingEventArgs.cs" />
<Compile Include="Persistence\FaultHandling\RetryLimitExceededException.cs" />
@@ -824,6 +837,7 @@
<Compile Include="Persistence\Mappers\DictionaryTranslationMapper.cs" />
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
<Compile Include="Persistence\Mappers\DtoMapModel.cs" />
<Compile Include="Persistence\Mappers\ExternalLoginMapper.cs" />
<Compile Include="Persistence\Mappers\IMapperCollection.cs" />
<Compile Include="Persistence\Mappers\LanguageMapper.cs" />
<Compile Include="Persistence\Mappers\MacroMapper.cs" />
@@ -847,6 +861,7 @@
<Compile Include="Persistence\Mappers\TaskTypeMapper.cs" />
<Compile Include="Persistence\Mappers\TemplateMapper.cs" />
<Compile Include="Persistence\Mappers\UmbracoEntityMapper.cs" />
<Compile Include="Persistence\Mappers\UserGroupMapper.cs" />
<Compile Include="Persistence\Mappers\UserMapper.cs" />
<Compile Include="Persistence\Mappers\UserSectionMapper.cs" />
<Compile Include="Persistence\Mappers\UserTypeMapper.cs" />
@@ -1079,6 +1094,7 @@
<Compile Include="Persistence\Querying\ValuePropertyMatchType.cs" />
<Compile Include="Persistence\RecordPersistenceType.cs" />
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
<Compile Include="Persistence\Repositories\ContentBlueprintRepository.cs" />
<Compile Include="Persistence\Repositories\ContentRepository.cs" />
<Compile Include="Persistence\Repositories\ContentTypeRepository.cs" />
<Compile Include="Persistence\Repositories\ContentTypeRepositoryBase.cs" />
@@ -1285,17 +1301,23 @@
<Compile Include="Security\BackOfficeUserManagerMarker.cs" />
<Compile Include="Security\BackOfficeUserPasswordCheckerResult.cs" />
<Compile Include="Security\BackOfficeUserStore.cs" />
<Compile Include="Security\BackOfficeUserValidator.cs" />
<Compile Include="Security\EmailService.cs" />
<Compile Include="Security\IBackOfficeUserManagerMarker.cs" />
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\IMembershipProviderPasswordHasher.cs" />
<Compile Include="Security\IUmbracoMemberTypeMembershipProvider.cs" />
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
<Compile Include="Security\IUsersMembershipProvider.cs" />
<Compile Include="Security\MembershipPasswordHasher.cs" />
<Compile Include="Security\MachineKeyGenerator.cs" />
<Compile Include="Security\MembershipProviderBase.cs" />
<Compile Include="Security\MembershipProviderExtensions.cs" />
<Compile Include="Security\MembershipProviderPasswordHasher.cs" />
<Compile Include="Security\MembershipProviderPasswordValidator.cs" />
<Compile Include="Security\OwinExtensions.cs" />
<Compile Include="Security\UmbracoBackOfficeIdentity.cs" />
<Compile Include="Security\UmbracoMembershipProviderBase.cs" />
<Compile Include="Security\UserAwareMembershipProviderPasswordHasher.cs" />
<Compile Include="Security\UserData.cs" />
<Compile Include="SemVersionExtensions.cs" />
<Compile Include="Serialization\AbstractSerializationService.cs" />
@@ -1344,6 +1366,7 @@
<Compile Include="Services\IContentTypeService.cs" />
<Compile Include="Services\IContentTypeServiceBase.cs" />
<Compile Include="Services\IDataTypeService.cs" />
<Compile Include="Services\IdkMap.cs" />
<Compile Include="Services\IDomainService.cs" />
<Compile Include="Services\IEntityService.cs" />
<Compile Include="Services\IExternalLoginService.cs" />
@@ -1494,6 +1517,8 @@
</ItemGroup>
<ItemGroup>
<None Include="FileResources\BlockingWeb.config" />
<None Include="Models\Rdbms\UserDto.cs.orig" />
<None Include="Models\Rdbms\UserTypeDto.cs.orig" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>