diff --git a/.gitignore b/.gitignore
index 86d37350ff..b37b1c5832 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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.*
diff --git a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs
index 62cd1ab59c..0add427ee3 100644
--- a/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs
+++ b/src/Umbraco.Core/Configuration/ClientDependencyConfiguration.cs
@@ -9,7 +9,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Configuration
-{
+{
///
/// A utility class for working with CDF config and cache files - use sparingly!
///
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs
index 5f682e07cd..3ada30ace9 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddIndexToDictionaryKeyColumn.cs
@@ -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)
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs
index 3061fe97bc..df0b71987a 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserGroupTables.cs
@@ -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);
+ }
+ }
+ }
+
+ ///
+ /// 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
+ ///
+ ///
+ ///
+ private void RemoveOldTablesIfExist(List 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 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(@"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>();
+ foreach (var userApps in userAppsData)
+ {
+ List apps;
+ if (usersWithApps.TryGetValue(userApps.id, out apps) == false)
+ {
+ apps = new List {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[] constraints)
+ private void DeleteOldTables(List tables, Tuple[] 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");
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs
index c446cd072d..8ec8a5d541 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/AddUserStartNodeTable.cs
@@ -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
{
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs
index d5ff6ff067..fa953e87e6 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/EnsureContentTemplatePermissions.cs
@@ -6,6 +6,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe
///
/// Ensures the built-in user groups have the blueprint permission by default on upgrade
///
+ [Migration("7.7.0", 5, Constants.System.UmbracoMigrationName)]
[Migration("8.0.0", 6, Constants.System.UmbracoMigrationName)]
public class EnsureContentTemplatePermissions : MigrationBase
{
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs
index fac6b19026..048d03aeeb 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/ReduceDictionaryKeyColumnsSize.cs
@@ -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
{
diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs
index 0030812f32..512404113d 100644
--- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs
@@ -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();
diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
index 318c1f03ee..4fca0c53a1 100644
--- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories
_publishedQuery = work.Query().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(sql, new { @published=true, @path=content.Path });
+ var count = Database.ExecuteScalar(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
///
- private class ContentByGuidReadRepository : PetaPocoRepositoryBase
+ private class ContentByGuidReadRepository : NPocoRepositoryBase
{
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(x => x.Newest, SqlSyntax)
- .OrderByDescending(x => x.VersionDate, SqlSyntax);
+ .Where(x => x.Newest)
+ .OrderByDescending(x => x.VersionDate);
- var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
+ var dto = Database.Fetch(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 PerformGetAll(params Guid[] ids)
{
- Func translate = s =>
+ Sql Translate(Sql s)
{
if (ids.Any())
{
s.Where("umbracoNode.uniqueID in (@ids)", new { ids });
}
//we only want the newest ones with this method
- s.Where(x => x.Newest, SqlSyntax);
+ s.Where(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(sql), many: true);
}
- protected override Sql GetBaseQuery(bool isCount)
+ protected override Sql 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
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
index 48f25c9196..4ac3bb0219 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs
@@ -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;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
index f88309aa5b..06b812d16e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs
@@ -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
///
///
///
- void AddOrUpdatePreviewXml(IMedia content, Func xml);
+ void AddOrUpdatePreviewXml(IMedia content, Func xml);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs
index 28835aa56a..1e2c0f8229 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs
@@ -56,7 +56,8 @@ namespace Umbraco.Core.Persistence.Repositories
///
IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords,
Expression> orderBy, Direction orderDirection = Direction.Ascending,
- string[] userGroups = null, UserState[] userState = null, IQuery filter = null);
+ string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null,
+ IQuery filter = null);
///
/// Returns a user by username
diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
index 44bd3767ce..1eb06a66ef 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs
@@ -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
///
- private class MediaByGuidReadRepository : PetaPocoRepositoryBase
+ private class MediaByGuidReadRepository : NPocoRepositoryBase
{
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(x => x.VersionDate, SqlSyntax);
+ sql.OrderByDescending(x => x.VersionDate);
- var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
+ var dto = Database.Fetch(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(sql));
}
- protected override Sql GetBaseQuery(bool isCount)
+ protected override Sql 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
diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs
index c51eebd93a..69658bf6e3 100644
--- a/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/PropertyValueEditor.cs
@@ -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)
diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs
index 899a145862..6de2951a89 100644
--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs
@@ -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();
}
diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
index 265e49300d..3593afe18e 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs
@@ -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 store,
IdentityFactoryOptions options,
MembershipProviderBase membershipProvider)
+ : this(store, options, membershipProvider, UmbracoConfig.For.UmbracoSettings().Content)
+ {
+ }
+
+ public BackOfficeUserManager(
+ IUserStore store,
+ IdentityFactoryOptions 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 options,
+ IUserService userService,
+ IExternalLoginService externalLoginService,
+ MembershipProviderBase membershipProvider)
+ {
+ return Create(options, userService,
+ ApplicationContext.Current.Services.EntityService,
+ externalLoginService, membershipProvider,
+ UmbracoConfig.For.UmbracoSettings().Content);
+ }
+
///
/// Creates a BackOfficeUserManager instance with all default options and the default BackOfficeUserManager
///
@@ -44,6 +72,7 @@ namespace Umbraco.Core.Security
///
///
///
+ ///
///
public static BackOfficeUserManager Create(
IdentityFactoryOptions 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 options,
+ BackOfficeUserStore customUserStore,
+ MembershipProviderBase membershipProvider)
+ {
+ var manager = new BackOfficeUserManager(customUserStore, options, membershipProvider);
return manager;
}
@@ -69,31 +110,45 @@ namespace Umbraco.Core.Security
///
///
///
+ ///
///
public static BackOfficeUserManager Create(
- IdentityFactoryOptions options,
- BackOfficeUserStore customUserStore,
- MembershipProviderBase membershipProvider)
+ IdentityFactoryOptions 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 options)
+ {
+ InitUserManager(manager, membershipProvider, UmbracoConfig.For.UmbracoSettings().Content, options);
+ }
+
///
/// Initializes the user manager with the correct options
///
///
///
+ ///
///
///
protected void InitUserManager(
BackOfficeUserManager manager,
MembershipProviderBase membershipProvider,
+ IContentSection contentSectionConfig,
IdentityFactoryOptions 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 manager,
+ MembershipProviderBase membershipProvider,
+ IDataProtectionProvider dataProtectionProvider)
+ {
+ InitUserManager(manager, membershipProvider, dataProtectionProvider, UmbracoConfig.For.UmbracoSettings().Content);
+ }
+
///
/// Initializes the user manager with the correct options
///
@@ -146,11 +211,13 @@ namespace Umbraco.Core.Security
/// The for the users called UsersMembershipProvider
///
///
+ ///
///
protected void InitUserManager(
BackOfficeUserManager manager,
MembershipProviderBase membershipProvider,
- IDataProtectionProvider dataProtectionProvider)
+ IDataProtectionProvider dataProtectionProvider,
+ IContentSection contentSectionConfig)
{
// Configure validation logic for usernames
manager.UserValidator = new BackOfficeUserValidator(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();
- 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;
}
+ ///
+ /// 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
+ ///
+ ///
+ ///
+ ///
+ /// 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
+ ///
+ public override async Task 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
diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
index 89f093bb19..74839d966a 100644
--- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs
+++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs
@@ -548,6 +548,9 @@ namespace Umbraco.Core.Security
///
///
///
+ ///
+ /// Currently we do not suport a timed lock out, when they are locked out, an admin will have to reset the status
+ ///
public Task SetLockoutEndDateAsync(BackOfficeIdentityUser user, DateTimeOffset lockoutEnd)
{
if (user == null) throw new ArgumentNullException(nameof(user));
diff --git a/src/Umbraco.Core/Security/EmailService.cs b/src/Umbraco.Core/Security/EmailService.cs
index f8f9af10ae..51d1b82207 100644
--- a/src/Umbraco.Core/Security/EmailService.cs
+++ b/src/Umbraco.Core/Security/EmailService.cs
@@ -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
{
+ ///
+ /// The implementation for Umbraco
+ ///
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
diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs
index 013e80b020..1c0f407429 100644
--- a/src/Umbraco.Core/Security/MembershipProviderBase.cs
+++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs
@@ -67,6 +67,19 @@ namespace Umbraco.Core.Security
get { return false; }
}
+ ///
+ /// Returns the raw password value for a given user
+ ///
+ ///
+ ///
+ ///
+ /// By default this will return an invalid attempt, inheritors will need to override this to support it
+ ///
+ protected virtual Attempt GetRawPassword(string username)
+ {
+ return Attempt.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.
///
/// The user to update the password for.
- /// This property is ignore for this provider
+ /// Required to change a user password if the user is not new and AllowManuallyChangingPassword is false
/// The new password for the specified user.
///
/// true if the password was updated successfully; otherwise, false.
@@ -311,10 +324,17 @@ namespace Umbraco.Core.Security
///
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);
diff --git a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs
index 71581ebeb8..ca01330212 100644
--- a/src/Umbraco.Core/Security/MembershipProviderExtensions.cs
+++ b/src/Umbraco.Core/Security/MembershipProviderExtensions.cs
@@ -18,6 +18,9 @@ namespace Umbraco.Core.Security
///
///
///
+ ///
+ /// An Admin can always reset the password
+ ///
internal static bool CanResetPassword(this MembershipProvider provider, IUserService userService)
{
if (provider == null) throw new ArgumentNullException("provider");
diff --git a/src/Umbraco.Core/Security/UmbracoEmailMessage.cs b/src/Umbraco.Core/Security/UmbracoEmailMessage.cs
new file mode 100644
index 0000000000..9ef6205ebf
--- /dev/null
+++ b/src/Umbraco.Core/Security/UmbracoEmailMessage.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNet.Identity;
+
+namespace Umbraco.Core.Security
+{
+ ///
+ /// A custom implementation for IdentityMessage that allows the customization of how an email is sent
+ ///
+ internal class UmbracoEmailMessage : IdentityMessage
+ {
+ public IEmailSender MailSender { get; private set; }
+
+ public UmbracoEmailMessage(IEmailSender mailSender)
+ {
+ MailSender = mailSender;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs b/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs
new file mode 100644
index 0000000000..e6473e7f8e
--- /dev/null
+++ b/src/Umbraco.Core/Serialization/KnownTypeUdiJsonConverter.cs
@@ -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();
+ return val == null ? null : Udi.Parse(val, true);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs
index 8dec6f3919..134faf3d1d 100644
--- a/src/Umbraco.Core/Serialization/UdiJsonConverter.cs
+++ b/src/Umbraco.Core/Serialization/UdiJsonConverter.cs
@@ -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)
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index 690d4aacf7..92b6d76b07 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -24,15 +24,13 @@ namespace Umbraco.Core.Services
{
private readonly MediaFileSystem _mediaFileSystem;
private IQuery _queryNotTrashed;
- private readonly IdkMap _idkMap;
#region Constructors
- public ContentService(IScopeUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, MediaFileSystem mediaFileSystem, 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();
}
- }
-
+ }
+
///
/// Returns implicit/inherited permissions assigned to the content item for all user groups
///
@@ -143,6 +141,26 @@ namespace Umbraco.Core.Services
#region Create
+ ///
+ /// Creates an object using the alias of the
+ /// that this Content should based on.
+ ///
+ ///
+ /// 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.
+ ///
+ /// Name of the Content object
+ /// Id of Parent for the new Content
+ /// Alias of the
+ /// Optional id of the user creating the content
+ ///
+ public IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0)
+ {
+ var parent = GetById(parentId);
+ return CreateContent(name, parent, contentTypeAlias, userId);
+ }
+
///
/// Creates an object of a specified content type.
///
@@ -316,7 +334,8 @@ namespace Umbraco.Core.Services
if (withIdentity)
{
- if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(content), "Saving"))
+ var saveEventArgs = new SaveEventArgs(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(content, false), "Saved");
+ saveEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
uow.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshNode).ToEventArgs());
}
uow.Events.Dispatch(Created, this, new NewEventArgs(content, false, content.ContentType.Alias, parent));
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
///
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();
+ return repository.Get(key);
+ }
+ }
- var a = _idkMap.GetIdForKey(key, UmbracoObjectTypes.Document);
- return a.Success ? GetById(a.Result) : null;
+ ///
+ /// Gets objects by Ids
+ ///
+ /// Ids of the Content to retrieve
+ ///
+ public IEnumerable GetByIds(IEnumerable ids)
+ {
+ var idsA = ids.ToArray();
+ if (idsA.Length == 0) return Enumerable.Empty();
+
+ using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
+ {
+ uow.ReadLock(Constants.Locks.ContentTree);
+ var repository = uow.CreateRepository();
+ 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();
+ }
}
///
@@ -626,7 +667,6 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
uow.ReadLock(Constants.Locks.ContentTree);
- var repository = uow.CreateRepository();
var filterQuery = filter.IsNullOrWhiteSpace()
? null
: uow.Query().Where(x => x.Name.Contains(filter));
@@ -660,7 +700,16 @@ namespace Umbraco.Core.Services
var query = uow.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));
+ {
+ var entityRepository = uow.CreateRepository();
+ var contentPath = entityRepository.GetAllPaths(Constants.ObjectTypes.DocumentGuid, id).ToArray();
+ if (contentPath.Length == 0)
+ {
+ totalChildren = 0;
+ return Enumerable.Empty();
+ }
+ 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();
- 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(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(content, evtMsgs), "Saving"))
+ var saveEventArgs = new SaveEventArgs(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(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(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(contentsA, evtMsgs), "Saving"))
+ var saveEventArgs = new SaveEventArgs(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(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(content, evtMsgs)))
+ var deleteEventArgs = new DeleteEventArgs(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();
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();
var originalPath = content.Path;
- if (uow.Events.DispatchCancelable(Trashing, this, new MoveEventArgs(new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent))))
+ var moveEventInfo = new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent);
+ var moveEventArgs = new MoveEventArgs(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(x.Item1, x.Item2, x.Item1.ParentId))
.ToArray();
- uow.Events.Dispatch(Trashed, this, new MoveEventArgs(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(new MoveEventInfo(content, content.Path, parentId))))
+ var moveEventInfo = new MoveEventInfo(content, content.Path, parentId);
+ var moveEventArgs = new MoveEventArgs(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(x.Item1, x.Item2, x.Item1.ParentId))
.ToArray();
- uow.Events.Dispatch(Moved, this, new MoveEventArgs(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
///
public void EmptyRecycleBin()
{
- var nodeObjectType = new Guid(Constants.ObjectTypes.Document);
+ var nodeObjectType = Constants.ObjectTypes.DocumentGuid;
var deleted = new List();
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(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(content, copy, parentId)))
+ var copyEventArgs = new CopyEventArgs(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(content)))
+ var sendToPublishEventArgs = new SendToPublishEventArgs(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(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(content)))
+ var rollbackEventArgs = new RollbackEventArgs(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(content, false));
+ rollbackEventArgs.CanCancel = false;
+ uow.Events.Dispatch(RolledBack, this, rollbackEventArgs);
uow.Events.Dispatch(TreeChanged, this, new TreeChange(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(itemsA), "Saving"))
+ var saveEventArgs = new SaveEventArgs(itemsA);
+ if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving"))
return false;
var published = new List();
@@ -1868,7 +1973,10 @@ namespace Umbraco.Core.Services
}
if (raiseEvents)
- uow.Events.Dispatch(Saved, this, new SaveEventArgs(saved, false), "Saved");
+ {
+ saveEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Saved, this, saveEventArgs, "Saved");
+ }
uow.Events.Dispatch(TreeChanged, this, saved.Select(x => new TreeChange(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(content), "Saving"))
+ var saveEventArgs = new SaveEventArgs(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(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.
///
public static event TypedEventHandler> 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();
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();
+ 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();
repository.Delete(content);
diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
index 077a9611a5..3d5bbfa4ed 100644
--- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
@@ -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
{
+ ///
+ /// Content service extension methods
+ ///
public static class ContentServiceExtensions
{
+ public static IEnumerable GetByIds(this IContentService contentService, IEnumerable ids)
+ {
+ var guids = new List();
+ 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));
+ }
+
+ ///
+ /// Method to create an IContent object based on the Udi of a parent
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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);
+ }
+
///
/// Remove all permissions for this user for all nodes
///
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTItemTService.cs
index feb7e66f47..9ba883a441 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTItemTService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTItemTService.cs
@@ -112,6 +112,12 @@ namespace Umbraco.Core.Services
uow.Events.DispatchCancelable(SavedContainer, This, args);
}
+ protected void OnRenamedContainer(IScopeUnitOfWork uow, SaveEventArgs 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 args)
{
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index 8552b0d0e3..34dd2811d9 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -385,7 +385,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
- if (OnSavingCancelled(uow, new SaveEventArgs(item)))
+ var saveEventArgs = new SaveEventArgs(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(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(itemsA)))
+ var saveEventArgs = new SaveEventArgs(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(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(item)))
+ var deleteEventArgs = new DeleteEventArgs(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(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(itemsA)))
+ var deleteEventArgs = new DeleteEventArgs(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(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>();
using (var uow = UowProvider.CreateUnitOfWork())
{
- if (OnMovingCancelled(uow, new MoveEventArgs(evtMsgs, new MoveEventInfo(moving, moving.Path, containerId))))
+ var moveEventInfo = new MoveEventInfo(moving, moving.Path, containerId);
+ var moveEventArgs = new MoveEventArgs(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(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(container, evtMsgs)))
+ var saveEventArgs = new SaveEventArgs(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(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(container, evtMsgs)))
+ var deleteEventArgs = new DeleteEventArgs(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(container, evtMsgs));
+ deleteEventArgs.CanCancel = false;
+ OnDeletedContainer(uow, deleteEventArgs);
return OperationStatus.Attempt.Succeed(evtMsgs);
//TODO: Audit trail ?
}
}
+ public Attempt> 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(container, evtMsgs));
+
+ return OperationStatus.Attempt.Succeed(OperationStatusType.Success, evtMsgs, container);
+ }
+ catch (Exception ex)
+ {
+ return OperationStatus.Attempt.Fail(evtMsgs, ex);
+ }
+ }
+ }
+
#endregion
#region Audit
diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs
index 9e0c2cb5c8..3e6b59448e 100644
--- a/src/Umbraco.Core/Services/DataTypeService.cs
+++ b/src/Umbraco.Core/Services/DataTypeService.cs
@@ -174,6 +174,38 @@ namespace Umbraco.Core.Services
return OperationStatus.Attempt.Succeed(evtMsgs);
}
+ public Attempt> RenameContainer(int id, string name, int userId = 0)
+ {
+ var evtMsgs = EventMessagesFactory.Get();
+ using (var uow = UowProvider.CreateUnitOfWork())
+ {
+ var repository = uow.CreateRepository();
+
+ 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(container, evtMsgs), "RenamedContainer");
+
+ return OperationStatus.Attempt.Succeed(OperationStatusType.Success, evtMsgs, container);
+ }
+ catch (Exception ex)
+ {
+ return OperationStatus.Attempt.Fail(evtMsgs, ex);
+ }
+ }
+ }
+
#endregion
///
@@ -301,7 +333,9 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
- if (uow.Events.DispatchCancelable(Moving, this, new MoveEventArgs(evtMsgs, new MoveEventInfo(toMove, toMove.Path, parentId))))
+ var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId);
+ var moveEventArgs = new MoveEventArgs(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(false, evtMsgs, moveInfo.ToArray()));
+ moveEventArgs.MoveInfoCollection = moveInfo;
+ moveEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Moved, this, moveEventArgs);
uow.Complete();
}
catch (DataOperationException ex)
@@ -345,7 +381,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
- if (uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(dataTypeDefinition)))
+ var saveEventArgs = new SaveEventArgs(dataTypeDefinition);
+ if (uow.Events.DispatchCancelable(Saving, this, saveEventArgs))
{
uow.Complete();
return;
@@ -359,7 +396,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository();
repository.AddOrUpdate(dataTypeDefinition);
- uow.Events.Dispatch(Saved, this, new SaveEventArgs(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 dataTypeDefinitions, int userId, bool raiseEvents)
{
var dataTypeDefinitionsA = dataTypeDefinitions.ToArray();
+ var saveEventArgs = new SaveEventArgs(dataTypeDefinitionsA);
using (var uow = UowProvider.CreateUnitOfWork())
{
- if (raiseEvents && uow.Events.DispatchCancelable(Saving, this, new SaveEventArgs(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(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(dataTypeDefinition)))
+ var saveEventArgs = new SaveEventArgs(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(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(dataTypeDefinition)))
+ var deleteEventArgs = new DeleteEventArgs(dataTypeDefinition);
+ if (uow.Events.DispatchCancelable(Deleting, this, deleteEventArgs))
{
uow.Complete();
return;
@@ -531,7 +576,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository();
repository.Delete(dataTypeDefinition);
- uow.Events.Dispatch(Deleted, this, new DeleteEventArgs(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();
diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs
index 7fa1a391af..b241dbad60 100644
--- a/src/Umbraco.Core/Services/DomainService.cs
+++ b/src/Umbraco.Core/Services/DomainService.cs
@@ -33,7 +33,8 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork())
{
- if (uow.Events.DispatchCancelable(Deleting, this, new DeleteEventArgs(domain, evtMsgs)))
+ var deleteEventArgs = new DeleteEventArgs(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(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(domainEntity, evtMsgs)))
+ var saveEventArgs = new SaveEventArgs(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();
repository.AddOrUpdate(domainEntity);
uow.Complete();
-
- uow.Events.Dispatch(Saved, this, new SaveEventArgs(domainEntity, false, evtMsgs));
+ saveEventArgs.CanCancel = false;
+ uow.Events.Dispatch(Saved, this, saveEventArgs);
}
return OperationStatus.Attempt.Succeed(evtMsgs);
diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs
index e910b5504b..a5d570b7d6 100644
--- a/src/Umbraco.Core/Services/EntityService.cs
+++ b/src/Umbraco.Core/Services/EntityService.cs
@@ -374,11 +374,22 @@ namespace Umbraco.Core.Services
using (var uow = UowProvider.CreateUnitOfWork(readOnly: true))
{
var repository = uow.CreateRepository();
-
+
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();
+ }
+ var itemPath = itemPaths[0].Path;
+
+ query.Where(x => x.Path.SqlStartsWith(itemPath + ",", TextColumnType.NVarchar));
+ }
IQuery 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();
-
+
var query = uow.Query();
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();
+ }
+
var clauses = new List>>();
foreach (var id in idsA)
{
- var qid = id;
- clauses.Add(x => x.Path.SqlContains(string.Format(",{0},", qid), TextColumnType.NVarchar) || x.Path.SqlEndsWith(string.Format(",{0}", qid), TextColumnType.NVarchar));
+ //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;
}
}
-
+
///
/// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1.
///
@@ -566,13 +592,13 @@ namespace Umbraco.Core.Services
return repository.GetAllPaths(objectTypeId, keys);
}
}
-
+
///
- /// Gets a collection of
+ /// Gets a collection of
///
/// Guid id of the UmbracoObjectType
///
- /// An enumerable list of objects
+ /// An enumerable list of objects
public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids)
{
var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId);
@@ -692,5 +718,34 @@ namespace Umbraco.Core.Services
return exists;
}
}
+
+ ///
+ 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(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;
+ }
}
}
diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs
index 976f5b7438..32b32fa401 100644
--- a/src/Umbraco.Core/Services/FileService.cs
+++ b/src/Umbraco.Core/Services/FileService.cs
@@ -64,7 +64,8 @@ namespace Umbraco.Core.Services
{
using (var uow = UowProvider.CreateUnitOfWork())
{
- if (uow.Events.DispatchCancelable(SavingStylesheet, this, new SaveEventArgs(stylesheet)))
+ var saveEventArgs = new SaveEventArgs(stylesheet);
+ if (uow.Events.DispatchCancelable(SavingStylesheet, this, saveEventArgs))
{
uow.Complete();
return;
@@ -72,8 +73,8 @@ namespace Umbraco.Core.Services
var repository = uow.CreateRepository();
repository.AddOrUpdate(stylesheet);
-
- uow.Events.Dispatch(SavedStylesheet, this, new SaveEventArgs(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)))
+ var deleteEventArgs = new DeleteEventArgs(stylesheet);
+ if (uow.Events.DispatchCancelable(DeletingStylesheet, this, deleteEventArgs))
{
uow.Complete();
return; // causes rollback
}
repository.Delete(stylesheet);
-
- uow.Events.Dispatch(DeletedStylesheet, this, new DeleteEventArgs(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