Port 7.7 - WIP

This commit is contained in:
Stephan
2017-09-18 15:33:13 +02:00
parent 9a28250a8d
commit d54658009c
54 changed files with 1423 additions and 401 deletions
+1
View File
@@ -70,6 +70,7 @@ src/Umbraco.Web.UI/[Ww]eb.config
*.transformed
node_modules
lib-bower
src/Umbraco.Web.UI/[Uu]mbraco/[Ll]ib/*
src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/umbraco.*
@@ -9,7 +9,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Configuration
{
{
/// <summary>
/// A utility class for working with CDF config and cache files - use sparingly!
/// </summary>
@@ -4,6 +4,7 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero
{
[Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)]
[Migration("8.0.0", 5, Constants.System.UmbracoMigrationName)]
public class AddIndexToDictionaryKeyColumn : MigrationBase
{
public AddIndexToDictionaryKeyColumn(IMigrationContext context)
@@ -1,20 +1,39 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero
{
[Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)]
[Migration("8.0.0", 1, Constants.System.UmbracoMigrationName)]
public class AddUserGroupTables : MigrationBase
{
private readonly string _collateSyntax;
public AddUserGroupTables(IMigrationContext context)
: base(context)
{ }
{
//For some of the migration data inserts we require to use a special MSSQL collate expression since
//some databases may have a custom collation specified and if that is the case, when we compare strings
//in dynamic SQL it will try to compare strings in different collations and this will yield errors.
_collateSyntax = SqlSyntax is MySqlSyntaxProvider || SqlSyntax is SqlCeSyntaxProvider
? string.Empty
: "COLLATE DATABASE_DEFAULT";
}
public override void Up()
{
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToList();
var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray();
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
//In some very rare cases, there might alraedy be user group tables that we'll need to remove first
//but of course we don't want to remove the tables we will be creating below if they already exist so
//need to do some checks first since these old rare tables have a different schema
RemoveOldTablesIfExist(tables, columns);
if (AddNewTables(tables))
{
@@ -23,6 +42,83 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
DeleteOldTables(tables, constraints);
SetDefaultIcons();
}
else
{
//if we aren't adding the tables, make sure that the umbracoUserGroup table has the correct FKs - these
//were added after the beta release so we need to do some cleanup
//if the FK doesn't exist
if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup")
&& x.Item2.InvariantEquals("startContentId")
&& x.Item3.InvariantEquals("FK_startContentId_umbracoNode_id")) == false)
{
//before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta
//release if a start node was assigned and then that start node was deleted.
Execute.Sql(@"UPDATE umbracoUserGroup SET startContentId = NULL WHERE startContentId NOT IN (SELECT id FROM umbracoNode)");
Create.ForeignKey("FK_startContentId_umbracoNode_id")
.FromTable("umbracoUserGroup")
.ForeignColumn("startContentId")
.ToTable("umbracoNode")
.PrimaryColumn("id")
.OnDelete(Rule.None)
.OnUpdate(Rule.None);
}
if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUserGroup")
&& x.Item2.InvariantEquals("startMediaId")
&& x.Item3.InvariantEquals("FK_startMediaId_umbracoNode_id")) == false)
{
//before we add any foreign key we need to make sure there's no stale data in there which would have happened in the beta
//release if a start node was assigned and then that start node was deleted.
Execute.Sql(@"UPDATE umbracoUserGroup SET startMediaId = NULL WHERE startMediaId NOT IN (SELECT id FROM umbracoNode)");
Create.ForeignKey("FK_startMediaId_umbracoNode_id")
.FromTable("umbracoUserGroup")
.ForeignColumn("startMediaId")
.ToTable("umbracoNode")
.PrimaryColumn("id")
.OnDelete(Rule.None)
.OnUpdate(Rule.None);
}
}
}
/// <summary>
/// In some very rare cases, there might alraedy be user group tables that we'll need to remove first
/// but of course we don't want to remove the tables we will be creating below if they already exist so
/// need to do some checks first since these old rare tables have a different schema
/// </summary>
/// <param name="tables"></param>
/// <param name="columns"></param>
private void RemoveOldTablesIfExist(List<string> tables, ColumnInfo[] columns)
{
if (tables.Contains("umbracoUser2userGroup", StringComparer.InvariantCultureIgnoreCase))
{
//this column doesn't exist in the 7.7 schema, so if it's there, then this is a super old table
var foundOldColumn = columns
.FirstOrDefault(x =>
x.ColumnName.Equals("user", StringComparison.InvariantCultureIgnoreCase)
&& x.TableName.Equals("umbracoUser2userGroup", StringComparison.InvariantCultureIgnoreCase));
if (foundOldColumn != null)
{
Delete.Table("umbracoUser2userGroup");
//remove from the tables list since this will be re-checked in further logic
tables.Remove("umbracoUser2userGroup");
}
}
if (tables.Contains("umbracoUserGroup", StringComparer.InvariantCultureIgnoreCase))
{
//The new schema has several columns, the super old one for this table only had 2 so if it's 2 get rid of it
var countOfCols = columns
.Count(x => x.TableName.Equals("umbracoUserGroup", StringComparison.InvariantCultureIgnoreCase));
if (countOfCols == 2)
{
Delete.Table("umbracoUserGroup");
//remove from the tables list since this will be re-checked in further logic
tables.Remove("umbracoUserGroup");
}
}
}
private void SetDefaultIcons()
@@ -33,7 +129,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
Execute.Sql("UPDATE umbracoUserGroup SET icon = \'icon-globe\' WHERE userGroupAlias = \'translator\'");
}
private bool AddNewTables(string[] tables)
private bool AddNewTables(List<string> tables)
{
var updated = false;
if (tables.InvariantContains("umbracoUserGroup") == false)
@@ -71,13 +167,15 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
FROM umbracoUserType");
// Add each user to the group created from their type
Execute.Sql(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId)
Execute.Sql(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId)
SELECT u.id, ug.id
FROM umbracoUser u
INNER JOIN umbracoUserType ut ON ut.id = u.userType
INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias = ut.userTypeAlias");
INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = ut.userTypeAlias {0}", _collateSyntax));
// Add the built-in administrator account to all apps
// this will lookup all of the apps that the admin currently has access to in order to assign the sections
// instead of use statically assigning since there could be extra sections we don't know about.
Execute.Sql(@"INSERT INTO umbracoUserGroup2app (userGroupId,app)
SELECT ug.id, app
FROM umbracoUserGroup ug
@@ -86,6 +184,89 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id
WHERE u.id = 0");
// Add the default section access to the other built-in accounts
// writer:
Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app)
SELECT ug.id, 'content' as app
FROM umbracoUserGroup ug
WHERE ug.userGroupAlias {0} = 'writer' {0}", _collateSyntax));
// editor
Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app)
SELECT ug.id, 'content' as app
FROM umbracoUserGroup ug
WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax));
Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app)
SELECT ug.id, 'media' as app
FROM umbracoUserGroup ug
WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax));
// translator
Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app)
SELECT ug.id, 'translation' as app
FROM umbracoUserGroup ug
WHERE ug.userGroupAlias {0} = 'translator' {0}", _collateSyntax));
//We need to lookup all distinct combinations of section access and create a group for each distinct collection
//and assign groups accordingly. We'll perform the lookup 'now' to then create the queued SQL migrations.
var userAppsData = Context.Database.Query<dynamic>(@"SELECT u.id, u2a.app FROM umbracoUser u
INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id
ORDER BY u.id, u2a.app");
var usersWithApps = new Dictionary<int, List<string>>();
foreach (var userApps in userAppsData)
{
List<string> apps;
if (usersWithApps.TryGetValue(userApps.id, out apps) == false)
{
apps = new List<string> {userApps.app};
usersWithApps.Add(userApps.id, apps);
}
else
{
apps.Add(userApps.app);
}
}
//At this stage we have a dictionary of users with a collection of their apps which are sorted
//and we need to determine the unique/distinct app collections for each user to create groups with.
//We can do this by creating a hash value of all of the app values and since they are already sorted we can get a distinct
//collection by this hash.
var distinctApps = usersWithApps
.Select(x => new {appCollection = x.Value, appsHash = string.Join("", x.Value).GenerateHash()})
.DistinctBy(x => x.appsHash)
.ToArray();
//Now we need to create user groups for each of these distinct app collections, and then assign the corresponding users to those groups
for (var i = 0; i < distinctApps.Length; i++)
{
//create the group
var alias = "MigratedSectionAccessGroup_" + (i + 1);
Insert.IntoTable("umbracoUserGroup").Row(new
{
userGroupAlias = "MigratedSectionAccessGroup_" + (i + 1),
userGroupName = "Migrated Section Access Group " + (i + 1)
});
//now assign the apps
var distinctApp = distinctApps[i];
foreach (var app in distinctApp.appCollection)
{
Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app)
SELECT ug.id, '" + app + @"' as app
FROM umbracoUserGroup ug
WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax));
}
//now assign the corresponding users to this group
foreach (var userWithApps in usersWithApps)
{
//check if this user's groups hash matches the current groups hash
var hash = string.Join("", userWithApps.Value).GenerateHash();
if (hash == distinctApp.appsHash)
{
//it matches so assign the user to this group
Execute.Sql(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId)
SELECT " + userWithApps.Key + @", ug.id
FROM umbracoUserGroup ug
WHERE ug.userGroupAlias {0} = '" + alias + "' {0}", _collateSyntax));
}
}
}
// Rename some groups for consistency (plural form)
Execute.Sql("UPDATE umbracoUserGroup SET userGroupName = 'Writers' WHERE userGroupAlias = 'writer'");
Execute.Sql("UPDATE umbracoUserGroup SET userGroupName = 'Translators' WHERE userGroupAlias = 'translator'");
@@ -105,49 +286,46 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
{
// Create user group records for all non-admin users that have specific permissions set
Execute.Sql(@"INSERT INTO umbracoUserGroup(userGroupAlias, userGroupName)
SELECT userName + 'Group', 'Group for ' + userName
SELECT 'permissionGroupFor' + userLogin, 'Migrated Permission Group for ' + userLogin
FROM umbracoUser
WHERE (id IN (
SELECT " + SqlSyntax.GetQuotedColumnName("user") + @"
FROM umbracoUser2app
) OR id IN (
SELECT userid
FROM umbracoUser2NodePermission
))
AND id > 0");
// Associate those groups with the users
Execute.Sql(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId)
Execute.Sql(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId)
SELECT u.id, ug.id
FROM umbracoUser u
INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias = userName + 'Group'");
INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = 'permissionGroupFor' + userLogin {0}", _collateSyntax));
// Create node permissions on the groups
Execute.Sql(@"INSERT INTO umbracoUserGroup2NodePermission (userGroupId,nodeId,permission)
Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2NodePermission (userGroupId,nodeId,permission)
SELECT ug.id, nodeId, permission
FROM umbracoUserGroup ug
INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id
INNER JOIN umbracoUser u ON u.id = u2ug.userId
INNER JOIN umbracoUser2NodePermission u2np ON u2np.userId = u.id
WHERE ug.userGroupAlias NOT IN (
SELECT userTypeAlias
WHERE ug.userGroupAlias {0} NOT IN (
SELECT userTypeAlias {0}
FROM umbracoUserType
)");
)", _collateSyntax));
// Create app permissions on the groups
Execute.Sql(@"INSERT INTO umbracoUserGroup2app (userGroupId,app)
Execute.Sql(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId,app)
SELECT ug.id, app
FROM umbracoUserGroup ug
INNER JOIN umbracoUser2UserGroup u2ug ON u2ug.userGroupId = ug.id
INNER JOIN umbracoUser u ON u.id = u2ug.userId
INNER JOIN umbracoUser2app u2a ON u2a." + SqlSyntax.GetQuotedColumnName("user") + @" = u.id
WHERE ug.userGroupAlias NOT IN (
SELECT userTypeAlias
WHERE ug.userGroupAlias {0} NOT IN (
SELECT userTypeAlias {0}
FROM umbracoUserType
)");
)", _collateSyntax));
}
private void DeleteOldTables(string[] tables, Tuple<string, string, string>[] constraints)
private void DeleteOldTables(List<string> tables, Tuple<string, string, string>[] constraints)
{
if (tables.InvariantContains("umbracoUser2App"))
{
@@ -165,6 +343,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
{
Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser");
}
//This is the super old constraint name of the FK for user type so check this one too
if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_user_userType")))
{
Delete.ForeignKey("FK_user_userType").OnTable("umbracoUser");
}
Delete.Column("userType").FromTable("umbracoUser");
Delete.Table("umbracoUserType");
@@ -3,6 +3,7 @@ using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero
{
[Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)]
[Migration("8.0.0", 2, Constants.System.UmbracoMigrationName)]
public class AddUserStartNodeTable : MigrationBase
{
@@ -6,6 +6,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
/// <summary>
/// Ensures the built-in user groups have the blueprint permission by default on upgrade
/// </summary>
[Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)]
[Migration("8.0.0", 6, Constants.System.UmbracoMigrationName)]
public class EnsureContentTemplatePermissions : MigrationBase
{
@@ -3,6 +3,7 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero
{
[Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)]
[Migration("8.0.0", 4, Constants.System.UmbracoMigrationName)]
public class ReduceDictionaryKeyColumnsSize : MigrationBase
{
@@ -6,6 +6,7 @@ using Umbraco.Core.Security;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZero
{
[Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)]
[Migration("8.0.0", 0, Constants.System.UmbracoMigrationName)]
public class UpdateUserTables : MigrationBase
{
@@ -30,6 +31,9 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("invitedDate")) == false)
Create.Column("invitedDate").OnTable("umbracoUser").AsDateTime().Nullable();
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("avatar")) == false)
Create.Column("avatar").OnTable("umbracoUser").AsString(500).Nullable();
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("passwordConfig")) == false)
{
Create.Column("passwordConfig").OnTable("umbracoUser").AsString(500).Nullable();
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories
_publishedQuery = work.Query<IContent>().Where(x => x.Published); // fixme not used?
_contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger, syntaxProvider);
_contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger);
EnsureUniqueNaming = settings.EnsureUniqueNaming;
}
@@ -760,21 +760,19 @@ namespace Umbraco.Core.Persistence.Repositories
return content.HasPublishedVersion;
var syntaxUmbracoNode = SqlSyntax.GetQuotedTableName("umbracoNode");
var syntaxPath = SqlSyntax.GetQuotedColumnName("path");
var syntaxConcat = SqlSyntax.GetConcat(syntaxUmbracoNode + "." + syntaxPath, "',%'");
var ids = content.Path.Split(',').Skip(1).Select(int.Parse);
var sql = string.Format(@"SELECT COUNT({0}.{1})
FROM {0}
JOIN {2} ON ({0}.{1}={2}.{3} AND {2}.{4}=@published)
WHERE (@path LIKE {5})",
WHERE {0}.{1} IN (@ids)",
syntaxUmbracoNode,
SqlSyntax.GetQuotedColumnName("id"),
SqlSyntax.GetQuotedTableName("cmsDocument"),
SqlSyntax.GetQuotedColumnName("nodeId"),
SqlSyntax.GetQuotedColumnName("published"),
syntaxConcat);
SqlSyntax.GetQuotedColumnName("published"));
var count = Database.ExecuteScalar<int>(sql, new { @published=true, @path=content.Path });
var count = Database.ExecuteScalar<int>(sql, new { published=true, ids });
count += 1; // because content does not count
return count == content.Level;
}
@@ -810,54 +808,51 @@ WHERE (@path LIKE {5})",
/// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things!
/// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them
/// </remarks>
private class ContentByGuidReadRepository : PetaPocoRepositoryBase<Guid, IContent>
private class ContentByGuidReadRepository : NPocoRepositoryBase<Guid, IContent>
{
private readonly ContentRepository _outerRepo;
public ContentByGuidReadRepository(ContentRepository outerRepo,
IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
public ContentByGuidReadRepository(ContentRepository outerRepo, IScopeUnitOfWork work, CacheHelper cache, ILogger logger)
: base(work, cache, logger)
{
_outerRepo = outerRepo;
}
protected override IContent PerformGet(Guid id)
{
var sql = _outerRepo.GetBaseQuery(BaseQueryType.FullSingle)
var sql = _outerRepo.GetBaseQuery(QueryType.Single)
.Where(GetBaseWhereClause(), new { Id = id })
.Where<DocumentDto>(x => x.Newest, SqlSyntax)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
.Where<DocumentDto>(x => x.Newest)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
var dto = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
var dto = Database.Fetch<DocumentDto>(sql.SelectTop(1)).FirstOrDefault();
if (dto == null)
return null;
var content = _outerRepo.CreateContentFromDto(dto, sql);
var content = _outerRepo.CreateContentFromDto(dto, dto.ContentVersionDto.VersionId);
return content;
}
protected override IEnumerable<IContent> PerformGetAll(params Guid[] ids)
{
Func<Sql, Sql> translate = s =>
Sql<SqlContext> Translate(Sql<SqlContext> s)
{
if (ids.Any())
{
s.Where("umbracoNode.uniqueID in (@ids)", new { ids });
}
//we only want the newest ones with this method
s.Where<DocumentDto>(x => x.Newest, SqlSyntax);
s.Where<DocumentDto>(x => x.Newest);
return s;
};
}
var sqlBaseFull = _outerRepo.GetBaseQuery(BaseQueryType.FullMultiple);
var sqlBaseIds = _outerRepo.GetBaseQuery(BaseQueryType.Ids);
return _outerRepo.ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds)));
var sql = Translate(GetBaseQuery(false));
return _outerRepo.MapQueryDtos(Database.Fetch<DocumentDto>(sql), many: true);
}
protected override Sql GetBaseQuery(bool isCount)
protected override Sql<SqlContext> GetBaseQuery(bool isCount)
{
return _outerRepo.GetBaseQuery(isCount);
}
@@ -867,10 +862,7 @@ WHERE (@path LIKE {5})",
return "umbracoNode.uniqueID = @Id";
}
protected override Guid NodeObjectTypeId
{
get { return _outerRepo.NodeObjectTypeId; }
}
protected override Guid NodeObjectTypeId => _outerRepo.NodeObjectTypeId;
#region Not needed to implement
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.Querying;
@@ -1,4 +1,6 @@
using Umbraco.Core.Models;
using System;
using System.Xml.Linq;
using Umbraco.Core.Models;
namespace Umbraco.Core.Persistence.Repositories
{
@@ -22,6 +24,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
/// <param name="content"></param>
/// <param name="xml"></param>
void AddOrUpdatePreviewXml(IMedia content, Func<IMedia, XElement> xml);
void AddOrUpdatePreviewXml(IMedia content, Func<IMedia, XElement> xml);
}
}
@@ -56,7 +56,8 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns></returns>
IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, long pageIndex, int pageSize, out long totalRecords,
Expression<Func<IUser, object>> orderBy, Direction orderDirection = Direction.Ascending,
string[] userGroups = null, UserState[] userState = null, IQuery<IUser> filter = null);
string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null,
IQuery<IUser> filter = null);
/// <summary>
/// Returns a user by username
@@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
_mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
_mediaByGuidReadRepository = new MediaByGuidReadRepository(this, work, cache, logger, sqlSyntax);
_mediaByGuidReadRepository = new MediaByGuidReadRepository(this, work, cache, logger);
EnsureUniqueNaming = contentSection.EnsureUniqueNaming;
}
@@ -441,13 +441,13 @@ namespace Umbraco.Core.Persistence.Repositories
/// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things!
/// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them
/// </remarks>
private class MediaByGuidReadRepository : PetaPocoRepositoryBase<Guid, IMedia>
private class MediaByGuidReadRepository : NPocoRepositoryBase<Guid, IMedia>
{
private readonly MediaRepository _outerRepo;
public MediaByGuidReadRepository(MediaRepository outerRepo,
IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
IScopeUnitOfWork work, CacheHelper cache, ILogger logger)
: base(work, cache, logger)
{
_outerRepo = outerRepo;
}
@@ -456,14 +456,14 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = GetBaseQuery(false);
sql.Where(GetBaseWhereClause(), new { Id = id });
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
var dto = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
var dto = Database.Fetch<ContentVersionDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
if (dto == null)
return null;
var content = _outerRepo.CreateMediaFromDto(dto, sql);
var content = _outerRepo.CreateMediaFromDto(dto, dto.VersionId);
return content;
}
@@ -476,10 +476,10 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("umbracoNode.uniqueID in (@ids)", new { ids = ids });
}
return _outerRepo.ProcessQuery(sql, new PagingSqlQuery(sql));
return _outerRepo.MapQueryDtos(Database.Fetch<ContentVersionDto>(sql));
}
protected override Sql GetBaseQuery(bool isCount)
protected override Sql<SqlContext> GetBaseQuery(bool isCount)
{
return _outerRepo.GetBaseQuery(isCount);
}
@@ -489,10 +489,7 @@ namespace Umbraco.Core.Persistence.Repositories
return "umbracoNode.uniqueID = @Id";
}
protected override Guid NodeObjectTypeId
{
get { return _outerRepo.NodeObjectTypeId; }
}
protected override Guid NodeObjectTypeId => _outerRepo.NodeObjectTypeId;
#region Not needed to implement
@@ -294,7 +294,7 @@ namespace Umbraco.Core.PropertyEditors
//swallow this exception, we thought it was json but it really isn't so continue returning a string
}
}
return property.Value.ToString();
return asString;
case DataTypeDatabaseType.Integer:
case DataTypeDatabaseType.Decimal:
//Decimals need to be formatted with invariant culture (dots, not commas)
@@ -52,7 +52,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
// fall back on normal behaviour
return values.Any() == false
? sourceString.Split(Environment.NewLine.ToCharArray())
? sourceString.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
: values.ToArray();
}
@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -6,6 +7,8 @@ using System.Web.Security;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security.DataProtection;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Services;
@@ -23,18 +26,43 @@ namespace Umbraco.Core.Security
{
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use the constructor specifying all dependencies instead")]
public BackOfficeUserManager(
IUserStore<BackOfficeIdentityUser, int> store,
IdentityFactoryOptions<BackOfficeUserManager> options,
MembershipProviderBase membershipProvider)
: this(store, options, membershipProvider, UmbracoConfig.For.UmbracoSettings().Content)
{
}
public BackOfficeUserManager(
IUserStore<BackOfficeIdentityUser, int> store,
IdentityFactoryOptions<BackOfficeUserManager> options,
MembershipProviderBase membershipProvider,
IContentSection contentSectionConfig)
: base(store)
{
if (options == null) throw new ArgumentNullException("options");;
InitUserManager(this, membershipProvider, options);
if (options == null) throw new ArgumentNullException("options"); ;
InitUserManager(this, membershipProvider, contentSectionConfig, options);
}
#region Static Create methods
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use the overload specifying all dependencies instead")]
public static BackOfficeUserManager Create(
IdentityFactoryOptions<BackOfficeUserManager> options,
IUserService userService,
IExternalLoginService externalLoginService,
MembershipProviderBase membershipProvider)
{
return Create(options, userService,
ApplicationContext.Current.Services.EntityService,
externalLoginService, membershipProvider,
UmbracoConfig.For.UmbracoSettings().Content);
}
/// <summary>
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
/// </summary>
@@ -44,6 +72,7 @@ namespace Umbraco.Core.Security
/// <param name="entityService"></param>
/// <param name="externalLoginService"></param>
/// <param name="membershipProvider"></param>
/// <param name="contentSectionConfig"></param>
/// <returns></returns>
public static BackOfficeUserManager Create(
IdentityFactoryOptions<BackOfficeUserManager> options,
@@ -51,7 +80,8 @@ namespace Umbraco.Core.Security
IMemberTypeService memberTypeService,
IEntityService entityService,
IExternalLoginService externalLoginService,
MembershipProviderBase membershipProvider)
MembershipProviderBase membershipProvider,
IContentSection contentSectionConfig)
{
if (options == null) throw new ArgumentNullException("options");
if (userService == null) throw new ArgumentNullException("userService");
@@ -59,7 +89,18 @@ namespace Umbraco.Core.Security
if (externalLoginService == null) throw new ArgumentNullException("externalLoginService");
var manager = new BackOfficeUserManager(new BackOfficeUserStore(userService, memberTypeService, entityService, externalLoginService, membershipProvider));
manager.InitUserManager(manager, membershipProvider, options);
manager.InitUserManager(manager, membershipProvider, contentSectionConfig, options);
return manager;
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use the overload specifying all dependencies instead")]
public static BackOfficeUserManager Create(
IdentityFactoryOptions<BackOfficeUserManager> options,
BackOfficeUserStore customUserStore,
MembershipProviderBase membershipProvider)
{
var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider);
return manager;
}
@@ -69,31 +110,45 @@ namespace Umbraco.Core.Security
/// <param name="options"></param>
/// <param name="customUserStore"></param>
/// <param name="membershipProvider"></param>
/// <param name="contentSectionConfig"></param>
/// <returns></returns>
public static BackOfficeUserManager Create(
IdentityFactoryOptions<BackOfficeUserManager> options,
BackOfficeUserStore customUserStore,
MembershipProviderBase membershipProvider)
IdentityFactoryOptions<BackOfficeUserManager> options,
BackOfficeUserStore customUserStore,
MembershipProviderBase membershipProvider,
IContentSection contentSectionConfig)
{
var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider);
var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider, contentSectionConfig);
return manager;
}
#endregion
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use the overload specifying all dependencies instead")]
protected void InitUserManager(
BackOfficeUserManager manager,
MembershipProviderBase membershipProvider,
IdentityFactoryOptions<BackOfficeUserManager> options)
{
InitUserManager(manager, membershipProvider, UmbracoConfig.For.UmbracoSettings().Content, options);
}
/// <summary>
/// Initializes the user manager with the correct options
/// </summary>
/// <param name="manager"></param>
/// <param name="membershipProvider"></param>
/// <param name="contentSectionConfig"></param>
/// <param name="options"></param>
/// <returns></returns>
protected void InitUserManager(
BackOfficeUserManager manager,
MembershipProviderBase membershipProvider,
IContentSection contentSectionConfig,
IdentityFactoryOptions<BackOfficeUserManager> options)
{
//NOTE: This method is mostly here for backwards compat
base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider);
base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider, contentSectionConfig);
}
}
@@ -138,6 +193,16 @@ namespace Umbraco.Core.Security
}
#endregion
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use the overload specifying all dependencies instead")]
protected void InitUserManager(
BackOfficeUserManager<T> manager,
MembershipProviderBase membershipProvider,
IDataProtectionProvider dataProtectionProvider)
{
InitUserManager(manager, membershipProvider, dataProtectionProvider, UmbracoConfig.For.UmbracoSettings().Content);
}
/// <summary>
/// Initializes the user manager with the correct options
/// </summary>
@@ -146,11 +211,13 @@ namespace Umbraco.Core.Security
/// The <see cref="MembershipProviderBase"/> for the users called UsersMembershipProvider
/// </param>
/// <param name="dataProtectionProvider"></param>
/// <param name="contentSectionConfig"></param>
/// <returns></returns>
protected void InitUserManager(
BackOfficeUserManager<T> manager,
MembershipProviderBase membershipProvider,
IDataProtectionProvider dataProtectionProvider)
IDataProtectionProvider dataProtectionProvider,
IContentSection contentSectionConfig)
{
// Configure validation logic for usernames
manager.UserValidator = new BackOfficeUserValidator<T>(manager)
@@ -180,7 +247,9 @@ namespace Umbraco.Core.Security
//custom identity factory for creating the identity object for which we auth against in the back office
manager.ClaimsIdentityFactory = new BackOfficeClaimsIdentityFactory<T>();
manager.EmailService = new EmailService();
manager.EmailService = new EmailService(
contentSectionConfig.NotificationEmailAddress,
new EmailSender());
//NOTE: Not implementing these, if people need custom 2 factor auth, they'll need to implement their own UserStore to suport it
@@ -266,6 +335,24 @@ namespace Umbraco.Core.Security
return password;
}
/// <summary>
/// Override to check the user approval value as well as the user lock out date, by default this only checks the user's locked out date
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
/// <remarks>
/// In the ASP.NET Identity world, there is only one value for being locked out, in Umbraco we have 2 so when checking this for Umbraco we need to check both values
/// </remarks>
public override async Task<bool> IsLockedOutAsync(int userId)
{
var user = await FindByIdAsync(userId);
if (user == null)
throw new InvalidOperationException("No user found by id " + userId);
if (user.IsApproved == false)
return true;
return await base.IsLockedOutAsync(userId);
}
#region Overrides for password logic
@@ -548,6 +548,9 @@ namespace Umbraco.Core.Security
/// </summary>
/// <param name="user"/><param name="lockoutEnd"/>
/// <returns/>
/// <remarks>
/// Currently we do not suport a timed lock out, when they are locked out, an admin will have to reset the status
/// </remarks>
public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset lockoutEnd)
{
if (user == null) throw new ArgumentNullException(nameof(user));
+31 -11
View File
@@ -1,16 +1,37 @@
using System.Net.Mail;
using System;
using System.ComponentModel;
using System.Net.Mail;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Configuration;
namespace Umbraco.Core.Security
{
/// <summary>
/// The <see cref="IIdentityMessageService"/> implementation for Umbraco
/// </summary>
public class EmailService : IIdentityMessageService
{
private readonly string _notificationEmailAddress;
private readonly IEmailSender _defaultEmailSender;
public EmailService(string notificationEmailAddress, IEmailSender defaultEmailSender)
{
_notificationEmailAddress = notificationEmailAddress;
_defaultEmailSender = defaultEmailSender;
}
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use the constructor specifying all dependencies")]
public EmailService()
: this(UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress, new EmailSender())
{
}
public async Task SendAsync(IdentityMessage message)
{
var mailMessage = new MailMessage(
UmbracoConfig.For.UmbracoSettings().Content.NotificationEmailAddress,
_notificationEmailAddress,
message.Destination,
message.Subject,
message.Body)
@@ -21,16 +42,15 @@ namespace Umbraco.Core.Security
try
{
using (var client = new SmtpClient())
//check if it's a custom message and if so use it's own defined mail sender
var umbMsg = message as UmbracoEmailMessage;
if (umbMsg != null)
{
if (client.DeliveryMethod == SmtpDeliveryMethod.Network)
{
await client.SendMailAsync(mailMessage);
}
else
{
client.Send(mailMessage);
}
await umbMsg.MailSender.SendAsync(mailMessage);
}
else
{
await _defaultEmailSender.SendAsync(mailMessage);
}
}
finally
@@ -67,6 +67,19 @@ namespace Umbraco.Core.Security
get { return false; }
}
/// <summary>
/// Returns the raw password value for a given user
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
/// <remarks>
/// By default this will return an invalid attempt, inheritors will need to override this to support it
/// </remarks>
protected virtual Attempt<string> GetRawPassword(string username)
{
return Attempt<string>.Fail();
}
private string _applicationName;
private bool _enablePasswordReset;
private bool _enablePasswordRetrieval;
@@ -301,7 +314,7 @@ namespace Umbraco.Core.Security
/// Processes a request to update the password for a membership user.
/// </summary>
/// <param name="username">The user to update the password for.</param>
/// <param name="oldPassword">This property is ignore for this provider</param>
/// <param name="oldPassword">Required to change a user password if the user is not new and AllowManuallyChangingPassword is false</param>
/// <param name="newPassword">The new password for the specified user.</param>
/// <returns>
/// true if the password was updated successfully; otherwise, false.
@@ -311,10 +324,17 @@ namespace Umbraco.Core.Security
/// </remarks>
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
string rawPasswordValue = string.Empty;
if (oldPassword.IsNullOrWhiteSpace() && AllowManuallyChangingPassword == false)
{
//If the old password is empty and AllowManuallyChangingPassword is false, than this provider cannot just arbitrarily change the password
throw new NotSupportedException("This provider does not support manually changing the password");
//we need to lookup the member since this could be a brand new member without a password set
var rawPassword = GetRawPassword(username);
rawPasswordValue = rawPassword.Success ? rawPassword.Result : string.Empty;
if (rawPassword.Success == false || rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix) == false)
{
//If the old password is empty and AllowManuallyChangingPassword is false, than this provider cannot just arbitrarily change the password
throw new NotSupportedException("This provider does not support manually changing the password");
}
}
var args = new ValidatePasswordEventArgs(username, newPassword, false);
@@ -327,10 +347,12 @@ namespace Umbraco.Core.Security
throw new MembershipPasswordException("Change password canceled due to password validation failure.");
}
//Special case to allow changing password without validating existing credentials
//This is used during installation only
var installing = Current.RuntimeState.Level == RuntimeLevel.Install;
if (AllowManuallyChangingPassword == false && installing && oldPassword == "default")
//Special cases to allow changing password without validating existing credentials
// * the member is new and doesn't have a password set
// * during installation to set the admin password
if (AllowManuallyChangingPassword == false
&& (rawPasswordValue.StartsWith(Constants.Security.EmptyPasswordPrefix)
|| (installing && oldPassword == "default")))
{
return PerformChangePassword(username, oldPassword, newPassword);
}
@@ -686,12 +708,12 @@ namespace Umbraco.Core.Security
{
var keyedHashAlgorithm = algorithm;
if (keyedHashAlgorithm.Key.Length == saltBytes.Length)
{
{
//if the salt bytes is the required key length for the algorithm, use it as-is
keyedHashAlgorithm.Key = saltBytes;
}
else if (keyedHashAlgorithm.Key.Length < saltBytes.Length)
{
{
//if the salt bytes is too long for the required key length for the algorithm, reduce it
var numArray2 = new byte[keyedHashAlgorithm.Key.Length];
Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length);
@@ -18,6 +18,9 @@ namespace Umbraco.Core.Security
/// <param name="provider"></param>
/// <param name="userService"></param>
/// <returns></returns>
/// <remarks>
/// An Admin can always reset the password
/// </remarks>
internal static bool CanResetPassword(this MembershipProvider provider, IUserService userService)
{
if (provider == null) throw new ArgumentNullException("provider");
@@ -0,0 +1,17 @@
using Microsoft.AspNet.Identity;
namespace Umbraco.Core.Security
{
/// <summary>
/// A custom implementation for IdentityMessage that allows the customization of how an email is sent
/// </summary>
internal class UmbracoEmailMessage : IdentityMessage
{
public IEmailSender MailSender { get; private set; }
public UmbracoEmailMessage(IEmailSender mailSender)
{
MailSender = mailSender;
}
}
}
@@ -0,0 +1,26 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Umbraco.Core.Serialization
{
public class KnownTypeUdiJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Udi).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JToken.ReadFrom(reader);
var val = jo.ToObject<string>();
return val == null ? null : Udi.Parse(val, true);
}
}
}
@@ -4,12 +4,11 @@ using Newtonsoft.Json.Linq;
namespace Umbraco.Core.Serialization
{
public class UdiJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Udi).IsAssignableFrom(objectType);
return typeof (Udi).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+176 -62
View File
@@ -24,15 +24,13 @@ 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, IdkMap idkMap)
public ContentService(IScopeUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem)
: base(provider, logger, eventMessagesFactory)
{
_mediaFileSystem = mediaFileSystem;
_idkMap = idkMap;
}
#endregion
@@ -122,8 +120,8 @@ namespace Umbraco.Core.Services
repo.AssignEntityPermission(entity, permission, groupIds);
uow.Complete();
}
}
}
/// <summary>
/// Returns implicit/inherited permissions assigned to the content item for all user groups
/// </summary>
@@ -143,6 +141,26 @@ namespace Umbraco.Core.Services
#region Create
/// <summary>
/// Creates an <see cref="IContent"/> object using the alias of the <see cref="IContentType"/>
/// that this Content should based on.
/// </summary>
/// <remarks>
/// Note that using this method will simply return a new IContent without any identity
/// as it has not yet been persisted. It is intended as a shortcut to creating new content objects
/// that does not invoke a save operation against the database.
/// </remarks>
/// <param name="name">Name of the Content object</param>
/// <param name="parentId">Id of Parent for the new Content</param>
/// <param name="contentTypeAlias">Alias of the <see cref="IContentType"/></param>
/// <param name="userId">Optional id of the user creating the content</param>
/// <returns><see cref="IContent"/></returns>
public IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0)
{
var parent = GetById(parentId);
return CreateContent(name, parent, contentTypeAlias, userId);
}
/// <summary>
/// Creates an <see cref="IContent"/> object of a specified content type.
/// </summary>
@@ -316,7 +334,8 @@ namespace Umbraco.Core.Services
if (withIdentity)
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(content), "Saving"))
var saveEventArgs = new SaveEventArgs<IContent>(content);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
{
content.WasCancelled = true;
return;
@@ -327,14 +346,15 @@ namespace Umbraco.Core.Services
uow.Flush(); // need everything so we can serialize
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(content, false), "Saved");
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs, "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));
if (withIdentity == false)
return;
return;
Audit(uow, AuditType.New, $"Content '{content.Name}' was created with Id {content.Id}", content.CreatorId, content.Id);
}
@@ -376,7 +396,7 @@ namespace Umbraco.Core.Services
var index = items.ToDictionary(x => x.Id, x => x);
return idsA.Select(x => index.TryGetValue(x, out IContent c) ? c : null).WhereNotNull();
return idsA.Select(x => index.TryGetValue(x, out var c) ? c : null).WhereNotNull();
}
}
@@ -387,13 +407,34 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IContent"/></returns>
public IContent GetById(Guid key)
{
// 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.
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.ContentTree);
var repository = uow.CreateRepository<IContentRepository>();
return repository.Get(key);
}
}
var a = _idkMap.GetIdForKey(key, UmbracoObjectTypes.Document);
return a.Success ? GetById(a.Result) : null;
/// <summary>
/// Gets <see cref="IContent"/> objects by Ids
/// </summary>
/// <param name="ids">Ids of the Content to retrieve</param>
/// <returns><see cref="IContent"/></returns>
public IEnumerable<IContent> GetByIds(IEnumerable<Guid> ids)
{
var idsA = ids.ToArray();
if (idsA.Length == 0) return Enumerable.Empty<IContent>();
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.ContentTree);
var repository = uow.CreateRepository<IContentRepository>();
var items = repository.GetAll(idsA);
var index = items.ToDictionary(x => x.Key, x => x);
return idsA.Select(x => index.TryGetValue(x, out var c) ? c : null).WhereNotNull();
}
}
/// <summary>
@@ -626,7 +667,6 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.ContentTree);
var repository = uow.CreateRepository<IContentRepository>();
var filterQuery = filter.IsNullOrWhiteSpace()
? null
: uow.Query<IContent>().Where(x => x.Name.Contains(filter));
@@ -660,7 +700,16 @@ namespace Umbraco.Core.Services
var query = uow.Query<IContent>();
//if the id is System Root, then just get all
if (id != Constants.System.Root)
query.Where(x => x.Path.SqlContains($",{id},", TextColumnType.NVarchar));
{
var entityRepository = uow.CreateRepository<IEntityRepository>();
var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray();
if (contentPath.Length == 0)
{
totalChildren = 0;
return Enumerable.Empty<IContent>();
}
query.Where(x => x.Path.SqlStartsWith($"{contentPath[0]},", TextColumnType.NVarchar));
}
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter);
}
}
@@ -895,15 +944,43 @@ namespace Umbraco.Core.Services
if (content.Trashed) return false; // trashed content is never publishable
// not trashed and has a parent: publishable if the parent is path-published
int[] ids;
if (content.HasIdentity)
{
// get ids from path (we have identity)
// skip the first one that has to be -1 - and we don't care
// skip the last one that has to be "this" - and it's ok to stop at the parent
ids = content.Path.Split(',').Skip(1).SkipLast().Select(int.Parse).ToArray();
}
else
{
// no path yet (no identity), have to move up to parent
// skip the first one that has to be -1 - and we don't care
// don't skip the last one that is "parent"
var parent = GetById(content.ParentId);
if (parent == null) return false;
ids = parent.Path.Split(',').Skip(1).Select(int.Parse).ToArray();
}
if (ids.Length == 0)
return false;
// if the first one is recycle bin, fail fast
if (ids[0] == Constants.System.RecycleBinContent)
return false;
// fixme - move to repository?
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.ContentTree);
var repo = uow.CreateRepository<IContentRepository>();
var parent = repo.Get(content.ParentId);
if (parent == null)
throw new Exception("Out of sync."); // causes rollback
return repo.IsPathPublished(parent);
}
var sql = uow.Sql(@"
SELECT id
FROM umbracoNode
JOIN cmsDocument ON umbracoNode.id=cmsDocument.nodeId AND cmsDocument.published=@0
WHERE umbracoNode.trashed=@1 AND umbracoNode.id IN (@2)",
true, false, ids);
var x = uow.Database.Fetch<int>(sql);
return ids.Length == x.Count;
}
}
public bool IsPathPublished(IContent content)
@@ -943,7 +1020,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(content, evtMsgs), "Saving"))
var saveEventArgs = new SaveEventArgs<IContent>(content, evtMsgs);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs);
@@ -971,7 +1049,10 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(content);
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(content, false, evtMsgs), "Saved");
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs, "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);
@@ -1010,7 +1091,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(contentsA, evtMsgs), "Saving"))
var saveEventArgs = new SaveEventArgs<IContent>(contentsA, evtMsgs);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs);
@@ -1036,7 +1118,10 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(contentsA, false, evtMsgs), "Saved");
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs, "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);
@@ -1264,7 +1349,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IContent>(content, evtMsgs)))
var deleteEventArgs = new DeleteEventArgs<IContent>(content, evtMsgs);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs);
@@ -1338,7 +1424,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, dateToRetain: versionDate)))
var deleteRevisionsEventArgs = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate);
if (uow.Events.DispatchCancelable(DeletingVersions, this, deleteRevisionsEventArgs))
{
uow.Complete();
return;
@@ -1348,7 +1435,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IContentRepository>();
repository.DeleteVersions(id, versionDate);
uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate));
deleteRevisionsEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs);
Audit(uow, AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root);
uow.Complete();
@@ -1423,7 +1511,9 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IContentRepository>();
var originalPath = content.Path;
if (uow.Events.DispatchCancelable(Trashing, this, new MoveEventArgs<IContent>(new MoveEventInfo<IContent>(content, originalPath, Constants.System.RecycleBinContent))))
var moveEventInfo = new MoveEventInfo<IContent>(content, originalPath, Constants.System.RecycleBinContent);
var moveEventArgs = new MoveEventArgs<IContent>(evtMsgs, moveEventInfo);
if (uow.Events.DispatchCancelable(Trashing, this, moveEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs); // causes rollback
@@ -1442,7 +1532,9 @@ namespace Umbraco.Core.Services
.Select(x => new MoveEventInfo<IContent>(x.Item1, x.Item2, x.Item1.ParentId))
.ToArray();
uow.Events.Dispatch(Trashed, this, new MoveEventArgs<IContent>(false, evtMsgs, moveInfo));
moveEventArgs.CanCancel = false;
moveEventArgs.MoveInfoCollection = moveInfo;
uow.Events.Dispatch(Trashed, this, moveEventArgs);
Audit(uow, AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id);
uow.Complete();
@@ -1482,7 +1574,9 @@ namespace Umbraco.Core.Services
if (parentId != Constants.System.Root && (parent == null || parent.Trashed))
throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback
if (uow.Events.DispatchCancelable(Moving, this, new MoveEventArgs<IContent>(new MoveEventInfo<IContent>(content, content.Path, parentId))))
var moveEventInfo = new MoveEventInfo<IContent>(content, content.Path, parentId);
var moveEventArgs = new MoveEventArgs<IContent>(moveEventInfo);
if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs))
{
uow.Complete();
return; // causes rollback
@@ -1511,7 +1605,9 @@ namespace Umbraco.Core.Services
.Select(x => new MoveEventInfo<IContent>(x.Item1, x.Item2, x.Item1.ParentId))
.ToArray();
uow.Events.Dispatch(Moved, this, new MoveEventArgs<IContent>(false, moveInfo));
moveEventArgs.MoveInfoCollection = moveInfo;
moveEventArgs.CanCancel = false;
uow.Events.Dispatch(Moved, this, moveEventArgs);
Audit(uow, AuditType.Move, "Move Content performed by user", userId, content.Id);
uow.Complete();
@@ -1577,7 +1673,7 @@ namespace Umbraco.Core.Services
/// </summary>
public void EmptyRecycleBin()
{
var nodeObjectType = new Guid(Constants.ObjectTypes.Document);
var nodeObjectType = Constants.ObjectTypes.DocumentGuid;
var deleted = new List<IContent>();
var evtMsgs = EventMessagesFactory.Get(); // todo - and then?
@@ -1591,7 +1687,8 @@ namespace Umbraco.Core.Services
// are managed by Delete, and not here.
// no idea what those events are for, keep a simplified version
if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, new RecycleBinEventArgs(nodeObjectType)))
var recycleBinEventArgs = new RecycleBinEventArgs(nodeObjectType);
if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, recycleBinEventArgs))
{
uow.Complete();
return; // causes rollback
@@ -1606,7 +1703,9 @@ namespace Umbraco.Core.Services
deleted.Add(content);
}
uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, true));
recycleBinEventArgs.CanCancel = false;
recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?!
uow.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs);
uow.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.Remove)).ToEventArgs());
Audit(uow, AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent);
@@ -1649,7 +1748,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Copying, this, new CopyEventArgs<IContent>(content, copy, parentId)))
var copyEventArgs = new CopyEventArgs<IContent>(content, copy, true, parentId, relateToOriginal);
if (uow.Events.DispatchCancelable(Copying, this, copyEventArgs))
{
uow.Complete();
return null;
@@ -1745,7 +1845,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SendingToPublish, this, new SendToPublishEventArgs<IContent>(content)))
var sendToPublishEventArgs = new SendToPublishEventArgs<IContent>(content);
if (uow.Events.DispatchCancelable(SendingToPublish, this, sendToPublishEventArgs))
{
uow.Complete();
return false;
@@ -1755,7 +1856,8 @@ namespace Umbraco.Core.Services
// fixme - nesting uow?
Save(content, userId);
uow.Events.Dispatch(SentToPublish, this, new SendToPublishEventArgs<IContent>(content, false));
sendToPublishEventArgs.CanCancel = false;
uow.Events.Dispatch(SentToPublish, this, sendToPublishEventArgs);
Audit(uow, AuditType.SendToPublish, "Send to Publish performed by user", content.WriterId, content.Id);
}
@@ -1780,7 +1882,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(RollingBack, this, new RollbackEventArgs<IContent>(content)))
var rollbackEventArgs = new RollbackEventArgs<IContent>(content);
if (uow.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs))
{
uow.Complete();
return content;
@@ -1802,7 +1905,8 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(content);
uow.Events.Dispatch(RolledBack, this, new RollbackEventArgs<IContent>(content, false));
rollbackEventArgs.CanCancel = false;
uow.Events.Dispatch(RolledBack, this, rollbackEventArgs);
uow.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshNode).ToEventArgs());
Audit(uow, AuditType.RollBack, "Content rollback performed by user", content.WriterId, content.Id);
@@ -1831,7 +1935,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(itemsA), "Saving"))
var saveEventArgs = new SaveEventArgs<IContent>(itemsA);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
return false;
var published = new List<IContent>();
@@ -1868,7 +1973,10 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(saved, false), "Saved");
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
}
uow.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange<IContent>(x, TreeChangeTypes.RefreshNode)).ToEventArgs());
@@ -2051,7 +2159,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IContent>(content), "Saving"))
var saveEventArgs = new SaveEventArgs<IContent>(content, evtMsgs);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
{
uow.Complete();
return Attempt.Fail(new PublishStatus(PublishStatusType.FailedCancelledByEvent, evtMsgs, content));
@@ -2080,7 +2189,10 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(content);
if (raiseEvents) // always
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IContent>(content, false, evtMsgs), "Saved");
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
}
if (status.Success == false)
{
@@ -2256,7 +2368,7 @@ namespace Umbraco.Core.Services
/// Occurs after a blueprint has been deleted.
/// </summary>
public static event TypedEventHandler<IContentService, DeleteEventArgs<IContent>> DeletedBlueprint;
#endregion
#region Publishing Strategies
@@ -2622,16 +2734,16 @@ namespace Umbraco.Core.Services
{
return GetContentType(uow, contentTypeAlias);
}
}
#endregion
}
#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);
@@ -2643,13 +2755,15 @@ namespace Umbraco.Core.Services
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;
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 void SaveBlueprint(IContent content, int userId = 0)
@@ -2662,7 +2776,7 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
uow.WriteLock(Constants.Locks.ContentTree);
uow.WriteLock(Constants.Locks.ContentTree);
if (string.IsNullOrWhiteSpace(content.Name))
{
@@ -2688,7 +2802,7 @@ namespace Umbraco.Core.Services
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);
@@ -1,10 +1,48 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Services
{
/// <summary>
/// Content service extension methods
/// </summary>
public static class ContentServiceExtensions
{
public static IEnumerable<IContent> GetByIds(this IContentService contentService, IEnumerable<Udi> ids)
{
var guids = new List<GuidUdi>();
foreach (var udi in ids)
{
var guidUdi = udi as GuidUdi;
if (guidUdi == null)
throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content");
guids.Add(guidUdi);
}
return contentService.GetByIds(guids.Select(x => x.Guid));
}
/// <summary>
/// Method to create an IContent object based on the Udi of a parent
/// </summary>
/// <param name="contentService"></param>
/// <param name="name"></param>
/// <param name="parentId"></param>
/// <param name="mediaTypeAlias"></param>
/// <param name="userId"></param>
/// <returns></returns>
public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string mediaTypeAlias, int userId = 0)
{
var guidUdi = parentId as GuidUdi;
if (guidUdi == null)
throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content");
var parent = contentService.GetById(guidUdi.Guid);
return contentService.CreateContent(name, parent, mediaTypeAlias, userId);
}
/// <summary>
/// Remove all permissions for this user for all nodes
/// </summary>
@@ -112,6 +112,12 @@ namespace Umbraco.Core.Services
uow.Events.DispatchCancelable(SavedContainer, This, args);
}
protected void OnRenamedContainer(IScopeUnitOfWork uow, SaveEventArgs<EntityContainer> args)
{
// fixme changing the name of the event?!
uow.Events.DispatchCancelable(SavedContainer, This, args, "RenamedContainer");
}
// fixme what is this?
protected void OnDeletingContainer(IScopeUnitOfWork uow, DeleteEventArgs<EntityContainer> args)
{
@@ -385,7 +385,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (OnSavingCancelled(uow, new SaveEventArgs<TItem>(item)))
var saveEventArgs = new SaveEventArgs<TItem>(item);
if (OnSavingCancelled(uow, saveEventArgs))
{
uow.Complete();
return;
@@ -412,7 +413,8 @@ namespace Umbraco.Core.Services
OnUowRefreshedEntity(args);
OnChanged(uow, args);
OnSaved(uow, new SaveEventArgs<TItem>(item, false));
saveEventArgs.CanCancel = false;
OnSaved(uow, saveEventArgs);
Audit(uow, AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, item.Id);
uow.Complete();
@@ -425,7 +427,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (OnSavingCancelled(uow, new SaveEventArgs<TItem>(itemsA)))
var saveEventArgs = new SaveEventArgs<TItem>(itemsA);
if (OnSavingCancelled(uow, saveEventArgs))
{
uow.Complete();
return;
@@ -454,7 +457,8 @@ namespace Umbraco.Core.Services
OnUowRefreshedEntity(args);
OnChanged(uow, args);
OnSaved(uow, new SaveEventArgs<TItem>(itemsA, false));
saveEventArgs.CanCancel = false;
OnSaved(uow, saveEventArgs);
Audit(uow, AuditType.Save, $"Save {typeof(TItem).Name} performed by user", userId, -1);
uow.Complete();
@@ -469,7 +473,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (OnDeletingCancelled(uow, new DeleteEventArgs<TItem>(item)))
var deleteEventArgs = new DeleteEventArgs<TItem>(item);
if (OnDeletingCancelled(uow, deleteEventArgs))
{
uow.Complete();
return;
@@ -511,7 +516,9 @@ namespace Umbraco.Core.Services
OnUowRefreshedEntity(args);
OnChanged(uow, args);
OnDeleted(uow, new DeleteEventArgs<TItem>(deleted, false));
deleteEventArgs.DeletedEntities = deleted.DistinctBy(x => x.Id);
deleteEventArgs.CanCancel = false;
OnDeleted(uow, deleteEventArgs);
Audit(uow, AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, item.Id);
uow.Complete();
@@ -524,7 +531,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (OnDeletingCancelled(uow, new DeleteEventArgs<TItem>(itemsA)))
var deleteEventArgs = new DeleteEventArgs<TItem>(itemsA);
if (OnDeletingCancelled(uow, deleteEventArgs))
{
uow.Complete();
return;
@@ -563,7 +571,9 @@ namespace Umbraco.Core.Services
OnUowRefreshedEntity(args);
OnChanged(uow, args);
OnDeleted(uow, new DeleteEventArgs<TItem>(deleted, false));
deleteEventArgs.DeletedEntities = deleted.DistinctBy(x => x.Id);
deleteEventArgs.CanCancel = false;
OnDeleted(uow, deleteEventArgs);
Audit(uow, AuditType.Delete, $"Delete {typeof(TItem).Name} performed by user", userId, -1);
uow.Complete();
@@ -692,7 +702,9 @@ namespace Umbraco.Core.Services
var moveInfo = new List<MoveEventInfo<TItem>>();
using (var uow = UowProvider.CreateUnitOfWork())
{
if (OnMovingCancelled(uow, new MoveEventArgs<TItem>(evtMsgs, new MoveEventInfo<TItem>(moving, moving.Path, containerId))))
var moveEventInfo = new MoveEventInfo<TItem>(moving, moving.Path, containerId);
var moveEventArgs = new MoveEventArgs<TItem>(evtMsgs, moveEventInfo);
if (OnMovingCancelled(uow, moveEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs);
@@ -725,7 +737,9 @@ namespace Umbraco.Core.Services
// has no impact on the published content types - would be entirely different if we were to support
// moving a content type under another content type.
OnMoved(uow, new MoveEventArgs<TItem>(false, evtMsgs, moveInfo.ToArray()));
moveEventArgs.MoveInfoCollection = moveInfo;
moveEventArgs.CanCancel = false;
OnMoved(uow, moveEventArgs);
}
return OperationStatus.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs);
@@ -756,7 +770,8 @@ namespace Umbraco.Core.Services
CreatorId = userId
};
if (OnSavingContainerCancelled(uow, new SaveEventArgs<EntityContainer>(container, evtMsgs)))
var saveEventArgs = new SaveEventArgs<EntityContainer>(container, evtMsgs);
if (OnSavingContainerCancelled(uow, saveEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs, container);
@@ -765,7 +780,8 @@ namespace Umbraco.Core.Services
repo.AddOrUpdate(container);
uow.Complete();
OnSavedContainer(uow, new SaveEventArgs<EntityContainer>(container, evtMsgs));
saveEventArgs.CanCancel = false;
OnSavedContainer(uow, saveEventArgs);
//TODO: Audit trail ?
return OperationStatus.Attempt.Succeed(evtMsgs, container);
@@ -895,7 +911,8 @@ namespace Umbraco.Core.Services
return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCannot, evtMsgs));
}
if (OnDeletingContainerCancelled(uow, new DeleteEventArgs<EntityContainer>(container, evtMsgs)))
var deleteEventArgs = new DeleteEventArgs<EntityContainer>(container, evtMsgs);
if (OnDeletingContainerCancelled(uow, deleteEventArgs))
{
uow.Complete();
return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs));
@@ -904,13 +921,45 @@ namespace Umbraco.Core.Services
repo.Delete(container);
uow.Complete();
OnDeletedContainer(uow, new DeleteEventArgs<EntityContainer>(container, evtMsgs));
deleteEventArgs.CanCancel = false;
OnDeletedContainer(uow, deleteEventArgs);
return OperationStatus.Attempt.Succeed(evtMsgs);
//TODO: Audit trail ?
}
}
public Attempt<OperationStatus<OperationStatusType, EntityContainer>> RenameContainer(int id, string name, int userId = 0)
{
var evtMsgs = EventMessagesFactory.Get();
using (var uow = UowProvider.CreateUnitOfWork())
{
uow.WriteLock(WriteLockIds); // also for containers
var repository = uow.CreateContainerRepository(ContainerObjectType);
try
{
var container = repository.Get(id);
//throw if null, this will be caught by the catch and a failed returned
if (container == null)
throw new InvalidOperationException("No container found with id " + id);
container.Name = name;
repository.AddOrUpdate(container);
uow.Complete();
OnRenamedContainer(uow, new SaveEventArgs<EntityContainer>(container, evtMsgs));
return OperationStatus.Attempt.Succeed(OperationStatusType.Success, evtMsgs, container);
}
catch (Exception ex)
{
return OperationStatus.Attempt.Fail<EntityContainer>(evtMsgs, ex);
}
}
}
#endregion
#region Audit
+56 -10
View File
@@ -174,6 +174,38 @@ namespace Umbraco.Core.Services
return OperationStatus.Attempt.Succeed(evtMsgs);
}
public Attempt<OperationStatus<OperationStatusType, EntityContainer>> RenameContainer(int id, string name, int userId = 0)
{
var evtMsgs = EventMessagesFactory.Get();
using (var uow = UowProvider.CreateUnitOfWork())
{
var repository = uow.CreateRepository<IDataTypeContainerRepository>();
try
{
var container = repository.Get(id);
//throw if null, this will be caught by the catch and a failed returned
if (container == null)
throw new InvalidOperationException("No container found with id " + id);
container.Name = name;
repository.AddOrUpdate(container);
uow.Complete();
// fixme - triggering SavedContainer with a different name?!
uow.Events.Dispatch(SavedContainer, this, new SaveEventArgs<EntityContainer>(container, evtMsgs), "RenamedContainer");
return OperationStatus.Attempt.Succeed(OperationStatusType.Success, evtMsgs, container);
}
catch (Exception ex)
{
return OperationStatus.Attempt.Fail<EntityContainer>(evtMsgs, ex);
}
}
}
#endregion
/// <summary>
@@ -301,7 +333,9 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Moving, this, new MoveEventArgs<IDataTypeDefinition>(evtMsgs, new MoveEventInfo<IDataTypeDefinition>(toMove, toMove.Path, parentId))))
var moveEventInfo = new MoveEventInfo<IDataTypeDefinition>(toMove, toMove.Path, parentId);
var moveEventArgs = new MoveEventArgs<IDataTypeDefinition>(evtMsgs, moveEventInfo);
if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs);
@@ -321,7 +355,9 @@ namespace Umbraco.Core.Services
}
moveInfo.AddRange(repository.Move(toMove, container));
uow.Events.Dispatch(Moved, this, new MoveEventArgs<IDataTypeDefinition>(false, evtMsgs, moveInfo.ToArray()));
moveEventArgs.MoveInfoCollection = moveInfo;
moveEventArgs.CanCancel = false;
uow.Events.Dispatch(Moved, this, moveEventArgs);
uow.Complete();
}
catch (DataOperationException<MoveOperationStatusType> ex)
@@ -345,7 +381,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition)))
var saveEventArgs = new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -359,7 +396,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IDataTypeDefinitionRepository>();
repository.AddOrUpdate(dataTypeDefinition);
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id);
uow.Complete();
}
@@ -384,10 +422,11 @@ namespace Umbraco.Core.Services
public void Save(IEnumerable<IDataTypeDefinition> dataTypeDefinitions, int userId, bool raiseEvents)
{
var dataTypeDefinitionsA = dataTypeDefinitions.ToArray();
var saveEventArgs = new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinitionsA);
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinitionsA)))
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -401,7 +440,10 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinitionsA, false));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, -1);
uow.Complete();
@@ -486,7 +528,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition)))
var saveEventArgs = new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -502,7 +545,8 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(dataTypeDefinition); // definition
repository.AddOrUpdatePreValues(dataTypeDefinition, values); //prevalues
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
Audit(uow, AuditType.Save, "Save DataTypeDefinition performed by user", userId, dataTypeDefinition.Id);
uow.Complete();
@@ -522,7 +566,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IDataTypeDefinition>(dataTypeDefinition)))
var deleteEventArgs = new DeleteEventArgs<IDataTypeDefinition>(dataTypeDefinition);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -531,7 +576,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IDataTypeDefinitionRepository>();
repository.Delete(dataTypeDefinition);
uow.Events.Dispatch(Deleted, this, new DeleteEventArgs<IDataTypeDefinition>(dataTypeDefinition, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(Deleted, this, deleteEventArgs);
Audit(uow, AuditType.Delete, "Delete DataTypeDefinition performed by user", userId, dataTypeDefinition.Id);
uow.Complete();
+8 -6
View File
@@ -33,7 +33,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IDomain>(domain, evtMsgs)))
var deleteEventArgs = new DeleteEventArgs<IDomain>(domain, evtMsgs);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs);
@@ -43,8 +44,8 @@ namespace Umbraco.Core.Services
repository.Delete(domain);
uow.Complete();
var args = new DeleteEventArgs<IDomain>(domain, false, evtMsgs);
uow.Events.Dispatch(Deleted, this, args);
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(Deleted, this, deleteEventArgs);
}
return OperationStatus.Attempt.Succeed(evtMsgs);
@@ -92,7 +93,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IDomain>(domainEntity, evtMsgs)))
var saveEventArgs = new SaveEventArgs<IDomain>(domainEntity, evtMsgs);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs);
@@ -101,8 +103,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IDomainRepository>();
repository.AddOrUpdate(domainEntity);
uow.Complete();
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IDomain>(domainEntity, false, evtMsgs));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
return OperationStatus.Attempt.Succeed(evtMsgs);
+64 -9
View File
@@ -374,11 +374,22 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
var repository = uow.CreateRepository<IEntityRepository>();
var query = repository.Query;
//if the id is System Root, then just get all
if (id != Constants.System.Root)
query.Where(x => x.Path.SqlContains($",{id},", TextColumnType.NVarchar));
{
//lookup the path so we can use it in the prefix query below
var itemPaths = repository.GetAllPaths(objectTypeId, id).ToArray();
if (itemPaths.Length == 0)
{
totalRecords = 0;
return Enumerable.Empty<IUmbracoEntity>();
}
var itemPath = itemPaths[0].Path;
query.Where(x => x.Path.SqlStartsWith(itemPath + ",", TextColumnType.NVarchar));
}
IQuery<IUmbracoEntity> filterQuery = null;
if (filter.IsNullOrWhiteSpace() == false)
@@ -407,15 +418,30 @@ namespace Umbraco.Core.Services
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))
{
//lookup the paths so we can use it in the prefix query below
var itemPaths = repository.GetAllPaths(objectTypeId, idsA).ToArray();
if (itemPaths.Length == 0)
{
totalRecords = 0;
return Enumerable.Empty<IUmbracoEntity>();
}
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));
//if the id is root then don't add any clauses
if (id != Constants.System.Root)
{
var itemPath = itemPaths.FirstOrDefault(x => x.Id == id);
if (itemPath == null) continue;
var path = itemPath.Path;
var qid = id;
clauses.Add(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar) || x.Path.SqlEndsWith("," + qid, TextColumnType.NVarchar));
}
}
query.WhereAny(clauses);
}
@@ -468,7 +494,7 @@ namespace Umbraco.Core.Services
return contents;
}
}
/// <summary>
/// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1.
/// </summary>
@@ -566,13 +592,13 @@ namespace Umbraco.Core.Services
return repository.GetAllPaths(objectTypeId, keys);
}
}
/// <summary>
/// Gets a collection of <see cref="IUmbracoEntity"/>
/// Gets a collection of <see cref="T:Umbraco.Core.Models.EntityBase.IUmbracoEntity" />
/// </summary>
/// <param name="objectTypeId">Guid id of the UmbracoObjectType</param>
/// <param name="ids"></param>
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/> objects</returns>
/// <returns>An enumerable list of <see cref="T:Umbraco.Core.Models.EntityBase.IUmbracoEntity" /> objects</returns>
public virtual IEnumerable<IUmbracoEntity> GetAll(Guid objectTypeId, params int[] ids)
{
var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId);
@@ -692,5 +718,34 @@ namespace Umbraco.Core.Services
return exists;
}
}
/// <inheritdoc />
public int ReserveId(Guid key)
{
NodeDto node;
using (var scope = UowProvider.ScopeProvider.CreateScope())
{
var sql = new Sql("SELECT * FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", key, Constants.ObjectTypes.IdReservationGuid);
node = scope.Database.SingleOrDefault<NodeDto>(sql);
if (node != null) throw new InvalidOperationException("An identifier has already been reserved for this Udi.");
node = new NodeDto
{
UniqueId = key,
Text = "RESERVED.ID",
NodeObjectType = Constants.ObjectTypes.IdReservationGuid,
CreateDate = DateTime.Now,
UserId = 0,
ParentId = -1,
Level = 1,
Path = "-1",
SortOrder = 0,
Trashed = false
};
scope.Database.Insert(node);
scope.Complete();
}
return node.NodeId;
}
}
}
+40 -28
View File
@@ -64,7 +64,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingStylesheet, this, new SaveEventArgs<Stylesheet>(stylesheet)))
var saveEventArgs = new SaveEventArgs<Stylesheet>(stylesheet);
if (uow.Events.DispatchCancelable(SavingStylesheet, this, saveEventArgs))
{
uow.Complete();
return;
@@ -72,8 +73,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IStylesheetRepository>();
repository.AddOrUpdate(stylesheet);
uow.Events.Dispatch(SavedStylesheet, this, new SaveEventArgs<Stylesheet>(stylesheet, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedStylesheet, this, saveEventArgs);
Audit(uow, AuditType.Save, "Save Stylesheet performed by user", userId, -1);
uow.Complete();
@@ -97,15 +98,16 @@ namespace Umbraco.Core.Services
return;
}
if (uow.Events.DispatchCancelable(DeletingStylesheet, this, new DeleteEventArgs<Stylesheet>(stylesheet)))
var deleteEventArgs = new DeleteEventArgs<Stylesheet>(stylesheet);
if (uow.Events.DispatchCancelable(DeletingStylesheet, this, deleteEventArgs))
{
uow.Complete();
return; // causes rollback
}
repository.Delete(stylesheet);
uow.Events.Dispatch(DeletedStylesheet, this, new DeleteEventArgs<Stylesheet>(stylesheet, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedStylesheet, this, deleteEventArgs);
Audit(uow, AuditType.Delete, "Delete Stylesheet performed by user", userId, -1);
uow.Complete();
@@ -194,7 +196,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingScript, this, new SaveEventArgs<Script>(script)))
var saveEventArgs = new SaveEventArgs<Script>(script);
if (uow.Events.DispatchCancelable(SavingScript, this, saveEventArgs))
{
uow.Complete();
return;
@@ -202,8 +205,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IScriptRepository>();
repository.AddOrUpdate(script);
uow.Events.Dispatch(SavedScript, this, new SaveEventArgs<Script>(script, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedScript, this, saveEventArgs);
Audit(uow, AuditType.Save, "Save Script performed by user", userId, -1);
uow.Complete();
@@ -227,15 +230,16 @@ namespace Umbraco.Core.Services
return;
}
if (uow.Events.DispatchCancelable(DeletingScript, this, new DeleteEventArgs<Script>(script)))
var deleteEventArgs = new DeleteEventArgs<Script>(script);
if (uow.Events.DispatchCancelable(DeletingScript, this, deleteEventArgs))
{
uow.Complete();
return;
}
repository.Delete(script);
uow.Events.Dispatch(DeletedScript, this, new DeleteEventArgs<Script>(script, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedScript, this, deleteEventArgs);
Audit(uow, AuditType.Delete, "Delete Script performed by user", userId, -1);
uow.Complete();
@@ -340,7 +344,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingTemplate, this, new SaveEventArgs<ITemplate>(template, true, evtMsgs, additionalData)))
var saveEventArgs = new SaveEventArgs<ITemplate>(template, true, evtMsgs, additionalData);
if (uow.Events.DispatchCancelable(SavingTemplate, this, saveEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Fail<OperationStatusType, ITemplate>(OperationStatusType.FailedCancelledByEvent, evtMsgs, template);
@@ -348,8 +353,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<ITemplateRepository>();
repository.AddOrUpdate(template);
uow.Events.Dispatch(SavedTemplate, this, new SaveEventArgs<ITemplate>(template, false, evtMsgs));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedTemplate, this, saveEventArgs);
Audit(uow, AuditType.Save, "Save Template performed by user", userId, template.Id);
uow.Complete();
@@ -616,16 +621,18 @@ namespace Umbraco.Core.Services
return;
}
if (uow.Events.DispatchCancelable(DeletingTemplate, this, new DeleteEventArgs<ITemplate>(template)))
var args = new DeleteEventArgs<ITemplate>(template);
if (uow.Events.DispatchCancelable(DeletingTemplate, this, args))
{
uow.Complete();
return;
}
repository.Delete(template);
uow.Events.Dispatch(DeletedTemplate, this, new DeleteEventArgs<ITemplate>(template, false));
args.CanCancel = false;
uow.Events.Dispatch(DeletedTemplate, this, args);
Audit(uow, AuditType.Delete, "Delete Template performed by user", userId, template.Id);
uow.Complete();
}
@@ -806,7 +813,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(CreatingPartialView, this, new NewEventArgs<IPartialView>(partialView, true, partialView.Alias, -1)))
var newEventArgs = new NewEventArgs<IPartialView>(partialView, true, partialView.Alias, -1);
if (uow.Events.DispatchCancelable(CreatingPartialView, this, newEventArgs))
{
uow.Complete();
return Attempt<IPartialView>.Fail();
@@ -815,8 +823,10 @@ namespace Umbraco.Core.Services
var repository = uow.CreatePartialViewRepository(partialViewType);
if (partialViewContent != null) partialView.Content = partialViewContent;
repository.AddOrUpdate(partialView);
newEventArgs.CanCancel = false;
uow.Events.Dispatch(CreatedPartialView, this, newEventArgs);
uow.Events.Dispatch(CreatedPartialView, this, new NewEventArgs<IPartialView>(partialView, false, partialView.Alias, -1));
Audit(uow, AuditType.Save, $"Save {partialViewType} performed by user", userId, -1);
uow.Complete();
@@ -847,15 +857,16 @@ namespace Umbraco.Core.Services
return true;
}
if (uow.Events.DispatchCancelable(DeletingPartialView, this, new DeleteEventArgs<IPartialView>(partialView)))
var deleteEventArgs = new DeleteEventArgs<IPartialView>(partialView);
if (uow.Events.DispatchCancelable(DeletingPartialView, this, deleteEventArgs))
{
uow.Complete();
return false; // causes rollback
return false;
}
repository.Delete(partialView);
uow.Events.Dispatch(DeletedPartialView, this, new DeleteEventArgs<IPartialView>(partialView, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedPartialView, this, deleteEventArgs);
Audit(uow, AuditType.Delete, $"Delete {partialViewType} performed by user", userId, -1);
uow.Complete();
@@ -878,7 +889,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingPartialView, this, new SaveEventArgs<IPartialView>(partialView)))
var saveEventArgs = new SaveEventArgs<IPartialView>(partialView);
if (uow.Events.DispatchCancelable(SavingPartialView, this, saveEventArgs))
{
uow.Complete();
return Attempt<IPartialView>.Fail();
@@ -886,9 +898,9 @@ namespace Umbraco.Core.Services
var repository = uow.CreatePartialViewRepository(partialViewType);
repository.AddOrUpdate(partialView);
saveEventArgs.CanCancel = false;
Audit(uow, AuditType.Save, $"Save {partialViewType} performed by user", userId, -1);
uow.Events.Dispatch(SavedPartialView, this, new SaveEventArgs<IPartialView>(partialView, false));
uow.Events.Dispatch(SavedPartialView, this, saveEventArgs);
uow.Complete();
}
@@ -137,6 +137,23 @@ namespace Umbraco.Core.Services
bool SendToPublication(IContent content, int userId = 0);
IEnumerable<IContent> GetByIds(IEnumerable<int> ids);
IEnumerable<IContent> GetByIds(IEnumerable<Guid> ids);
/// <summary>
/// Creates an <see cref="IContent"/> object using the alias of the <see cref="IContentType"/>
/// that this Content should based on.
/// </summary>
/// <remarks>
/// Note that using this method will simply return a new IContent without any identity
/// as it has not yet been persisted. It is intended as a shortcut to creating new content objects
/// that does not invoke a save operation against the database.
/// </remarks>
/// <param name="name">Name of the Content object</param>
/// <param name="parentId">Id of Parent for the new Content</param>
/// <param name="contentTypeAlias">Alias of the <see cref="IContentType"/></param>
/// <param name="userId">Optional id of the user creating the content</param>
/// <returns><see cref="IContent"/></returns>
IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0);
/// <summary>
/// Creates an <see cref="IContent"/> object using the alias of the <see cref="IContentType"/>
@@ -49,6 +49,7 @@ namespace Umbraco.Core.Services
IEnumerable<EntityContainer> GetContainers(TItem contentType);
IEnumerable<EntityContainer> GetContainers(string folderName, int level);
Attempt<OperationStatus> DeleteContainer(int containerId, int userId = 0);
Attempt<OperationStatus<OperationStatusType, EntityContainer>> RenameContainer(int id, string name, int userId = 0);
Attempt<OperationStatus<MoveOperationStatusType>> Move(TItem moving, int containerId);
Attempt<OperationStatus<MoveOperationStatusType, TItem>> Copy(TItem copying, int containerId);
@@ -17,6 +17,7 @@ namespace Umbraco.Core.Services
IEnumerable<EntityContainer> GetContainers(IDataTypeDefinition dataTypeDefinition);
IEnumerable<EntityContainer> GetContainers(int[] containerIds);
Attempt<OperationStatus> DeleteContainer(int containerId, int userId = 0);
Attempt<OperationStatus<OperationStatusType, EntityContainer>> RenameContainer(int id, string name, int userId = 0);
/// <summary>
/// Gets a <see cref="IDataTypeDefinition"/> by its Name
@@ -286,5 +286,13 @@ namespace Umbraco.Core.Services
/// <param name="umbracoObjectType"><see cref="UmbracoObjectTypes"/></param>
/// <returns>Type of the entity</returns>
Type GetEntityType(UmbracoObjectTypes umbracoObjectType);
/// <summary>
/// Reserves an identifier for a key.
/// </summary>
/// <param name="key">They key.</param>
/// <returns>The identifier.</returns>
/// <remarks>When a new content or a media is saved with the key, it will have the reserved identifier.</remarks>
int ReserveId(Guid key);
}
}
@@ -136,5 +136,11 @@ namespace Umbraco.Core.Services
/// <param name="language"><see cref="ILanguage"/> to delete</param>
/// <param name="userId">Optional id of the user deleting the language</param>
void Delete(ILanguage language, int userId = 0);
/// <summary>
/// Gets the full dictionary key map.
/// </summary>
/// <returns>The full dictionary key map.</returns>
Dictionary<string, Guid> GetDictionaryItemKeyMap();
}
}
}
@@ -66,6 +66,23 @@ namespace Umbraco.Core.Services
int CountDescendants(int parentId, string mediaTypeAlias = null);
IEnumerable<IMedia> GetByIds(IEnumerable<int> ids);
IEnumerable<IMedia> GetByIds(IEnumerable<Guid> ids);
/// <summary>
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
/// that this Media should based on.
/// </summary>
/// <remarks>
/// Note that using this method will simply return a new IMedia without any identity
/// as it has not yet been persisted. It is intended as a shortcut to creating new media objects
/// that does not invoke a save operation against the database.
/// </remarks>
/// <param name="name">Name of the Media object</param>
/// <param name="parentId">Id of Parent for the new Media item</param>
/// <param name="mediaTypeAlias">Alias of the <see cref="IMediaType"/></param>
/// <param name="userId">Optional id of the user creating the media item</param>
/// <returns><see cref="IMedia"/></returns>
IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = 0);
/// <summary>
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
+9 -3
View File
@@ -95,10 +95,16 @@ namespace Umbraco.Core.Services
IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType);
/// <summary>
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method which can be
/// used during Member creation.
/// </summary>
/// <remarks>This method exists so that Umbraco developers can use one entry point to create/update
/// Members if they choose to. </remarks>
/// <remarks>
/// <remarks>This method exists so that Umbraco developers can use one entry point to create/update
/// this will not work for updating members in most cases (depends on your membership provider settings)
///
/// It is preferred to use the membership APIs for working with passwords, in the near future this method will be obsoleted
/// and the ASP.NET Identity APIs should be used instead.
/// </remarks>
/// <param name="member">The Member to save the password for</param>
/// <param name="password">The password to encrypt and save</param>
void SavePassword(IMember member, string password);
+30 -2
View File
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Services
{
@@ -24,14 +26,40 @@ namespace Umbraco.Core.Services
/// <param name="orderBy"></param>
/// <param name="orderDirection"></param>
/// <param name="userState"></param>
/// <param name="userGroups"></param>
/// <param name="includeUserGroups">
/// A filter to only include user that belong to these user groups
/// </param>
/// <param name="excludeUserGroups">
/// A filter to only include users that do not belong to these user groups
/// </param>
/// <param name="filter"></param>
/// <returns></returns>
IEnumerable<IUser> GetAll(long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection,
UserState[] userState = null,
string[] includeUserGroups = null,
string[] excludeUserGroups = null,
IQuery<IUser> filter = null);
/// <summary>
/// Get paged users
/// </summary>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderBy"></param>
/// <param name="orderDirection"></param>
/// <param name="userState"></param>
/// <param name="userGroups">
/// A filter to only include user that belong to these user groups
/// </param>
/// <param name="filter"></param>
/// <returns></returns>
IEnumerable<IUser> GetAll(long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection,
UserState[] userState = null,
string[] userGroups = null,
string filter = "");
string filter = null);
/// <summary>
/// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method
+47 -18
View File
@@ -10,8 +10,9 @@ namespace Umbraco.Core.Services
{
private readonly IScopeUnitOfWorkProvider _uowProvider;
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private readonly Dictionary<int, Guid> _id2Key = new Dictionary<int, Guid>();
private readonly Dictionary<Guid, int> _key2Id = new Dictionary<Guid, int>();
private readonly Dictionary<int, TypedId<Guid>> _id2Key = new Dictionary<int, TypedId<Guid>>();
private readonly Dictionary<Guid, TypedId<int>> _key2Id = new Dictionary<Guid, TypedId<int>>();
public IdkMap(IScopeUnitOfWorkProvider uowProvider)
{
@@ -23,11 +24,11 @@ namespace Umbraco.Core.Services
public Attempt<int> GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
{
int id;
TypedId<int> id;
try
{
_locker.EnterReadLock();
if (_key2Id.TryGetValue(key, out id)) return Attempt.Succeed(id);
if (_key2Id.TryGetValue(key, out id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
}
finally
{
@@ -44,13 +45,16 @@ namespace Umbraco.Core.Services
}
if (val == null) return Attempt<int>.Fail();
id = val.Value;
// cache reservations, when something is saved this cache is cleared anyways
//if (umbracoObjectType == UmbracoObjectTypes.IdReservation)
// Attempt.Succeed(val.Value);
try
{
_locker.EnterWriteLock();
_id2Key[id] = key;
_key2Id[key] = id;
_id2Key[val.Value] = new TypedId<Guid>(key, umbracoObjectType);
_key2Id[key] = new TypedId<int>(val.Value, umbracoObjectType);
}
finally
{
@@ -58,7 +62,7 @@ namespace Umbraco.Core.Services
_locker.ExitWriteLock();
}
return Attempt.Succeed(id);
return Attempt.Succeed(val.Value);
}
public Attempt<int> GetIdForUdi(Udi udi)
@@ -73,11 +77,11 @@ namespace Umbraco.Core.Services
public Attempt<Guid> GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
{
Guid key;
TypedId<Guid> key;
try
{
_locker.EnterReadLock();
if (_id2Key.TryGetValue(id, out key)) return Attempt.Succeed(key);
if (_id2Key.TryGetValue(id, out key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
}
finally
{
@@ -94,13 +98,16 @@ namespace Umbraco.Core.Services
}
if (val == null) return Attempt<Guid>.Fail();
key = val.Value;
// cache reservations, when something is saved this cache is cleared anyways
//if (umbracoObjectType == UmbracoObjectTypes.IdReservation)
// Attempt.Succeed(val.Value);
try
{
_locker.EnterWriteLock();
_id2Key[id] = key;
_key2Id[key] = id;
_id2Key[id] = new TypedId<Guid>(val.Value, umbracoObjectType); ;
_key2Id[val.Value] = new TypedId<int>();
}
finally
{
@@ -108,7 +115,7 @@ namespace Umbraco.Core.Services
_locker.ExitWriteLock();
}
return Attempt.Succeed(key);
return Attempt.Succeed(val.Value);
}
private static Guid GetNodeObjectTypeGuid(UmbracoObjectTypes umbracoObjectType)
@@ -139,10 +146,10 @@ namespace Umbraco.Core.Services
try
{
_locker.EnterWriteLock();
Guid key;
TypedId<Guid> key;
if (_id2Key.TryGetValue(id, out key) == false) return;
_id2Key.Remove(id);
_key2Id.Remove(key);
_key2Id.Remove(key.Id);
}
finally
{
@@ -156,9 +163,9 @@ namespace Umbraco.Core.Services
try
{
_locker.EnterWriteLock();
int id;
TypedId<int> id;
if (_key2Id.TryGetValue(key, out id) == false) return;
_id2Key.Remove(id);
_id2Key.Remove(id.Id);
_key2Id.Remove(key);
}
finally
@@ -167,5 +174,27 @@ namespace Umbraco.Core.Services
_locker.ExitWriteLock();
}
}
private struct TypedId<T>
{
private readonly T _id;
private readonly UmbracoObjectTypes _umbracoObjectType;
public T Id
{
get { return _id; }
}
public UmbracoObjectTypes UmbracoObjectType
{
get { return _umbracoObjectType; }
}
public TypedId(T id, UmbracoObjectTypes umbracoObjectType)
{
_umbracoObjectType = umbracoObjectType;
_id = id;
}
}
}
}
@@ -80,7 +80,8 @@ namespace Umbraco.Core.Services
item.Translations = translations;
}
if (uow.Events.DispatchCancelable(SavingDictionaryItem, this, new SaveEventArgs<IDictionaryItem>(item)))
var saveEventArgs = new SaveEventArgs<IDictionaryItem>(item);
if (uow.Events.DispatchCancelable(SavingDictionaryItem, this, saveEventArgs))
{
uow.Complete();
return item;
@@ -91,8 +92,8 @@ namespace Umbraco.Core.Services
// ensure the lazy Language callback is assigned
EnsureDictionaryItemLanguageCallback(item);
uow.Events.Dispatch(SavedDictionaryItem, this, new SaveEventArgs<IDictionaryItem>(item));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedDictionaryItem, this, saveEventArgs);
return item;
}
@@ -257,7 +258,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingDictionaryItem, this, new DeleteEventArgs<IDictionaryItem>(dictionaryItem)))
var deleteEventArgs = new DeleteEventArgs<IDictionaryItem>(dictionaryItem);
if (uow.Events.DispatchCancelable(DeletingDictionaryItem, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -265,8 +267,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IDictionaryRepository>();
repository.Delete(dictionaryItem);
uow.Events.Dispatch(DeletedDictionaryItem, this, new DeleteEventArgs<IDictionaryItem>(dictionaryItem, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedDictionaryItem, this, deleteEventArgs);
Audit(uow, AuditType.Delete, "Delete DictionaryItem performed by user", userId, dictionaryItem.Id);
@@ -338,7 +340,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingLanguage, this, new SaveEventArgs<ILanguage>(language)))
var saveEventArgs = new SaveEventArgs<ILanguage>(language);
if (uow.Events.DispatchCancelable(SavingLanguage, this, saveEventArgs))
{
uow.Complete();
return;
@@ -346,8 +349,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<ILanguageRepository>();
repository.AddOrUpdate(language);
uow.Events.Dispatch(SavedLanguage, this, new SaveEventArgs<ILanguage>(language, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedLanguage, this, saveEventArgs);
Audit(uow, AuditType.Save, "Save Language performed by user", userId, language.Id);
@@ -364,7 +367,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingLanguage, this, new DeleteEventArgs<ILanguage>(language)))
var deleteEventArgs = new DeleteEventArgs<ILanguage>(language);
if (uow.Events.DispatchCancelable(DeletingLanguage, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -374,7 +378,8 @@ namespace Umbraco.Core.Services
//NOTE: There isn't any constraints in the db, so possible references aren't deleted
repository.Delete(language);
uow.Events.Dispatch(DeletedLanguage, this, new DeleteEventArgs<ILanguage>(language, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedLanguage, this, deleteEventArgs);
Audit(uow, AuditType.Delete, "Delete Language performed by user", userId, language.Id);
@@ -404,6 +409,15 @@ namespace Umbraco.Core.Services
trans.GetLanguage = GetLanguageById;
}
public Dictionary<string, Guid> GetDictionaryItemKeyMap()
{
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateDictionaryRepository(uow);
return repository.GetDictionaryItemKeyMap();
}
}
#region Event Handlers
/// <summary>
/// Occurs before Delete
+8 -6
View File
@@ -101,7 +101,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IMacro>(macro)))
var deleteEventArgs = new DeleteEventArgs<IMacro>(macro);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -109,8 +110,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IMacroRepository>();
repository.Delete(macro);
uow.Events.Dispatch(Deleted, this, new DeleteEventArgs<IMacro>(macro, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(Deleted, this, deleteEventArgs);
Audit(uow, AuditType.Delete, "Delete Macro performed by user", userId, -1);
uow.Complete();
@@ -126,7 +127,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMacro>(macro)))
var saveEventArgs = new SaveEventArgs<IMacro>(macro);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -139,8 +141,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IMacroRepository>();
repository.AddOrUpdate(macro);
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMacro>(macro, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
Audit(uow, AuditType.Save, "Save Macro performed by user", userId, -1);
uow.Complete();
+96 -27
View File
@@ -22,15 +22,13 @@ 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, IdkMap idkMap)
public MediaService(IScopeUnitOfWorkProvider provider, MediaFileSystem mediaFileSystem, ILogger logger, IEventMessagesFactory eventMessagesFactory)
: base(provider, logger, eventMessagesFactory)
{
_mediaFileSystem = mediaFileSystem;
_idkMap = idkMap;
}
#endregion
@@ -94,6 +92,26 @@ namespace Umbraco.Core.Services
#region Create
/// <summary>
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
/// that this Media should based on.
/// </summary>
/// <remarks>
/// Note that using this method will simply return a new IMedia without any identity
/// as it has not yet been persisted. It is intended as a shortcut to creating new media objects
/// that does not invoke a save operation against the database.
/// </remarks>
/// <param name="name">Name of the Media object</param>
/// <param name="parentId">Id of Parent for the new Media item</param>
/// <param name="mediaTypeAlias">Alias of the <see cref="IMediaType"/></param>
/// <param name="userId">Optional id of the user creating the media item</param>
/// <returns><see cref="IMedia"/></returns>
public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = 0)
{
var parent = GetById(parentId);
return CreateMedia(name, parent, mediaTypeAlias, userId);
}
/// <summary>
/// Creates an <see cref="IMedia"/> object of a specified media type.
/// </summary>
@@ -266,7 +284,8 @@ namespace Umbraco.Core.Services
if (withIdentity)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMedia>(media), this))
var saveEventArgs = new SaveEventArgs<IMedia>(media);
if (Saving.IsRaisedEventCancelled(saveEventArgs, this))
{
media.WasCancelled = true;
return;
@@ -275,11 +294,13 @@ namespace Umbraco.Core.Services
var repo = uow.CreateRepository<IMediaRepository>();
repo.AddOrUpdate(media);
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMedia>(media, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
uow.Events.Dispatch(TreeChanged, this, new TreeChange<IMedia>(media, TreeChangeTypes.RefreshNode).ToEventArgs());
}
uow.Events.Dispatch(Created, this, new NewEventArgs<IMedia>(media, false, media.ContentType.Alias, parent));
newArgs.CanCancel = false;
uow.Events.Dispatch(Created, this, newArgs);
var msg = withIdentity
? "Media '{0}' was created with Id {1}"
@@ -332,15 +353,32 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IMedia"/></returns>
public IMedia GetById(Guid key)
{
// 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;
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.MediaTree);
var repository = uow.CreateRepository<IMediaRepository>();
return repository.Get(key);
}
}
/// <summary>
/// Gets an <see cref="IMedia"/> object by Id
/// </summary>
/// <param name="ids">Ids of the Media to retrieve</param>
/// <returns><see cref="IMedia"/></returns>
public IEnumerable<IMedia> GetByIds(IEnumerable<Guid> ids)
{
var idsA = ids.ToArray();
if (idsA.Length == 0) return Enumerable.Empty<IMedia>();
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.MediaTree);
var repository = uow.CreateRepository<IMediaRepository>();
return repository.GetAll(idsA);
}
}
/// <summary>
/// Gets a collection of <see cref="IMedia"/> objects by the Id of the <see cref="IMediaType"/>
/// </summary>
@@ -594,7 +632,16 @@ namespace Umbraco.Core.Services
var query = uow.Query<IMedia>();
//if the id is System Root, then just get all
if (id != Constants.System.Root)
query.Where(x => x.Path.SqlContains($",{id},", TextColumnType.NVarchar));
{
var entityRepository = uow.CreateRepository<IEntityRepository>();
var mediaPath = entityRepository.GetAllPaths(Constants.ObjectTypes.MediaGuid, id).ToArray();
if (mediaPath.Length == 0)
{
totalChildren = 0;
return Enumerable.Empty<IMedia>();
}
query.Where(x => x.Path.SqlStartsWith(mediaPath[0] + ",", TextColumnType.NVarchar));
}
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter);
}
}
@@ -750,7 +797,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMedia>(media, evtMsgs)))
var saveEventArgs = new SaveEventArgs<IMedia>(media, evtMsgs);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return OperationStatus.Attempt.Cancel(evtMsgs);
@@ -770,7 +818,10 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(media);
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMedia>(media, false, evtMsgs));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode;
uow.Events.Dispatch(TreeChanged, this, new TreeChange<IMedia>(media, changeType).ToEventArgs());
Audit(uow, AuditType.Save, "Save Media performed by user", userId, media.Id);
@@ -805,6 +856,7 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
var saveEventArgs = new SaveEventArgs<IMedia>(mediasA, evtMsgs);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMedia>(mediasA, evtMsgs)))
{
uow.Complete();
@@ -824,7 +876,10 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMedia>(mediasA, false, evtMsgs));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
uow.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs());
Audit(uow, AuditType.Save, "Bulk Save media performed by user", userId == -1 ? 0 : userId, Constants.System.Root);
@@ -954,7 +1009,8 @@ namespace Umbraco.Core.Services
private void DeleteVersions(IScopeUnitOfWork uow, ref IMediaRepository repository, int id, DateTime versionDate, int userId = 0)
{
if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, dateToRetain: versionDate)))
var args = new DeleteRevisionsEventArgs(id, dateToRetain: versionDate);
if (uow.Events.DispatchCancelable(DeletingVersions, this, args))
return;
if (repository == null)
@@ -964,7 +1020,8 @@ namespace Umbraco.Core.Services
}
repository.DeleteVersions(id, versionDate);
uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, dateToRetain: versionDate));
args.CanCancel = false;
uow.Events.Dispatch(DeletedVersions, this, args);
Audit(uow, AuditType.Delete, "Delete Media by version date performed by user", userId, Constants.System.Root);
}
@@ -980,7 +1037,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingVersions, this, new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId)))
var args = new DeleteRevisionsEventArgs(id, /*specificVersion:*/ versionId);
if (uow.Events.DispatchCancelable(DeletingVersions, this, args))
{
uow.Complete();
return;
@@ -1000,7 +1058,8 @@ namespace Umbraco.Core.Services
}
repository.DeleteVersion(versionId);
uow.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false, /*specificVersion:*/ versionId));
args.CanCancel = false;
uow.Events.Dispatch(DeletedVersions, this, args);
Audit(uow, AuditType.Delete, "Delete Media by version performed by user", userId, Constants.System.Root);
uow.Complete();
@@ -1089,7 +1148,9 @@ namespace Umbraco.Core.Services
if (parentId != Constants.System.Root && (parent == null || parent.Trashed))
throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback
if (uow.Events.DispatchCancelable(Moving, this, new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(media, media.Path, parentId))))
var moveEventInfo = new MoveEventInfo<IMedia>(media, media.Path, parentId);
var moveEventArgs = new MoveEventArgs<IMedia>(moveEventInfo);
if (uow.Events.DispatchCancelable(Moving, this, moveEventArgs))
{
uow.Complete();
return;
@@ -1108,7 +1169,9 @@ namespace Umbraco.Core.Services
.Select(x => new MoveEventInfo<IMedia>(x.Item1, x.Item2, x.Item1.ParentId))
.ToArray();
uow.Events.Dispatch(Moved, this, new MoveEventArgs<IMedia>(false, moveInfo));
moveEventArgs.MoveInfoCollection = moveInfo;
moveEventArgs.CanCancel = false;
uow.Events.Dispatch(Moved, this, moveEventArgs);
Audit(uow, AuditType.Move, "Move Media performed by user", userId, media.Id);
uow.Complete();
}
@@ -1183,7 +1246,8 @@ namespace Umbraco.Core.Services
// are managed by Delete, and not here.
// no idea what those events are for, keep a simplified version
if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, new RecycleBinEventArgs(nodeObjectType)))
var args = new RecycleBinEventArgs(nodeObjectType);
if (uow.Events.DispatchCancelable(EmptyingRecycleBin, this, args))
{
uow.Complete();
return;
@@ -1198,7 +1262,8 @@ namespace Umbraco.Core.Services
deleted.Add(media);
}
uow.Events.Dispatch(EmptiedRecycleBin, this, new RecycleBinEventArgs(nodeObjectType, true));
args.CanCancel = false;
uow.Events.Dispatch(EmptiedRecycleBin, this, args);
uow.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange<IMedia>(x, TreeChangeTypes.Remove)).ToEventArgs());
Audit(uow, AuditType.Delete, "Empty Media Recycle Bin performed by user", 0, Constants.System.RecycleBinMedia);
uow.Complete();
@@ -1224,7 +1289,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMedia>(itemsA)))
var args = new SaveEventArgs<IMedia>(itemsA);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, args))
{
uow.Complete();
return false;
@@ -1254,7 +1320,10 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMedia>(saved, false));
{
args.CanCancel = false;
uow.Events.Dispatch(Saved, this, args);
}
uow.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange<IMedia>(x, TreeChangeTypes.RefreshNode)).ToEventArgs());
Audit(uow, AuditType.Sort, "Sorting Media performed by user", userId, 0);
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
namespace Umbraco.Core.Services
{
/// <summary>
/// Media service extension methods
/// </summary>
/// <remarks>
/// Many of these have to do with UDI lookups but we don't need to add these methods to the service interface since a UDI is just a GUID
/// and the services already support GUIDs
/// </remarks>
public static class MediaServiceExtensions
{
public static IEnumerable<IMedia> GetByIds(this IMediaService mediaService, IEnumerable<Udi> ids)
{
var guids = new List<GuidUdi>();
foreach (var udi in ids)
{
var guidUdi = udi as GuidUdi;
if (guidUdi == null)
throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media");
guids.Add(guidUdi);
}
return mediaService.GetByIds(guids.Select(x => x.Guid));
}
public static IMedia CreateMedia(this IMediaService mediaService, string name, Udi parentId, string mediaTypeAlias, int userId = 0)
{
var guidUdi = parentId as GuidUdi;
if (guidUdi == null)
throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media");
var parent = mediaService.GetById(guidUdi.Guid);
return mediaService.CreateMedia(name, parent, mediaTypeAlias, userId);
}
}
}
@@ -78,7 +78,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMemberGroup>(memberGroup)))
var saveEventArgs = new SaveEventArgs<IMemberGroup>(memberGroup);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -89,7 +90,10 @@ namespace Umbraco.Core.Services
uow.Complete();
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMemberGroup>(memberGroup, false));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
}
}
@@ -97,7 +101,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IMemberGroup>(memberGroup)))
var deleteEventArgs = new DeleteEventArgs<IMemberGroup>(memberGroup);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -106,8 +111,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IMemberGroupRepository>();
repository.Delete(memberGroup);
uow.Complete();
uow.Events.Dispatch(Deleted, this, new DeleteEventArgs<IMemberGroup>(memberGroup, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(Deleted, this, deleteEventArgs);
}
}
+38 -20
View File
@@ -25,6 +25,9 @@ namespace Umbraco.Core.Services
private readonly IMemberGroupService _memberGroupService;
private readonly MediaFileSystem _mediaFileSystem;
//only for unit tests!
internal MembershipProviderBase MembershipProvider { get; set; }
#region Constructor
public MemberService(IScopeUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, MediaFileSystem mediaFileSystem)
@@ -190,7 +193,7 @@ namespace Umbraco.Core.Services
{
return CreateMemberWithIdentity(username, email, username, "", memberTypeAlias);
}
public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved)
{
return CreateMemberWithIdentity(username, email, username, "", memberTypeAlias, isApproved);
@@ -200,11 +203,11 @@ namespace Umbraco.Core.Services
{
return CreateMemberWithIdentity(username, email, name, "", memberTypeAlias);
}
public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved)
{
return CreateMemberWithIdentity(username, email, name, "", memberTypeAlias, isApproved);
}
}
/// <summary>
/// Creates and persists a Member
@@ -239,7 +242,7 @@ namespace Umbraco.Core.Services
public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType)
{
return CreateMemberWithIdentity(username, email, username, "", memberType);
}
}
/// <summary>
/// Creates and persists a Member
@@ -258,7 +261,7 @@ namespace Umbraco.Core.Services
public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType)
{
return CreateMemberWithIdentity(username, email, name, "", memberType);
}
}
/// <summary>
/// Creates and persists a Member
@@ -315,7 +318,8 @@ namespace Umbraco.Core.Services
if (withIdentity)
{
if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMember>(member)))
var saveEventArgs = new SaveEventArgs<IMember>(member);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
member.WasCancelled = true;
return;
@@ -324,7 +328,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IMemberRepository>();
repository.AddOrUpdate(member);
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMember>(member, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
uow.Events.Dispatch(Created, this, new NewEventArgs<IMember>(member, false, member.ContentType.Alias, -1));
@@ -868,7 +873,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMember>(member)))
var saveEventArgs = new SaveEventArgs<IMember>(member);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -885,7 +891,10 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(member);
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMember>(member, false));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
Audit(uow, AuditType.Save, "Save Member performed by user", 0, member.Id);
uow.Complete();
@@ -904,7 +913,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs<IMember>(membersA)))
var saveEventArgs = new SaveEventArgs<IMember>(membersA);
if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -916,7 +926,10 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(member);
if (raiseEvents)
uow.Events.Dispatch(Saved, this, new SaveEventArgs<IMember>(membersA, false));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
Audit(uow, AuditType.Save, "Save Member items performed by user", 0, -1);
uow.Complete();
@@ -935,7 +948,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IMember>(member)))
var deleteEventArgs = new DeleteEventArgs<IMember>(member);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -943,18 +957,21 @@ namespace Umbraco.Core.Services
uow.WriteLock(Constants.Locks.MemberTree);
var repository = uow.CreateRepository<IMemberRepository>();
DeleteLocked(uow, repository, member);
DeleteLocked(uow, repository, member, deleteEventArgs);
Audit(uow, AuditType.Delete, "Delete Member performed by user", 0, member.Id);
uow.Complete();
}
}
private void DeleteLocked(IScopeUnitOfWork uow, IMemberRepository repository, IMember member)
private void DeleteLocked(IScopeUnitOfWork uow, IMemberRepository repository, IMember member, DeleteEventArgs<IMember> args = null)
{
// a member has no descendants
repository.Delete(member);
var args = new DeleteEventArgs<IMember>(member, false); // raise event & get flagged files
repository.Delete(member);
if (args == null)
args = new DeleteEventArgs<IMember>(member, false); // raise event & get flagged files
else
args.CanCancel = false;
uow.Events.Dispatch(Deleted, this, args);
// fixme - this is MOOT because the event will not trigger immediately
@@ -1179,7 +1196,7 @@ namespace Umbraco.Core.Services
{
if (member == null) throw new ArgumentNullException(nameof(member));
var provider = MembershipProviderExtensions.GetMembersMembershipProvider();
var provider = MembershipProvider ?? MembershipProviderExtensions.GetMembersMembershipProvider();
if (provider.IsUmbracoMembershipProvider())
provider.ChangePassword(member.Username, "", password); // this is actually updating the password
else
@@ -1294,7 +1311,8 @@ namespace Umbraco.Core.Services
var query = uow.Query<IMember>().Where(x => x.ContentTypeId == memberTypeId);
var members = repository.GetByQuery(query).ToArray();
if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs<IMember>(members)))
var deleteEventArgs = new DeleteEventArgs<IMember>(members);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -1335,12 +1353,12 @@ namespace Umbraco.Core.Services
return GetMemberType(uow, memberTypeAlias);
}
}
// fixme - this should not be here, or???
public string GetDefaultMemberType()
{
return Current.Services.MemberTypeService.GetDefault();
}
}
#endregion
}
+9 -5
View File
@@ -17,9 +17,8 @@ namespace Umbraco.Core.Services
/// <param name="eventMessages">Event messages produced by the operation.</param>
public OperationStatus(TStatusType statusType, EventMessages eventMessages)
{
if (eventMessages == null) throw new ArgumentNullException(nameof(eventMessages));
StatusType = statusType;
EventMessages = eventMessages;
EventMessages = eventMessages ?? throw new ArgumentNullException(nameof(eventMessages));
}
/// <summary>
@@ -34,6 +33,7 @@ namespace Umbraco.Core.Services
public EventMessages EventMessages { get; }
}
/// <inheritdoc />
/// <summary>
/// Represents the status of a service operation that manages (processes, produces...) a value.
/// </summary>
@@ -42,8 +42,9 @@ namespace Umbraco.Core.Services
public class OperationStatus<TStatusType, TValue> : OperationStatus<TStatusType>
where TStatusType : struct
{
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="OperationStatus{TStatusType, TValue}"/> class with a status type and event messages.
/// Initializes a new instance of the <see cref="T:Umbraco.Core.Services.OperationStatus`2" /> class with a status type and event messages.
/// </summary>
/// <param name="statusType">The status of the operation.</param>
/// <param name="eventMessages">Event messages produced by the operation.</param>
@@ -51,8 +52,9 @@ namespace Umbraco.Core.Services
: base(statusType, eventMessages)
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="OperationStatus{TStatusType, TValue}"/> class with a status type, event messages and a value.
/// Initializes a new instance of the <see cref="T:Umbraco.Core.Services.OperationStatus`2" /> class with a status type, event messages and a value.
/// </summary>
/// <param name="statusType">The status of the operation.</param>
/// <param name="eventMessages">Event messages produced by the operation.</param>
@@ -69,14 +71,16 @@ namespace Umbraco.Core.Services
public TValue Value { get; }
}
/// <inheritdoc />
/// <summary>
/// Represents the default operation status.
/// </summary>
/// <remarks>Also provides static helper methods to create operation statuses.</remarks>
public class OperationStatus : OperationStatus<OperationStatusType>
{
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="OperationStatus"/> class with a status and event messages.
/// Initializes a new instance of the <see cref="T:Umbraco.Core.Services.OperationStatus" /> class with a status and event messages.
/// </summary>
/// <param name="statusType">The status of the operation.</param>
/// <param name="eventMessages">Event messages produced by the operation.</param>
@@ -129,15 +129,20 @@ namespace Umbraco.Core.Services
return OperationStatus.Attempt.Succeed(evtMsgs, entry);
}
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs), this))
return OperationStatus.Attempt.Cancel(evtMsgs, entry); // causes rollback
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Commit();
return OperationStatus.Attempt.Cancel(evtMsgs, entry);
}
repo.AddOrUpdate(entry);
uow.Complete();
}
Saved.RaiseEvent(new SaveEventArgs<PublicAccessEntry>(entry, false, evtMsgs), this);
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
return OperationStatus.Attempt.Succeed(evtMsgs, entry);
}
@@ -163,14 +168,19 @@ namespace Umbraco.Core.Services
entry.RemoveRule(existingRule);
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs), this))
return OperationStatus.Attempt.Cancel(evtMsgs); // causes rollback
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Commit();
return OperationStatus.Attempt.Cancel(evtMsgs);
}
repo.AddOrUpdate(entry);
uow.Complete();
}
Saved.RaiseEvent(new SaveEventArgs<PublicAccessEntry>(entry, false, evtMsgs), this);
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
return OperationStatus.Attempt.Succeed(evtMsgs);
}
@@ -181,21 +191,23 @@ namespace Umbraco.Core.Services
public Attempt<OperationStatus> Save(PublicAccessEntry entry)
{
var evtMsgs = EventMessagesFactory.Get();
if (Saving.IsRaisedEventCancelled(
new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs),
this))
{
return OperationStatus.Attempt.Cancel(evtMsgs);
}
using (var uow = UowProvider.CreateUnitOfWork())
{
var saveEventArgs = new SaveEventArgs<PublicAccessEntry>(entry, evtMsgs);
if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Commit();
return OperationStatus.Attempt.Cancel(evtMsgs);
}
var repo = uow.CreateRepository<IPublicAccessRepository>();
repo.AddOrUpdate(entry);
uow.Complete();
}
Saved.RaiseEvent(new SaveEventArgs<PublicAccessEntry>(entry, false, evtMsgs), this);
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
return OperationStatus.Attempt.Succeed(evtMsgs);
}
@@ -206,21 +218,23 @@ namespace Umbraco.Core.Services
public Attempt<OperationStatus> Delete(PublicAccessEntry entry)
{
var evtMsgs = EventMessagesFactory.Get();
if (Deleting.IsRaisedEventCancelled(
new DeleteEventArgs<PublicAccessEntry>(entry, evtMsgs),
this))
{
return OperationStatus.Attempt.Cancel(evtMsgs);
}
using (var uow = UowProvider.CreateUnitOfWork())
{
var deleteEventArgs = new DeleteEventArgs<PublicAccessEntry>(entry, evtMsgs);
if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Commit();
return OperationStatus.Cancelled(evtMsgs);
}
var repo = uow.CreateRepository<IPublicAccessRepository>();
repo.Delete(entry);
uow.Complete();
}
Deleted.RaiseEvent(new DeleteEventArgs<PublicAccessEntry>(entry, false, evtMsgs), this);
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(Deleted, this, deleteEventArgs);
return OperationStatus.Attempt.Succeed(evtMsgs);
}
@@ -55,10 +55,28 @@ namespace Umbraco.Core.Services
public static bool HasAccess(this IPublicAccessService publicAccessService, string path, MembershipUser member, RoleProvider roleProvider)
{
return publicAccessService.HasAccess(path, member.UserName, roleProvider.GetRolesForUser);
}
/// <summary>
/// Checks if the member with the specified username has access to the path which is also based on the passed in roles for the member
/// </summary>
/// <param name="publicAccessService"></param>
/// <param name="path"></param>
/// <param name="username"></param>
/// <param name="rolesCallback">A callback to retrieve the roles for this member</param>
/// <returns></returns>
public static bool HasAccess(this IPublicAccessService publicAccessService, string path, string username, Func<string, IEnumerable<string>> rolesCallback)
{
if (rolesCallback == null) throw new ArgumentNullException("roles");
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username");
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path");
var entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path));
if (entry == null) return true;
var roles = roleProvider.GetRolesForUser(member.UserName);
var roles = rolesCallback(username);
return entry.Rules.Any(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType
&& roles.Contains(x.RuleValue));
}
+24 -17
View File
@@ -400,7 +400,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingRelation, this, new SaveEventArgs<IRelation>(relation)))
var saveEventArgs = new SaveEventArgs<IRelation>(relation);
if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
{
uow.Complete();
return relation; // fixme - returning sth that does not exist here?!
@@ -408,7 +409,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IRelationRepository>();
repository.AddOrUpdate(relation);
uow.Events.Dispatch(SavedRelation, this, new SaveEventArgs<IRelation>(relation, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedRelation, this, saveEventArgs);
uow.Complete();
}
@@ -432,7 +434,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingRelation, this, new SaveEventArgs<IRelation>(relation)))
var saveEventArgs = new SaveEventArgs<IRelation>(relation);
if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
{
uow.Complete();
return relation; // fixme - returning sth that does not exist here?!
@@ -440,8 +443,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IRelationRepository>();
repository.AddOrUpdate(relation);
uow.Events.Dispatch(SavedRelation, this, new SaveEventArgs<IRelation>(relation, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedRelation, this, saveEventArgs);
uow.Complete();
}
@@ -560,7 +563,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingRelation, this, new SaveEventArgs<IRelation>(relation)))
var saveEventArgs = new SaveEventArgs<IRelation>(relation);
if (uow.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
{
uow.Complete();
return;
@@ -569,8 +573,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IRelationRepository>();
repository.AddOrUpdate(relation);
uow.Complete();
uow.Events.Dispatch(SavedRelation, this, new SaveEventArgs<IRelation>(relation, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedRelation, this, saveEventArgs);
}
}
@@ -582,7 +586,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(SavingRelationType, this, new SaveEventArgs<IRelationType>(relationType)))
var saveEventArgs = new SaveEventArgs<IRelationType>(relationType);
if (uow.Events.DispatchCancelable(SavingRelationType, this, saveEventArgs))
{
uow.Complete();
return;
@@ -591,8 +596,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IRelationTypeRepository>();
repository.AddOrUpdate(relationType);
uow.Complete();
uow.Events.Dispatch(SavedRelationType, this, new SaveEventArgs<IRelationType>(relationType, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedRelationType, this, saveEventArgs);
}
}
@@ -604,7 +609,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingRelation, this, new DeleteEventArgs<IRelation>(relation)))
var deleteEventArgs = new DeleteEventArgs<IRelation>(relation);
if (uow.Events.DispatchCancelable(DeletingRelation, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -613,8 +619,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IRelationRepository>();
repository.Delete(relation);
uow.Complete();
uow.Events.Dispatch(DeletedRelation, this, new DeleteEventArgs<IRelation>(relation, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedRelation, this, deleteEventArgs);
}
}
@@ -626,7 +632,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingRelationType, this, new DeleteEventArgs<IRelationType>(relationType)))
var deleteEventArgs = new DeleteEventArgs<IRelationType>(relationType);
if (uow.Events.DispatchCancelable(DeletingRelationType, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -635,8 +642,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IRelationTypeRepository>();
repository.Delete(relationType);
uow.Complete();
uow.Events.Dispatch(DeletedRelationType, this, new DeleteEventArgs<IRelationType>(relationType, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedRelationType, this, deleteEventArgs);
}
}
+58 -28
View File
@@ -124,7 +124,8 @@ namespace Umbraco.Core.Services
IsApproved = isApproved
};
if (uow.Events.DispatchCancelable(SavingUser, this, new SaveEventArgs<IUser>(user)))
var saveEventArgs = new SaveEventArgs<IUser>(user);
if (uow.Events.DispatchCancelable(SavingUser, this, saveEventArgs))
{
uow.Complete();
return user;
@@ -132,7 +133,8 @@ namespace Umbraco.Core.Services
repository.AddOrUpdate(user);
uow.Events.Dispatch(SavedUser, this, new SaveEventArgs<IUser>(user, false));
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedUser, this, saveEventArgs);
uow.Complete();
}
@@ -266,7 +268,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingUser, this, new DeleteEventArgs<IUser>(user)))
var deleteEventArgs = new DeleteEventArgs<IUser>(user);
if (uow.Events.DispatchCancelable(DeletingUser, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -275,7 +278,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IUserRepository>();
repository.Delete(user);
uow.Events.Dispatch(DeletedUser, this, new DeleteEventArgs<IUser>(user, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedUser, this, deleteEventArgs);
uow.Complete();
}
}
@@ -291,7 +295,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(SavingUser, this, new SaveEventArgs<IUser>(entity)))
var saveEventArgs = new SaveEventArgs<IUser>(entity);
if (raiseEvents && uow.Events.DispatchCancelable(SavingUser, this, saveEventArgs))
{
uow.Complete();
return;
@@ -305,7 +310,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IUserRepository>();
//Now we have to check for backwards compat hacks
//Now we have to check for backwards compat hacks, we'll need to process any groups
//to save first before we update the user since these groups might be new groups.
var explicitUser = entity as User;
if (explicitUser != null && explicitUser.GroupsToSave.Count > 0)
{
@@ -320,7 +326,10 @@ namespace Umbraco.Core.Services
{
repository.AddOrUpdate(entity);
if (raiseEvents)
uow.Events.Dispatch(SavedUser, this, new SaveEventArgs<IUser>(entity, false));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedUser, this, saveEventArgs);
}
// try to flush the unit of work
// ie executes the SQL but does not commit the trx yet
@@ -355,8 +364,9 @@ namespace Umbraco.Core.Services
var entitiesA = entities.ToArray();
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(SavingUser, this, new SaveEventArgs<IUser>(entitiesA)))
{
var saveEventArgs = new SaveEventArgs<IUser>(entitiesA);
if (raiseEvents && uow.Events.DispatchCancelable(SavingUser, this, saveEventArgs))
{
uow.Complete();
return;
@@ -386,7 +396,10 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
uow.Events.Dispatch(SavedUser, this, new SaveEventArgs<IUser>(entitiesA, false));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedUser, this, saveEventArgs);
}
//commit the whole lot in one go
uow.Complete();
@@ -537,7 +550,25 @@ namespace Umbraco.Core.Services
}
}
public IEnumerable<IUser> GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState[] userState = null, string[] userGroups = null, string filter = "")
public IEnumerable<IUser> GetAll(long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection,
UserState[] userState = null,
string[] userGroups = null,
string filter = null)
{
IQuery<IUser> filterQuery = null;
if (filter.IsNullOrWhiteSpace() == false)
{
filterQuery = UowProvider.DatabaseContext.Query<IUser>().Where(x => x.Name.Contains(filter) || x.Username.Contains(filter));
}
return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, userState, userGroups, null, filterQuery);
}
public IEnumerable<IUser> GetAll(long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection,
UserState[] userState = null, string[] includeUserGroups = null, string[] excludeUserGroups = null,
IQuery<IUser> filter = null)
{
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
@@ -578,14 +609,8 @@ namespace Umbraco.Core.Services
throw new IndexOutOfRangeException("The orderBy parameter " + orderBy + " is not valid");
}
IQuery<IUser> filterQuery = null;
if (filter.IsNullOrWhiteSpace() == false)
{
filterQuery = uow.Query<IUser>().Where(x => x.Name.Contains(filter) || x.Username.Contains(filter));
}
var repository = uow.CreateRepository<IUserRepository>();
return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, sort, orderDirection, userGroups, userState, filterQuery);
return repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, sort, orderDirection, includeUserGroups, excludeUserGroups, userState, filter);
}
}
@@ -653,11 +678,10 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IProfile"/></returns>
public IProfile GetProfileById(int id)
{
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
var repository = uow.CreateRepository<IUserRepository>();
return repository.GetProfile(id);
}
//This is called a TON. Go get the full user from cache which should already be IProfile
var fullUser = GetUserById(id);
var asProfile = fullUser as IProfile;
return asProfile ?? new UserProfile(fullUser.Id, fullUser.Name);
}
/// <summary>
@@ -798,7 +822,7 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IUserGroupRepository>();
var query = uow.Query<IUserGroup>().Where(x => aliases.SqlIn(x.Alias));
var contents = repository.GetByQuery(query);
return contents.ToArray();
return contents.WhereNotNull().ToArray();
}
}
@@ -848,7 +872,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (raiseEvents && uow.Events.DispatchCancelable(SavingUserGroup, this, new SaveEventArgs<IUserGroup>(userGroup)))
var saveEventArgs = new SaveEventArgs<IUserGroup>(userGroup);
if (raiseEvents && uow.Events.DispatchCancelable(SavingUserGroup, this, saveEventArgs))
{
uow.Complete();
return;
@@ -858,7 +883,10 @@ namespace Umbraco.Core.Services
repository.AddOrUpdateGroupWithUsers(userGroup, userIds);
if (raiseEvents)
uow.Events.Dispatch(SavedUserGroup, this, new SaveEventArgs<IUserGroup>(userGroup, false));
{
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(SavedUserGroup, this, saveEventArgs);
}
uow.Complete();
}
@@ -872,7 +900,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
if (uow.Events.DispatchCancelable(DeletingUserGroup, this, new DeleteEventArgs<IUserGroup>(userGroup)))
var deleteEventArgs = new DeleteEventArgs<IUserGroup>(userGroup);
if (uow.Events.DispatchCancelable(DeletingUserGroup, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -881,7 +910,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository<IUserGroupRepository>();
repository.Delete(userGroup);
uow.Events.Dispatch(DeletedUserGroup, this, new DeleteEventArgs<IUserGroup>(userGroup, false));
deleteEventArgs.CanCancel = false;
uow.Events.Dispatch(DeletedUserGroup, this, deleteEventArgs);
uow.Complete();
}
@@ -127,10 +127,12 @@ namespace Umbraco.Core.Sync
public static string GetServerHash(string machineName, string appDomainAppId)
{
var hasher = new HashCodeCombiner();
hasher.AddCaseInsensitiveString(appDomainAppId);
hasher.AddCaseInsensitiveString(machineName);
return hasher.GetCombinedHashCode();
using (var generator = new HashGenerator())
{
generator.AddString(machineName);
generator.AddString(appDomainAppId);
return generator.GenerateHash();
}
}
protected override bool RequiresDistributed(IEnumerable<IServerAddress> servers, ICacheRefresher refresher, MessageType messageType)
+3 -1
View File
@@ -383,6 +383,7 @@
<Compile Include="Events\RollbackEventArgs.cs" />
<Compile Include="Events\SaveEventArgs.cs" />
<Compile Include="Events\ScopeLifetimeMessagesFactory.cs" />
<Compile Include="Events\SendEmailEventArgs.cs" />
<Compile Include="Events\SendToPublishEventArgs.cs" />
<Compile Include="Events\SupersedeEventAttribute.cs" />
<Compile Include="Events\TransientEventMessagesFactory.cs" />
@@ -1166,7 +1167,7 @@
<Compile Include="Persistence\Repositories\RepositoryBaseOfTIdTEntity.cs" />
<Compile Include="Persistence\Repositories\ScriptRepository.cs" />
<Compile Include="Persistence\Repositories\ServerRegistrationRepository.cs" />
<Compile Include="Persistence\Repositories\SimilarNodeNameComparer.cs" />
<Compile Include="Persistence\Repositories\SimilarNodeName.cs" />
<Compile Include="Persistence\Repositories\SimpleGetRepository.cs" />
<Compile Include="Persistence\Repositories\StylesheetRepository.cs" />
<Compile Include="Persistence\Repositories\TagRepository.cs" />
@@ -1323,6 +1324,7 @@
<Compile Include="Serialization\JsonCreationConverter.cs" />
<Compile Include="Serialization\JsonNetSerializer.cs" />
<Compile Include="Serialization\JsonToStringConverter.cs" />
<Compile Include="Serialization\KnownTypeUdiJsonConverter.cs" />
<Compile Include="Serialization\NoTypeConverterJsonConverter.cs" />
<Compile Include="Serialization\SerializationExtensions.cs" />
<Compile Include="Serialization\SerializationService.cs" />
@@ -61,7 +61,7 @@ namespace Umbraco.Core.Xml
return -1;
});
const string rootXpath = "descendant::*[@id={0}]";
const string rootXpath = "id({0})";
//parseable items:
var vars = new Dictionary<string, Func<string, string>>();