diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index f3f8f47b57..56462fcc40 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -52,7 +52,7 @@ - + diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 3c8fed78f5..614a816f3f 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -53,9 +53,9 @@ - - - + + + diff --git a/src/Umbraco.Core/Compose/AuditEventsComponent.cs b/src/Umbraco.Core/Compose/AuditEventsComponent.cs index 15fdfeacff..453fd6314a 100644 --- a/src/Umbraco.Core/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Compose/AuditEventsComponent.cs @@ -44,21 +44,22 @@ namespace Umbraco.Core.Compose public void Terminate() { } + internal static IUser UnknownUser => new User { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" }; + private IUser CurrentPerformingUser { get { var identity = Thread.CurrentPrincipal?.GetUmbracoIdentity(); - return identity == null - ? new User { Id = 0, Name = "SYSTEM", Email = "" } - : _userService.GetUserById(Convert.ToInt32(identity.Id)); + var user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id)); + return user ?? UnknownUser; } } private IUser GetPerformingUser(int userId) { var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? new User {Id = 0, Name = "SYSTEM", Email = ""}; + return found ?? UnknownUser; } private string PerformingIp diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 80f18eb2e0..27d6716000 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -15,13 +15,18 @@ namespace Umbraco.Core public const int SuperUserId = -1; /// - /// The id for the 'unknown' user + /// The id for the 'unknown' user. /// /// /// This is a user row that exists in the DB only for referential integrity but the user is never returned from any of the services /// public const int UnknownUserId = 0; + /// + /// The name of the 'unknown' user. + /// + public const string UnknownUserName = "SYTEM"; + public const string AdminGroupAlias = "admin"; public const string SensitiveDataGroupAlias = "sensitiveData"; public const string TranslatorGroupAlias = "translator"; diff --git a/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs index b11ef7e2c8..6bf450a9b8 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Create/KeysAndIndexes/CreateKeysAndIndexesBuilder.cs @@ -25,11 +25,25 @@ namespace Umbraco.Core.Migrations.Expressions.Create.KeysAndIndexes var syntax = _context.SqlContext.SqlSyntax; var tableDefinition = DefinitionFactory.GetTableDefinition(TypeOfDto, syntax); + // note: of course we are creating the keys and indexes as per the DTO, so + // changing the DTO may break old migrations - or, better, these migrations + // should capture a copy of the DTO class that will not change + ExecuteSql(syntax.FormatPrimaryKey(tableDefinition)); foreach (var sql in syntax.Format(tableDefinition.Indexes)) ExecuteSql(sql); foreach (var sql in syntax.Format(tableDefinition.ForeignKeys)) ExecuteSql(sql); + + // note: we do *not* create the DF_ default constraints + /* + foreach (var column in tableDefinition.Columns) + { + var sql = syntax.FormatDefaultConstraint(column); + if (!sql.IsNullOrWhiteSpace()) + ExecuteSql(sql); + } + */ } private void ExecuteSql(string sql) diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs index 373b375fa8..92bc11b04d 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DefaultConstraint/DeleteDefaultConstraintBuilder.cs @@ -10,9 +10,13 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint IDeleteDefaultConstraintOnTableBuilder, IDeleteDefaultConstraintOnColumnBuilder { - public DeleteDefaultConstraintBuilder(DeleteDefaultConstraintExpression expression) + private readonly IMigrationContext _context; + + public DeleteDefaultConstraintBuilder(IMigrationContext context, DeleteDefaultConstraintExpression expression) : base(expression) - { } + { + _context = context; + } /// public IDeleteDefaultConstraintOnColumnBuilder OnTable(string tableName) @@ -25,6 +29,9 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.DefaultConstraint public IExecutableBuilder OnColumn(string columnName) { Expression.ColumnName = columnName; + Expression.HasDefaultConstraint = _context.SqlContext.SqlSyntax.TryGetDefaultConstraint(_context.Database, Expression.TableName, columnName, out var constraintName); + Expression.ConstraintName = constraintName ?? string.Empty; + return new ExecutableBuilder(Expression); } } diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs index d081305cde..9a4f437f62 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/DeleteBuilder.cs @@ -1,4 +1,4 @@ -using NPoco; +using Umbraco.Core.Exceptions; using Umbraco.Core.Migrations.Expressions.Common; using Umbraco.Core.Migrations.Expressions.Delete.Column; using Umbraco.Core.Migrations.Expressions.Delete.Constraint; @@ -29,9 +29,19 @@ namespace Umbraco.Core.Migrations.Expressions.Delete } /// - public IExecutableBuilder KeysAndIndexes(string tableName = null) + public IExecutableBuilder KeysAndIndexes(bool local = true, bool foreign = true) { - return new DeleteKeysAndIndexesBuilder(_context) { TableName = tableName }; + var syntax = _context.SqlContext.SqlSyntax; + var tableDefinition = DefinitionFactory.GetTableDefinition(typeof(TDto), syntax); + return KeysAndIndexes(tableDefinition.Name, local, foreign); + } + + /// + public IExecutableBuilder KeysAndIndexes(string tableName, bool local = true, bool foreign = true) + { + if (tableName.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(tableName)); + return new DeleteKeysAndIndexesBuilder(_context) { TableName = tableName, DeleteLocal = local, DeleteForeign = foreign }; } /// @@ -100,7 +110,7 @@ namespace Umbraco.Core.Migrations.Expressions.Delete public IDeleteDefaultConstraintOnTableBuilder DefaultConstraint() { var expression = new DeleteDefaultConstraintExpression(_context); - return new DeleteDefaultConstraintBuilder(expression); + return new DeleteDefaultConstraintBuilder(_context, expression); } } } diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs index 8b0b20c0e2..b73d3f0d13 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/Expressions/DeleteDefaultConstraintExpression.cs @@ -10,12 +10,17 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.Expressions public virtual string TableName { get; set; } public virtual string ColumnName { get; set; } + public virtual string ConstraintName { get; set; } + public virtual bool HasDefaultConstraint { get; set; } protected override string GetSql() { - return string.Format(SqlSyntax.DeleteDefaultConstraint, - SqlSyntax.GetQuotedTableName(TableName), - SqlSyntax.GetQuotedColumnName(ColumnName)); + return HasDefaultConstraint + ? string.Format(SqlSyntax.DeleteDefaultConstraint, + SqlSyntax.GetQuotedTableName(TableName), + SqlSyntax.GetQuotedColumnName(ColumnName), + SqlSyntax.GetQuotedName(ConstraintName)) + : string.Empty; } } } diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs index 07faf5028e..84e44d0d93 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/IDeleteBuilder.cs @@ -19,9 +19,14 @@ namespace Umbraco.Core.Migrations.Expressions.Delete IExecutableBuilder Table(string tableName); /// - /// Specifies the table to delete keys and indexes for. + /// Builds a Delete Keys and Indexes expression, and executes. /// - IExecutableBuilder KeysAndIndexes(string tableName = null); + IExecutableBuilder KeysAndIndexes(bool local = true, bool foreign = true); + + /// + /// Builds a Delete Keys and Indexes expression, and executes. + /// + IExecutableBuilder KeysAndIndexes(string tableName, bool local = true, bool foreign = true); /// /// Specifies the column to delete. diff --git a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs index 1c595d9103..9b13457b76 100644 --- a/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs +++ b/src/Umbraco.Core/Migrations/Expressions/Delete/KeysAndIndexes/DeleteKeysAndIndexesBuilder.cs @@ -18,34 +18,38 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.KeysAndIndexes public string TableName { get; set; } + public bool DeleteLocal { get; set; } + + public bool DeleteForeign { get; set; } + /// public void Do() { - if (TableName == null) - { - // drop keys - var keys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToArray(); - foreach (var key in keys.Where(x => x.Item2.StartsWith("FK_"))) - Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); - foreach (var key in keys.Where(x => x.Item2.StartsWith("PK_"))) - Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); + _context.BuildingExpression = false; - // drop indexes - var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToArray(); - foreach (var index in indexes) - Delete.Index(index.IndexName).OnTable(index.TableName).Do(); + // drop keys + if (DeleteLocal || DeleteForeign) + { + // table, constraint + var tableKeys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToList(); + if (DeleteForeign) + { + foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_"))) + Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); + } + if (DeleteLocal) + { + foreach (var key in tableKeys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_"))) + Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); + + // note: we do *not* delete the DEFAULT constraints + } } - else - { - // drop keys - var keys = _context.SqlContext.SqlSyntax.GetConstraintsPerTable(_context.Database).DistinctBy(x => x.Item2).ToArray(); - foreach (var key in keys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("FK_"))) - Delete.ForeignKey(key.Item2).OnTable(key.Item1).Do(); - foreach (var key in keys.Where(x => x.Item1 == TableName && x.Item2.StartsWith("PK_"))) - Delete.PrimaryKey(key.Item2).FromTable(key.Item1).Do(); - // drop indexes - var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToArray(); + // drop indexes + if (DeleteLocal) + { + var indexes = _context.SqlContext.SqlSyntax.GetDefinedIndexesDefinitions(_context.Database).DistinctBy(x => x.IndexName).ToList(); foreach (var index in indexes.Where(x => x.TableName == TableName)) Delete.Index(index.IndexName).OnTable(index.TableName).Do(); } diff --git a/src/Umbraco.Core/Migrations/IMigrationContext.cs b/src/Umbraco.Core/Migrations/IMigrationContext.cs index 2baadc79eb..c76de8dfb3 100644 --- a/src/Umbraco.Core/Migrations/IMigrationContext.cs +++ b/src/Umbraco.Core/Migrations/IMigrationContext.cs @@ -36,7 +36,7 @@ namespace Umbraco.Core.Migrations bool BuildingExpression { get; set; } /// - /// Adds a post-migrations. + /// Adds a post-migration. /// void AddPostMigration() where TMigration : IMigration; diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index 5c4defab0c..2295745637 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -539,7 +539,7 @@ namespace Umbraco.Core.Migrations.Install { Message = "The database configuration failed with the following message: " + ex.Message + - "\n Please check log file for additional information (can be found in '/App_Data/Logs/UmbracoTraceLog.txt')", + "\n Please check log file for additional information (can be found in '/App_Data/Logs/')", Success = false, Percentage = "90" }; diff --git a/src/Umbraco.Core/Migrations/MigrationContext.cs b/src/Umbraco.Core/Migrations/MigrationContext.cs index a8d052036c..0bb2bc11ae 100644 --- a/src/Umbraco.Core/Migrations/MigrationContext.cs +++ b/src/Umbraco.Core/Migrations/MigrationContext.cs @@ -41,6 +41,7 @@ namespace Umbraco.Core.Migrations public void AddPostMigration() where TMigration : IMigration { + // just adding - will be de-duplicated when executing PostMigrations.Add(typeof(TMigration)); } } diff --git a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs index 8b5d9cc78c..be06a32ec7 100644 --- a/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs +++ b/src/Umbraco.Core/Migrations/MigrationExpressionBase.cs @@ -92,6 +92,7 @@ namespace Umbraco.Core.Migrations if (_executed) throw new InvalidOperationException("This expression has already been executed."); _executed = true; + Context.BuildingExpression = false; if (sql == null) { diff --git a/src/Umbraco.Core/Migrations/MigrationPlan.cs b/src/Umbraco.Core/Migrations/MigrationPlan.cs index 4be4ae7d30..37d1a03a5a 100644 --- a/src/Umbraco.Core/Migrations/MigrationPlan.cs +++ b/src/Umbraco.Core/Migrations/MigrationPlan.cs @@ -240,7 +240,8 @@ namespace Umbraco.Core.Migrations if (finalState == null) finalState = kvp.Key; else - throw new Exception("Multiple final states have been detected."); + throw new InvalidOperationException($"Multiple final states have been detected in the plan (\"{finalState}\", \"{kvp.Key}\")." + + " Make sure the plan contains only one final state."); } // now check for loops @@ -254,7 +255,8 @@ namespace Umbraco.Core.Migrations while (nextTransition != null && !verified.Contains(nextTransition.SourceState)) { if (visited.Contains(nextTransition.SourceState)) - throw new Exception("A loop has been detected."); + throw new InvalidOperationException($"A loop has been detected in the plan around state \"{nextTransition.SourceState}\"." + + " Make sure the plan does not contain circular transition paths."); visited.Add(nextTransition.SourceState); nextTransition = _transitions[nextTransition.TargetState]; } @@ -264,6 +266,14 @@ namespace Umbraco.Core.Migrations _finalState = finalState; } + /// + /// Throws an exception when the initial state is unknown. + /// + protected virtual void ThrowOnUnknownInitialState(string state) + { + throw new InvalidOperationException($"The migration plan does not support migrating from state \"{state}\"."); + } + /// /// Executes the plan. /// @@ -287,13 +297,15 @@ namespace Umbraco.Core.Migrations logger.Info("At {OrigState}", string.IsNullOrWhiteSpace(origState) ? "origin": origState); if (!_transitions.TryGetValue(origState, out var transition)) - throw new Exception($"Unknown state \"{origState}\"."); + ThrowOnUnknownInitialState(origState); var context = new MigrationContext(scope.Database, logger); context.PostMigrations.AddRange(_postMigrationTypes); while (transition != null) { + logger.Info("Execute {MigrationType}", transition.MigrationType.Name); + var migration = migrationBuilder.Build(transition.MigrationType, context); migration.Migrate(); @@ -302,6 +314,8 @@ namespace Umbraco.Core.Migrations logger.Info("At {OrigState}", origState); + // throw a raw exception here: this should never happen as the plan has + // been validated - this is just a paranoid safety test if (!_transitions.TryGetValue(origState, out transition)) throw new Exception($"Unknown state \"{origState}\"."); } @@ -322,7 +336,8 @@ namespace Umbraco.Core.Migrations logger.Info("Done (pending scope completion)."); - // safety check + // safety check - again, this should never happen as the plan has been validated, + // and this is just a paranoid safety test if (origState != _finalState) throw new Exception($"Internal error, reached state {origState} which is not final state {_finalState}"); diff --git a/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs b/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs new file mode 100644 index 0000000000..29006c8811 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/Common/CreateKeysAndIndexes.cs @@ -0,0 +1,21 @@ +using Umbraco.Core.Migrations.Install; + +namespace Umbraco.Core.Migrations.Upgrade.Common +{ + public class CreateKeysAndIndexes : MigrationBase + { + public CreateKeysAndIndexes(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // remove those that may already have keys + Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.KeyValue).Do(); + + // re-create *all* keys and indexes + foreach (var x in DatabaseSchemaCreator.OrderedTables) + Create.KeysAndIndexes(x).Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/Common/DeleteKeysAndIndexes.cs b/src/Umbraco.Core/Migrations/Upgrade/Common/DeleteKeysAndIndexes.cs new file mode 100644 index 0000000000..9e4af17c09 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/Common/DeleteKeysAndIndexes.cs @@ -0,0 +1,75 @@ +namespace Umbraco.Core.Migrations.Upgrade.Common +{ + public class DeleteKeysAndIndexes : MigrationBase + { + public DeleteKeysAndIndexes(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // all v7.14 tables + var tables = new[] + { + "cmsContent", + "cmsContentType", + "cmsContentType2ContentType", + "cmsContentTypeAllowedContentType", + "cmsContentVersion", + "cmsContentXml", + "cmsDataType", + "cmsDataTypePreValues", + "cmsDictionary", + "cmsDocument", + "cmsDocumentType", + "cmsLanguageText", + "cmsMacro", + "cmsMacroProperty", + "cmsMedia", + "cmsMember", + "cmsMember2MemberGroup", + "cmsMemberType", + "cmsPreviewXml", + "cmsPropertyData", + "cmsPropertyType", + "cmsPropertyTypeGroup", + "cmsTagRelationship", + "cmsTags", + "cmsTask", + "cmsTaskType", + "cmsTemplate", + "umbracoAccess", + "umbracoAccessRule", + "umbracoAudit", + "umbracoCacheInstruction", + "umbracoConsent", + "umbracoDomains", + "umbracoExternalLogin", + "umbracoLanguage", + "umbracoLock", + "umbracoLog", + "umbracoMigration", + "umbracoNode", + "umbracoRedirectUrl", + "umbracoRelation", + "umbracoRelationType", + "umbracoServer", + "umbracoUser", + "umbracoUser2NodeNotify", + "umbracoUser2UserGroup", + "umbracoUserGroup", + "umbracoUserGroup2App", + "umbracoUserGroup2NodePermission", + "umbracoUserLogin", + "umbracoUserStartNode", + }; + + // delete *all* keys and indexes - because of FKs + // on known v7 tables only + foreach (var table in tables) + Delete.KeysAndIndexes(table, false, true).Do(); + foreach (var table in tables) + Delete.KeysAndIndexes(table, true, false).Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index cf06a16d31..834eade986 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -2,8 +2,7 @@ using System.Configuration; using Semver; using Umbraco.Core.Configuration; -using Umbraco.Core.Migrations.Upgrade.V_7_12_0; -using Umbraco.Core.Migrations.Upgrade.V_7_14_0; +using Umbraco.Core.Migrations.Upgrade.Common; using Umbraco.Core.Migrations.Upgrade.V_8_0_0; using Umbraco.Core.Migrations.Upgrade.V_8_0_1; using Umbraco.Core.Migrations.Upgrade.V_8_1_0; @@ -15,6 +14,9 @@ namespace Umbraco.Core.Migrations.Upgrade /// public class UmbracoPlan : MigrationPlan { + private const string InitPrefix = "{init-"; + private const string InitSuffix = "}"; + /// /// Initializes a new instance of the class. /// @@ -24,6 +26,27 @@ namespace Umbraco.Core.Migrations.Upgrade DefinePlan(); } + /// + /// Gets the initial state corresponding to a version. + /// + private static string GetInitState(SemVersion version) + => InitPrefix + version + InitSuffix; + + /// + /// Tries to extract a version from an initial state. + /// + private static bool TryGetInitStateVersion(string state, out string version) + { + if (state.StartsWith(InitPrefix) && state.EndsWith(InitSuffix)) + { + version = state.TrimStart(InitPrefix).TrimEnd(InitSuffix); + return true; + } + + version = null; + return false; + } + /// /// /// The default initial state in plans is string.Empty. @@ -41,23 +64,32 @@ namespace Umbraco.Core.Migrations.Upgrade if (!SemVersion.TryParse(ConfigurationManager.AppSettings[Constants.AppSettings.ConfigurationStatus], out var currentVersion)) throw new InvalidOperationException($"Could not get current version from web.config {Constants.AppSettings.ConfigurationStatus} appSetting."); - // we currently support upgrading from 7.10.0 and later - if (currentVersion < new SemVersion(7, 10)) - throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}."); - // cannot go back in time if (currentVersion > UmbracoVersion.SemanticVersion) throw new InvalidOperationException($"Version {currentVersion} cannot be downgraded to {UmbracoVersion.SemanticVersion}."); - // upgrading from version 7 => initial state is eg "{init-7.10.0}" - // anything else is not supported - ie if 8 and above, we should have an initial state already - if (currentVersion.Major != 7) - throw new InvalidOperationException($"Version {currentVersion} is not supported by the migration plan."); + // only from 7.14.0 and above + var minVersion = new SemVersion(7, 14); + if (currentVersion < minVersion) + throw new InvalidOperationException($"Version {currentVersion} cannot be migrated to {UmbracoVersion.SemanticVersion}." + + $" Please upgrade first to at least {minVersion}."); - return "{init-" + currentVersion + "}"; + // initial state is eg "{init-7.14.0}" + return GetInitState(currentVersion); } } + protected override void ThrowOnUnknownInitialState(string state) + { + if (TryGetInitStateVersion(state, out var initVersion)) + { + throw new InvalidOperationException($"Version {UmbracoVersion.SemanticVersion} does not support migrating from {initVersion}." + + $" Please verify which versions support migrating from {initVersion}."); + } + + base.ThrowOnUnknownInitialState(state); + } + // define the plan protected void DefinePlan() { @@ -70,21 +102,23 @@ namespace Umbraco.Core.Migrations.Upgrade // // If the new migration causes a merge conflict, because someone else also added another // new migration, you NEED to fix the conflict by providing one default path, and paths - // out of the conflict states (see example below). + // out of the conflict states (see examples below). // // * Porting from version 7: // Append the ported migration to the main chain, using a new guid (same as above). - // Create a new special chain from the {init-...} state to the main chain (see example - // below). + // Create a new special chain from the {init-...} state to the main chain. - // plan starts at 7.10.0 (anything before 7.10.0 is not supported) - // upgrades from 7 to 8, and then takes care of all eventual upgrades + // plan starts at 7.14.0 (anything before 7.14.0 is not supported) // - From("{init-7.10.0}"); + From(GetInitState(new SemVersion(7, 14, 0))); + + // begin migrating from v7 - remove all keys and indexes + To("{B36B9ABD-374E-465B-9C5F-26AB0D39326F}"); + To("{7C447271-CA3F-4A6A-A913-5D77015655CB}"); To("{CBFF58A2-7B50-4F75-8E98-249920DB0F37}"); - To("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}"); + To("{5CB66059-45F4-48BA-BCBD-C5035D79206B}"); To("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}"); To("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}"); To("{8640C9E4-A1C0-4C59-99BB-609B4E604981}"); @@ -95,91 +129,55 @@ namespace Umbraco.Core.Migrations.Upgrade ToWithReplace("{941B2ABA-2D06-4E04-81F5-74224F1DB037}", "{76DF5CD7-A884-41A5-8DC6-7860D95B1DF5}"); // kill AddVariationTable1 To("{A7540C58-171D-462A-91C5-7A9AA5CB8BFD}"); - Merge().To("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}") // shannon added that one - .With().To("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}") // stephan added that one + Merge() + .To("{3E44F712-E2E3-473A-AE49-5D7F8E67CE3F}") + .With() + .To("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}") .As("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); To("{1350617A-4930-4D61-852F-E3AA9E692173}"); - To("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0 - - Merge() - .To("{0541A62B-EF87-4CA2-8225-F0EB98ECCC9F}") // from 7.12.0 - .To("{EB34B5DC-BB87-4005-985E-D983EA496C38}") // from 7.12.0 - .To("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}") // from 7.12.0 - .To("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}") // from 7.12.0 - .With() - .To("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}") // andy added that one - .As("{8B14CEBD-EE47-4AAD-A841-93551D917F11}"); - - ToWithReplace("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}", "{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); // merge - ToWithReplace("{B19BF0F2-E1C6-4AEB-A146-BC559D97A2C6}", "{290C18EE-B3DE-4769-84F1-1F467F3F76DA}"); // merge + To("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}"); + To("{5F4597F4-A4E0-4AFE-90B5-6D2F896830EB}"); + To("{290C18EE-B3DE-4769-84F1-1F467F3F76DA}"); To("{6A2C7C1B-A9DB-4EA9-B6AB-78E7D5B722A7}"); - To("{77874C77-93E5-4488-A404-A630907CEEF0}"); To("{8804D8E8-FE62-4E3A-B8A2-C047C2118C38}"); To("{23275462-446E-44C7-8C2C-3B8C1127B07D}"); To("{6B251841-3069-4AD5-8AE9-861F9523E8DA}"); To("{EE429F1B-9B26-43CA-89F8-A86017C809A3}"); To("{08919C4B-B431-449C-90EC-2B8445B5C6B1}"); To("{7EB0254C-CB8B-4C75-B15B-D48C55B449EB}"); - To("{648A2D5F-7467-48F8-B309-E99CEEE00E2A}"); // fixed version To("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}"); To("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}"); To("{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}"); - To("{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); // from 7.14.0 To("{ED28B66A-E248-4D94-8CDB-9BDF574023F0}"); To("{38C809D5-6C34-426B-9BEA-EFD39162595C}"); To("{6017F044-8E70-4E10-B2A3-336949692ADD}"); - To("{98339BEF-E4B2-48A8-B9D1-D173DC842BBE}"); Merge() .To("{CDBEDEE4-9496-4903-9CF2-4104E00FF960}") .With() - .To("{940FD19A-00A8-4D5C-B8FF-939143585726}") + .To("{940FD19A-00A8-4D5C-B8FF-939143585726}") .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); + To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); + + // finish migrating from v7 - recreate all keys and indexes + To("{3F9764F5-73D0-4D45-8804-1240A66E43A2}"); + To("{E0CBE54D-A84F-4A8F-9B13-900945FD7ED9}"); To("{78BAF571-90D0-4D28-8175-EF96316DA789}"); + // release-8.0.0 + + // to 8.0.1... To("{80C0A0CB-0DD5-4573-B000-C4B7C313C70D}"); + // release-8.0.1 + + // to 8.1.0... To("{B69B6E8C-A769-4044-A27E-4A4E18D1645A}"); + To("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); + To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); //FINAL - - - - - // and then, need to support upgrading from more recent 7.x - // - From("{init-7.10.1}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.2}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.3}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.4}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.10.5}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.11.0}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.11.1}").To("{init-7.10.0}"); // same as 7.10.0 - From("{init-7.11.2}").To("{init-7.10.0}"); // same as 7.10.0 - - // 7.12.0 has migrations, define a custom chain which copies the chain - // going from {init-7.10.0} to former final (1350617A) , and then goes straight to - // main chain, skipping the migrations - // - From("{init-7.12.0}"); - // clone start / clone stop / target - ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); - - From("{init-7.12.1}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.12.2}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.12.3}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0 - From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0 - - // 7.14.0 has migrations, handle it... - // clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then - // clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations), - // ending in 8A027815 (after 7.14 migrations) - From("{init-7.14.0}") - .ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{9109B8AF-6B34-46EE-9484-7434196D0C79}") - .ToWithClone("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}", "{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}", "{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs deleted file mode 100644 index 48e6d0085d..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_10_0/RenamePreviewFolder.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.IO; -using Umbraco.Core.IO; -using File = System.IO.File; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_10_0 -{ - /// - /// Renames the preview folder containing static HTML files to ensure it does not interfere with the MVC route - /// that is now supposed to render these views dynamically. We don't want to delete as people may have made - /// customizations to these files that would need to be migrated to the new .cshtml view files. - /// - public class RenamePreviewFolder : MigrationBase - { - public RenamePreviewFolder(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var previewFolderPath = IOHelper.MapPath(SystemDirectories.Umbraco + "/preview"); - if (Directory.Exists(previewFolderPath)) - { - var newPath = previewFolderPath.Replace("preview", "preview.old"); - if (Directory.Exists(newPath) == false) - { - Directory.Move(previewFolderPath, newPath); - var readmeText = - $"Static HTML files used for preview and canvas editing functionality no longer live in this directory.\r\n" + - $"Instead they have been recreated as MVC views and can now be found in '~/Umbraco/Views/Preview'.\r\n" + - $"See issue: http://issues.umbraco.org/issue/U4-11090"; - File.WriteAllText(Path.Combine(newPath, "readme.txt"), readmeText); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/AddRelationTypeForMediaFolderOnDelete.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/AddRelationTypeForMediaFolderOnDelete.cs deleted file mode 100644 index f34ed9fb68..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/AddRelationTypeForMediaFolderOnDelete.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - public class AddRelationTypeForMediaFolderOnDelete : MigrationBase - { - - public AddRelationTypeForMediaFolderOnDelete(IMigrationContext context) : base(context) - { - } - - public override void Migrate() - { - var relationTypeCount = Context.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoRelationType WHERE alias=@alias", - new { alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias }); - - if (relationTypeCount > 0) - return; - - var uniqueId = (Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias + "____" + Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName).ToGuid(); - Insert.IntoTable("umbracoRelationType").Row(new - { - typeUniqueId = uniqueId, - alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, - name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName, - childObjectType = Constants.ObjectTypes.MediaType, - parentObjectType = Constants.ObjectTypes.MediaType, - dual = false - }).Do(); - } - - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs deleted file mode 100644 index fc7a21f4fa..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/IncreaseLanguageIsoCodeColumnLength.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - public class IncreaseLanguageIsoCodeColumnLength : MigrationBase - { - public IncreaseLanguageIsoCodeColumnLength(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // Some people seem to have a constraint in their DB instead of an index, we'd need to drop that one - // See: https://our.umbraco.com/forum/using-umbraco-and-getting-started/93282-upgrade-from-711-to-712-fails - var constraints = SqlSyntax.GetConstraintsPerTable(Context.Database).Distinct().ToArray(); - if (constraints.Any(x => x.Item2.InvariantEquals("IX_umbracoLanguage_languageISOCode"))) - { - Delete.UniqueConstraint("IX_umbracoLanguage_languageISOCode").FromTable("umbracoLanguage").Do(); - } - - //Now check for indexes of that name and drop that if it exists - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoLanguage_languageISOCode"))) - { - Delete.Index("IX_umbracoLanguage_languageISOCode").OnTable("umbracoLanguage").Do(); - } - - Alter.Table("umbracoLanguage") - .AlterColumn("languageISOCode") - .AsString(14) - .Nullable() - .Do(); - - Create.Index("IX_umbracoLanguage_languageISOCode") - .OnTable("umbracoLanguage") - .OnColumn("languageISOCode") - .Ascending() - .WithOptions() - .Unique() - .Do(); - } - - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/RenameTrueFalseField.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/RenameTrueFalseField.cs deleted file mode 100644 index 4022739ece..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/RenameTrueFalseField.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - public class RenameTrueFalseField : MigrationBase - { - public RenameTrueFalseField(IMigrationContext context) : base(context) - { - } - - public override void Migrate() - { - //rename the existing true/false field - Update.Table(NodeDto.TableName).Set(new { text = "Checkbox" }).Where(new { id = Constants.DataTypes.Boolean }).Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/SetDefaultTagsStorageType.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/SetDefaultTagsStorageType.cs deleted file mode 100644 index c8d65961f4..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/SetDefaultTagsStorageType.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ - /// - /// Set the default storageType for the tags datatype to "CSV" to ensure backwards compatibility since the default is going to be JSON in new versions. - /// - public class SetDefaultTagsStorageType : MigrationBase - { - public SetDefaultTagsStorageType(IMigrationContext context) - : base(context) - { } - - // dummy editor for deserialization - private class TagConfigurationEditor : ConfigurationEditor - { } - - public override void Migrate() - { - // get all Umbraco.Tags datatypes - var dataTypeDtos = Database.Fetch(Context.SqlContext.Sql() - .Select() - .From() - .Where(x => x.EditorAlias == Constants.PropertyEditors.Aliases.Tags)); - - // get a dummy editor for deserialization - var editor = new TagConfigurationEditor(); - - foreach (var dataTypeDto in dataTypeDtos) - { - // need to check storageType on raw dictionary, as TagConfiguration would have a default value - var dictionary = JsonConvert.DeserializeObject(dataTypeDto.Configuration); - - // if missing, use TagConfiguration to properly update the configuration - // due to ... reasons ... the key can start with a lower or upper 'S' - if (!dictionary.ContainsKey("storageType") && !dictionary.ContainsKey("StorageType")) - { - var configuration = (TagConfiguration)editor.FromDatabase(dataTypeDto.Configuration); - configuration.StorageType = TagsStorageType.Csv; - dataTypeDto.Configuration = ConfigurationEditor.ToDatabase(configuration); - Database.Update(dataTypeDto); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/UpdateUmbracoConsent.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/UpdateUmbracoConsent.cs deleted file mode 100644 index 7596e3d7dd..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_12_0/UpdateUmbracoConsent.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0 -{ public class UpdateUmbracoConsent : MigrationBase { - public UpdateUmbracoConsent(IMigrationContext context) - : base(context) - { } - - public override void Migrate() { Alter.Table(Constants.DatabaseSchema.Tables.Consent).AlterColumn("comment").AsString().Nullable().Do(); } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs deleted file mode 100644 index c70f42076f..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_14_0/UpdateMemberGroupPickerData.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Umbraco.Core.Migrations.Upgrade.V_7_14_0 -{ - /// - /// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268. - /// - public class UpdateMemberGroupPickerData : MigrationBase - { - /// - /// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268. - /// - public UpdateMemberGroupPickerData(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - Database.Execute($@"UPDATE umbracoPropertyData SET textValue = varcharValue, varcharValue = NULL - WHERE textValue IS NULL AND id IN ( - SELECT id FROM umbracoPropertyData WHERE propertyTypeId in ( - SELECT id from cmsPropertyType where dataTypeId IN ( - SELECT nodeId FROM umbracoDataType WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}' - ) - ) - )"); - - Database.Execute($"UPDATE umbracoDataType SET dbType = 'Ntext' WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}'"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs deleted file mode 100644 index 7040874a74..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/AddRedirectUrlTable.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 -{ - public class AddRedirectUrlTable : MigrationBase - { - public AddRedirectUrlTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - var umbracoRedirectUrlTableName = "umbracoRedirectUrl"; - - var tables = SqlSyntax.GetTablesInSchema(database).ToArray(); - - if (tables.InvariantContains(umbracoRedirectUrlTableName)) - { - var columns = SqlSyntax.GetColumnsInSchema(database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals(umbracoRedirectUrlTableName) && x.ColumnName.InvariantEquals("id") && x.DataType == "uniqueidentifier")) - return; - Delete.Table(umbracoRedirectUrlTableName).Do(); - } - - Create.Table(umbracoRedirectUrlTableName) - .WithColumn("id").AsGuid().NotNullable().PrimaryKey("PK_" + umbracoRedirectUrlTableName) - .WithColumn("createDateUtc").AsDateTime().NotNullable() - .WithColumn("url").AsString(2048).NotNullable() - .WithColumn("contentKey").AsGuid().NotNullable() - .WithColumn("urlHash").AsString(40).NotNullable() - .Do(); - - Create.Index("IX_" + umbracoRedirectUrlTableName).OnTable(umbracoRedirectUrlTableName) - .OnColumn("urlHash") - .Ascending() - .OnColumn("contentKey") - .Ascending() - .OnColumn("createDateUtc") - .Descending() - .WithOptions().NonClustered() - .Do(); - - Create.ForeignKey("FK_" + umbracoRedirectUrlTableName) - .FromTable(umbracoRedirectUrlTableName).ForeignColumn("contentKey") - .ToTable("umbracoNode").PrimaryColumn("uniqueID") - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs deleted file mode 100644 index d27ed11a21..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/RemoveStylesheetDataAndTablesAgain.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 -{ - /// - /// This is here to re-remove these tables, we dropped them in 7.3 but new installs created them again so we're going to re-drop them - /// - public class RemoveStylesheetDataAndTablesAgain : MigrationBase - { - public RemoveStylesheetDataAndTablesAgain(IMigrationContext context) - : base(context) - { - } - - public override void Migrate() - { - // these have been obsoleted, need to copy the values here - var stylesheetPropertyObjectType = new Guid("5555da4f-a123-42b2-4488-dcdfb25e4111"); - var stylesheetObjectType = new Guid("9F68DA4F-A3A8-44C2-8226-DCBD125E4840"); - - //Clear all stylesheet data if the tables exist - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("cmsStylesheetProperty")) - { - Delete.FromTable("cmsStylesheetProperty").AllRows().Do(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = stylesheetPropertyObjectType }).Do(); - - Delete.Table("cmsStylesheetProperty").Do(); - } - if (tables.InvariantContains("cmsStylesheet")) - { - Delete.FromTable("cmsStylesheet").AllRows().Do(); - Delete.FromTable("umbracoNode").Row(new { nodeObjectType = stylesheetObjectType }).Do(); - - Delete.Table("cmsStylesheet").Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs deleted file mode 100644 index 8c688cfd28..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_0/UpdateUniqueIndexOnPropertyData.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_0 -{ - /// - /// See: http://issues.umbraco.org/issue/U4-8522 - /// - public class UpdateUniqueIndexOnPropertyData : MigrationBase - { - public UpdateUniqueIndexOnPropertyData(IMigrationContext context) - : base(context) - { - } - - public override void Migrate() - { - //Clear all stylesheet data if the tables exist - //tuple = tablename, indexname, columnname, unique - var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); - var found = indexes.FirstOrDefault( - x => x.Item1.InvariantEquals("cmsPropertyData") - && x.Item2.InvariantEquals("IX_cmsPropertyData_1") - //we're searching for the old index which is not unique - && x.Item4 == false); - - if (found != null) - { - Database.Execute("DELETE FROM cmsPropertyData WHERE id NOT IN (SELECT MIN(id) FROM cmsPropertyData GROUP BY nodeId, versionId, propertytypeid HAVING MIN(id) IS NOT NULL)"); - - //we need to re create this index - Delete.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData").Do(); - Create.Index("IX_cmsPropertyData_1").OnTable("cmsPropertyData") - .OnColumn("nodeId").Ascending() - .OnColumn("versionId").Ascending() - .OnColumn("propertytypeid").Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs deleted file mode 100644 index 362b8e251c..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_5_5/UpdateAllowedMediaTypesAtRoot.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Umbraco.Core.Migrations.Upgrade.V_7_5_5 -{ - /// - /// See: http://issues.umbraco.org/issue/U4-4196 - /// - public class UpdateAllowedMediaTypesAtRoot : MigrationBase - { - public UpdateAllowedMediaTypesAtRoot(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - Database.Execute("UPDATE cmsContentType SET allowAtRoot = 1 WHERE nodeId = 1032 OR nodeId = 1033"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs deleted file mode 100644 index a223d76a07..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToCmsMemberLoginName.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexToCmsMemberLoginName : MigrationBase - { - public AddIndexToCmsMemberLoginName(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - - //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long - //http://issues.umbraco.org/issue/U4-9758 - - var colLen = database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); - - if (colLen < 900 == false && colLen != null) - { - return; - } - - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false) - { - //we can apply the index - Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember") - .OnColumn("LoginName") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs deleted file mode 100644 index 191f410b47..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUmbracoNodePath.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexToUmbracoNodePath : MigrationBase - { - public AddIndexToUmbracoNodePath(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false) - { - Create.Index("IX_umbracoNodePath").OnTable("umbracoNode") - .OnColumn("path") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs deleted file mode 100644 index 87b392b09c..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexToUser2NodePermission.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexToUser2NodePermission : MigrationBase - { - public AddIndexToUser2NodePermission(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoUser2NodePermission_nodeId")) == false) - { - Create.Index("IX_umbracoUser2NodePermission_nodeId").OnTable("umbracoUser2NodePermission") - .OnColumn("nodeId") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs deleted file mode 100644 index 9042ae105e..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddIndexesToUmbracoRelationTables.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddIndexesToUmbracoRelationTables : MigrationBase - { - public AddIndexesToUmbracoRelationTables(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray(); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false) - { - //This will remove any corrupt/duplicate data in the relation table before the index is applied - //Ensure this executes in a deferred block which will be done inside of the migration transaction - var database = Database; - - //We need to check if this index has corrupted data and then clear that data - var duplicates = database.Fetch("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1"); - if (duplicates.Count > 0) - { - //need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though - foreach (var duplicate in duplicates) - { - var ids = database.Fetch("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC", - new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType }); - - if (ids.Count == 1) - { - //this is just a safety check, this should absolutely never happen - throw new InvalidOperationException("Duplicates were detected but could not be discovered"); - } - - //delete the others - ids = ids.Skip(0).ToList(); - - //iterate in groups of 2000 to avoid the max sql parameter limit - foreach (var idGroup in ids.InGroupsOf(2000)) - { - database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup }); - } - } - } - - //unique index to prevent duplicates - and for better perf - Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation") - .OnColumn("parentId").Ascending() - .OnColumn("childId").Ascending() - .OnColumn("relType").Ascending() - .WithOptions() - .Unique() - .Do(); - } - - //need indexes on alias and name for relation type since these are queried against - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false) - { - Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType") - .OnColumn("alias") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false) - { - Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType") - .OnColumn("name") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs deleted file mode 100644 index 2cc62cd2f9..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockObjects.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddLockObjects : MigrationBase - { - public AddLockObjects(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - EnsureLockObject(Constants.Locks.Servers, "Servers"); - } - - private void EnsureLockObject(int id, string name) - { - var db = Database; - var exists = db.Exists(id); - if (exists) return; - // be safe: delete old umbracoNode lock objects if any - db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id }); - // then create umbracoLock object - db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs deleted file mode 100644 index 369bda0758..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddLockTable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddLockTable : MigrationBase - { - public AddLockTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoLock") == false) - { - Create.Table("umbracoLock") - .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") - .WithColumn("value").AsInt32().NotNullable() - .WithColumn("name").AsString(64).NotNullable() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs deleted file mode 100644 index 82888ab970..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddMacroUniqueIdColumn.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddMacroUniqueIdColumn : MigrationBase - { - public AddMacroUniqueIdColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("cmsMacro") && x.ColumnName.InvariantEquals("uniqueId")) == false) - { - Create.Column("uniqueId").OnTable("cmsMacro").AsGuid().Nullable().Do(); - UpdateMacroGuids(); - Alter.Table("cmsMacro").AlterColumn("uniqueId").AsGuid().NotNullable().Do(); - Create.Index("IX_cmsMacro_UniqueId").OnTable("cmsMacro").OnColumn("uniqueId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - - } - - if (columns.Any(x => x.TableName.InvariantEquals("cmsMacroProperty") && x.ColumnName.InvariantEquals("uniquePropertyId")) == false) - { - Create.Column("uniquePropertyId").OnTable("cmsMacroProperty").AsGuid().Nullable().Do(); - UpdateMacroPropertyGuids(); - Alter.Table("cmsMacroProperty").AlterColumn("uniquePropertyId").AsGuid().NotNullable().Do(); - Create.Index("IX_cmsMacroProperty_UniquePropertyId").OnTable("cmsMacroProperty").OnColumn("uniquePropertyId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - } - } - - private void UpdateMacroGuids() - { - var database = Database; - - var updates = database.Query("SELECT id, macroAlias FROM cmsMacro") - .Select(macro => Tuple.Create((int) macro.id, ("macro____" + (string) macro.macroAlias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE cmsMacro set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - - private void UpdateMacroPropertyGuids() - { - var database = Database; - - var updates = database.Query(@"SELECT cmsMacroProperty.id id, macroPropertyAlias propertyAlias, cmsMacro.macroAlias macroAlias -FROM cmsMacroProperty -JOIN cmsMacro ON cmsMacroProperty.macro=cmsMacro.id") - .Select(prop => Tuple.Create((int) prop.id, ("macro____" + (string) prop.macroAlias + "____" + (string) prop.propertyAlias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE cmsMacroProperty set uniquePropertyId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs deleted file mode 100644 index e166a4582d..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/AddRelationTypeUniqueIdColumn.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class AddRelationTypeUniqueIdColumn : MigrationBase - { - public AddRelationTypeUniqueIdColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoRelationType") && x.ColumnName.InvariantEquals("typeUniqueId")) == false) - { - Create.Column("typeUniqueId").OnTable("umbracoRelationType").AsGuid().Nullable().Do(); - UpdateRelationTypeGuids(); - Alter.Table("umbracoRelationType").AlterColumn("typeUniqueId").AsGuid().NotNullable().Do(); - Create.Index("IX_umbracoRelationType_UniqueId").OnTable("umbracoRelationType").OnColumn("typeUniqueId") - .Ascending() - .WithOptions().NonClustered() - .WithOptions().Unique() - .Do(); - } - } - - private void UpdateRelationTypeGuids() - { - var database = Database; - var updates = database.Query("SELECT id, alias, name FROM umbracoRelationType") - .Select(relationType => Tuple.Create((int) relationType.id, ("relationType____" + (string) relationType.alias + "____" + (string) relationType.name).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE umbracoRelationType set typeUniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs deleted file mode 100644 index 9e8c739104..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/NormalizeTemplateGuids.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class NormalizeTemplateGuids : MigrationBase - { - public NormalizeTemplateGuids(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - - // we need this migration because ppl running pre-7.6 on Cloud and Courier have templates in different - // environments having different GUIDs (Courier does not sync template GUIDs) and we need to normalize - // these GUIDs so templates with the same alias on different environments have the same GUID. - // however, if already running a prerelease version of 7.6, we do NOT want to normalize the GUIDs as quite - // probably, we are already running Deploy and the GUIDs are OK. assuming noone is running a prerelease - // of 7.6 on Courier. - // so... testing if we already have a 7.6.0 version installed. not pretty but...? - // - var version = database.FirstOrDefault("SELECT version FROM umbracoMigration WHERE name=@name ORDER BY version DESC", new { name = Constants.System.UmbracoUpgradePlanName }); - if (version != null && version.StartsWith("7.6.0")) return; - - var updates = database.Query(@"SELECT umbracoNode.id, cmsTemplate.alias FROM umbracoNode -JOIN cmsTemplate ON umbracoNode.id=cmsTemplate.nodeId -WHERE nodeObjectType = @guid", new { guid = Constants.ObjectTypes.TemplateType }) - .Select(template => Tuple.Create((int) template.id, ("template____" + (string) template.alias).ToGuid())) - .ToList(); - - foreach (var update in updates) - database.Execute("UPDATE umbracoNode set uniqueId=@guid WHERE id=@id", new { guid = update.Item2, id = update.Item1 }); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs deleted file mode 100644 index 1af21617f3..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/ReduceLoginNameColumnsSize.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class ReduceLoginNameColumnsSize : MigrationBase - { - public ReduceLoginNameColumnsSize(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //Now we need to check if we can actually d6 this because we won't be able to if there's data in there that is too long - //http://issues.umbraco.org/issue/U4-9758 - - var database = Database; - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); - - var colLen = database.ExecuteScalar("select max(datalength(LoginName)) from cmsMember"); - - if (colLen < 900 == false) return; - - //if an index exists on this table we need to drop it. Normally we'd check via index name but in some odd cases (i.e. Our) - //the index name is something odd (starts with "mi_"). In any case, the index cannot exist if we want to alter the column - //so we'll drop whatever index is there and add one with the correct name after. - var loginNameIndex = dbIndexes.FirstOrDefault(x => x.TableName.InvariantEquals("cmsMember") && x.ColumnName.InvariantEquals("LoginName")); - if (loginNameIndex != null) - { - Delete.Index(loginNameIndex.IndexName).OnTable("cmsMember").Do(); - } - - //we can apply the col length change - Alter.Table("cmsMember") - .AlterColumn("LoginName") - .AsString(225) - .NotNullable() - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs deleted file mode 100644 index 804714d1ff..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemovePropertyDataIdIndex.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - /// - /// See: http://issues.umbraco.org/issue/U4-9188 - /// - public class UpdateUniqueIndexOnPropertyData : MigrationBase - { - public UpdateUniqueIndexOnPropertyData(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //tuple = tablename, indexname, columnname, unique - var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); - var found = indexes.FirstOrDefault( - x => x.Item1.InvariantEquals("cmsPropertyData") - && x.Item2.InvariantEquals("IX_cmsPropertyData")); - - if (found != null) - { - //drop the index - Delete.Index("IX_cmsPropertyData").OnTable("cmsPropertyData").Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs deleted file mode 100644 index 744b441b32..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_6_0/RemoveUmbracoDeployTables.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_6_0 -{ - public class RemoveUmbracoDeployTables : MigrationBase - { - public RemoveUmbracoDeployTables(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - // there are two versions of umbracoDeployDependency, - // 1. one created by 7.4 and never used, we need to remove it (has a sourceId column) - // 2. one created by Deploy itself, we need to keep it (has a sourceUdi column) - if (tables.InvariantContains("umbracoDeployDependency")) - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - if (columns.Any(x => x.TableName.InvariantEquals("umbracoDeployDependency") && x.ColumnName.InvariantEquals("sourceId"))) - Delete.Table("umbracoDeployDependency").Do(); - } - - // always remove umbracoDeployChecksum - if (tables.InvariantContains("umbracoDeployChecksum")) - Delete.Table("umbracoDeployChecksum").Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs deleted file mode 100644 index b366c7dab9..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddIndexToDictionaryKeyColumn.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class AddIndexToDictionaryKeyColumn : MigrationBase - { - public AddIndexToDictionaryKeyColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long - var colLen = database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); - - if (colLen < 900 == false && colLen != null) - { - return; - } - - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key")) == false) - { - //we can apply the index - Create.Index("IX_cmsDictionary_key").OnTable("cmsDictionary") - .OnColumn("key") - .Ascending() - .WithOptions() - .NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs deleted file mode 100644 index edd78e6c84..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserGroupTables.cs +++ /dev/null @@ -1,357 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using ColumnInfo = Umbraco.Core.Persistence.SqlSyntax.ColumnInfo; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - 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 = "COLLATE DATABASE_DEFAULT"; - } - - public override void Migrate() - { - 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 already 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)) - { - MigrateUserPermissions(); - MigrateUserTypesToGroups(); - 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. - Database.Execute(@"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) - .Do(); - } - - 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. - Database.Execute(@"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) - .Do(); - } - } - } - - /// - /// In some very rare cases, there might already 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").Do(); - //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").Do(); - //remove from the tables list since this will be re-checked in further logic - tables.Remove("umbracoUserGroup"); - } - } - } - - private void SetDefaultIcons() - { - Database.Execute($"UPDATE umbracoUserGroup SET icon = \'\' WHERE userGroupAlias = \'{Constants.Security.AdminGroupAlias}\'"); - Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-edit\' WHERE userGroupAlias = \'writer\'"); - Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-tools\' WHERE userGroupAlias = \'editor\'"); - Database.Execute("UPDATE umbracoUserGroup SET icon = \'icon-globe\' WHERE userGroupAlias = \'translator\'"); - } - - private bool AddNewTables(List tables) - { - var updated = false; - if (tables.InvariantContains("umbracoUserGroup") == false) - { - Create.Table().Do(); - updated = true; - } - - if (tables.InvariantContains("umbracoUser2UserGroup") == false) - { - Create.Table().Do(); - updated = true; - } - - if (tables.InvariantContains("umbracoUserGroup2App") == false) - { - Create.Table().Do(); - updated = true; - } - - if (tables.InvariantContains("umbracoUserGroup2NodePermission") == false) - { - Create.Table().Do(); - updated = true; - } - - return updated; - } - - private void MigrateUserTypesToGroups() - { - // Create a user group for each user type - Database.Execute(@"INSERT INTO umbracoUserGroup (userGroupAlias, userGroupName, userGroupDefaultPermissions) - SELECT userTypeAlias, userTypeName, userTypeDefaultPermissions - FROM umbracoUserType"); - - // Add each user to the group created from their type - Database.Execute(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 {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. - Database.Execute(@"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 u.id = 0"); - - // Add the default section access to the other built-in accounts - // writer: - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'content' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'writer' {0}", _collateSyntax)); - // editor - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'content' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); - Database.Execute(string.Format(@"INSERT INTO umbracoUserGroup2app (userGroupId, app) - SELECT ug.id, 'media' as app - FROM umbracoUserGroup ug - WHERE ug.userGroupAlias {0} = 'editor' {0}", _collateSyntax)); - // translator - Database.Execute(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) - { - if (usersWithApps.TryGetValue(userApps.id, out List 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) - }).Do(); - //now assign the apps - var distinctApp = distinctApps[i]; - foreach (var app in distinctApp.appCollection) - { - Database.Execute(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 - Database.Execute(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) - Database.Execute("UPDATE umbracoUserGroup SET userGroupName = 'Writers' WHERE userGroupAlias = 'writer'"); - Database.Execute("UPDATE umbracoUserGroup SET userGroupName = 'Translators' WHERE userGroupAlias = 'translator'"); - - //Ensure all built in groups have a start node of -1 - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'editor'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'editor'"); - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'writer'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'writer'"); - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'translator'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'translator'"); - Database.Execute("UPDATE umbracoUserGroup SET startContentId = -1 WHERE userGroupAlias = 'admin'"); - Database.Execute("UPDATE umbracoUserGroup SET startMediaId = -1 WHERE userGroupAlias = 'admin'"); - } - - private void MigrateUserPermissions() - { - // Create user group records for all non-admin users that have specific permissions set - Database.Execute(@"INSERT INTO umbracoUserGroup(userGroupAlias, userGroupName) - SELECT 'permissionGroupFor' + userLogin, 'Migrated Permission Group for ' + userLogin - FROM umbracoUser - WHERE (id IN ( - SELECT userid - FROM umbracoUser2NodePermission - )) - AND id > 0"); - - // Associate those groups with the users - Database.Execute(string.Format(@"INSERT INTO umbracoUser2UserGroup (userId, userGroupId) - SELECT u.id, ug.id - FROM umbracoUser u - INNER JOIN umbracoUserGroup ug ON ug.userGroupAlias {0} = 'permissionGroupFor' + userLogin {0}", _collateSyntax)); - - // Create node permissions on the groups - Database.Execute(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 {0} NOT IN ( - SELECT userTypeAlias {0} - FROM umbracoUserType - )", _collateSyntax)); - - // Create app permissions on the groups - Database.Execute(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 {0} NOT IN ( - SELECT userTypeAlias {0} - FROM umbracoUserType - )", _collateSyntax)); - } - - private void DeleteOldTables(List tables, Tuple[] constraints) - { - if (tables.InvariantContains("umbracoUser2App")) - { - Delete.Table("umbracoUser2App").Do(); - } - - if (tables.InvariantContains("umbracoUser2NodePermission")) - { - Delete.Table("umbracoUser2NodePermission").Do(); - } - - if (tables.InvariantContains("umbracoUserType") && tables.InvariantContains("umbracoUser")) - { - //Delete the FK if it exists before dropping the column - if (constraints.Any(x => x.Item1.InvariantEquals("umbracoUser") && x.Item3.InvariantEquals("FK_umbracoUser_umbracoUserType_id"))) - { - Delete.ForeignKey("FK_umbracoUser_umbracoUserType_id").OnTable("umbracoUser").Do(); - } - - //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").Do(); - } - - Delete.Column("userType").FromTable("umbracoUser").Do(); - Delete.Table("umbracoUserType").Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs deleted file mode 100644 index 54545e06d3..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/AddUserStartNodeTable.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class AddUserStartNodeTable : MigrationBase - { - public AddUserStartNodeTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains("umbracoUserStartNode")) return; - - Create.Table().Do(); - - MigrateUserStartNodes(); - - //now remove the old columns - - Delete.Column("startStructureID").FromTable("umbracoUser").Do(); - Delete.Column("startMediaID").FromTable("umbracoUser").Do(); - } - - private void MigrateUserStartNodes() - { - Database.Execute(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) - SELECT id, startStructureID, 1 - FROM umbracoUser - WHERE startStructureID IS NOT NULL AND startStructureID > 0 AND startStructureID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Document + "')"); - - Database.Execute(@"INSERT INTO umbracoUserStartNode (userId, startNode, startNodeType) - SELECT id, startMediaID, 2 - FROM umbracoUser - WHERE startMediaID IS NOT NULL AND startMediaID > 0 AND startMediaID IN (SELECT id FROM umbracoNode WHERE nodeObjectType='" + Constants.ObjectTypes.Media + "')"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs deleted file mode 100644 index 1f7e2faf33..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/EnsureContentTemplatePermissions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - /// - /// Ensures the built-in user groups have the blueprint permission by default on upgrade - /// - public class EnsureContentTemplatePermissions : MigrationBase - { - public EnsureContentTemplatePermissions(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var database = Database; - var userGroups = database.Fetch( - Context.SqlContext.Sql().Select("*") - .From() - .Where(x => x.Alias == "admin" || x.Alias == "editor")); - - foreach (var userGroup in userGroups) - { - if (userGroup.DefaultPermissions.Contains('�') == false) - { - userGroup.DefaultPermissions += "�"; - Update.Table("umbracoUserGroup") - .Set(new { userGroupDefaultPermissions = userGroup.DefaultPermissions }) - .Where(new { id = userGroup.Id }) - .Do(); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs deleted file mode 100644 index 17dd3064eb..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/ReduceDictionaryKeyColumnsSize.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class ReduceDictionaryKeyColumnsSize : MigrationBase - { - public ReduceDictionaryKeyColumnsSize(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //Now we need to check if we can actually do this because we won't be able to if there's data in there that is too long - - var database = Database; - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(database); - - var colLen = database.ExecuteScalar(string.Format("select max(datalength({0})) from cmsDictionary", SqlSyntax.GetQuotedColumnName("key"))); - - if (colLen < 900 == false) return; - - //if it exists we need to drop it first - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsDictionary_key"))) - { - Delete.Index("IX_cmsDictionary_key").OnTable("cmsDictionary").Do(); - } - - //we can apply the col length change - Alter.Table("cmsDictionary") - .AlterColumn("key") - .AsString(450) - .NotNullable() - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs deleted file mode 100644 index 509b3f91dd..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_7_0/UpdateUserTables.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Linq; -using System.Web.Security; -using Newtonsoft.Json; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; -using Umbraco.Core.Security; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_7_0 -{ - public class UpdateUserTables : MigrationBase - { - public UpdateUserTables(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - //Don't execute if the column is already there - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("createDate")) == false) - Create.Column("createDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime).Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("updateDate")) == false) - Create.Column("updateDate").OnTable("umbracoUser").AsDateTime().NotNullable().WithDefault(SystemMethods.CurrentDateTime).Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("emailConfirmedDate")) == false) - Create.Column("emailConfirmedDate").OnTable("umbracoUser").AsDateTime().Nullable().Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("invitedDate")) == false) - Create.Column("invitedDate").OnTable("umbracoUser").AsDateTime().Nullable().Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("avatar")) == false) - Create.Column("avatar").OnTable("umbracoUser").AsString(500).Nullable().Do(); - - if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("passwordConfig")) == false) - { - Create.Column("passwordConfig").OnTable("umbracoUser").AsString(500).Nullable().Do(); - //Check if we have a known config, we only want to store config for hashing - var membershipProvider = MembershipProviderExtensions.GetUsersMembershipProvider(); - if (membershipProvider.PasswordFormat == MembershipPasswordFormat.Hashed) - { - var json = JsonConvert.SerializeObject(new { hashAlgorithm = Membership.HashAlgorithmType }); - Database.Execute("UPDATE umbracoUser SET passwordConfig = '" + json + "'"); - } - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs deleted file mode 100644 index ddb084a609..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddIndexToPropertyTypeAliasColumn.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.SqlSyntax; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddIndexToPropertyTypeAliasColumn : MigrationBase - { - public AddIndexToPropertyTypeAliasColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database); - - //make sure it doesn't already exist - if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false) - { - //we can apply the index - Create.Index("IX_cmsPropertyTypeAlias").OnTable(Constants.DatabaseSchema.Tables.PropertyType) - .OnColumn("Alias") - .Ascending().WithOptions().NonClustered() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs deleted file mode 100644 index 0ce2c91f2e..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddInstructionCountColumn.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddInstructionCountColumn : MigrationBase - { - public AddInstructionCountColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.CacheInstruction) && x.ColumnName.InvariantEquals("instructionCount")) == false) - AddColumn("instructionCount"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs deleted file mode 100644 index b4c0062770..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddMediaVersionTable.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Factories; -using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddMediaVersionTable : MigrationBase - { - public AddMediaVersionTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.MediaVersion)) return; - - Create.Table().Do(); - MigrateMediaPaths(); - } - - private void MigrateMediaPaths() - { - // this may not be the most efficient way to do it, compared to how it's done in v7, but this - // migration should only run for v8 sites that are being developed, before v8 is released, so - // no big sites and performances don't matter here - keep it simple - - var sql = Sql() - .Select(x => x.VarcharValue, x => x.TextValue) - .AndSelect(x => Alias(x.Id, "versionId")) - .From() - .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) - .InnerJoin().On((left, right) => left.VersionId == right.Id) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .Where(x => x.Alias == "umbracoFile") - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media); - - var paths = new List(); - - //using QUERY = a db cursor, we won't load this all into memory first, just row by row - foreach (var row in Database.Query(sql)) - { - // if there's values then ensure there's a media path match and extract it - string mediaPath = null; - if ( - (row.varcharValue != null && ContentBaseFactory.TryMatch((string) row.varcharValue, out mediaPath)) - || (row.textValue != null && ContentBaseFactory.TryMatch((string) row.textValue, out mediaPath))) - { - paths.Add(new MediaVersionDto - { - Id = (int) row.versionId, - Path = mediaPath - }); - } - } - - // bulk insert - Database.BulkInsertRecords(paths); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs deleted file mode 100644 index cd2678205f..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddTourDataUserColumn.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddTourDataUserColumn : MigrationBase - { - public AddTourDataUserColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.User) && x.ColumnName.InvariantEquals("tourData")) == false) - AddColumn("tourData"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs deleted file mode 100644 index 7a55362072..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_8_0/AddUserLoginTable.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_8_0 -{ - internal class AddUserLoginTable : MigrationBase - { - public AddUserLoginTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.UserLogin) == false) - { - Create.Table().Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs deleted file mode 100644 index 2b5f481769..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class AddIsSensitiveMemberTypeColumn : MigrationBase - { - public AddIsSensitiveMemberTypeColumn(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray(); - - if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberPropertyType) && x.ColumnName.InvariantEquals("isSensitive")) == false) - AddColumn("isSensitive"); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs deleted file mode 100644 index e7880dfc73..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoAuditTable.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class AddUmbracoAuditTable : MigrationBase - { - public AddUmbracoAuditTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.AuditEntry)) - return; - - Create.Table().Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs deleted file mode 100644 index e3656f69ac..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddUmbracoConsentTable.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Linq; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class AddUmbracoConsentTable : MigrationBase - { - public AddUmbracoConsentTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - - if (tables.InvariantContains(Constants.DatabaseSchema.Tables.Consent)) - return; - - Create.Table().Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs deleted file mode 100644 index ce656aa0c1..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/CreateSensitiveDataUserGroup.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0 -{ - internal class CreateSensitiveDataUserGroup : MigrationBase - { - public CreateSensitiveDataUserGroup(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var sql = Sql() - .SelectCount() - .From() - .Where(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); - - var exists = Database.ExecuteScalar(sql) > 0; - if (exists) return; - - var groupId = Database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", new UserGroupDto { StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" }); - Database.Insert(new User2UserGroupDto { UserGroupId = Convert.ToInt32(groupId), UserId = Constants.Security.SuperUserId }); // add super to sensitive data - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs index efa54a0f59..5342755ebf 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddContentNuTable.cs @@ -1,6 +1,7 @@ using System.Data; using System.Linq; using Umbraco.Core.Persistence.DatabaseAnnotations; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -12,31 +13,10 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); + var tables = SqlSyntax.GetTablesInSchema(Context.Database); if (tables.InvariantContains("cmsContentNu")) return; - var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT); - - Create.Table("cmsContentNu") - .WithColumn("nodeId").AsInt32().NotNullable() - .WithColumn("published").AsBoolean().NotNullable() - .WithColumn("data").AsCustom(textType).NotNullable() - .WithColumn("rv").AsInt64().NotNullable().WithDefaultValue(0) - .Do(); - - Create.PrimaryKey("PK_cmsContentNu") - .OnTable("cmsContentNu") - .Columns(new[] { "nodeId", "published" }) - .Do(); - - Create.ForeignKey("FK_cmsContentNu_umbracoNode_id") - .FromTable("cmsContentNu") - .ForeignColumn("nodeId") - .ToTable("umbracoNode") - .PrimaryColumn("id") - .OnDelete(Rule.Cascade) - .OnUpdate(Rule.None) - .Do(); + Create.Table(true).Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs deleted file mode 100644 index aa14bc81c8..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockTable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class AddLockTable : MigrationBase - { - public AddLockTable(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray(); - if (tables.InvariantContains("umbracoLock") == false) - { - Create.Table("umbracoLock") - .WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock") - .WithColumn("value").AsInt32().NotNullable() - .WithColumn("name").AsString(64).NotNullable() - .Do(); - } - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs deleted file mode 100644 index 0ccc2d93ff..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddUserLoginDtoDateIndex.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.Persistence.Dtos; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class AddUserLoginDtoDateIndex : MigrationBase - { - public AddUserLoginDtoDateIndex(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - if (!IndexExists("IX_umbracoUserLogin_lastValidatedUtc")) - Create.Index("IX_umbracoUserLogin_lastValidatedUtc") - .OnTable(UserLoginDto.TableName) - .OnColumn("lastValidatedUtc") - .Ascending() - .WithOptions().NonClustered() - .Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs index c6cad2eac1..4044b5a173 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddVariationTables2.cs @@ -11,8 +11,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - Create.Table().Do(); - Create.Table().Do(); + Create.Table(true).Do(); + Create.Table(true).Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs index d7180385f0..438b02385b 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs @@ -1,28 +1,33 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Reflection; using Newtonsoft.Json; -using NPoco; -using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { public class DataTypeMigration : MigrationBase { - public DataTypeMigration(IMigrationContext context) + private readonly PreValueMigratorCollection _preValueMigrators; + private readonly PropertyEditorCollection _propertyEditors; + private readonly ILogger _logger; + + public DataTypeMigration(IMigrationContext context, PreValueMigratorCollection preValueMigrators, PropertyEditorCollection propertyEditors, ILogger logger) : base(context) - { } + { + _preValueMigrators = preValueMigrators; + _propertyEditors = propertyEditors; + _logger = logger; + } public override void Migrate() { - // delete *all* keys and indexes - because of FKs - Delete.KeysAndIndexes().Do(); - // drop and create columns Delete.Column("pk").FromTable("cmsDataType").Do(); @@ -31,21 +36,17 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 // create column AddColumn(Constants.DatabaseSchema.Tables.DataType, "config"); - Execute.Sql(Sql().Update(u => u.Set(x => x.Configuration, string.Empty)).SQL).Do(); - - // re-create *all* keys and indexes - foreach (var x in DatabaseSchemaCreator.OrderedTables) - Create.KeysAndIndexes(x).Do(); + Execute.Sql(Sql().Update(u => u.Set(x => x.Configuration, string.Empty))).Do(); // renames Execute.Sql(Sql() .Update(u => u.Set(x => x.EditorAlias, "Umbraco.ColorPicker")) - .Where(x => x.EditorAlias == "Umbraco.ColorPickerAlias").SQL).Do(); + .Where(x => x.EditorAlias == "Umbraco.ColorPickerAlias")).Do(); // from preValues to configuration... var sql = Sql() .Select() - .AndSelect(x => x.Alias, x => x.SortOrder, x => x.Value) + .AndSelect(x => x.Id, x => x.Alias, x => x.SortOrder, x => x.Value) .From() .InnerJoin().On((left, right) => left.NodeId == right.NodeId) .OrderBy(x => x.NodeId) @@ -60,43 +61,45 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 .From() .Where(x => x.NodeId == group.Key)).First(); - var aliases = group.Select(x => x.Alias).Distinct().ToArray(); - if (aliases.Length == 1 && string.IsNullOrWhiteSpace(aliases[0])) + // migrate the preValues to configuration + var migrator = _preValueMigrators.GetMigrator(dataType.EditorAlias) ?? new DefaultPreValueMigrator(); + var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, group.ToDictionary(x => x.Alias, x => x)); + var json = JsonConvert.SerializeObject(config); + + // validate - and kill the migration if it fails + var newAlias = migrator.GetNewAlias(dataType.EditorAlias); + if (newAlias == null) { - // array-based prevalues - var values = new Dictionary { ["values"] = group.OrderBy(x => x.SortOrder).Select(x => x.Value).ToArray() }; - dataType.Configuration = JsonConvert.SerializeObject(values); + _logger.Warn("Skipping validation of configuration for data type {NodeId} : {EditorAlias}." + + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, dataType.EditorAlias); + } + else if (!_propertyEditors.TryGet(newAlias, out var propertyEditor)) + { + _logger.Warn("Skipping validation of configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})" + + " because no property editor with that alias was found." + + " Please ensure that the configuration is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, newAlias, dataType.EditorAlias); } else { - // assuming we don't want to fall back to array - if (aliases.Length != group.Count() || aliases.Any(string.IsNullOrWhiteSpace)) - throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataType.NodeId} preValues: duplicate or null/empty alias."); - - // dictionary-base prevalues - var values = group.ToDictionary(x => x.Alias, x => x.Value); - dataType.Configuration = JsonConvert.SerializeObject(values); + var configEditor = propertyEditor.GetConfigurationEditor(); + try + { + var _ = configEditor.FromDatabase(json); + } + catch (Exception e) + { + _logger.Warn(e, "Failed to validate configuration for data type {NodeId} : {NewEditorAlias} (was: {EditorAlias})." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", + dataType.NodeId, newAlias, dataType.EditorAlias); + } } + // update + dataType.Configuration = JsonConvert.SerializeObject(config); Database.Update(dataType); } } - - [TableName("cmsDataTypePreValues")] - [ExplicitColumns] - public class PreValueDto - { - [Column("datatypeNodeId")] - public int NodeId { get; set; } - - [Column("alias")] - public string Alias { get; set; } - - [Column("sortorder")] - public int SortOrder { get; set; } - - [Column("value")] - public string Value { get; set; } - } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs new file mode 100644 index 0000000000..2e341ad091 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ContentPickerPreValueMigrator.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class ContentPickerPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.ContentPicker2"; + + public override string GetNewAlias(string editorAlias) + => null; + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "showOpenButton" || + preValue.Alias == "ignoreUserStartNodes") + return preValue.Value == "1"; + + return base.GetPreValueValue(preValue); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DecimalPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DecimalPreValueMigrator.cs new file mode 100644 index 0000000000..8dc0f1dcd5 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DecimalPreValueMigrator.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class DecimalPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.Decimal"; + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "min" || + preValue.Alias == "step" || + preValue.Alias == "max") + return decimal.TryParse(preValue.Value, out var d) ? (decimal?) d : null; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs new file mode 100644 index 0000000000..7112679de2 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/DefaultPreValueMigrator.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class DefaultPreValueMigrator : IPreValueMigrator + { + public virtual bool CanMigrate(string editorAlias) + => true; + + public virtual string GetNewAlias(string editorAlias) + => editorAlias; + + public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + var preValuesA = preValues.Values.ToList(); + var aliases = preValuesA.Select(x => x.Alias).Distinct().ToArray(); + if (aliases.Length == 1 && string.IsNullOrWhiteSpace(aliases[0])) + { + // array-based prevalues + return new Dictionary { ["values"] = preValuesA.OrderBy(x => x.SortOrder).Select(x => x.Value).ToArray() }; + } + + // assuming we don't want to fall back to array + if (aliases.Length != preValuesA.Count || aliases.Any(string.IsNullOrWhiteSpace)) + throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataTypeId} preValues: duplicate or null/empty alias."); + + // dictionary-base prevalues + return GetPreValues(preValuesA).ToDictionary(x => x.Alias, GetPreValueValue); + } + + protected virtual IEnumerable GetPreValues(IEnumerable preValues) + => preValues; + + protected virtual object GetPreValueValue(PreValueDto preValue) + { + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/IPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/IPreValueMigrator.cs new file mode 100644 index 0000000000..01e0ed3875 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/IPreValueMigrator.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + /// + /// Defines a service migrating preValues. + /// + public interface IPreValueMigrator + { + /// + /// Determines whether this migrator can migrate a data type. + /// + /// The data type editor alias. + bool CanMigrate(string editorAlias); + + /// + /// Gets the v8 codebase data type editor alias. + /// + /// The original v7 codebase editor alias. + /// + /// This is used to validate that the migrated configuration can be parsed + /// by the new property editor. Return null to bypass this validation, + /// when for instance we know it will fail, and another, later migration will + /// deal with it. + /// + string GetNewAlias(string editorAlias); + + /// + /// Gets the configuration object corresponding to preValue. + /// + /// The data type identifier. + /// The data type editor alias. + /// PreValues. + object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues); + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ListViewPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ListViewPreValueMigrator.cs new file mode 100644 index 0000000000..d8daf9b5e6 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ListViewPreValueMigrator.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class ListViewPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.ListView"; + + protected override IEnumerable GetPreValues(IEnumerable preValues) + { + return preValues.Where(preValue => preValue.Alias != "displayAtTabNumber"); + } + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "pageSize") + return int.TryParse(preValue.Value, out var i) ? (int?)i : null; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs new file mode 100644 index 0000000000..a46b1eefb7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/MediaPickerPreValueMigrator.cs @@ -0,0 +1,37 @@ +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class MediaPickerPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase + { + private readonly string[] _editors = + { + "Umbraco.MediaPicker2", + "Umbraco.MediaPicker" + }; + + public override bool CanMigrate(string editorAlias) + => _editors.Contains(editorAlias); + + public override string GetNewAlias(string editorAlias) + => "Umbraco.MediaPicker"; + + // you wish - but MediaPickerConfiguration lives in Umbraco.Web + /* + public override object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + return new MediaPickerConfiguration { ... }; + } + */ + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "multiPicker" || + preValue.Alias == "onlyImages" || + preValue.Alias == "disableFolderSelect") + return preValue.Value == "1"; + + return base.GetPreValueValue(preValue); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/NestedContentPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/NestedContentPreValueMigrator.cs new file mode 100644 index 0000000000..22c87f8503 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/NestedContentPreValueMigrator.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class NestedContentPreValueMigrator : DefaultPreValueMigrator //PreValueMigratorBase + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.NestedContent"; + + // you wish - but NestedContentConfiguration lives in Umbraco.Web + /* + public override object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + return new NestedContentConfiguration { ... }; + } + */ + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "confirmDeletes" || + preValue.Alias == "showIcons" || + preValue.Alias == "hideLabel") + return preValue.Value == "1"; + + if (preValue.Alias == "minItems" || + preValue.Alias == "maxItems") + return int.TryParse(preValue.Value, out var i) ? (int?)i : null; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueDto.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueDto.cs new file mode 100644 index 0000000000..b6ab622510 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueDto.cs @@ -0,0 +1,24 @@ +using NPoco; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + [TableName("cmsDataTypePreValues")] + [ExplicitColumns] + public class PreValueDto + { + [Column("id")] + public int Id { get; set; } + + [Column("datatypeNodeId")] + public int NodeId { get; set; } + + [Column("alias")] + public string Alias { get; set; } + + [Column("sortorder")] + public int SortOrder { get; set; } + + [Column("value")] + public string Value { get; set; } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorBase.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorBase.cs new file mode 100644 index 0000000000..62e2b2347b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorBase.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + public abstract class PreValueMigratorBase : IPreValueMigrator + { + public abstract bool CanMigrate(string editorAlias); + + public virtual string GetNewAlias(string editorAlias) + => editorAlias; + + public abstract object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues); + + protected bool GetBoolValue(Dictionary preValues, string alias, bool defaultValue = false) + => preValues.TryGetValue(alias, out var preValue) ? preValue.Value == "1" : defaultValue; + + protected decimal GetDecimalValue(Dictionary preValues, string alias, decimal defaultValue = 0) + => preValues.TryGetValue(alias, out var preValue) && decimal.TryParse(preValue.Value, out var value) ? value : defaultValue; + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollection.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollection.cs new file mode 100644 index 0000000000..06f5d45deb --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollection.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + public class PreValueMigratorCollection : BuilderCollectionBase + { + private readonly ILogger _logger; + + public PreValueMigratorCollection(IEnumerable items, ILogger logger) + : base(items) + { + _logger = logger; + _logger.Debug(GetType(), "Migrators: " + string.Join(", ", items.Select(x => x.GetType().Name))); + } + + public IPreValueMigrator GetMigrator(string editorAlias) + { + var migrator = this.FirstOrDefault(x => x.CanMigrate(editorAlias)); + _logger.Debug(GetType(), "Getting migrator for \"{EditorAlias}\" = {MigratorType}", editorAlias, migrator == null ? "" : migrator.GetType().Name); + return migrator; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollectionBuilder.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollectionBuilder.cs new file mode 100644 index 0000000000..3859e63767 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorCollectionBuilder.cs @@ -0,0 +1,9 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + public class PreValueMigratorCollectionBuilder : OrderedCollectionBuilderBase + { + protected override PreValueMigratorCollectionBuilder This => this; + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs new file mode 100644 index 0000000000..9e2dec9b70 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/PreValueMigratorComposer.cs @@ -0,0 +1,25 @@ +using Umbraco.Core.Composing; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + [RuntimeLevel(MinLevel = RuntimeLevel.Upgrade, MaxLevel = RuntimeLevel.Upgrade)] // only on upgrades + public class PreValueMigratorComposer : ICoreComposer + { + public void Compose(Composition composition) + { + // do NOT add DefaultPreValueMigrator to this list! + // it will be automatically used if nothing matches + + composition.WithCollectionBuilder() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs new file mode 100644 index 0000000000..c04e7c8fda --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RenamingPreValueMigrator.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class RenamingPreValueMigrator : DefaultPreValueMigrator + { + private readonly string[] _editors = + { + "Umbraco.NoEdit" + }; + + public override bool CanMigrate(string editorAlias) + => _editors.Contains(editorAlias); + + public override string GetNewAlias(string editorAlias) + { + switch (editorAlias) + { + case "Umbraco.NoEdit": + return Constants.PropertyEditors.Aliases.Label; + default: + throw new Exception("panic"); + } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RichTextPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RichTextPreValueMigrator.cs new file mode 100644 index 0000000000..3670c52c64 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/RichTextPreValueMigrator.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class RichTextPreValueMigrator : DefaultPreValueMigrator + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.TinyMCEv3"; + + public override string GetNewAlias(string editorAlias) + => Constants.PropertyEditors.Aliases.TinyMce; + + protected override object GetPreValueValue(PreValueDto preValue) + { + if (preValue.Alias == "hideLabel") + return preValue.Value == "1"; + + return preValue.Value.DetectIsJson() ? JsonConvert.DeserializeObject(preValue.Value) : preValue.Value; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/UmbracoSliderPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/UmbracoSliderPreValueMigrator.cs new file mode 100644 index 0000000000..dc6de5e493 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/UmbracoSliderPreValueMigrator.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class UmbracoSliderPreValueMigrator : PreValueMigratorBase + { + public override bool CanMigrate(string editorAlias) + => editorAlias == "Umbraco.Slider"; + + public override object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + return new SliderConfiguration + { + EnableRange = GetBoolValue(preValues, "enableRange"), + InitialValue = GetDecimalValue(preValues, "initVal1"), + InitialValue2 = GetDecimalValue(preValues, "initVal2"), + MaximumValue = GetDecimalValue(preValues, "maxVal"), + MinimumValue = GetDecimalValue(preValues, "minVal"), + StepIncrements = GetDecimalValue(preValues, "step") + }; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs new file mode 100644 index 0000000000..7249ebd6ec --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypes/ValueListPreValueMigrator.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes +{ + class ValueListPreValueMigrator : IPreValueMigrator + { + private readonly string[] _editors = + { + "Umbraco.RadioButtonList", + "Umbraco.DropDown", + "Umbraco.DropdownlistPublishingKeys", + "Umbraco.DropDownMultiple", + "Umbraco.DropdownlistMultiplePublishKeys" + }; + + public bool CanMigrate(string editorAlias) + => _editors.Contains(editorAlias); + + public virtual string GetNewAlias(string editorAlias) + => null; + + public object GetConfiguration(int dataTypeId, string editorAlias, Dictionary preValues) + { + var config = new ValueListConfiguration(); + foreach (var preValue in preValues.Values) + config.Items.Add(new ValueListConfiguration.ValueListItem { Id = preValue.Id, Value = preValue.Value }); + return config; + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs index ed2990aff7..d30719231a 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropDownPropertyEditorsMigration.cs @@ -1,39 +1,35 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Cache; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.PostMigrations; using Umbraco.Core.Models; -using Umbraco.Core.Sync; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { - public class DropDownPropertyEditorsMigration : MigrationBase + public class DropDownPropertyEditorsMigration : PropertyEditorsMigrationBase { - private readonly CacheRefresherCollection _cacheRefreshers; - private readonly IServerMessenger _serverMessenger; - - public DropDownPropertyEditorsMigration(IMigrationContext context, CacheRefresherCollection cacheRefreshers, IServerMessenger serverMessenger) + public DropDownPropertyEditorsMigration(IMigrationContext context) : base(context) - { - _cacheRefreshers = cacheRefreshers; - _serverMessenger = serverMessenger; - } - - // dummy editor for deserialization - private class ValueListConfigurationEditor : ConfigurationEditor { } public override void Migrate() { - //need to convert the old drop down data types to use the new one - var dataTypes = Database.Fetch(Sql() - .Select() - .From() - .Where(x => x.EditorAlias.Contains(".DropDown"))); + var refreshCache = Migrate(GetDataTypes(".DropDown", false)); + + // if some data types have been updated directly in the database (editing DataTypeDto and/or PropertyDataDto), + // bypassing the services, then we need to rebuild the cache entirely, including the umbracoContentNu table + if (refreshCache) + Context.AddPostMigration(); + } + + private bool Migrate(IEnumerable dataTypes) + { + var refreshCache = false; + ConfigurationEditor configurationEditor = null; foreach (var dataType in dataTypes) { @@ -42,14 +38,16 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (!dataType.Configuration.IsNullOrWhiteSpace()) { // parse configuration, and update everything accordingly + if (configurationEditor == null) + configurationEditor = new ValueListConfigurationEditor(); try { - config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase(dataType.Configuration); + config = (ValueListConfiguration) configurationEditor.FromDatabase(dataType.Configuration); } catch (Exception ex) { Logger.Error( - ex, "Invalid drop down configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", + ex, "Invalid configuration: \"{Configuration}\", cannot convert editor.", dataType.Configuration); // reset @@ -65,7 +63,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 .Where(x => x.DataTypeId == dataType.NodeId)); // update dtos - var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config)); + var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config, true)); // persist changes foreach (var propertyDataDto in updatedDtos) @@ -77,7 +75,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 config = new ValueListConfiguration(); } - var requiresCacheRebuild = false; switch (dataType.EditorAlias) { case string ea when ea.InvariantEquals("Umbraco.DropDown"): @@ -85,29 +82,25 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 break; case string ea when ea.InvariantEquals("Umbraco.DropdownlistPublishingKeys"): UpdateDataType(dataType, config, false); - requiresCacheRebuild = true; break; case string ea when ea.InvariantEquals("Umbraco.DropDownMultiple"): UpdateDataType(dataType, config, true); break; case string ea when ea.InvariantEquals("Umbraco.DropdownlistMultiplePublishKeys"): UpdateDataType(dataType, config, true); - requiresCacheRebuild = true; break; } - if (requiresCacheRebuild) - { - var dataTypeCacheRefresher = _cacheRefreshers[Guid.Parse("35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2")]; - _serverMessenger.PerformRefreshAll(dataTypeCacheRefresher); - } + refreshCache = true; } + + return refreshCache; } private void UpdateDataType(DataTypeDto dataType, ValueListConfiguration config, bool isMultiple) { - dataType.EditorAlias = Constants.PropertyEditors.Aliases.DropDownListFlexible; dataType.DbType = ValueStorageType.Nvarchar.ToString(); + dataType.EditorAlias = Constants.PropertyEditors.Aliases.DropDownListFlexible; var flexConfig = new DropDownFlexibleConfiguration { @@ -118,68 +111,5 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Database.Update(dataType); } - - private bool UpdatePropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) - { - //Get the INT ids stored for this property/drop down - int[] ids = null; - if (!propData.VarcharValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.VarcharValue); - } - else if (!propData.TextValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.TextValue); - } - else if (propData.IntegerValue.HasValue) - { - ids = new[] { propData.IntegerValue.Value }; - } - - //if there are INT ids, convert them to values based on the configured pre-values - if (ids != null && ids.Length > 0) - { - //map the ids to values - var vals = new List(); - var canConvert = true; - foreach (var id in ids) - { - var val = config.Items.FirstOrDefault(x => x.Id == id); - if (val != null) - vals.Add(val.Value); - else - { - Logger.Warn( - "Could not find associated data type configuration for stored Id {DataTypeId}", id); - canConvert = false; - } - } - if (canConvert) - { - propData.VarcharValue = string.Join(",", vals); - propData.TextValue = null; - propData.IntegerValue = null; - return true; - } - } - - return false; - } - - private int[] ConvertStringValues(string val) - { - var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - var intVals = splitVals - .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) - .Where(x => x != int.MinValue) - .ToArray(); - - //only return if the number of values are the same (i.e. All INTs) - if (splitVals.Length == intVals.Length) - return intVals; - - return null; - } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs index 26668361bd..def6a93400 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropMigrationsTable.cs @@ -8,7 +8,8 @@ public override void Migrate() { - Delete.Table("umbracoMigration").Do(); + if (TableExists("umbracoMigration")) + Delete.Table("umbracoMigration").Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs index fa6e47fac7..918510d13c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DropPreValueTable.cs @@ -3,8 +3,7 @@ public class DropPreValueTable : MigrationBase { public DropPreValueTable(IMigrationContext context) : base(context) - { - } + { } public override void Migrate() { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLanguageIsoCodeLength.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLanguageIsoCodeLength.cs new file mode 100644 index 0000000000..8de06c9a6f --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLanguageIsoCodeLength.cs @@ -0,0 +1,21 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class FixLanguageIsoCodeLength : MigrationBase + { + public FixLanguageIsoCodeLength(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // there is some confusion here when upgrading from v7 + // it should be 14 already but that's not always the case + + Alter.Table("umbracoLanguage") + .AlterColumn("languageISOCode") + .AsString(14) + .Nullable() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs deleted file mode 100644 index fbb233927b..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/FixLockTablePrimaryKey.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class FixLockTablePrimaryKey : MigrationBase - { - public FixLockTablePrimaryKey(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - // at some point, the KeyValueService dropped the PK and failed to re-create it, - // so the PK is gone - make sure we have one, and create if needed - - var constraints = SqlSyntax.GetConstraintsPerTable(Database); - var exists = constraints.Any(x => x.Item2 == "PK_umbracoLock"); - - if (!exists) - Create.PrimaryKey("PK_umbracoLock").OnTable(Constants.DatabaseSchema.Tables.Lock).Column("id").Do(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs index 2e366c7c14..651c95e6bd 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeRedirectUrlVariant.cs @@ -11,19 +11,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { AddColumn("culture"); - - Delete.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl).Do(); - Create.Index("IX_umbracoRedirectUrl").OnTable(Constants.DatabaseSchema.Tables.RedirectUrl) - .OnColumn("urlHash") - .Ascending() - .OnColumn("contentKey") - .Ascending() - .OnColumn("culture") - .Ascending() - .OnColumn("createDateUtc") - .Ascending() - .WithOptions().Unique() - .Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs index 9ccd6d5e76..c898187884 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MakeTagsVariant.cs @@ -11,17 +11,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { AddColumn("languageId"); - - Delete.Index($"IX_{Constants.DatabaseSchema.Tables.Tag}").OnTable(Constants.DatabaseSchema.Tables.Tag).Do(); - Create.Index($"IX_{Constants.DatabaseSchema.Tables.Tag}").OnTable(Constants.DatabaseSchema.Tables.Tag) - .OnColumn("group") - .Ascending() - .OnColumn("tag") - .Ascending() - .OnColumn("languageId") - .Ascending() - .WithOptions().Unique() - .Do(); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs new file mode 100644 index 0000000000..605f8a9eed --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/PropertyEditorsMigrationBase.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public abstract class PropertyEditorsMigrationBase : MigrationBase + { + protected PropertyEditorsMigrationBase(IMigrationContext context) + : base(context) + { } + + internal List GetDataTypes(string editorAlias, bool strict = true) + { + var sql = Sql() + .Select() + .From(); + + sql = strict + ? sql.Where(x => x.EditorAlias == editorAlias) + : sql.Where(x => x.EditorAlias.Contains(editorAlias)); + + return Database.Fetch(sql); + } + + protected int[] ConvertStringValues(string val) + { + var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + var intVals = splitVals + .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) + .Where(x => x != int.MinValue) + .ToArray(); + + //only return if the number of values are the same (i.e. All INTs) + if (splitVals.Length == intVals.Length) + return intVals; + + return null; + } + + internal bool UpdatePropertyDataDto(PropertyDataDto propData, ValueListConfiguration config, bool isMultiple) + { + //Get the INT ids stored for this property/drop down + int[] ids = null; + if (!propData.VarcharValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.VarcharValue); + } + else if (!propData.TextValue.IsNullOrWhiteSpace()) + { + ids = ConvertStringValues(propData.TextValue); + } + else if (propData.IntegerValue.HasValue) + { + ids = new[] { propData.IntegerValue.Value }; + } + + // if there are INT ids, convert them to values based on the configuration + if (ids == null || ids.Length <= 0) return false; + + // map ids to values + var values = new List(); + var canConvert = true; + + foreach (var id in ids) + { + var val = config.Items.FirstOrDefault(x => x.Id == id); + if (val != null) + { + values.Add(val.Value); + continue; + } + + Logger.Warn(GetType(), "Could not find PropertyData {PropertyDataId} value '{PropertyValue}' in the datatype configuration: {Values}.", + propData.Id, id, string.Join(", ", config.Items.Select(x => x.Id + ":" + x.Value))); + canConvert = false; + } + + if (!canConvert) return false; + + propData.VarcharValue = isMultiple ? JsonConvert.SerializeObject(values) : values[0]; + propData.TextValue = null; + propData.IntegerValue = null; + return true; + } + + // dummy editor for deserialization + protected class ValueListConfigurationEditor : ConfigurationEditor + { } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs deleted file mode 100644 index 1944c8079f..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxAndDropdownPropertyEditorsMigration.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Umbraco.Core.Logging; -using Umbraco.Core.Migrations.PostMigrations; -using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class RadioAndCheckboxAndDropdownPropertyEditorsMigration : MigrationBase - { - public RadioAndCheckboxAndDropdownPropertyEditorsMigration(IMigrationContext context) - : base(context) - { - } - - public override void Migrate() - { - var refreshCache = false; - - refreshCache |= Migrate(Constants.PropertyEditors.Aliases.RadioButtonList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, true)); - refreshCache |= Migrate(Constants.PropertyEditors.Aliases.CheckBoxList, (dto, configuration) => UpdateRadioOrCheckboxPropertyDataDto(dto, configuration, false)); - refreshCache |= Migrate(Constants.PropertyEditors.Aliases.DropDownListFlexible, UpdateDropDownPropertyDataDto); - - if (refreshCache) - { - Context.AddPostMigration(); - } - } - - private bool Migrate(string editorAlias, Func updateRadioPropertyDataFunc) - { - var refreshCache = false; - var dataTypes = GetDataTypes(editorAlias); - - foreach (var dataType in dataTypes) - { - ValueListConfiguration config; - - if (dataType.Configuration.IsNullOrWhiteSpace()) - continue; - - // parse configuration, and update everything accordingly - try - { - config = (ValueListConfiguration) new ValueListConfigurationEditor().FromDatabase( - dataType.Configuration); - } - catch (Exception ex) - { - Logger.Error( - ex, - "Invalid property editor configuration detected: \"{Configuration}\", cannot convert editor, values will be cleared", - dataType.Configuration); - - continue; - } - - // get property data dtos - var propertyDataDtos = Database.Fetch(Sql() - .Select() - .From() - .InnerJoin() - .On((pt, pd) => pt.Id == pd.PropertyTypeId) - .InnerJoin() - .On((dt, pt) => dt.NodeId == pt.DataTypeId) - .Where(x => x.DataTypeId == dataType.NodeId)); - - // update dtos - var updatedDtos = propertyDataDtos.Where(x => updateRadioPropertyDataFunc(x, config)); - - // persist changes - foreach (var propertyDataDto in updatedDtos) Database.Update(propertyDataDto); - - UpdateDataType(dataType); - refreshCache = true; - } - - return refreshCache; - } - - private List GetDataTypes(string editorAlias) - { - //need to convert the old drop down data types to use the new one - var dataTypes = Database.Fetch(Sql() - .Select() - .From() - .Where(x => x.EditorAlias == editorAlias)); - return dataTypes; - } - - private void UpdateDataType(DataTypeDto dataType) - { - dataType.DbType = ValueStorageType.Nvarchar.ToString(); - Database.Update(dataType); - } - - private bool UpdateRadioOrCheckboxPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config, bool singleValue) - { - //Get the INT ids stored for this property/drop down - int[] ids = null; - if (!propData.VarcharValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.VarcharValue); - } - else if (!propData.TextValue.IsNullOrWhiteSpace()) - { - ids = ConvertStringValues(propData.TextValue); - } - else if (propData.IntegerValue.HasValue) - { - ids = new[] {propData.IntegerValue.Value}; - } - - //if there are INT ids, convert them to values based on the configuration - if (ids == null || ids.Length <= 0) return false; - - //map the ids to values - var values = new List(); - var canConvert = true; - - foreach (var id in ids) - { - var val = config.Items.FirstOrDefault(x => x.Id == id); - if (val != null) - values.Add(val.Value); - else - { - Logger.Warn( - "Could not find associated data type configuration for stored Id {DataTypeId}", id); - canConvert = false; - } - } - - if (!canConvert) return false; - - //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first - propData.VarcharValue = singleValue ? values[0] : JsonConvert.SerializeObject(values); - propData.TextValue = null; - propData.IntegerValue = null; - return true; - } - - private bool UpdateDropDownPropertyDataDto(PropertyDataDto propData, ValueListConfiguration config) - { - //Get the INT ids stored for this property/drop down - var values = propData.VarcharValue.Split(new []{","}, StringSplitOptions.RemoveEmptyEntries); - - //if there are INT ids, convert them to values based on the configuration - if (values == null || values.Length <= 0) return false; - - //The radio button only supports selecting a single value, so if there are multiple for some insane reason we can only use the first - propData.VarcharValue = JsonConvert.SerializeObject(values); - propData.TextValue = null; - propData.IntegerValue = null; - return true; - } - - private int[] ConvertStringValues(string val) - { - var splitVals = val.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - - var intVals = splitVals - .Select(x => int.TryParse(x, out var i) ? i : int.MinValue) - .Where(x => x != int.MinValue) - .ToArray(); - - //only return if the number of values are the same (i.e. All INTs) - if (splitVals.Length == intVals.Length) - return intVals; - - return null; - } - - private class ValueListConfigurationEditor : ConfigurationEditor - { - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxPropertyEditorsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxPropertyEditorsMigration.cs new file mode 100644 index 0000000000..e96fa1f7e9 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RadioAndCheckboxPropertyEditorsMigration.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Migrations.PostMigrations; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RadioAndCheckboxPropertyEditorsMigration : PropertyEditorsMigrationBase + { + public RadioAndCheckboxPropertyEditorsMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var refreshCache = false; + + refreshCache |= Migrate(GetDataTypes(Constants.PropertyEditors.Aliases.RadioButtonList), false); + refreshCache |= Migrate(GetDataTypes(Constants.PropertyEditors.Aliases.CheckBoxList), true); + + // if some data types have been updated directly in the database (editing DataTypeDto and/or PropertyDataDto), + // bypassing the services, then we need to rebuild the cache entirely, including the umbracoContentNu table + if (refreshCache) + Context.AddPostMigration(); + } + + private bool Migrate(IEnumerable dataTypes, bool isMultiple) + { + var refreshCache = false; + ConfigurationEditor configurationEditor = null; + + foreach (var dataType in dataTypes) + { + ValueListConfiguration config; + + if (dataType.Configuration.IsNullOrWhiteSpace()) + continue; + + // parse configuration, and update everything accordingly + if (configurationEditor == null) + configurationEditor = new ValueListConfigurationEditor(); + try + { + config = (ValueListConfiguration) configurationEditor.FromDatabase(dataType.Configuration); + } + catch (Exception ex) + { + Logger.Error( + ex, "Invalid configuration: \"{Configuration}\", cannot convert editor.", + dataType.Configuration); + + continue; + } + + // get property data dtos + var propertyDataDtos = Database.Fetch(Sql() + .Select() + .From() + .InnerJoin().On((pt, pd) => pt.Id == pd.PropertyTypeId) + .InnerJoin().On((dt, pt) => dt.NodeId == pt.DataTypeId) + .Where(x => x.DataTypeId == dataType.NodeId)); + + // update dtos + var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config, isMultiple)); + + // persist changes + foreach (var propertyDataDto in updatedDtos) + Database.Update(propertyDataDto); + + UpdateDataType(dataType); + refreshCache = true; + } + + return refreshCache; + } + + private void UpdateDataType(DataTypeDto dataType) + { + dataType.DbType = ValueStorageType.Nvarchar.ToString(); + Database.Update(dataType); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs deleted file mode 100644 index c683940f60..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorXmlColumns.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 -{ - public class RefactorXmlColumns : MigrationBase - { - public RefactorXmlColumns(IMigrationContext context) - : base(context) - { } - - public override void Migrate() - { - if (ColumnExists("cmsContentXml", "Rv") == false) - Alter.Table("cmsContentXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0).Do(); - - if (ColumnExists("cmsPreviewXml", "Rv") == false) - Alter.Table("cmsPreviewXml").AddColumn("Rv").AsInt64().NotNullable().WithDefaultValue(0).Do(); - - // remove the any PK_ and the FK_ to cmsContentVersion.VersionId - var constraints = SqlSyntax.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - var dups = new List(); - foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("PK_"))) - { - var keyName = c.Item3.ToLowerInvariant(); - if (dups.Contains(keyName)) - { - Logger.Warn("Duplicate constraint '{Constraint}'", c.Item3); - continue; - } - dups.Add(keyName); - Delete.PrimaryKey(c.Item3).FromTable(c.Item1).Do(); - } - foreach (var c in constraints.Where(x => x.Item1.InvariantEquals("cmsPreviewXml") && x.Item3.InvariantStartsWith("FK_cmsPreviewXml_cmsContentVersion"))) - { - Delete.ForeignKey().FromTable("cmsPreviewXml").ForeignColumn("VersionId") - .ToTable("cmsContentVersion").PrimaryColumn("VersionId") - .Do(); - } - - if (ColumnExists("cmsPreviewXml", "Timestamp")) - Delete.Column("Timestamp").FromTable("cmsPreviewXml").Do(); - - if (ColumnExists("cmsPreviewXml", "VersionId")) - { - RemoveDuplicates(); - Delete.Column("VersionId").FromTable("cmsPreviewXml").Do(); - } - - // re-create the primary key - Create.PrimaryKey("PK_cmsPreviewXml") - .OnTable("cmsPreviewXml") - .Columns(new[] { "nodeId" }) - .Do(); - } - - private void RemoveDuplicates() - { - const string sql = @"delete from cmsPreviewXml where versionId in ( -select cmsPreviewXml.versionId from cmsPreviewXml -join cmsDocument on cmsPreviewXml.versionId=cmsDocument.versionId -where cmsDocument.newest <> 1)"; - - Context.Database.Execute(sql); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs new file mode 100644 index 0000000000..b60923fcba --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameMediaVersionTable.cs @@ -0,0 +1,46 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class RenameMediaVersionTable : MigrationBase + { + public RenameMediaVersionTable(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Rename.Table("cmsMedia").To(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + + // that is not supported on SqlCE + //Rename.Column("versionId").OnTable(Constants.DatabaseSchema.Tables.MediaVersion).To("id").Do(); + + AddColumn("id", out var sqls); + + // SQLCE does not support UPDATE...FROM + var temp2 = Database.Fetch($@"SELECT v.versionId, v.id +FROM cmsContentVersion v +JOIN umbracoNode n on v.contentId=n.id +WHERE n.nodeObjectType='{Constants.ObjectTypes.Media}'"); + foreach (var t in temp2) + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET id={t.id} WHERE versionId='{t.versionId}'").Do(); + + foreach (var sql in sqls) + Execute.Sql(sql).Do(); + + AddColumn("path", out sqls); + + Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.MediaVersion} SET path=mediaPath").Do(); + + foreach (var sql in sqls) + Execute.Sql(sql).Do(); + + // we had to run sqls to get the NULL constraints, but we need to get rid of most + Delete.KeysAndIndexes(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + + Delete.Column("mediaPath").FromTable(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + Delete.Column("versionId").FromTable(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + Delete.Column("nodeId").FromTable(Constants.DatabaseSchema.Tables.MediaVersion).Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs index 9bbccf368c..0543b57fcc 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RenameUmbracoDomainsTable.cs @@ -2,7 +2,9 @@ { public class RenameUmbracoDomainsTable : MigrationBase { - public RenameUmbracoDomainsTable(IMigrationContext context) : base(context) { } + public RenameUmbracoDomainsTable(IMigrationContext context) + : base(context) + { } public override void Migrate() { diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs index ba29880e79..64ac20d175 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs @@ -11,20 +11,27 @@ var exists = Database.Fetch("select id from umbracoUser where id=-1;").Count > 0; if (exists) return; - Database.Execute("update umbracoUser set userLogin = userLogin + '__' where userId=0"); + Database.Execute("update umbracoUser set userLogin = userLogin + '__' where id=0"); Database.Execute("set identity_insert umbracoUser on;"); Database.Execute(@" - insert into umbracoUser select -1, - userDisabled, userNoConsole, userName, substring(userLogin, 1, len(userLogin) - 2), userPassword, passwordConfig, + insert into umbracoUser (id, + userDisabled, userNoConsole, userName, userLogin, userPassword, passwordConfig, + userEmail, userLanguage, securityStampToken, failedLoginAttempts, lastLockoutDate, + lastPasswordChangeDate, lastLoginDate, emailConfirmedDate, invitedDate, + createDate, updateDate, avatar, tourData) + select + -1 id, + userDisabled, userNoConsole, userName, substring(userLogin, 1, len(userLogin) - 2) userLogin, userPassword, passwordConfig, userEmail, userLanguage, securityStampToken, failedLoginAttempts, lastLockoutDate, lastPasswordChangeDate, lastLoginDate, emailConfirmedDate, invitedDate, - createDate, updateDate, avatar + createDate, updateDate, avatar, tourData from umbracoUser where id=0;"); Database.Execute("set identity_insert umbracoUser off;"); Database.Execute("update umbracoUser2UserGroup set userId=-1 where userId=0;"); Database.Execute("update umbracoNode set nodeUser=-1 where nodeUser=0;"); + Database.Execute("update umbracoUserLogin set userId=-1 where userId=0;"); Database.Execute($"update {Constants.DatabaseSchema.Tables.ContentVersion} set userId=-1 where userId=0;"); Database.Execute("delete from umbracoUser where id=0;"); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs index cd4de179bd..70dbe3d29e 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TablesForScheduledPublishing.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 Delete.Column("releaseDate").FromTable("umbracoDocument").Do(); Delete.Column("expireDate").FromTable("umbracoDocument").Do(); //add new table - Create.Table().Do(); + Create.Table(true).Do(); //migrate the schedule foreach(var s in schedules) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs index 5dc5e0b6fe..d6a5380e31 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/TagsMigration.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.Dtos; +using System.Linq; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -14,13 +15,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 AlterColumn(Constants.DatabaseSchema.Tables.Tag, "group"); AlterColumn(Constants.DatabaseSchema.Tables.Tag, "tag"); - //AddColumn(Constants.DatabaseSchema.Tables.Tag, "key"); - // kill unused parentId column - Delete.ForeignKey("FK_cmsTags_cmsTags").OnTable(Constants.DatabaseSchema.Tables.Tag).Do(); Delete.Column("ParentId").FromTable(Constants.DatabaseSchema.Tables.Tag).Do(); } } - - // fixes TagsMigration that... originally failed to properly drop the ParentId column } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs index 2e37c79632..7f7cf8474c 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdatePickerIntegerValuesToUdi.cs @@ -2,6 +2,7 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs index 68a68ec53e..f16c9cd761 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UserForeignKeys.cs @@ -1,6 +1,4 @@ -using NPoco; -using Umbraco.Core.Persistence.DatabaseAnnotations; -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -15,24 +13,19 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - //first allow NULL-able + // first allow NULL-able Alter.Table(ContentVersionCultureVariationDto.TableName).AlterColumn("availableUserId").AsInt32().Nullable().Do(); Alter.Table(ContentVersionDto.TableName).AlterColumn("userId").AsInt32().Nullable().Do(); Alter.Table(Constants.DatabaseSchema.Tables.Log).AlterColumn("userId").AsInt32().Nullable().Do(); Alter.Table(NodeDto.TableName).AlterColumn("nodeUser").AsInt32().Nullable().Do(); - //then we can update any non existing users to NULL + // then we can update any non existing users to NULL Execute.Sql($"UPDATE {ContentVersionCultureVariationDto.TableName} SET availableUserId = NULL WHERE availableUserId NOT IN (SELECT id FROM {UserDto.TableName})").Do(); Execute.Sql($"UPDATE {ContentVersionDto.TableName} SET userId = NULL WHERE userId NOT IN (SELECT id FROM {UserDto.TableName})").Do(); Execute.Sql($"UPDATE {Constants.DatabaseSchema.Tables.Log} SET userId = NULL WHERE userId NOT IN (SELECT id FROM {UserDto.TableName})").Do(); Execute.Sql($"UPDATE {NodeDto.TableName} SET nodeUser = NULL WHERE nodeUser NOT IN (SELECT id FROM {UserDto.TableName})").Do(); - //now NULL-able with FKs - Alter.Table(ContentVersionCultureVariationDto.TableName).AlterColumn("availableUserId").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); - Alter.Table(ContentVersionDto.TableName).AlterColumn("userId").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); - Alter.Table(Constants.DatabaseSchema.Tables.Log).AlterColumn("userId").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); - Alter.Table(NodeDto.TableName).AlterColumn("nodeUser").AsInt32().Nullable().ForeignKey(UserDto.TableName, "id").Do(); + // FKs will be created after migrations } - } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs index c193fdeb1f..8c60d30680 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/VariantsMigration.cs @@ -18,9 +18,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 public override void Migrate() { - // delete *all* keys and indexes - because of FKs - Delete.KeysAndIndexes().Do(); - MigratePropertyData(); MigrateContentAndPropertyTypes(); MigrateContent(); @@ -46,10 +43,6 @@ HAVING COUNT(v2.id) <> 1").Any()) Debugger.Break(); throw new Exception("Migration failed: missing or duplicate 'current' content versions."); } - - // re-create *all* keys and indexes - foreach (var x in DatabaseSchemaCreator.OrderedTables) - Create.KeysAndIndexes(x).Do(); } private void MigratePropertyData() @@ -220,8 +213,10 @@ WHERE versionId NOT IN (SELECT (versionId) FROM {PreTables.ContentVersion} WHERE Delete.Column("text").FromTable(PreTables.Document).Do(); Delete.Column("templateId").FromTable(PreTables.Document).Do(); Delete.Column("documentUser").FromTable(PreTables.Document).Do(); + Delete.DefaultConstraint().OnTable(PreTables.Document).OnColumn("updateDate").Do(); Delete.Column("updateDate").FromTable(PreTables.Document).Do(); Delete.Column("versionId").FromTable(PreTables.Document).Do(); + Delete.DefaultConstraint().OnTable(PreTables.Document).OnColumn("newest").Do(); Delete.Column("newest").FromTable(PreTables.Document).Do(); // add and populate edited column @@ -320,7 +315,7 @@ WHERE v1.propertyTypeId=v2.propertyTypeId AND v1.languageId=v2.languageId AND v1 public const string Tag = "cmsTags"; public const string TagRelationship = "cmsTagRelationship"; - + // ReSharper restore UnusedMember.Local } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs deleted file mode 100644 index 0ceb366e1c..0000000000 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ChangeNuCacheJsonFormat.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Core.Migrations.PostMigrations; - -namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 -{ - public class ChangeNuCacheJsonFormat : MigrationBase - { - public ChangeNuCacheJsonFormat(IMigrationContext context) : base(context) - { } - - public override void Migrate() - { - // nothing - just adding the post-migration - Context.AddPostMigration(); - } - } -} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs index 50c7daf65d..bf048bf2bd 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/ConvertTinyMceAndGridMediaUrlsToLocalLink.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Text.RegularExpressions; using Newtonsoft.Json; @@ -26,8 +26,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); var sqlPropertyData = Sql() - .Select(x => x.Id, x => x.TextValue) - .AndSelect(x => x.EditorAlias) + .Select(r => r.Select(x => x.PropertyTypeDto, r1 => r1.Select(x => x.DataTypeDto))) .From() .InnerJoin().On((left, right) => left.PropertyTypeId == right.Id) .InnerJoin().On((left, right) => left.DataTypeId == right.NodeId) @@ -63,7 +62,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 property.TextValue = UpdateMediaUrls(mediaLinkPattern, value); } - Database.Update(property, x => x.TextValue); + Database.Update(property); } Context.AddPostMigration(); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/FixContentNuCascade.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/FixContentNuCascade.cs new file mode 100644 index 0000000000..09ea941742 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/FixContentNuCascade.cs @@ -0,0 +1,17 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class FixContentNuCascade : MigrationBase + { + public FixContentNuCascade(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + Delete.KeysAndIndexes().Do(); + Create.KeysAndIndexes().Do(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/RenameUserLoginDtoDateIndex.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/RenameUserLoginDtoDateIndex.cs new file mode 100644 index 0000000000..c0b9c8f2db --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/RenameUserLoginDtoDateIndex.cs @@ -0,0 +1,36 @@ +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class RenameUserLoginDtoDateIndex : MigrationBase + { + public RenameUserLoginDtoDateIndex(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // there has been some confusion with an index name, resulting in + // different names depending on which migration path was followed, + // and discrepancies between an upgraded or an installed database. + // better normalize + + if (IndexExists("IX_umbracoUserLogin_lastValidatedUtc")) + return; + + if (IndexExists("IX_userLoginDto_lastValidatedUtc")) + Delete + .Index("IX_userLoginDto_lastValidatedUtc") + .OnTable(UserLoginDto.TableName) + .Do(); + + Create + .Index("IX_umbracoUserLogin_lastValidatedUtc") + .OnTable(UserLoginDto.TableName) + .OnColumn("lastValidatedUtc") + .Ascending() + .WithOptions().NonClustered() + .Do(); + } + } +} diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index 8745e6dbec..c237f6381c 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -48,7 +48,16 @@ namespace Umbraco.Core.Models var configuration = Configuration; var json = JsonConvert.SerializeObject(configuration); _editor = value; - Configuration = _editor.GetConfigurationEditor().FromDatabase(json); + + try + { + Configuration = _editor.GetConfigurationEditor().FromDatabase(json); + } + catch (Exception e) + { + throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); + } } } @@ -76,7 +85,16 @@ namespace Umbraco.Core.Models if (_hasConfiguration) return _configuration; - _configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson); + try + { + _configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson); + } + catch (Exception e) + { + throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); + } + _hasConfiguration = true; _configurationJson = null; @@ -158,7 +176,18 @@ namespace Umbraco.Core.Models // else, create a Lazy de-serializer var capturedConfiguration = _configurationJson; var capturedEditor = _editor; - return new Lazy(() => capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration)); + return new Lazy(() => + { + try + { + return capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration); + } + catch (Exception e) + { + throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); + } + }); } } } diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 2fb293c349..3d071b0a18 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -54,7 +54,6 @@ namespace Umbraco.Core.Models.Membership _isLockedOut = false; _startContentIds = new int[] { }; _startMediaIds = new int[] { }; - } /// diff --git a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs index fceb32d609..2137b2ff44 100644 --- a/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs +++ b/src/Umbraco.Core/Persistence/DatabaseAnnotations/ForeignKeyAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Data; namespace Umbraco.Core.Persistence.DatabaseAnnotations { @@ -9,11 +10,17 @@ namespace Umbraco.Core.Persistence.DatabaseAnnotations public class ForeignKeyAttribute : ReferencesAttribute { public ForeignKeyAttribute(Type type) : base(type) - { - } + { } - internal string OnDelete { get; set; } - internal string OnUpdate { get; set; } + /// + /// Gets or sets the cascade rule for deletions. + /// + public Rule OnDelete { get; set; } = Rule.None; + + /// + /// Gets or sets the cascade rule for updates. + /// + public Rule OnUpdate { get; set; } = Rule.None; /// /// Gets or sets the name of the foreign key reference diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index e2d3ac97ae..5925e58afc 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -134,7 +134,9 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions { Name = foreignKeyName, ForeignTable = tableName, - PrimaryTable = referencedTable.Value + PrimaryTable = referencedTable.Value, + OnDelete = attribute.OnDelete, + OnUpdate = attribute.OnUpdate }; definition.ForeignColumns.Add(columnName); definition.PrimaryColumns.Add(referencedColumn); diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs index eea3d5d537..c6269d5317 100644 --- a/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/ContentNuDto.cs @@ -1,4 +1,5 @@ -using NPoco; +using System.Data; +using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; namespace Umbraco.Core.Persistence.Dtos @@ -10,7 +11,7 @@ namespace Umbraco.Core.Persistence.Dtos { [Column("nodeId")] [PrimaryKeyColumn(AutoIncrement = false, Name = "PK_cmsContentNu", OnColumns = "nodeId, published")] - [ForeignKey(typeof(ContentDto), Column = "nodeId")] + [ForeignKey(typeof(ContentDto), Column = "nodeId", OnDelete = Rule.Cascade)] public int NodeId { get; set; } [Column("published")] diff --git a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs index d7d02631b7..8bf254ea31 100644 --- a/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/UserLoginDto.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Persistence.Dtos /// [Column("lastValidatedUtc")] [NullSetting(NullSetting = NullSettings.NotNull)] - [Index(IndexTypes.NonClustered, Name = "IX_userLoginDto_lastValidatedUtc")] + [Index(IndexTypes.NonClustered, Name = "IX_umbracoUserLogin_lastValidatedUtc")] public DateTime LastValidatedUtc { get; set; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index f5144e34eb..f189d38d05 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -15,7 +15,8 @@ namespace Umbraco.Core.Persistence.Factories { if (!editors.TryGet(dto.EditorAlias, out var editor)) { - logger.Warn(typeof(DataTypeFactory), "Could not find an editor with alias {EditorAlias}, converting to label", dto.EditorAlias); + logger.Warn(typeof(DataType), "Could not find an editor with alias {EditorAlias}, treating as Label." + +" The site may fail to boot and / or load data types and run.", dto.EditorAlias); //convert to label editor = new LabelPropertyEditor(logger); } diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index dae70d502f..c64eb14bb7 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -36,6 +36,13 @@ namespace Umbraco.Core.Persistence.Factories user.InvitedDate = dto.InvitedDate; user.TourData = dto.TourData; + // we should never get user with ID zero from database, except + // when upgrading from v7 - mark that user so that we do not + // save it back to database (as that would create a *new* user) + // see also: UserRepository.PersistNewItem + if (dto.Id == 0) + user.AdditionalData["IS_V7_ZERO"] = true; + // reset dirty initial properties (U4-1946) user.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 9027e9269c..91a20c5bdd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -434,6 +434,17 @@ ORDER BY colName"; protected override void PersistNewItem(IUser entity) { + // the use may have no identity, ie ID is zero, and be v7 super + // user - then it has been marked - and we must not persist it + // as new, as we do not want to create a new user - instead, persist + // it as updated + // see also: UserFactory.BuildEntity + if (((User) entity).AdditionalData.ContainsKey("IS_V7_ZERO")) + { + PersistUpdatedItem(entity); + return; + } + ((User) entity).AddingEntity(); // ensure security stamp if missing diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index c352e312ac..55625ff04e 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -44,7 +44,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax string TruncateTable { get; } string CreateConstraint { get; } string DeleteConstraint { get; } - + string DeleteDefaultConstraint { get; } string FormatDateTime(DateTime date, bool includeTime = true); string Format(TableDefinition table); @@ -106,5 +106,20 @@ namespace Umbraco.Core.Persistence.SqlSyntax /// A Tuple containing: TableName, IndexName, ColumnName, IsUnique /// IEnumerable> GetDefinedIndexes(IDatabase db); + + /// + /// Tries to gets the name of the default constraint on a column. + /// + /// The database. + /// The table name. + /// The column name. + /// The constraint name. + /// A value indicating whether a default constraint was found. + /// + /// Some database engines (e.g. SqlCe) may not have names for default constraints, + /// in which case the function may return true, but is + /// unspecified. + /// + bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName); } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 8f39e36fb9..cb4b7a5176 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -132,6 +132,17 @@ ORDER BY TABLE_NAME, INDEX_NAME"); item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE)); } + /// + public override bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName) + { + // cannot return a true default constraint name (does not exist on SqlCe) + // but we won't really need it anyways - just check whether there is a constraint + constraintName = null; + var hasDefault = db.Fetch(@"select column_hasdefault from information_schema.columns +where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault(); + return hasDefault; + } + public override bool DoesTableExist(IDatabase db, string tableName) { var result = @@ -175,7 +186,7 @@ ORDER BY TABLE_NAME, INDEX_NAME"); { get { - return "ALTER TABLE [{0}] ALTER COLUMN [{1}] DROP DEFAULT"; + return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; } } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index e51aa547b8..fab7526a6b 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -225,6 +225,18 @@ order by T.name, I.name"); } + /// + public override bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName) + { + constraintName = db.Fetch(@"select con.[name] as [constraintName] +from sys.default_constraints con +join sys.columns col on con.object_id=col.default_object_id +join sys.tables tbl on col.object_id=tbl.object_id +where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName) + .FirstOrDefault(); + return !constraintName.IsNullOrWhiteSpace(); + } + public override bool DoesTableExist(IDatabase db, string tableName) { var result = @@ -276,7 +288,7 @@ order by T.name, I.name"); return null; } - public override string DeleteDefaultConstraint => "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; + public override string DeleteDefaultConstraint => "ALTER TABLE {0} DROP CONSTRAINT {2}"; public override string DropIndex => "DROP INDEX {0} ON {1}"; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 5b6a9afb04..0c27ac2d50 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -223,6 +223,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax public abstract IEnumerable> GetDefinedIndexes(IDatabase db); + public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName); + public virtual bool DoesTableExist(IDatabase db, string tableName) { return false; @@ -552,6 +554,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public virtual string CreateConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} {2} ({3})"; public virtual string DeleteConstraint => "ALTER TABLE {0} DROP CONSTRAINT {1}"; public virtual string CreateForeignKeyConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} FOREIGN KEY ({2}) REFERENCES {3} ({4}){5}{6}"; + public virtual string CreateDefaultConstraint => "ALTER TABLE {0} ADD CONSTRAINT {1} DEFAULT ({2}) FOR {3}"; public virtual string ConvertIntegerToOrderableString => "REPLACE(STR({0}, 8), SPACE(1), '0')"; public virtual string ConvertDateToOrderableString => "CONVERT(nvarchar, {0}, 102)"; diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 9d1193ac82..1654bb3f20 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -113,7 +113,7 @@ namespace Umbraco.Core.PropertyEditors } catch (Exception e) { - throw new Exception($"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", e); + throw new InvalidOperationException($"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", e); } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index bd77daffbe..5cc1a584b1 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -757,6 +757,11 @@ namespace Umbraco.Core.Services.Implement if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) throw new InvalidOperationException("Cannot save (un)publishing content, use the dedicated SavePublished method."); + if (content.Name != null && content.Name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } + var evtMsgs = EventMessagesFactory.Get(); using (var scope = ScopeProvider.CreateScope()) @@ -870,6 +875,11 @@ namespace Umbraco.Core.Services.Implement throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); } + if(content.Name != null && content.Name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); @@ -900,6 +910,11 @@ namespace Umbraco.Core.Services.Implement if (content == null) throw new ArgumentNullException(nameof(content)); if (cultures == null) throw new ArgumentNullException(nameof(cultures)); + if (content.Name != null && content.Name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } + using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.ContentTree); diff --git a/src/Umbraco.Core/Services/Implement/KeyValueService.cs b/src/Umbraco.Core/Services/Implement/KeyValueService.cs index eb68cea25c..b3f3f2468d 100644 --- a/src/Umbraco.Core/Services/Implement/KeyValueService.cs +++ b/src/Umbraco.Core/Services/Implement/KeyValueService.cs @@ -28,7 +28,6 @@ namespace Umbraco.Core.Services.Implement { if (_initialized) return; Initialize(); - _initialized = true; } } @@ -41,7 +40,10 @@ namespace Umbraco.Core.Services.Implement // then everything should be ok (the table should exist, etc) if (UmbracoVersion.LocalVersion != null && UmbracoVersion.LocalVersion.Major >= 8) + { + _initialized = true; return; + } // else we are upgrading from 7, we can assume that the locks table // exists, but we need to create everything for key/value @@ -53,6 +55,10 @@ namespace Umbraco.Core.Services.Implement initMigration.Migrate(); scope.Complete(); } + + // but don't assume we are initializing + // we are upgrading from v7 and if anything goes wrong, + // the table and everything will be rolled back } /// diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 51fdb7e8e3..ce5b3ef8c4 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -83,10 +83,10 @@ namespace Umbraco.Core.Services internal static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, string text) { var cultureDictionary = CultureDictionary; - return UmbracoDictionaryTranslate(text, cultureDictionary); + return manager.UmbracoDictionaryTranslate(text, cultureDictionary); } - private static string UmbracoDictionaryTranslate(string text, ICultureDictionary cultureDictionary) + private static string UmbracoDictionaryTranslate(this ILocalizedTextService manager, string text, ICultureDictionary cultureDictionary) { if (text == null) return null; @@ -95,7 +95,14 @@ namespace Umbraco.Core.Services return text; text = text.Substring(1); - return cultureDictionary[text].IfNullOrWhiteSpace(text); + var value = cultureDictionary[text]; + if (value.IsNullOrWhiteSpace() == false) + { + return value; + } + + value = manager.Localize(text.Replace('_', '/')); + return value.StartsWith("[") ? text : value; } private static ICultureDictionary CultureDictionary diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2acffe7afa..ae1c643b2b 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -219,13 +219,35 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -389,23 +411,6 @@ - - - - - - - - - - - - - - - - - @@ -418,14 +423,13 @@ - - + @@ -435,6 +439,7 @@ + @@ -707,7 +712,6 @@ - @@ -1141,30 +1145,7 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs index 5ff0dcffc1..c13d141fa5 100644 --- a/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs +++ b/src/Umbraco.Tests/Migrations/AdvancedMigrationTests.cs @@ -217,7 +217,11 @@ namespace Umbraco.Tests.Migrations //Execute.DropKeysAndIndexes("umbracoUser"); // drops *all* tables keys and indexes - Delete.KeysAndIndexes().Do(); + var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToList(); + foreach (var table in tables) + Delete.KeysAndIndexes(table, false, true).Do(); + foreach (var table in tables) + Delete.KeysAndIndexes(table, true, false).Do(); } } @@ -262,7 +266,7 @@ namespace Umbraco.Tests.Migrations public override void Migrate() { // cannot delete the column without this, of course - Delete.KeysAndIndexes().Do(); + Delete.KeysAndIndexes("umbracoUser").Do(); Delete.Column("id").FromTable("umbracoUser").Do(); diff --git a/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs b/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs index add278f9eb..ab065eb0a9 100644 --- a/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationPlanTests.cs @@ -119,7 +119,7 @@ namespace Umbraco.Tests.Migrations .To("bbb") .From("ccc") .To("ddd"); - Assert.Throws(() => plan.Validate()); + Assert.Throws(() => plan.Validate()); } [Test] @@ -131,7 +131,7 @@ namespace Umbraco.Tests.Migrations .To("bbb") .To("ccc") .To("aaa"); - Assert.Throws(() => plan.Validate()); + Assert.Throws(() => plan.Validate()); } [Test] diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less index f17e8cadb4..2c01fb771c 100644 --- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less +++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/scaffolding.less @@ -28,6 +28,11 @@ a:focus { color: @linkColorHover; text-decoration: underline; } +a[ng-click], +a[data-ng-click], +a[x-ng-click] { + cursor: pointer; +} // Images diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index 14c9878839..b8ee797c82 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -42,7 +42,7 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se function calculateWidth() { $timeout(function () { //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width() - 200; + var windowWidth = $(window).width() - 150; var sectionsWidth = 0; scope.totalSections = scope.sections.length; scope.maxSections = maxSections; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index f7704ab870..3a874f83c6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -67,16 +67,12 @@ //We fetch all ancestors of the node to generate the footer breadcrumb navigation if (!$scope.page.isNew) { if (content.parentId && content.parentId !== -1) { - entityResource.getAncestors(content.id, "document", $scope.culture) - .then(function (anc) { - $scope.ancestors = anc; - }); + loadBreadcrumb(); $scope.$watch('culture', function (value, oldValue) { - entityResource.getAncestors(content.id, "document", value) - .then(function (anc) { - $scope.ancestors = anc; - }); + if (value !== oldValue) { + loadBreadcrumb(); + } }); } } @@ -86,6 +82,12 @@ resetVariantFlags(); } + function loadBreadcrumb() { + entityResource.getAncestors($scope.content.id, "document", $scope.culture) + .then(function (anc) { + $scope.ancestors = anc; + }); + } /** * This will reset isDirty flags if save is true. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 77f2ffb54a..eb5fd055eb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -101,14 +101,8 @@ angular.module('umbraco.directives') var eventBindings = []; function oneTimeClick(event) { - var el = event.target.nodeName; - - //ignore link and button clicks - var els = ["INPUT","A","BUTTON"]; - if(els.indexOf(el) >= 0){return;} - // ignore clicks on new overlay - var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour"); + var parents = $(event.target).parents(".umb-overlay,.umb-tour"); if(parents.length > 0){ return; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index da51528bd2..396699866c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -3,7 +3,7 @@ function GroupsBuilderDirective(contentTypeHelper, contentTypeResource, mediaTypeResource, dataTypeHelper, dataTypeResource, $filter, iconHelper, $q, $timeout, notificationsService, - localizationService, editorService, eventsService) { + localizationService, editorService, eventsService, overlayService) { function link(scope, el, attr, ctrl) { @@ -474,10 +474,11 @@ if (!property.inherited) { var oldPropertyModel = angular.copy(property); + var propertyModel = angular.copy(property); var propertySettings = { title: "Property settings", - property: property, + property: propertyModel, contentType: scope.contentType, contentTypeName: scope.model.name, contentTypeAllowCultureVariant: scope.model.allowCultureVariant, @@ -487,7 +488,25 @@ property.inherited = false; property.dialogIsOpen = false; - + property.propertyState = "active"; + + // apply all property changes + property.label = propertyModel.label; + property.alias = propertyModel.alias; + property.description = propertyModel.description; + property.config = propertyModel.config; + property.editor = propertyModel.editor; + property.view = propertyModel.view; + property.dataTypeId = propertyModel.dataTypeId; + property.dataTypeIcon = propertyModel.dataTypeIcon; + property.dataTypeName = propertyModel.dataTypeName; + property.validation.mandatory = propertyModel.validation.mandatory; + property.validation.pattern = propertyModel.validation.pattern; + property.showOnMemberProfile = propertyModel.showOnMemberProfile; + property.memberCanEdit = propertyModel.memberCanEdit; + property.isSensitiveValue = propertyModel.isSensitiveValue; + property.allowCultureVariant = propertyModel.allowCultureVariant; + // update existing data types if(model.updateSameDataTypes) { updateSameDataTypes(property); @@ -508,43 +527,38 @@ }, close: function() { + if(_.isEqual(oldPropertyModel, propertyModel) === false) { + localizationService.localizeMany(["general_confirm", "contentTypeEditor_propertyHasChanges", "general_cancel", "general_ok"]).then(function (data) { + const overlay = { + title: data[0], + content: data[1], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + close: function () { + overlayService.close(); + }, + submit: function () { + // close the confirmation + overlayService.close(); + // close the editor + editorService.close(); + } + }; - // reset all property changes - property.label = oldPropertyModel.label; - property.alias = oldPropertyModel.alias; - property.description = oldPropertyModel.description; - property.config = oldPropertyModel.config; - property.editor = oldPropertyModel.editor; - property.view = oldPropertyModel.view; - property.dataTypeId = oldPropertyModel.dataTypeId; - property.dataTypeIcon = oldPropertyModel.dataTypeIcon; - property.dataTypeName = oldPropertyModel.dataTypeName; - property.validation.mandatory = oldPropertyModel.validation.mandatory; - property.validation.pattern = oldPropertyModel.validation.pattern; - property.showOnMemberProfile = oldPropertyModel.showOnMemberProfile; - property.memberCanEdit = oldPropertyModel.memberCanEdit; - property.isSensitiveValue = oldPropertyModel.isSensitiveValue; - - // because we set state to active, to show a preview, we have to check if has been filled out - // label is required so if it is not filled we know it is a placeholder - if(oldPropertyModel.editor === undefined || oldPropertyModel.editor === null || oldPropertyModel.editor === "") { - property.propertyState = "init"; - } else { - property.propertyState = oldPropertyModel.propertyState; + overlayService.open(overlay); + }); + } + else { + // remove the editor + editorService.close(); } - - // remove the editor - editorService.close(); - } }; // open property settings editor editorService.open(propertySettings); - // set state to active to access the preview - property.propertyState = "active"; - // set property states property.dialogIsOpen = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js index 190c504aa6..2c52506f42 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/util/noPasswordManager.directive.js @@ -1,6 +1,6 @@ /** * @ngdoc directive -* @name umbraco.directives.directive:no-password-manager +* @name umbraco.directives.directive:noPasswordManager * @attribte * @function * @description diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 1efcf84dcb..1b2be5f635 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -197,6 +197,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica letter: ch, labelKey: "buttons_schedulePublish", handler: args.methods.schedulePublish, + hotKey: "alt+shift+s", + hotKeyWhenHidden: true, alias: "schedulePublish", addEllipsis: "true" }; @@ -207,6 +209,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica letter: ch, labelKey: "buttons_publishDescendants", handler: args.methods.publishDescendants, + hotKey: "alt+shift+p", + hotKeyWhenHidden: true, alias: "publishDescendant", addEllipsis: "true" }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js index 57c86cba90..7856714ccd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/menuactions.service.js @@ -71,7 +71,9 @@ function umbracoMenuActions(treeService, $location, navigationService, appState, if (treeRoot && treeRoot.root) { var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias); if (treeNode) { - treeService.loadNodeChildren({ node: treeNode, section: args.section }); + treeService.loadNodeChildren({ node: treeNode, section: args.section }).then(function () { + navigationService.hideMenu(); + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 61f96fed28..555e276485 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -360,7 +360,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s icon: "code", tooltip: "View Source Code", onclick: function(){ - callback(); + if (callback) { + angularHelper.safeApply($rootScope, function() { + callback(); + }); + } } }); diff --git a/src/Umbraco.Web.UI.Client/src/less/application/animations.less b/src/Umbraco.Web.UI.Client/src/less/application/animations.less index 91e2213775..e081c4919a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/animations.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/animations.less @@ -40,10 +40,6 @@ // TREE ANIMATION -.umb-tree-item.ng-animate { - display: none; -} - .umb-tree-item--deleted.ng-leave { animation: leave 600ms cubic-bezier(0.445, 0.050, 0.550, 0.950); display: block; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less index 1d9aaabd44..18e331756e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-backdrop.less @@ -2,7 +2,7 @@ height: 100%; width: 100%; position: fixed; - z-index: @zindexOverlayBackdrop; + z-index: @zindexUmbOverlay; top: 0; left: 0; pointer-events: none; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less index a8fc9c7f8e..70e4f3d372 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less @@ -4,7 +4,7 @@ */ .umb-search { position: fixed; - z-index: @zindexUmbOverlay; + z-index: @zindexSearchBox; width: 660px; max-width: 90%; transform: translate(-50%, 0); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less index 0dbc4c381c..9947c793c2 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/prevalues/multivalues.less @@ -1,5 +1,5 @@ .umb-prevalues-multivalues { - width: 400px; + width: 425px; max-width: 100%; .umb-overlay & { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less index 1f0808a592..f972633bf4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-confirm-action.less @@ -102,6 +102,7 @@ border-radius: 40px; box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); font-size: 18px; + cursor: pointer; } .umb_confirm-action__overlay-action:hover { diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 966fc1fb11..cfbb8b78ab 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -809,6 +809,7 @@ legend + .control-group { // Labels on own row .form-horizontal .control-label { + float:none; width: 100%; } .form-horizontal .controls { diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 726b3fa5ed..0b6d1b7a60 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -221,3 +221,12 @@ pre { border: 0; } } + +/* Styling for content/media sort order dialog */ +.sort-order { + td.tree-icon { + font-size:20px; + width:20px; + padding-right:0; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 435b64ad34..9e8dd37ab9 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -165,6 +165,8 @@ .sp-replacer { display: inline-flex; margin-right: 18px; + border: solid 1px @gray-8; + border-radius: 3px; } label { diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index ccf52acc53..a13dbaeb43 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -5,10 +5,6 @@ position: relative; .umb-property-editor--limit-width(); - - .-loading { - position: absolute; - } } .umb-rte .mce-tinymce { diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 5a1de02617..ef6c5f5046 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -57,16 +57,17 @@ ul.sections>li.current>a::after { opacity: 1; transform: translateY(0px); } -ul.sections>li.current>a .section__name, -ul.sections>li>a:hover .section__name, -ul.sections>li>a:focus .section__name { - opacity: 1; +ul.sections > li.current > a .section__name, +ul.sections > li > a:hover .section__name { + opacity: 1; -webkit-font-smoothing: subpixel-antialiased; } -ul.sections>li>a:focus .section__name { +ul.sections > li > a:focus .section__name { .tabbing-active & { - box-shadow: 0 0 2px @pinkLight, inset 0 0 2px 1px @pinkLight; + + border: 1px solid; + border-color: @gray-9; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index cc9dced7df..a1dc0ba187 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -426,7 +426,7 @@ @zindexFixedNavbar: 1030; @zindexModalBackdrop: 1040; @zindexModal: 1050; - +@zindexSearchBox: 8000; @zindexUmbOverlay: 7500; @zindexOverlayBackdrop: 2000; diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 001888f3ca..e2a2cfe938 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -39,10 +39,14 @@ app.config(function ($routeProvider) { $route.current.params.section = "content"; } + var found = _.find(user.allowedSections, function (s) { + return s.localeCompare($route.current.params.section, undefined, { sensitivity: 'accent' }) === 0; + }) + // U4-5430, Benjamin Howarth // We need to change the current route params if the user only has access to a single section // To do this we need to grab the current user's allowed sections, then reject the promise with the correct path. - if (user.allowedSections.indexOf($route.current.params.section) > -1) { + if (found) { //this will resolve successfully so the route will continue return $q.when(true); } else { @@ -119,7 +123,7 @@ app.config(function ($routeProvider) { sectionService.getSectionsForUser().then(function(sections) { //find the one we're requesting var found = _.find(sections, function(s) { - return s.alias === $routeParams.section; + return s.alias.localeCompare($routeParams.section, undefined, { sensitivity: 'accent' }) === 0; }) if (found && found.routePath) { //there's a custom route path so redirect diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js index 029f765985..fb93df28f9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js @@ -1,6 +1,6 @@ //used for the media picker dialog angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", - function ($scope, eventsService, entityResource, contentResource, mediaResource, mediaHelper, udiParser, userService, localizationService, tinyMceService, editorService) { + function ($scope, eventsService, entityResource, contentResource, mediaResource, mediaHelper, udiParser, userService, localizationService, tinyMceService, editorService, contentEditingHelper) { var vm = this; var dialogOptions = $scope.model; @@ -94,8 +94,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; contentResource.getById(id, options).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0].text; + handleContentTarget(resp); }); } } else if ($scope.model.target.url.length) { @@ -148,8 +147,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", options.ignoreUserStartNodes = dialogOptions.ignoreUserStartNodes; contentResource.getById(args.node.id, options).then(function (resp) { - $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties)); - $scope.model.target.url = resp.urls[0].text; + handleContentTarget(resp); }); } @@ -158,6 +156,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController", } } + function handleContentTarget(content) { + $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(contentEditingHelper.getAllProps(content.variants[0]))); + $scope.model.target.url = content.urls.filter(item => item.culture === $scope.currentNode.metaData.culture)[0].text; + } + function nodeExpandedHandler(args) { // open mini list view for list views if (args.node.metaData.isContainer) { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html index a14e930b8a..ac919d3e41 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html @@ -20,7 +20,7 @@
  • - + -
    -
    {{vm.errorMsg}}
    +
    +
    -
    @@ -169,12 +168,11 @@
    - +
    Forgotten password? diff --git a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html index 054681d7f1..6ae256f4ee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/buttons/umb-button-group.html @@ -16,14 +16,16 @@ add-ellipsis={{defaultButton.addEllipsis}}> - - + {{subButton.labelKey}} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html index d3bf14b58c..0f7a61fa01 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-content-header.html @@ -26,7 +26,7 @@ umb-auto-focus val-server-field="{{serverValidationNameField}}" required - autocomplete="off" /> + autocomplete="off" maxlength="255" /> diff --git a/src/Umbraco.Web.UI.Client/src/views/content/sort.html b/src/Umbraco.Web.UI.Client/src/views/content/sort.html index 789f7fe6b5..471f3de956 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/sort.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/sort.html @@ -1,6 +1,6 @@
    - - +
    - - - + + @@ -124,7 +124,7 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/views/media/sort.html b/src/Umbraco.Web.UI.Client/src/views/media/sort.html index 92632a3256..b893517f1c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/sort.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/sort.html @@ -1,6 +1,6 @@
    -
    + Timestamp   LevelMachineLevelMachine Message
    {{ log.Timestamp | date:'medium' }} {{ log.Level }} {{ log.Properties.MachineName.Value }}{{ log.RenderedMessage }}{{ log.RenderedMessage }}
    - - + -
    + @@ -37,22 +37,13 @@ - - - - - - - -
    {{ child.name }} {{ child.createDate }}{{ child.sortOrder }}
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 33cf24962f..4d695c97b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -157,6 +157,11 @@ function contentPickerController($scope, entityResource, editorState, iconHelper // pre-value config on to the dialog options angular.extend(dialogOptions, $scope.model.config); + // if we can't pick more than one item, explicitly disable multiPicker in the dialog options + if ($scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) === 1) { + dialogOptions.multiPicker = false; + } + // add the current filter (if any) as title for the filtered out nodes if ($scope.model.config.filter) { localizationService.localize("contentPicker_allowedItemTypes", [$scope.model.config.filter]).then(function (data) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js index 9ba8822843..a02215f452 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.controller.js @@ -29,12 +29,6 @@ vm.removeLayout = removeLayout; vm.openIconPicker = openIconPicker; - function activate() { - - - - } - function addLayout() { vm.focusLayoutName = false; @@ -62,13 +56,17 @@ $scope.model.value.splice($index, 1); } - function openIconPicker(layout) { + function openIconPicker(layout) { var iconPicker = { - submit: function(model) { - if (model.color) { - layout.icon = model.icon + " " + model.color; - } else { - layout.icon = model.icon; + icon: layout.icon.split(' ')[0], + color: layout.icon.split(' ')[1], + submit: function (model) { + if (model.icon) { + if (model.color) { + layout.icon = model.icon + " " + model.color; + } else { + layout.icon = model.icon; + } } vm.focusLayoutName = true; editorService.close(); @@ -80,8 +78,6 @@ editorService.iconPicker(iconPicker); } - activate(); - } angular.module("umbraco").controller("Umbraco.PrevalueEditors.ListViewLayoutsPreValsController", ListViewLayoutsPreValsController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 7a67e07e0c..4e8b35a276 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -95,8 +95,6 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop "eventsService", function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService) { - - var inited = false; var contentTypeAliases = []; _.each($scope.model.config.contentTypes, function (contentType) { @@ -114,6 +112,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop $scope.realCurrentNode = undefined; $scope.scaffolds = undefined; $scope.sorting = false; + $scope.inited = false; $scope.minItems = $scope.model.config.minItems || 0; $scope.maxItems = $scope.model.config.maxItems || 0; @@ -252,30 +251,45 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop $scope.getName = function (idx) { - var name = "Item " + (idx + 1); + var name = ""; if ($scope.model.value[idx]) { var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias); - if (contentType != null && contentType.nameExp) { - // Run the expression against the stored dictionary value, NOT the node object - var item = $scope.model.value[idx]; + if (contentType != null) { + // first try getting a name using the configured label template + if (contentType.nameExp) { + // Run the expression against the stored dictionary value, NOT the node object + var item = $scope.model.value[idx]; - // Add a temporary index property - item["$index"] = (idx + 1); + // Add a temporary index property + item["$index"] = (idx + 1); - var newName = contentType.nameExp(item); - if (newName && (newName = $.trim(newName))) { - name = newName; + var newName = contentType.nameExp(item); + if (newName && (newName = $.trim(newName))) { + name = newName; + } + + // Delete the index property as we don't want to persist it + delete item["$index"]; } - // Delete the index property as we don't want to persist it - delete item["$index"]; + // if we still do not have a name and we have multiple content types to choose from, use the content type name (same as is shown in the content type picker) + if (!name && $scope.scaffolds.length > 1) { + var scaffold = $scope.getScaffold(contentType.ncAlias); + if (scaffold) { + name = scaffold.contentTypeName; + } + } } } + if (!name) { + name = "Item " + (idx + 1); + } + // Update the nodes actual name value if ($scope.nodes[idx].name !== name) { $scope.nodes[idx].name = name; @@ -356,6 +370,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop newNode.key = String.CreateGuid(); $scope.nodes.push(newNode); + $scope.setDirty(); //updateModel();// done by setting current node... $scope.currentNode = newNode; @@ -449,7 +464,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop $scope.currentNode = $scope.nodes[0]; } - inited = true; + $scope.inited = true; checkAbilityToPasteContent(); } @@ -518,7 +533,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop function updateModel() { syncCurrentNode(); - if (inited) { + if ($scope.inited) { var newValues = []; for (var i = 0; i < $scope.nodes.length; i++) { newValues.push(convertNodeIntoNCEntry($scope.nodes[i])); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html index a8a77e2d53..1eb7311ca7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.html @@ -1,7 +1,10 @@ 
    - + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 3c34890fa9..3ed5f49b92 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -20,6 +20,13 @@ angular.module("umbraco") editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize; } + var width = editorConfig.dimensions ? parseInt(editorConfig.dimensions.width, 10) || null : null; + var height = editorConfig.dimensions ? parseInt(editorConfig.dimensions.height, 10) || null : null; + + $scope.containerWidth = editorConfig.mode === "distraction-free" ? (width ? width : "auto") : "auto"; + $scope.containerHeight = editorConfig.mode === "distraction-free" ? (height ? height : "auto") : "auto"; + $scope.containerOverflow = editorConfig.mode === "distraction-free" ? (height ? "auto" : "inherit") : "inherit"; + var promises = []; //queue file loading @@ -41,10 +48,16 @@ angular.module("umbraco") $q.all(promises).then(function (result) { var standardConfig = result[promises.length - 1]; - + + if (height !== null) { + standardConfig.plugins.splice(standardConfig.plugins.indexOf("autoresize"), 1); + } + //create a baseline Config to extend upon var baseLineConfigObj = { - maxImageSize: editorConfig.maxImageSize + maxImageSize: editorConfig.maxImageSize, + width: width, + height: height }; angular.extend(baseLineConfigObj, standardConfig); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html index 57e837c497..7dde17bb06 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.html @@ -1,6 +1,6 @@
    -
    Loading...
    + -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index ca6178fea9..0d083f95b9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -25,7 +25,7 @@
    - +
    -
    +
    @@ -84,7 +84,7 @@
    @@ -119,4 +119,9 @@
    + + + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html index 24f504be63..4cab09da60 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.html @@ -173,7 +173,7 @@ diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 79ac6bc641..9e6bdc5e57 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -1319,6 +1319,7 @@ Mange hilsner fra Umbraco robotten Er en Element-type En Element-type er tiltænkt brug i f.eks. Nested Content, ikke i indholdstræet Dette benyttes ikke for en Element-type + Du har lavet ændringer til denne egenskab. Er du sikker på at du vil kassere dem? Tilføj sprog diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index d00f428b09..189bd9f10b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -472,7 +472,7 @@ %0%' below
    You can add additional languages under the 'languages' in the menu on the left + Edit the different language versions for the dictionary item '%0%' below ]]>
    Culture Name Element type Is an Element type An Element type is meant to be used for instance in Nested Content, and not in the tree - This is not applicable for an Element type + This is not applicable for an Element type + You have made changes to this property. Are you sure you want to discard them? Add language @@ -2038,7 +2039,7 @@ To manage your website, simply open the Umbraco back office and start adding con A setting to create a header protecting against MIME sniffing vulnerabilities has been added to your web.config file. Strict-Transport-Security, also known as the HSTS-header, was found.]]> Strict-Transport-Security was not found.]]> - Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400; preload' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). + Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). The HSTS header has been added to your web.config file. X-XSS-Protection was found.]]> X-XSS-Protection was not found.]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 133901732a..6ce6f82ccc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -476,7 +476,7 @@ %0%' below
    You can add additional languages under the 'languages' in the menu on the left + Edit the different language versions for the dictionary item '%0%' below ]]>
    Culture Name Is an Element type An Element type is meant to be used for instance in Nested Content, and not in the tree This is not applicable for an Element type + You have made changes to this property. Are you sure you want to discard them? Add language @@ -2052,7 +2053,7 @@ To manage your website, simply open the Umbraco back office and start adding con A setting to create a header protecting against MIME sniffing vulnerabilities has been added to your web.config file. Strict-Transport-Security, also known as the HSTS-header, was found.]]> Strict-Transport-Security was not found.]]> - Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400; preload' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). + Adds the header 'Strict-Transport-Security' with the value 'max-age=10886400' to the httpProtocol/customHeaders section of web.config. Use this fix only if you will have your domains running with https for the next 18 weeks (minimum). The HSTS header has been added to your web.config file. X-XSS-Protection was found.]]> X-XSS-Protection was not found.]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml index 66cad5fd26..f8b31577e7 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/es.xml @@ -366,7 +366,7 @@ Selecciona snippet - Editar las diferentes versiones lingüísticas para la entrada en el diccionario '% 0%' debajo añadir otros idiomas en el menú de 'idiomas' en el menú de la izquierda + Editar las diferentes versiones lingüísticas para la entrada en el diccionario '% 0%' debajo %0%' ci-dessous.
    Vous pouvez ajouter d'autres langues depuis le menu ci-dessous "Langues". + Editez les différentes versions de langues pour l'élément de dictionaire '%0%' ci-dessous. ]]>
    Nom de Culture Une configuration a été ajoutée dans votre fichier web.config pour créer un header protégeant contre les vulnérabilités de MIME sniffing. Strict-Transport-Security, aussi connu sous le nom de HSTS-header, a été trouvé.]]> Strict-Transport-Security, aussi connu sous le nom de HSTS-header, n'a pas été trouvé.]]> - Ajoute l'en-tête 'Strict-Transport-Security' avec la valeur 'max-age=10886400; preload' à la section httpProtocol/customHeaders du fichier web.config. Utilisez cette correction uniquement si vos domaines vont fonctionner en https pour les 18 prochaines semaines (minimum). + Ajoute l'en-tête 'Strict-Transport-Security' avec la valeur 'max-age=10886400' à la section httpProtocol/customHeaders du fichier web.config. Utilisez cette correction uniquement si vos domaines vont fonctionner en https pour les 18 prochaines semaines (minimum). L'en-tête HSTS a été ajouté dans votre fichier web.config. X-XSS-Protection a été trouvé.]]> X-XSS-Protection n'a pas été trouvé.]]> diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml index 7698453b4a..5f138efc81 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/ru.xml @@ -743,7 +743,7 @@ Значение, добавляющее заголовок, препятствующий использованию MIME-уязвимостей, успешно добавлено в файл web.config. Strict-Transport-Security, известный также как HSTS-header, обнаружен.]]> Strict-Transport-Security не найден.]]> - Добавляет заголовок 'Strict-Transport-Security' и его значение 'max-age=10886400; preload' в секцию httpProtocol/customHeaders файла web.config. Применяйте этот способ только в случае, если доступ к Вашим сайтам будет осуществляться по протоколу https как минимум ближайшие 18 недель. + Добавляет заголовок 'Strict-Transport-Security' и его значение 'max-age=10886400' в секцию httpProtocol/customHeaders файла web.config. Применяйте этот способ только в случае, если доступ к Вашим сайтам будет осуществляться по протоколу https как минимум ближайшие 18 недель. Заголовок HSTS-header успешно добавлен в файл web.config. X-XSS-Protection обнаружен.]]> X-XSS-Protection не найден.]]> diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs index 9bb1889b68..4244d575af 100644 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs @@ -1,5 +1,6 @@ using System; using Umbraco.Core; +using Umbraco.Core.Compose; using Umbraco.Core.Composing; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -39,7 +40,7 @@ namespace Umbraco.Web.Compose private IUser GetPerformingUser(int userId) { var found = userId >= 0 ? _userService.GetUserById(userId) : null; - return found ?? new User { Id = 0, Name = "SYSTEM", Email = "" }; + return found ?? AuditEventsComponent.UnknownUser; } private static string FormatEmail(IMembershipUser user) diff --git a/src/Umbraco.Web/Editors/CurrentUserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs index 3c8aed247e..46151f2a9e 100644 --- a/src/Umbraco.Web/Editors/CurrentUserController.cs +++ b/src/Umbraco.Web/Editors/CurrentUserController.cs @@ -135,6 +135,8 @@ namespace Umbraco.Web.Editors //They've successfully set their password, we can now update their user account to be approved Security.CurrentUser.IsApproved = true; + //They've successfully set their password, and will now get fully logged into the back office, so the lastlogindate is set so the backoffice shows they have logged in + Security.CurrentUser.LastLoginDate = DateTime.UtcNow; Services.UserService.Save(Security.CurrentUser); //now we can return their full object since they are now really logged into the back office diff --git a/src/Umbraco.Web/Editors/DictionaryController.cs b/src/Umbraco.Web/Editors/DictionaryController.cs index 120ebeded7..d132fdc201 100644 --- a/src/Umbraco.Web/Editors/DictionaryController.cs +++ b/src/Umbraco.Web/Editors/DictionaryController.cs @@ -52,6 +52,13 @@ namespace Umbraco.Web.Editors if (foundDictionary == null) throw new HttpResponseException(HttpStatusCode.NotFound); + var foundDictionaryDescendants = Services.LocalizationService.GetDictionaryItemDescendants(foundDictionary.Key); + + foreach (var dictionaryItem in foundDictionaryDescendants) + { + Services.LocalizationService.Delete(dictionaryItem, Security.CurrentUser.Id); + } + Services.LocalizationService.Delete(foundDictionary, Security.CurrentUser.Id); return Request.CreateResponse(HttpStatusCode.OK); diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 05f7b6525c..3ef8a92cdc 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -153,22 +153,20 @@ namespace Umbraco.Web.Editors var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); - foreach (var searchableTree in _searchableTreeCollection.SearchableApplicationTrees) + foreach (var searchableTree in _searchableTreeCollection.SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder)) { if (allowedSections.Contains(searchableTree.Value.AppAlias)) { var tree = _treeService.GetByAlias(searchableTree.Key); if (tree == null) continue; //shouldn't occur - var searchableTreeAttribute = searchableTree.Value.SearchableTree.GetType().GetCustomAttribute(false); - result[Tree.GetRootNodeDisplayName(tree, Services.TextService)] = new TreeSearchResult { Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out var total), TreeAlias = searchableTree.Key, AppAlias = searchableTree.Value.AppAlias, - JsFormatterService = searchableTreeAttribute == null ? "" : searchableTreeAttribute.ServiceName, - JsFormatterMethod = searchableTreeAttribute == null ? "" : searchableTreeAttribute.MethodName + JsFormatterService = searchableTree.Value.FormatterService, + JsFormatterMethod = searchableTree.Value.FormatterMethod }; } } @@ -893,7 +891,7 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.Media: return UmbracoObjectTypes.Media; case UmbracoEntityTypes.MemberType: - return UmbracoObjectTypes.MediaType; + return UmbracoObjectTypes.MemberType; case UmbracoEntityTypes.MemberGroup: return UmbracoObjectTypes.MemberGroup; case UmbracoEntityTypes.MediaType: diff --git a/src/Umbraco.Web/Editors/TourController.cs b/src/Umbraco.Web/Editors/TourController.cs index a61926740a..166e5a894d 100644 --- a/src/Umbraco.Web/Editors/TourController.cs +++ b/src/Umbraco.Web/Editors/TourController.cs @@ -30,6 +30,10 @@ namespace Umbraco.Web.Editors if (Current.Configs.Settings().BackOffice.Tours.EnableTours == false) return result; + var user = Composing.Current.UmbracoContext.Security.CurrentUser; + if (user == null) + return result; + //get all filters that will be applied to all tour aliases var aliasOnlyFilters = _filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList(); @@ -71,7 +75,7 @@ namespace Umbraco.Web.Editors } } //Get all allowed sections for the current user - var allowedSections = Composing.Current.UmbracoContext.Security.CurrentUser.AllowedSections.ToList(); + var allowedSections = user.AllowedSections.ToList(); var toursToBeRemoved = new List(); diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/HstsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/HstsCheck.cs index 18827b0c81..d0da243ced 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/HstsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/HstsCheck.cs @@ -6,17 +6,17 @@ namespace Umbraco.Web.HealthCheck.Checks.Security [HealthCheck( "E2048C48-21C5-4BE1-A80B-8062162DF124", "Cookie hijacking and protocol downgrade attacks Protection (Strict-Transport-Security Header (HSTS))", - Description = "Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS). If not, it adds with a default of 100 days.", + Description = "Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS). If not, it adds with a default of 18 weeks.", Group = "Security")] public class HstsCheck : BaseHttpHeaderCheck { // The check is mostly based on the instructions in the OWASP CheatSheet - // (https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet) + // (https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md) // and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/) - // If you want do to it perfectly, you have to submit it https://hstspreload.appspot.com/, + // If you want do to it perfectly, you have to submit it https://hstspreload.org/, // but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. public HstsCheck(IRuntimeState runtime, ILocalizedTextService textService) - : base(runtime, textService, "Strict-Transport-Security", "max-age=10886400; preload", "hSTS", true) + : base(runtime, textService, "Strict-Transport-Security", "max-age=10886400", "hSTS", true) { } } diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index c1268ca675..b5fdea32b7 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -34,18 +34,32 @@ namespace Umbraco.Web.Install.InstallSteps public override Task ExecuteAsync(object model) { - //During a new install we'll log the default user in (which is id = 0). - // During an upgrade, the user will already need to be logged in order to run the installer. - var security = new WebSecurity(_httpContext, _userService, _globalSettings); - //we do this check here because for upgrades the user will already be logged in, for brand new installs, - // they will not be logged in, however we cannot check the current installation status because it will tell - // us that it is in 'upgrade' because we already have a database conn configured and a database. + if (security.IsAuthenticated() == false && _globalSettings.ConfigurationStatus.IsNullOrWhiteSpace()) { security.PerformLogin(-1); } + if (security.IsAuthenticated()) + { + // when a user is already logged in, we need to check whether it's user 'zero' + // which is the legacy super user from v7 - and then we need to actually log the + // true super user in - but before that we need to log off, else audit events + // will try to reference user zero and fail + var userIdAttempt = security.GetUserId(); + if (userIdAttempt && userIdAttempt.Result == 0) + { + security.ClearCurrentLogin(); + security.PerformLogin(Constants.Security.SuperUserId); + } + } + else if (_globalSettings.ConfigurationStatus.IsNullOrWhiteSpace()) + { + // for installs, we need to log the super user in + security.PerformLogin(Constants.Security.SuperUserId); + } + // Update configurationStatus _globalSettings.ConfigurationStatus = UmbracoVersion.SemanticVersion.ToSemanticString(); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs index 61dacfb2aa..deadac949a 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Core.Models; using Umbraco.Core.Models.Validation; @@ -16,6 +17,7 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "name", IsRequired = true)] [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] + [MaxLength(255, ErrorMessage ="Name must be less than 255 characters")] public string Name { get; set; } [DataMember(Name = "properties")] diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs index a96ef24e6f..6ee37ea443 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -250,6 +250,7 @@ namespace Umbraco.Web.Models.Mapping var result = Roles.GetAllRoles().Distinct() // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access .Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) .ToDictionary(x => x, x => false); // if user has no roles, just return the dictionary diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index 0ee6ff6f6c..d402a0e714 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -43,7 +43,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters => PropertyCacheLevel.Snapshot; public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IEnumerable); + => IsSingleNodePicker(propertyType) + ? typeof(IPublishedContent) + : typeof(IEnumerable); public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) { @@ -73,6 +75,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) { var udis = (Udi[])source; + var isSingleNodePicker = IsSingleNodePicker(propertyType); if ((propertyType.Alias != null && PropertiesToExclude.InvariantContains(propertyType.Alias)) == false) { @@ -102,9 +105,17 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (multiNodeTreePickerItem != null && multiNodeTreePickerItem.ContentType.ItemType != PublishedItemType.Element) { multiNodeTreePicker.Add(multiNodeTreePickerItem); + if (isSingleNodePicker) + { + break; + } } } + if (isSingleNodePicker) + { + return multiNodeTreePicker.FirstOrDefault(); + } return multiNodeTreePicker; } @@ -141,5 +152,10 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters } return content; } + + private static bool IsSingleNodePicker(PublishedPropertyType propertyType) + { + return propertyType.DataType.ConfigurationAs().MaxNumber == 1; + } } } diff --git a/src/Umbraco.Web/Search/SearchableApplicationTree.cs b/src/Umbraco.Web/Search/SearchableApplicationTree.cs index 997d8ce8e8..346f106b84 100644 --- a/src/Umbraco.Web/Search/SearchableApplicationTree.cs +++ b/src/Umbraco.Web/Search/SearchableApplicationTree.cs @@ -4,15 +4,21 @@ namespace Umbraco.Web.Search { public class SearchableApplicationTree { - public SearchableApplicationTree(string appAlias, string treeAlias, ISearchableTree searchableTree) + public SearchableApplicationTree(string appAlias, string treeAlias, int sortOrder, string formatterService, string formatterMethod, ISearchableTree searchableTree) { AppAlias = appAlias; TreeAlias = treeAlias; + SortOrder = sortOrder; + FormatterService = formatterService; + FormatterMethod = formatterMethod; SearchableTree = searchableTree; } public string AppAlias { get; } public string TreeAlias { get; } + public int SortOrder { get; } + public string FormatterService { get; } + public string FormatterMethod { get; } public ISearchableTree SearchableTree { get; } } } diff --git a/src/Umbraco.Web/Search/SearchableTreeAttribute.cs b/src/Umbraco.Web/Search/SearchableTreeAttribute.cs index 6c44b8946d..a2311ae989 100644 --- a/src/Umbraco.Web/Search/SearchableTreeAttribute.cs +++ b/src/Umbraco.Web/Search/SearchableTreeAttribute.cs @@ -6,16 +6,27 @@ namespace Umbraco.Web.Search [AttributeUsage(AttributeTargets.Class)] public sealed class SearchableTreeAttribute : Attribute { + public const int DefaultSortOrder = 1000; + /// /// This constructor defines both the angular service and method name to use /// /// /// - public SearchableTreeAttribute(string serviceName, string methodName) + public SearchableTreeAttribute(string serviceName, string methodName) : this(serviceName, methodName, DefaultSortOrder) { } + + /// + /// This constructor defines both the angular service and method name to use and explicitly defines a sort order for the results + /// + /// + /// + /// + public SearchableTreeAttribute(string serviceName, string methodName, int sortOrder) { if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentNullOrEmptyException(nameof(methodName)); MethodName = methodName; + SortOrder = sortOrder; ServiceName = serviceName; } @@ -28,9 +39,11 @@ namespace Umbraco.Web.Search if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentNullOrEmptyException(nameof(serviceName)); MethodName = ""; ServiceName = serviceName; + SortOrder = DefaultSortOrder; } public string MethodName { get; } public string ServiceName { get; } + public int SortOrder { get; } } } diff --git a/src/Umbraco.Web/Search/SearchableTreeCollection.cs b/src/Umbraco.Web/Search/SearchableTreeCollection.cs index 8f7c6ece0b..0e7b1cd268 100644 --- a/src/Umbraco.Web/Search/SearchableTreeCollection.cs +++ b/src/Umbraco.Web/Search/SearchableTreeCollection.cs @@ -32,7 +32,15 @@ namespace Umbraco.Web.Search var found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias)); if (found != null) { - dictionary[found.TreeAlias] = new SearchableApplicationTree(appTree.SectionAlias, appTree.TreeAlias, found); + var searchableTreeAttribute = found.GetType().GetCustomAttribute(false); + dictionary[found.TreeAlias] = new SearchableApplicationTree( + appTree.SectionAlias, + appTree.TreeAlias, + searchableTreeAttribute?.SortOrder ?? SearchableTreeAttribute.DefaultSortOrder, + searchableTreeAttribute?.ServiceName ?? string.Empty, + searchableTreeAttribute?.MethodName ?? string.Empty, + found + ); } } return dictionary; diff --git a/src/Umbraco.Web/Services/SectionService.cs b/src/Umbraco.Web/Services/SectionService.cs index c001233152..2696186301 100644 --- a/src/Umbraco.Web/Services/SectionService.cs +++ b/src/Umbraco.Web/Services/SectionService.cs @@ -39,6 +39,6 @@ namespace Umbraco.Web.Services /// public ISection GetByAlias(string appAlias) - => GetSections().FirstOrDefault(t => t.Alias == appAlias); + => GetSections().FirstOrDefault(t => t.Alias.Equals(appAlias, StringComparison.OrdinalIgnoreCase)); } } diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index 4249d1639e..748c97c522 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Content, Constants.Trees.Content)] [PluginController("UmbracoTrees")] [CoreTree] - [SearchableTree("searchResultFormatter", "configureContentResult")] + [SearchableTree("searchResultFormatter", "configureContentResult", 10)] public class ContentTreeController : ContentTreeControllerBase, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher; @@ -248,8 +248,11 @@ namespace Umbraco.Web.Trees OpensDialog = true }); } - - menu.Items.Add(new RefreshNode(Services.TextService, true)); + + if((item is DocumentEntitySlim documentEntity && documentEntity.IsContainer) == false) + { + menu.Items.Add(new RefreshNode(Services.TextService, true)); + } return menu; } diff --git a/src/Umbraco.Web/Trees/MacrosTreeController.cs b/src/Umbraco.Web/Trees/MacrosTreeController.cs index 02636cbea8..fcfd2e0b3b 100644 --- a/src/Umbraco.Web/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web/Trees/MacrosTreeController.cs @@ -1,8 +1,6 @@ -using System; -using System.Linq; +using System.Linq; using System.Net.Http.Formatting; using Umbraco.Core; -using Umbraco.Core.Models.Entities; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; @@ -31,7 +29,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.RootString) { - foreach (var macro in Services.MacroService.GetAll()) + foreach (var macro in Services.MacroService.GetAll().OrderBy(m => m.Name)) { nodes.Add(CreateTreeNode( macro.Id.ToString(), diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index fc3d23f092..d050b51a91 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -30,7 +30,7 @@ namespace Umbraco.Web.Trees [Tree(Constants.Applications.Media, Constants.Trees.Media)] [PluginController("UmbracoTrees")] [CoreTree] - [SearchableTree("searchResultFormatter", "configureMediaResult")] + [SearchableTree("searchResultFormatter", "configureMediaResult", 20)] public class MediaTreeController : ContentTreeControllerBase, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher;