From 7aabf459ea0e5e51c7468801136c49022501e9d1 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 25 Jan 2013 15:05:42 -0100 Subject: [PATCH 01/16] Adding schema validation to the DatabaseSchemaCreation class. Helps determine if valid database exists and which version it corresponds to. On startup the legacy connectionstring is used if one exists, so its not ignore but rather reconfigured. Relates to U4-1520. --- src/Umbraco.Core/DatabaseContext.cs | 63 ++++++++-- .../DefinitionFactory.cs | 18 ++- .../Initial/DatabaseSchemaCreation.cs | 102 ++++++++++++++-- .../Initial/DatabaseSchemaResult.cs | 70 ++++++++++- .../Persistence/SqlSyntax/ColumnInfo.cs | 31 +++++ .../SqlSyntax/ISqlSyntaxProvider.cs | 5 + .../SqlSyntax/MySqlSyntaxProvider.cs | 22 ++++ .../SqlSyntax/SqlCeSyntaxProvider.cs | 45 +++++++ .../SqlSyntax/SqlServerSyntaxProvider.cs | 37 +++++- .../SqlSyntax/SqlSyntaxProviderBase.cs | 20 ++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Migrations/Upgrades/BaseUpgradeTest.cs | 1 - .../Upgrades/ValidateOlderSchemaTest.cs | 113 ++++++++++++++++++ .../Persistence/SchemaValidationTest.cs | 38 ++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + .../install/steps/Definitions/Database.cs | 13 +- 16 files changed, 547 insertions(+), 34 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs create mode 100644 src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs create mode 100644 src/Umbraco.Tests/Persistence/SchemaValidationTest.cs diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index a79792d84b..fda9f55f00 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Persistence.Migrations.Initial; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core @@ -242,20 +243,37 @@ namespace Umbraco.Core } - if (providerName.StartsWith("MySql")) + Initialize(providerName); + } + else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName)) + { + //A valid connectionstring does not exist, but the legacy appSettings key was found, so we'll reconfigure the conn.string. + var legacyConnString = ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]; + if (legacyConnString.ToLowerInvariant().Contains("sqlce4umbraco")) { - SyntaxConfig.SqlSyntaxProvider = MySqlSyntax.Provider; + ConfigureDatabaseConnection(); } - else if (providerName.Contains("SqlServerCe")) + else if (legacyConnString.ToLowerInvariant().Contains("database.windows.net") && + legacyConnString.ToLowerInvariant().Contains("tcp:")) { - SyntaxConfig.SqlSyntaxProvider = SqlCeSyntax.Provider; + //Must be sql azure + SaveConnectionString(legacyConnString, "System.Data.SqlClient"); + } + else if (legacyConnString.ToLowerInvariant().Contains("Uid") && + legacyConnString.ToLowerInvariant().Contains("Pwd") && + legacyConnString.ToLowerInvariant().Contains("Server")) + { + //Must be MySql + SaveConnectionString(legacyConnString, "MySql.Data.MySqlClient"); } else { - SyntaxConfig.SqlSyntaxProvider = SqlServerSyntax.Provider; + //Must be sql + SaveConnectionString(legacyConnString, "System.Data.SqlClient"); } - _configured = true; + //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. + GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName); } else { @@ -277,28 +295,51 @@ namespace Umbraco.Core { SyntaxConfig.SqlSyntaxProvider = SqlServerSyntax.Provider; } - + + _providerName = providerName; _configured = true; } + internal DatabaseSchemaResult ValidateDatabaseSchema() + { + if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) + return new DatabaseSchemaResult(); + + var database = new UmbracoDatabase(_connectionString, ProviderName); + var dbSchema = new DatabaseSchemaCreation(database); + var result = dbSchema.ValidateSchema(); + return result; + } + internal Result CreateDatabaseSchemaAndDataOrUpgrade() { if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) { - return new Result{Message = "Database configuration is invalid", Success = false, Percentage = "10"}; + return new Result + { + Message = + "Database configuration is invalid. Please check that the entered database exists and that the provided username and password has write access to the database.", + Success = false, + Percentage = "10" + }; } try { var database = new UmbracoDatabase(_connectionString, ProviderName); - //If Configuration Status is empty its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus)) + var schemaResult = ValidateDatabaseSchema(); + var installedVersion = schemaResult.DetermineInstalledVersion(); + + //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing + if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedVersion.Equals(new Version(0, 0, 0))) { database.CreateDatabaseSchema(); } else { - var configuredVersion = new Version(GlobalSettings.ConfigurationStatus); + var configuredVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) + ? installedVersion + : new Version(GlobalSettings.ConfigurationStatus); var targetVersion = UmbracoVersion.Current; var runner = new MigrationRunner(configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName); var upgraded = runner.Execute(database, true); diff --git a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs index ce66c70713..f987e69b3d 100644 --- a/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs @@ -84,10 +84,14 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions var primaryKeyColumnAttribute = propertyInfo.FirstAttribute(); if (primaryKeyColumnAttribute != null) { + string primaryKeyName = string.IsNullOrEmpty(primaryKeyColumnAttribute.Name) + ? string.Format("PK_{0}", tableName) + : primaryKeyColumnAttribute.Name; + definition.IsPrimaryKey = true; definition.IsIdentity = primaryKeyColumnAttribute.AutoIncrement; definition.IsIndexed = primaryKeyColumnAttribute.Clustered; - definition.PrimaryKeyName = primaryKeyColumnAttribute.Name ?? string.Empty; + definition.PrimaryKeyName = primaryKeyName; definition.PrimaryKeyColumns = primaryKeyColumnAttribute.OnColumns ?? string.Empty; definition.Seeding = primaryKeyColumnAttribute.IdentitySeed; } @@ -120,9 +124,13 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions ? referencedPrimaryKey.Value : attribute.Column; + string foreignKeyName = string.IsNullOrEmpty(attribute.Name) + ? string.Format("FK_{0}_{1}_{2}", tableName, referencedTable.Value, referencedColumn) + : attribute.Name; + var definition = new ForeignKeyDefinition { - Name = attribute.Name, + Name = foreignKeyName, ForeignTable = tableName, PrimaryTable = referencedTable.Value }; @@ -134,9 +142,13 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions public static IndexDefinition GetIndexDefinition(Type modelType, PropertyInfo propertyInfo, IndexAttribute attribute, string columnName, string tableName) { + string indexName = string.IsNullOrEmpty(attribute.Name) + ? string.Format("IX_{0}_{1}", tableName, columnName) + : attribute.Name; + var definition = new IndexDefinition { - Name = attribute.Name, + Name = indexName, IndexType = attribute.IndexType, ColumnName = columnName, TableName = tableName, diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 9392c047bf..2c3e245dff 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Initial { @@ -95,19 +97,97 @@ namespace Umbraco.Core.Persistence.Migrations.Initial foreach (var item in OrderedTables.OrderBy(x => x.Key)) { - var tableNameAttribute = item.Value.FirstAttribute(); - if (tableNameAttribute != null) + var tableDefinition = DefinitionFactory.GetTableDefinition(item.Value); + result.TableDefinitions.Add(tableDefinition); + } + + //Check tables in configured database against tables in schema + var tablesInDatabase = SyntaxConfig.SqlSyntaxProvider.GetTablesInSchema(_database).ToList(); + var tablesInSchema = result.TableDefinitions.Select(x => x.Name).ToList(); + //Add valid and invalid table differences to the result object + var validTableDifferences = tablesInDatabase.Intersect(tablesInSchema); + foreach (var tableName in validTableDifferences) + { + result.ValidTables.Add(tableName); + } + var invalidTableDifferences = tablesInDatabase.Except(tablesInSchema); + foreach (var tableName in invalidTableDifferences) + { + result.Errors.Add(new Tuple("Table", tableName)); + } + + //Check columns in configured database against columns in schema + var columnsInDatabase = SyntaxConfig.SqlSyntaxProvider.GetColumnsInSchema(_database); + var columnsPerTableInDatabase = columnsInDatabase.Select(x => string.Concat(x.TableName, ",", x.ColumnName)).ToList(); + var columnsPerTableInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => string.Concat(y.TableName, ",", y.Name))).ToList(); + //Add valid and invalid column differences to the result object + var validColumnDifferences = columnsPerTableInDatabase.Intersect(columnsPerTableInSchema); + foreach (var column in validColumnDifferences) + { + result.ValidColumns.Add(column); + } + var invalidColumnDifferences = columnsPerTableInDatabase.Except(columnsPerTableInSchema); + foreach (var column in invalidColumnDifferences) + { + result.Errors.Add(new Tuple("Column", column)); + } + + //Check constraints in configured database against constraints in schema + var constraintsInDatabase = SyntaxConfig.SqlSyntaxProvider.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList(); + var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.StartsWith("FK_")).Select(x => x.Item3).ToList(); + var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.StartsWith("PK_")).Select(x => x.Item3).ToList(); + var indexesInDatabase = constraintsInDatabase.Where(x => x.Item3.StartsWith("IX_")).Select(x => x.Item3).ToList(); + var unknownConstraintsInDatabase = + constraintsInDatabase.Where( + x => + x.Item3.StartsWith("FK_") == false && x.Item3.StartsWith("PK_") == false && + x.Item3.StartsWith("IX_") == false).Select(x => x.Item3).ToList(); + var foreignKeysInSchema = result.TableDefinitions.SelectMany(x => x.ForeignKeys.Select(y => y.Name)).ToList(); + var primaryKeysInSchema = result.TableDefinitions.SelectMany(x => x.Columns.Select(y => y.PrimaryKeyName)).ToList(); + var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); + //Add valid and invalid foreign key differences to the result object + foreach (var unknown in unknownConstraintsInDatabase) + { + if (foreignKeysInSchema.Contains(unknown) || primaryKeysInSchema.Contains(unknown) || indexesInSchema.Contains(unknown)) { - var tableExist = _database.TableExist(tableNameAttribute.Value); - if (tableExist) - { - result.Successes.Add(tableNameAttribute.Value, "Table exists"); - } - else - { - result.Errors.Add(tableNameAttribute.Value, "Table does not exist"); - } + result.ValidConstraints.Add(unknown); } + else + { + result.Errors.Add(new Tuple("Unknown", unknown)); + } + } + var validForeignKeyDifferences = foreignKeysInDatabase.Intersect(foreignKeysInSchema); + foreach (var foreignKey in validForeignKeyDifferences) + { + result.ValidConstraints.Add(foreignKey); + } + var invalidForeignKeyDifferences = foreignKeysInDatabase.Except(foreignKeysInSchema); + foreach (var foreignKey in invalidForeignKeyDifferences) + { + result.Errors.Add(new Tuple("Constraint", foreignKey)); + } + //Add valid and invalid primary key differences to the result object + var validPrimaryKeyDifferences = primaryKeysInDatabase.Intersect(primaryKeysInSchema); + foreach (var primaryKey in validPrimaryKeyDifferences) + { + result.ValidConstraints.Add(primaryKey); + } + var invalidPrimaryKeyDifferences = primaryKeysInDatabase.Except(primaryKeysInSchema); + foreach (var primaryKey in invalidPrimaryKeyDifferences) + { + result.Errors.Add(new Tuple("Constraint", primaryKey)); + } + //Add valid and invalid index differences to the result object + var validIndexDifferences = indexesInDatabase.Intersect(indexesInSchema); + foreach (var index in validIndexDifferences) + { + result.ValidConstraints.Add(index); + } + var invalidIndexDifferences = indexesInDatabase.Except(indexesInSchema); + foreach (var index in invalidIndexDifferences) + { + result.Errors.Add(new Tuple("Constraint", index)); } return result; diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 71997028ce..9b0ae69270 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.Migrations.Initial { @@ -6,12 +10,68 @@ namespace Umbraco.Core.Persistence.Migrations.Initial { public DatabaseSchemaResult() { - Errors = new Dictionary(); - Successes = new Dictionary(); + Errors = new List>(); + TableDefinitions = new List(); + ValidTables = new List(); + ValidColumns = new List(); + ValidConstraints = new List(); } - public IDictionary Errors { get; set; } + public List> Errors { get; set; } - public IDictionary Successes { get; set; } + public List TableDefinitions { get; set; } + + public List ValidTables { get; set; } + + public List ValidColumns { get; set; } + + public List ValidConstraints { get; set; } + + /// + /// Determines the version of the currently installed database. + /// + /// + /// A with Major and Minor values for + /// non-empty database, otherwise "0.0.0" for empty databases. + /// + public Version DetermineInstalledVersion() + { + //If (ValidTables.Count == 0) database is empty and we return -> new Version(0, 0, 0); + if(ValidTables.Count == 0) + return new Version(0, 0, 0); + + //If Errors is empty then we're at current version + if (Errors.Any() == false) + return UmbracoVersion.Current; + + //If Errors contains umbracoApp or umbracoAppTree its pre-6.0.0 -> new Version(4, 10, 0); + if ( + Errors.Any( + x => x.Item1.Equals("Table") && (x.Item2.Equals("umbracoApp") || x.Item2.Equals("umbracoAppTree")))) + { + //If Errors contains umbracoUser2app or umbracoAppTree foreignkey to umbracoApp exists its pre-4.8.0 -> new Version(4, 7, 0); + if ( + Errors.Any( + x => + x.Item1.Equals("Constraint") && + (x.Item2.Contains("umbracoUser2app_umbracoApp") || x.Item2.Contains("umbracoAppTree_umbracoApp")))) + { + return new Version(4, 7, 0); + } + + return new Version(4, 10, 0); + } + + return new Version(0, 0, 0); + } + + /// + /// Gets a summary of the schema validation result + /// + /// A string containing a human readable string with a summary message + public string GetSummary() + { + return string.Empty; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs new file mode 100644 index 0000000000..71b6d58d8b --- /dev/null +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ColumnInfo.cs @@ -0,0 +1,31 @@ +namespace Umbraco.Core.Persistence.SqlSyntax +{ + public class ColumnInfo + { + public ColumnInfo(string tableName, string columnName, int ordinal, string columnDefault, string isNullable, string dataType) + { + TableName = tableName; + ColumnName = columnName; + Ordinal = ordinal; + ColumnDefault = columnDefault; + IsNullable = isNullable.Equals("YES"); + DataType = dataType; + } + + public ColumnInfo(string tableName, string columnName, int ordinal, string isNullable, string dataType) + { + TableName = tableName; + ColumnName = columnName; + Ordinal = ordinal; + IsNullable = isNullable.Equals("YES"); + DataType = dataType; + } + + public string TableName { get; set; } + public string ColumnName { get; set; } + public int Ordinal { get; set; } + public string ColumnDefault { get; set; } + public bool IsNullable { get; set; } + public string DataType { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs index 02426fba52..5aa5c3591d 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/ISqlSyntaxProvider.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -47,5 +48,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax string FormatTableRename(string oldName, string newName); bool SupportsClustered(); bool SupportsIdentityInsert(); + IEnumerable GetTablesInSchema(Database db); + IEnumerable GetColumnsInSchema(Database db); + IEnumerable> GetConstraintsPerTable(Database db); + IEnumerable> GetConstraintsPerColumn(Database db); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 1d124b3687..92f0d617f3 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Persistence.DatabaseAnnotations; using Umbraco.Core.Persistence.DatabaseModelDefinitions; @@ -39,6 +40,27 @@ namespace Umbraco.Core.Persistence.SqlSyntax DefaultValueFormat = "DEFAULT '{0}'"; } + public override IEnumerable GetTablesInSchema(Database db) + { + var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); + return items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + + public override IEnumerable GetColumnsInSchema(Database db) + { + return new List(); + } + + public override IEnumerable> GetConstraintsPerTable(Database db) + { + return new List>(); + } + + public override IEnumerable> GetConstraintsPerColumn(Database db) + { + return new List>(); + } + public override bool DoesTableExist(Database db, string tableName) { long result; diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs index 28383a666e..06d37c9df0 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlCeSyntaxProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using Umbraco.Core.Persistence.DatabaseAnnotations; @@ -112,6 +113,50 @@ namespace Umbraco.Core.Persistence.SqlSyntax columns); } + public override IEnumerable GetTablesInSchema(Database db) + { + var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); + return items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + + public override IEnumerable GetColumnsInSchema(Database db) + { + var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); + return + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + + public override IEnumerable> GetConstraintsPerTable(Database db) + { + var items = db.Fetch("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS"); + var indexItems = db.Fetch("SELECT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES"); + return + items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)) + .Union( + indexItems.Select( + indexItem => new Tuple(indexItem.TABLE_NAME, indexItem.INDEX_NAME))) + .ToList(); + } + + public override IEnumerable> GetConstraintsPerColumn(Database db) + { + var items = + db.Fetch( + "SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE"); + var indexItems = db.Fetch("SELECT INDEX_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.INDEXES"); + return + items.Select( + item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) + .Union( + indexItems.Select( + indexItem => + new Tuple(indexItem.TABLE_NAME, indexItem.COLUMN_NAME, + indexItem.INDEX_NAME))).ToList(); + } + public override bool DoesTableExist(Database db, string tableName) { var result = diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index b97033c17c..eda04199bd 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.SqlSyntax { @@ -49,6 +52,38 @@ namespace Umbraco.Core.Persistence.SqlSyntax return string.Format("[{0}]", name); } + public override IEnumerable GetTablesInSchema(Database db) + { + var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); + return items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + + public override IEnumerable GetColumnsInSchema(Database db) + { + var items = db.Fetch("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS"); + return + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + + public override IEnumerable> GetConstraintsPerTable(Database db) + { + var items = + db.Fetch( + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM SELECT * FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE"); + return items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); + } + + public override IEnumerable> GetConstraintsPerColumn(Database db) + { + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE"); + return items.Select(item => new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList(); + } + public override bool DoesTableExist(Database db, string tableName) { var result = diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs index 0f6f832c66..c453b46dc4 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs @@ -151,6 +151,26 @@ namespace Umbraco.Core.Persistence.SqlSyntax return "NVARCHAR"; } + public virtual IEnumerable GetTablesInSchema(Database db) + { + return new List(); + } + + public virtual IEnumerable GetColumnsInSchema(Database db) + { + return new List(); + } + + public virtual IEnumerable> GetConstraintsPerTable(Database db) + { + return new List>(); + } + + public virtual IEnumerable> GetConstraintsPerColumn(Database db) + { + return new List>(); + } + public virtual bool DoesTableExist(Database db, string tableName) { return false; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0aeab69175..96e43ad6a7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -439,6 +439,7 @@ + diff --git a/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs index e229655699..326aa304c2 100644 --- a/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs +++ b/src/Umbraco.Tests/Migrations/Upgrades/BaseUpgradeTest.cs @@ -10,7 +10,6 @@ using Umbraco.Core.Persistence.Migrations; using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSix; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Strategies.Migrations; using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings; namespace Umbraco.Tests.Migrations.Upgrades diff --git a/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs new file mode 100644 index 0000000000..9515970f3c --- /dev/null +++ b/src/Umbraco.Tests/Migrations/Upgrades/ValidateOlderSchemaTest.cs @@ -0,0 +1,113 @@ +using System; +using System.Configuration; +using System.Data.SqlServerCe; +using System.IO; +using System.Text.RegularExpressions; +using NUnit.Framework; +using SQLCE4Umbraco; +using Umbraco.Core.Configuration; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Migrations.Initial; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Migrations.Upgrades +{ + [TestFixture] + public class ValidateOlderSchemaTest + { + /// Regular expression that finds multiline block comments. + private static readonly Regex FindComments = new Regex(@"\/\*.*?\*\/", RegexOptions.Singleline | RegexOptions.Compiled); + + [Test] + public virtual void DatabaseSchemaCreation_Returns_DatabaseSchemaResult_Where_DetermineInstalledVersion_Is_4_7_0() + { + // Arrange + var db = GetConfiguredDatabase(); + var schema = new DatabaseSchemaCreation(db); + + //Create db schema and data from old Total.sql file for Sql Ce + string statements = GetDatabaseSpecificSqlScript(); + // replace block comments by whitespace + statements = FindComments.Replace(statements, " "); + // execute all non-empty statements + foreach (string statement in statements.Split(";".ToCharArray())) + { + string rawStatement = statement.Replace("GO", "").Trim(); + if (rawStatement.Length > 0) + db.Execute(new Sql(rawStatement)); + } + + // Act + var result = schema.ValidateSchema(); + + // Assert + var expected = new Version(4, 7, 0); + Assert.AreEqual(expected, result.DetermineInstalledVersion()); + } + + [SetUp] + public virtual void Initialize() + { + TestHelper.SetupLog4NetForTests(); + TestHelper.InitializeContentDirectories(); + + Path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", Path); + + UmbracoSettings.UseLegacyXmlSchema = false; + + Resolution.Freeze(); + + //Delete database file before continueing + string filePath = string.Concat(Path, "\\UmbracoPetaPocoTests.sdf"); + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + + //Get the connectionstring settings from config + var settings = ConfigurationManager.ConnectionStrings[Core.Configuration.GlobalSettings.UmbracoConnectionName]; + + //Create the Sql CE database + var engine = new SqlCeEngine(settings.ConnectionString); + engine.CreateDatabase(); + + SyntaxConfig.SqlSyntaxProvider = SqlCeSyntax.Provider; + } + + [TearDown] + public virtual void TearDown() + { + SyntaxConfig.SqlSyntaxProvider = null; + Resolution.IsFrozen = false; + + TestHelper.CleanContentDirectories(); + + Path = TestHelper.CurrentAssemblyDirectory; + AppDomain.CurrentDomain.SetData("DataDirectory", null); + + //legacy API database connection close + SqlCeContextGuardian.CloseBackgroundConnection(); + + string filePath = string.Concat(Path, "\\UmbracoPetaPocoTests.sdf"); + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + + public string Path { get; set; } + + public UmbracoDatabase GetConfiguredDatabase() + { + return new UmbracoDatabase("Datasource=|DataDirectory|UmbracoPetaPocoTests.sdf", "System.Data.SqlServerCe.4.0"); + } + + public string GetDatabaseSpecificSqlScript() + { + return SqlScripts.SqlResources.SqlCeTotal_480; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs new file mode 100644 index 0000000000..f4aaa7c064 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs @@ -0,0 +1,38 @@ +using NUnit.Framework; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.Migrations.Initial; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Persistence +{ + [TestFixture] + public class SchemaValidationTest : BaseDatabaseFactoryTest + { + [SetUp] + public override void Initialize() + { + base.Initialize(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + [Test] + public void DatabaseSchemaCreation_Produces_DatabaseSchemaResult_With_Zero_Errors() + { + // Arrange + var db = DatabaseContext.Database; + var schema = new DatabaseSchemaCreation(db); + + // Act + var result = schema.ValidateSchema(); + + // Assert + Assert.That(result.Errors.Count, Is.EqualTo(0)); + Assert.AreEqual(result.DetermineInstalledVersion(), UmbracoVersion.Current); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4bff2016c1..66cd2854c4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -175,6 +175,7 @@ + @@ -183,6 +184,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs index 315e80b805..0c2b61108f 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Core; using Umbraco.Core.Configuration; using umbraco.cms.businesslogic.installer; using umbraco.IO; @@ -35,8 +36,16 @@ namespace umbraco.presentation.install.steps.Definitions public override bool Completed() { // Fresh installs don't have a version number so this step cannot be complete yet - if (string.IsNullOrEmpty(Umbraco.Core.Configuration.GlobalSettings.ConfigurationStatus)) - return false; + if (string.IsNullOrEmpty(Umbraco.Core.Configuration.GlobalSettings.ConfigurationStatus)) + { + //Even though the ConfigurationStatus is blank we try to determine the version if we can connect to the database + var result = ApplicationContext.Current.DatabaseContext.ValidateDatabaseSchema(); + var determinedVersion = result.DetermineInstalledVersion(); + if(determinedVersion.Equals(new Version(0, 0, 0))) + return false; + + return UmbracoVersion.Current < determinedVersion; + } var configuredVersion = new Version(Umbraco.Core.Configuration.GlobalSettings.ConfigurationStatus); var targetVersion = UmbracoVersion.Current; From d0c5357625db4c85e8b077c3864cea4e5512a4c6 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Fri, 25 Jan 2013 15:17:05 -0100 Subject: [PATCH 02/16] Adding initialize call to the legacy conn.string being reconfigured. --- src/Umbraco.Core/DatabaseContext.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index fda9f55f00..b4955f36ad 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -258,6 +258,7 @@ namespace Umbraco.Core { //Must be sql azure SaveConnectionString(legacyConnString, "System.Data.SqlClient"); + Initialize("System.Data.SqlClient"); } else if (legacyConnString.ToLowerInvariant().Contains("Uid") && legacyConnString.ToLowerInvariant().Contains("Pwd") && @@ -265,11 +266,13 @@ namespace Umbraco.Core { //Must be MySql SaveConnectionString(legacyConnString, "MySql.Data.MySqlClient"); + Initialize("MySql.Data.MySqlClient"); } else { //Must be sql SaveConnectionString(legacyConnString, "System.Data.SqlClient"); + Initialize("System.Data.SqlClient"); } //Remove the legacy connection string, so we don't end up in a loop if something goes wrong. From 0689b928c27837a3bb7e33f5b21de4c1c10eba1e Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 28 Jan 2013 11:15:30 -0100 Subject: [PATCH 03/16] Fixes U4-1510 Missing content in Umbraco 6 beta after publish --- src/Umbraco.Core/Models/ContentTypeBase.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index f0dad5f280..33a7b1b48e 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -154,12 +154,7 @@ namespace Umbraco.Core.Models get { return _alias; } set { - //Ensures a valid ContentType alias - //Would have liked to use .ToUmbracoAlias() but that would break casing upon saving older/upgraded ContentTypes - var result = Regex.Replace(value, @"[^a-zA-Z0-9\s\.-]+", "", RegexOptions.Compiled); - result = result.Replace(" ", ""); - - _alias = result; + _alias = value.ToSafeAlias(); OnPropertyChanged(AliasSelector); } } From 2f00fa079129fa5ef72391fdaf93bdbeeee905db Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Mon, 28 Jan 2013 19:36:06 +0600 Subject: [PATCH 04/16] Fixes: #U4-1547 - duplicate trees on upgrade in rare cases. --- src/Umbraco.Web.UI/config/trees.config | 1 + src/umbraco.businesslogic/ApplicationTree.cs | 19 +++++++++++++++++-- .../ApplicationTreeRegistrar.cs | 6 ++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 6834440077..34558bbdaf 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -20,6 +20,7 @@ + diff --git a/src/umbraco.businesslogic/ApplicationTree.cs b/src/umbraco.businesslogic/ApplicationTree.cs index fdebf70488..da9e5f3d69 100644 --- a/src/umbraco.businesslogic/ApplicationTree.cs +++ b/src/umbraco.businesslogic/ApplicationTree.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Web; using System.Xml.Linq; +using Umbraco.Core; using umbraco.DataLayer; using umbraco.IO; @@ -395,7 +396,18 @@ namespace umbraco.BusinessLogic return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0; })) { - list.Add(new ApplicationTree( + + var applicationAlias = (string)addElement.Attribute("application"); + var type = (string)addElement.Attribute("type"); + var assembly = (string)addElement.Attribute("assembly"); + + //check if the tree definition (applicationAlias + type + assembly) is already in the list + + if (!list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) + && tree.Type.InvariantEquals(type) + && tree.AssemblyName.InvariantEquals(assembly))) + { + list.Add(new ApplicationTree( addElement.Attribute("silent") != null ? Convert.ToBoolean(addElement.Attribute("silent").Value) : false, addElement.Attribute("initialize") != null ? Convert.ToBoolean(addElement.Attribute("initialize").Value) : true, addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0, @@ -404,9 +416,12 @@ namespace umbraco.BusinessLogic addElement.Attribute("title").Value, addElement.Attribute("iconClosed").Value, addElement.Attribute("iconOpen").Value, - (string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360 + (string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360 addElement.Attribute("type").Value, addElement.Attribute("action") != null ? addElement.Attribute("action").Value : "")); + } + + } }, false); diff --git a/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs b/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs index 67c07ff025..e12eb2f21b 100644 --- a/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs +++ b/src/umbraco.businesslogic/ApplicationTreeRegistrar.cs @@ -13,8 +13,10 @@ namespace umbraco.BusinessLogic { public ApplicationTreeRegistrar() { - //don't do anything if the application is not configured! - if (ApplicationContext.Current == null || !ApplicationContext.Current.IsConfigured) + //don't do anything if the application or database is not configured! + if (ApplicationContext.Current == null + || !ApplicationContext.Current.IsConfigured + || !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured) return; // Load all Trees by attribute and add them to the XML config From 5d0aced9a7004c5f4de3f1b49d44e5f0d8f2d41a Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 28 Jan 2013 14:01:38 -0100 Subject: [PATCH 05/16] Giving the install/upgrade a bit of TLC. Fixes U4-1520 --- build/Build.bat | 2 + src/Umbraco.Core/DatabaseContext.cs | 37 ++++++++-- .../Initial/DatabaseSchemaCreation.cs | 5 ++ .../Initial/DatabaseSchemaResult.cs | 47 +++++++++++- .../SqlSyntax/MySqlSyntaxProvider.cs | 73 +++++++++++++++++-- .../SqlSyntax/SqlServerSyntaxProvider.cs | 2 +- src/Umbraco.Core/Services/ContentService.cs | 5 +- src/Umbraco.Tests/App.config | 2 + src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +- .../install/steps/database.ascx | 72 +++++++++--------- src/Umbraco.Web.UI/web.Template.config | 2 +- .../install/steps/Definitions/Database.cs | 2 +- .../install/steps/Definitions/Welcome.cs | 6 +- .../install/steps/database.ascx | 72 +++++++++--------- .../install/steps/database.ascx.cs | 23 +++++- .../install/steps/database.ascx.designer.cs | 17 +---- .../install/steps/welcome.ascx.cs | 36 ++++----- 17 files changed, 279 insertions(+), 128 deletions(-) diff --git a/build/Build.bat b/build/Build.bat index 525f3cf41b..fa786d3c85 100644 --- a/build/Build.bat +++ b/build/Build.bat @@ -16,6 +16,8 @@ echo This file is only here so that the containing folder will be included in th echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\media\dummy.txt echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\scripts\dummy.txt echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\usercontrols\dummy.txt +echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\Views\Partials\dummy.txt +echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\Views\MacroPartials\dummy.txt echo This file is only here so that the containing folder will be included in the NuGet package, it is safe to delete. > .\_BuildOutput\WebApp\xslt\dummy.txt ..\src\.nuget\NuGet.exe pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index b4955f36ad..078a3e1963 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -26,8 +26,9 @@ namespace Umbraco.Core private bool _configured; private string _connectionString; private string _providerName; + private DatabaseSchemaResult _result; - internal DatabaseContext(IDatabaseFactory factory) + internal DatabaseContext(IDatabaseFactory factory) { _factory = factory; } @@ -245,7 +246,7 @@ namespace Umbraco.Core Initialize(providerName); } - else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName)) + else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false) { //A valid connectionstring does not exist, but the legacy appSettings key was found, so we'll reconfigure the conn.string. var legacyConnString = ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]; @@ -308,10 +309,13 @@ namespace Umbraco.Core if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName))) return new DatabaseSchemaResult(); - var database = new UmbracoDatabase(_connectionString, ProviderName); - var dbSchema = new DatabaseSchemaCreation(database); - var result = dbSchema.ValidateSchema(); - return result; + if (_result == null) + { + var database = new UmbracoDatabase(_connectionString, ProviderName); + var dbSchema = new DatabaseSchemaCreation(database); + _result = dbSchema.ValidateSchema(); + } + return _result; } internal Result CreateDatabaseSchemaAndDataOrUpgrade() @@ -332,11 +336,13 @@ namespace Umbraco.Core var database = new UmbracoDatabase(_connectionString, ProviderName); var schemaResult = ValidateDatabaseSchema(); var installedVersion = schemaResult.DetermineInstalledVersion(); + string message; //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedVersion.Equals(new Version(0, 0, 0))) { database.CreateDatabaseSchema(); + message = "Installation completed!"; } else { @@ -346,13 +352,28 @@ namespace Umbraco.Core var targetVersion = UmbracoVersion.Current; var runner = new MigrationRunner(configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName); var upgraded = runner.Execute(database, true); + message = "Upgrade completed!"; } - return new Result { Message = "Installation completed!", Success = true, Percentage = "100" }; + return new Result { Message = message, Success = true, Percentage = "100" }; } catch (Exception ex) { - return new Result { Message = ex.Message, Success = false, Percentage = "90" }; + LogHelper.Info("Database configuration failed with the following error and stack trace: " + ex.Message + "\n" + ex.StackTrace); + + if (_result != null) + { + LogHelper.Info("The database schema validation produced the following summary: \n" + _result.GetSummary()); + } + + return new Result + { + Message = + "The database configuration failed with the following message: " + ex.Message + + "\n Please check log file for addtional information (can be found in '/App_Data/Logs/UmbracoTraceLog.txt')", + Success = false, + Percentage = "90" + }; } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 2c3e245dff..756d97ff13 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -132,6 +132,11 @@ namespace Umbraco.Core.Persistence.Migrations.Initial result.Errors.Add(new Tuple("Column", column)); } + //MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks. + //NOTE: At a later point we do other checks for MySql, but ideally it should be necessary to do special checks for different providers. + if (SyntaxConfig.SqlSyntaxProvider is MySqlSyntaxProvider) + return result; + //Check constraints in configured database against constraints in schema var constraintsInDatabase = SyntaxConfig.SqlSyntaxProvider.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList(); var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.StartsWith("FK_")).Select(x => x.Item3).ToList(); diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 9b0ae69270..144f0f7098 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Initial { @@ -59,7 +61,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial return new Version(4, 7, 0); } - return new Version(4, 10, 0); + return new Version(4, 9, 0); } return new Version(0, 0, 0); @@ -71,7 +73,48 @@ namespace Umbraco.Core.Persistence.Migrations.Initial /// A string containing a human readable string with a summary message public string GetSummary() { - return string.Empty; + var sb = new StringBuilder(); + if (Errors.Any() == false) + { + sb.AppendLine("The database schema validation didn't find any errors."); + return sb.ToString(); + } + + //Table error summary + if (Errors.Any(x => x.Item1.Equals("Table"))) + { + sb.AppendLine("The following tables were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Table")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + //Column error summary + if (Errors.Any(x => x.Item1.Equals("Column"))) + { + sb.AppendLine("The following columns were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Column")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + //Constraint error summary + if (Errors.Any(x => x.Item1.Equals("Constraint"))) + { + sb.AppendLine("The following constraints (Primary Keys, Foreign Keys and Indexes) were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Constraint")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + //Unknown constraint error summary + if (Errors.Any(x => x.Item1.Equals("Unknown"))) + { + sb.AppendLine("The following unknown constraints (Primary Keys, Foreign Keys and Indexes) were found in the database, but are not in the current schema:"); + sb.AppendLine(string.Join(",", Errors.Where(x => x.Item1.Equals("Unknown")).Select(x => x.Item2))); + sb.AppendLine(" "); + } + + if (SyntaxConfig.SqlSyntaxProvider is MySqlSyntaxProvider) + { + sb.AppendLine("Please note that the constraints could not be validated because the current dataprovider is MySql."); + } + + return sb.ToString(); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 92f0d617f3..99431e0894 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -42,23 +42,86 @@ namespace Umbraco.Core.Persistence.SqlSyntax public override IEnumerable GetTablesInSchema(Database db) { - var items = db.Fetch("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES"); - return items.Select(x => x.TABLE_NAME).Cast().ToList(); + List list; + try + { + db.OpenSharedConnection(); + var items = + db.Fetch( + "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", + new {TableSchema = db.Connection.Database}); + list = items.Select(x => x.TABLE_NAME).Cast().ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; } public override IEnumerable GetColumnsInSchema(Database db) { - return new List(); + List list; + try + { + db.OpenSharedConnection(); + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @TableSchema", + new {TableSchema = db.Connection.Database}); + list = + items.Select( + item => + new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT, + item.IS_NULLABLE, item.DATA_TYPE)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; } public override IEnumerable> GetConstraintsPerTable(Database db) { - return new List>(); + List> list; + try + { + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", + new {TableSchema = db.Connection.Database}); + list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; } public override IEnumerable> GetConstraintsPerColumn(Database db) { - return new List>(); + List> list; + try + { + //Does not include indexes and constraints are named differently + var items = + db.Fetch( + "SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = @TableSchema", + new {TableSchema = db.Connection.Database}); + list = + items.Select( + item => + new Tuple(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)) + .ToList(); + } + finally + { + db.CloseSharedConnection(); + } + return list; } public override bool DoesTableExist(Database db, string tableName) diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index eda04199bd..7167026d9a 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -72,7 +72,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax { var items = db.Fetch( - "SELECT TABLE_NAME, CONSTRAINT_NAME FROM SELECT * FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE"); + "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_TABLE_USAGE"); return items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 38bd853e11..89b87663a0 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1228,13 +1228,12 @@ namespace Umbraco.Core.Services _publishingStrategy.PublishingFinalized(content); //We need to check if children and their publish state to ensure that we republish content that was previously published - if (HasChildren(content.Id)) + if (omitCacheRefresh == false && HasChildren(content.Id)) { var children = GetDescendants(content); var shouldBeRepublished = children.Where(child => HasPublishedVersion(child.Id)); - if (omitCacheRefresh == false) - _publishingStrategy.PublishingFinalized(shouldBeRepublished, false); + _publishingStrategy.PublishingFinalized(shouldBeRepublished, false); } Audit.Add(AuditTypes.Publish, "Save and Publish performed by user", userId, content.Id); diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index fc8a3a6def..20f0ce5aae 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -76,6 +76,8 @@ + + diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 66cd2854c4..a729aacabb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -313,7 +313,9 @@ - + + Designer + diff --git a/src/Umbraco.Web.UI/install/steps/database.ascx b/src/Umbraco.Web.UI/install/steps/database.ascx index d3446f49cb..3234698de5 100644 --- a/src/Umbraco.Web.UI/install/steps/database.ascx +++ b/src/Umbraco.Web.UI/install/steps/database.ascx @@ -269,10 +269,42 @@
-

Installing Umbraco

-

- The Umbraco database is being configured. This process populates your chosen database with a blank Umbraco instance. -

+ +
+

Installing Umbraco

+

+ The Umbraco database is being configured. This process populates your chosen database with a blank Umbraco instance. +

+
+ +
+ +
+

Upgrading Umbraco

+

+ The Umbraco database is being configured. This process upgrades your Umbraco database. +

+
+ +
@@ -310,6 +342,8 @@ if (json.Success) { $(".btn-box").show(); $('.ui-progressbar-value').css("background-image", "url(../umbraco_client/installer/images/pbar.gif)"); + $(".result-status-container").show(); + $(".progress-status-container").hide(); } else { $(".btn-continue").hide(); $(".btn-back").show(); @@ -320,32 +354,4 @@ }); - - - - -

Database installed

-
-

- Umbraco - <%=UmbracoVersion.Current.ToString(3)%> - has now been copied to your database. Press Continue to proceed. -

-
-
- -

Database upgraded

-
-

- Your database has been upgraded to version: - <%=UmbracoVersion.Current.ToString(3)%>.
- Press Continue to proceed. -

-
-
- -
-
 
- Continue -
-
+ \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index e1934cd61c..d83c0e0cef 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -66,7 +66,7 @@ - + diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs index 0c2b61108f..f5a695e8cf 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Database.cs @@ -41,7 +41,7 @@ namespace umbraco.presentation.install.steps.Definitions //Even though the ConfigurationStatus is blank we try to determine the version if we can connect to the database var result = ApplicationContext.Current.DatabaseContext.ValidateDatabaseSchema(); var determinedVersion = result.DetermineInstalledVersion(); - if(determinedVersion.Equals(new Version(0, 0, 0))) + if (determinedVersion.Equals(new Version(0, 0, 0))) return false; return UmbracoVersion.Current < determinedVersion; diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Welcome.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Welcome.cs index 868f5c6ab5..944eaf4e89 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Welcome.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/Definitions/Welcome.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using umbraco.cms.businesslogic.installer; +using umbraco.cms.businesslogic.installer; namespace umbraco.presentation.install.steps.Definitions { diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx index d3446f49cb..3234698de5 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx @@ -269,10 +269,42 @@
-

Installing Umbraco

-

- The Umbraco database is being configured. This process populates your chosen database with a blank Umbraco instance. -

+ +
+

Installing Umbraco

+

+ The Umbraco database is being configured. This process populates your chosen database with a blank Umbraco instance. +

+
+ +
+ +
+

Upgrading Umbraco

+

+ The Umbraco database is being configured. This process upgrades your Umbraco database. +

+
+ +
@@ -310,6 +342,8 @@ if (json.Success) { $(".btn-box").show(); $('.ui-progressbar-value').css("background-image", "url(../umbraco_client/installer/images/pbar.gif)"); + $(".result-status-container").show(); + $(".progress-status-container").hide(); } else { $(".btn-continue").hide(); $(".btn-back").show(); @@ -320,32 +354,4 @@ }); - - - - -

Database installed

-
-

- Umbraco - <%=UmbracoVersion.Current.ToString(3)%> - has now been copied to your database. Press Continue to proceed. -

-
-
- -

Database upgraded

-
-

- Your database has been upgraded to version: - <%=UmbracoVersion.Current.ToString(3)%>.
- Press Continue to proceed. -

-
-
- -
-
 
- Continue -
-
+ \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs index d05f87af9a..a7a564e904 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.cs @@ -71,8 +71,29 @@ namespace umbraco.presentation.install.steps { // Does the user have to enter a connection string? if (settings.Visible && !Page.IsPostBack) - ShowDatabaseSettings(); + { + //If the connection string is already present in web.config we don't need to show the settings page and we jump to installing/upgrading. + if ( + ConfigurationManager.ConnectionStrings[ + Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName] == null + || + string.IsNullOrEmpty( + ConfigurationManager.ConnectionStrings[ + Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName].ConnectionString)) + { + installProgress.Visible = true; + upgradeProgress.Visible = false; + ShowDatabaseSettings(); + } + else + { + installProgress.Visible = false; + upgradeProgress.Visible = true; + settings.Visible = false; + installing.Visible = true; + } + } } /// diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.designer.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.designer.cs index d54355b647..35cc3f8879 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.designer.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/database.ascx.designer.cs @@ -220,30 +220,21 @@ namespace umbraco.presentation.install.steps { protected global::System.Web.UI.WebControls.PlaceHolder installing; /// - /// confirms control. + /// installProgress control. /// /// /// Auto-generated field. /// To modify move field declaration from designer file to code-behind file. /// - protected global::System.Web.UI.WebControls.Panel confirms; + protected global::System.Web.UI.WebControls.PlaceHolder installProgress; /// - /// installConfirm control. + /// upgradeProgress control. /// /// /// Auto-generated field. /// To modify move field declaration from designer file to code-behind file. /// - protected global::System.Web.UI.WebControls.PlaceHolder installConfirm; - - /// - /// upgradeConfirm control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.PlaceHolder upgradeConfirm; + protected global::System.Web.UI.WebControls.PlaceHolder upgradeProgress; } } diff --git a/src/Umbraco.Web/umbraco.presentation/install/steps/welcome.ascx.cs b/src/Umbraco.Web/umbraco.presentation/install/steps/welcome.ascx.cs index b69ab00bf3..bb5b765a8c 100644 --- a/src/Umbraco.Web/umbraco.presentation/install/steps/welcome.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/install/steps/welcome.ascx.cs @@ -1,14 +1,8 @@ -using umbraco; +using System; +using Umbraco.Core; namespace umbraco.presentation.install { - using System; - using System.Data; - using System.Drawing; - using System.Web; - using System.Web.UI.WebControls; - using System.Web.UI.HtmlControls; - /// /// Summary description for welcome. /// @@ -17,24 +11,24 @@ namespace umbraco.presentation.install protected void Page_Load(object sender, System.EventArgs e) { + var result = ApplicationContext.Current.DatabaseContext.ValidateDatabaseSchema(); + var determinedVersion = result.DetermineInstalledVersion(); + // Display the Umbraco upgrade message if Umbraco is already installed - if (String.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus) == false) + if (String.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus) == false || determinedVersion.Equals(new Version(0, 0, 0)) == false) { ph_install.Visible = false; ph_upgrade.Visible = true; } - - // Check for config! - if (GlobalSettings.Configured) - { - Application.Lock(); - Application["umbracoNeedConfiguration"] = null; - Application.UnLock(); - Response.Redirect(Request.QueryString["url"] ?? "/", true); - } - - - + + // Check for config! + if (GlobalSettings.Configured) + { + Application.Lock(); + Application["umbracoNeedConfiguration"] = null; + Application.UnLock(); + Response.Redirect(Request.QueryString["url"] ?? "/", true); + } } protected void gotoNextStep(object sender, EventArgs e) { From 0dbebaf12ebd3158bd53a15b5ddc18bb854a8ce9 Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 28 Jan 2013 14:31:40 -0100 Subject: [PATCH 06/16] Fixes U4-1557 so the name of a Media item is properly saved. This has also be tested and verified for content. --- .../Models/EntityBase/IUmbracoEntity.cs | 23 ++++++++++------- src/Umbraco.Core/Models/IContentBase.cs | 5 ---- src/Umbraco.Core/Models/IContentTypeBase.cs | 5 ---- .../Models/IDataTypeDefinition.cs | 5 ---- src/umbraco.cms/businesslogic/CMSNode.cs | 25 ++++++++++++++++++- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs b/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs index 7ae9422a25..62130fdab9 100644 --- a/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/EntityBase/IUmbracoEntity.cs @@ -3,29 +3,34 @@ public interface IUmbracoEntity : IAggregateRoot { /// - /// Gets or sets the Id of the Parent entity + /// Profile of the user who created this Entity /// - int ParentId { get; set; } - - /// - /// Gets or sets the sort order of the Entity - /// - int SortOrder { get; set; } + int CreatorId { get; set; } /// /// Gets or sets the level of the Entity /// int Level { get; set; } + /// + /// Gets or Sets the Name of the Entity + /// + string Name { get; set; } + + /// + /// Gets or sets the Id of the Parent Entity + /// + int ParentId { get; set; } + /// /// Gets or sets the path to the Entity /// string Path { get; set; } /// - /// Profile of the user who created this Entity + /// Gets or sets the sort order of the Entity /// - int CreatorId { get; set; } + int SortOrder { get; set; } /// /// Boolean indicating whether this Entity is Trashed or not. diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 42174ee757..269f6b8572 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -10,11 +10,6 @@ namespace Umbraco.Core.Models /// public interface IContentBase : IUmbracoEntity { - /// - /// Gets or Sets the Name of the Content - /// - string Name { get; set; } - /// /// Integer Id of the default ContentType /// diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 2b19dd216c..35abf1e241 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -15,11 +15,6 @@ namespace Umbraco.Core.Models /// string Alias { get; set; } - /// - /// Gets or Sets the Name of the ContentType - /// - string Name { get; set; } - /// /// Gets or Sets the Description for the ContentType /// diff --git a/src/Umbraco.Core/Models/IDataTypeDefinition.cs b/src/Umbraco.Core/Models/IDataTypeDefinition.cs index 3b1391ae8d..e2ed1ef52e 100644 --- a/src/Umbraco.Core/Models/IDataTypeDefinition.cs +++ b/src/Umbraco.Core/Models/IDataTypeDefinition.cs @@ -5,11 +5,6 @@ namespace Umbraco.Core.Models { public interface IDataTypeDefinition : IUmbracoEntity { - /// - /// Gets or sets the name of the current entity - /// - string Name { get; set; } - /// /// Id of the DataType control /// diff --git a/src/umbraco.cms/businesslogic/CMSNode.cs b/src/umbraco.cms/businesslogic/CMSNode.cs index 4838cdc355..9d4091555b 100644 --- a/src/umbraco.cms/businesslogic/CMSNode.cs +++ b/src/umbraco.cms/businesslogic/CMSNode.cs @@ -48,6 +48,7 @@ namespace umbraco.cms.businesslogic private bool _hasChildrenInitialized; private string m_image = "default.png"; private bool? _isTrashed = null; + private IUmbracoEntity _entity; #endregion @@ -55,6 +56,7 @@ namespace umbraco.cms.businesslogic private static readonly string m_DefaultIconCssFile = IOHelper.MapPath(SystemDirectories.Umbraco_client + "/Tree/treeIcons.css"); private static List m_DefaultIconClasses = new List(); + private static void initializeIconClasses() { StreamReader re = File.OpenText(m_DefaultIconCssFile); @@ -394,9 +396,10 @@ namespace umbraco.cms.businesslogic PopulateCMSNodeFromReader(reader); } - protected internal CMSNode(IEntity entity) + protected internal CMSNode(IUmbracoEntity entity) { _id = entity.Id; + _entity = entity; } #endregion @@ -576,6 +579,7 @@ order by level,sortOrder"; /// Moves the CMSNode from the current position in the hierarchy to the target /// /// Target CMSNode id + [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentService.Move() or Umbraco.Core.Services.MediaService.Move()", false)] public virtual void Move(int newParentId) { CMSNode parent = new CMSNode(newParentId); @@ -705,6 +709,9 @@ order by level,sortOrder"; { _sortOrder = value; SqlHelper.ExecuteNonQuery("update umbracoNode set sortOrder = '" + value + "' where id = " + this.Id.ToString()); + + if (_entity != null) + _entity.SortOrder = value; } } @@ -766,6 +773,9 @@ order by level,sortOrder"; { _parentid = value.Id; SqlHelper.ExecuteNonQuery("update umbracoNode set parentId = " + value.Id.ToString() + " where id = " + this.Id.ToString()); + + if (_entity != null) + _entity.ParentId = value.Id; } } @@ -781,6 +791,9 @@ order by level,sortOrder"; { _path = value; SqlHelper.ExecuteNonQuery("update umbracoNode set path = '" + _path + "' where id = " + this.Id.ToString()); + + if (_entity != null) + _entity.Path = value; } } @@ -796,6 +809,9 @@ order by level,sortOrder"; { _level = value; SqlHelper.ExecuteNonQuery("update umbracoNode set level = " + _level.ToString() + " where id = " + this.Id.ToString()); + + if (_entity != null) + _entity.Level = value; } } @@ -915,6 +931,8 @@ order by level,sortOrder"; SqlHelper.CreateParameter("@text", value.Trim()), SqlHelper.CreateParameter("@id", this.Id)); + if (_entity != null) + _entity.Name = value; } } @@ -966,6 +984,9 @@ order by level,sortOrder"; protected void SetText(string txt) { _text = txt; + + if (_entity != null) + _entity.Name = txt; } /// @@ -1086,6 +1107,7 @@ order by level,sortOrder"; _userId = content.CreatorId; _createDate = content.CreateDate; _isTrashed = content.Trashed; + _entity = content; } internal protected void PopulateCMSNodeFromContentTypeBase(IContentTypeBase contentType, Guid objectType) @@ -1100,6 +1122,7 @@ order by level,sortOrder"; _userId = contentType.CreatorId; _createDate = contentType.CreateDate; _isTrashed = false; + _entity = contentType; } #endregion From 0dfe8c08b90c7505302aecaacfde9ec1c5edc5eb Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 28 Jan 2013 14:41:36 -0100 Subject: [PATCH 07/16] Add App_Code, App_Data and App_Plugins folders to be created during app startup --- .../EnsureSystemPathsApplicationStartupHandler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs index 4fa5d515a1..553abb1589 100644 --- a/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs +++ b/src/Umbraco.Web/umbraco.presentation/EnsureSystemPathsApplicationStartupHandler.cs @@ -13,8 +13,10 @@ namespace umbraco.presentation { public EnsureSystemPathsApplicationStartupHandler() { + EnsurePathExists("~/App_Code"); + EnsurePathExists("~/App_Data"); + EnsurePathExists(SystemDirectories.AppPlugins); EnsurePathExists(SystemDirectories.Css); - EnsurePathExists(SystemDirectories.Data); EnsurePathExists(SystemDirectories.MacroScripts); EnsurePathExists(SystemDirectories.Masterpages); EnsurePathExists(SystemDirectories.Media); From 1e1d7ce1cd7d3e83d8d6c6002019c42724f6ee91 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 28 Jan 2013 14:54:51 -0100 Subject: [PATCH 08/16] Fixes U4-1566 6.0.0-RC Exception when assigning HttpPostedFileBase type posted file object to a property --- .../businesslogic/datatype/FileHandlerData.cs | 57 ++++++++++--------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs index 067bef6626..8add1b3f73 100644 --- a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs +++ b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs @@ -18,38 +18,41 @@ namespace umbraco.cms.businesslogic.datatype _thumbnailSizes = thumbnailSizes; } - /// - /// Gets/sets the loaded Conent object which we can resolve from other classes since this class sets it's properties - /// - internal Content LoadedContentItem { get; set; } + /// + /// Gets/sets the loaded Conent object which we can resolve from other classes since this class sets it's properties + /// + internal Content LoadedContentItem { get; set; } - /// - /// Called to ensure we have a valid LoadedContentItem. - /// - /// - private void EnsureLoadedContentItem(Guid version) - { - if (LoadedContentItem == null) - { - LoadedContentItem = Content.GetContentFromVersion(Version); - } - } + /// + /// Called to ensure we have a valid LoadedContentItem. + /// + /// + private void EnsureLoadedContentItem(Guid version) + { + if (LoadedContentItem == null) + { + LoadedContentItem = Content.GetContentFromVersion(Version); + } + } public override object Value { get { return base.Value; } set { - if (value is HttpPostedFile || value is HttpPostedFileBase) + if (value is HttpPostedFile || value is HttpPostedFileBase) { - Stream fileStream = null; + var filename = value is HttpPostedFile + ? ((HttpPostedFile)value).FileName + : ((HttpPostedFileBase)value).FileName; - var file = value as HttpPostedFile; - var name = IOHelper.SafeFileName(file.FileName.Substring(file.FileName.LastIndexOf(IOHelper.DirSepChar) + 1, file.FileName.Length - file.FileName.LastIndexOf(IOHelper.DirSepChar) - 1).ToLower()); - fileStream = file.InputStream; + var name = IOHelper.SafeFileName(filename.Substring(filename.LastIndexOf(IOHelper.DirSepChar) + 1, filename.Length - filename.LastIndexOf(IOHelper.DirSepChar) - 1).ToLower()); - // handle upload + var fileStream = value is HttpPostedFile + ? ((HttpPostedFile)value).InputStream + : ((HttpPostedFileBase)value).InputStream; + // handle upload if (name != String.Empty) { string fileName = UmbracoSettings.UploadAllowDirectories @@ -91,8 +94,8 @@ namespace umbraco.cms.businesslogic.datatype if (uploadFieldConfigNode != null) { - EnsureLoadedContentItem(Version); - FillProperties(uploadFieldConfigNode, LoadedContentItem, um); + EnsureLoadedContentItem(Version); + FillProperties(uploadFieldConfigNode, LoadedContentItem, um); } } @@ -127,12 +130,12 @@ namespace umbraco.cms.businesslogic.datatype { // get the current document //Content legacy = Content.GetContentFromVersion(Version); - EnsureLoadedContentItem(Version); + EnsureLoadedContentItem(Version); // only add dimensions to web images UpdateContentProperty(uploadFieldConfigNode, LoadedContentItem, "widthFieldAlias", String.Empty); - UpdateContentProperty(uploadFieldConfigNode, LoadedContentItem, "heightFieldAlias", String.Empty); - UpdateContentProperty(uploadFieldConfigNode, LoadedContentItem, "lengthFieldAlias", String.Empty); - UpdateContentProperty(uploadFieldConfigNode, LoadedContentItem, "extensionFieldAlias", String.Empty); + UpdateContentProperty(uploadFieldConfigNode, LoadedContentItem, "heightFieldAlias", String.Empty); + UpdateContentProperty(uploadFieldConfigNode, LoadedContentItem, "lengthFieldAlias", String.Empty); + UpdateContentProperty(uploadFieldConfigNode, LoadedContentItem, "extensionFieldAlias", String.Empty); } } } From 463c3cf1d543a10309a6183c015e4a2a0cb449b8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 28 Jan 2013 15:03:05 -0100 Subject: [PATCH 09/16] Improve variable name and make it use camel case. --- src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs index 8add1b3f73..b19ca3f14a 100644 --- a/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs +++ b/src/umbraco.cms/businesslogic/datatype/FileHandlerData.cs @@ -42,11 +42,11 @@ namespace umbraco.cms.businesslogic.datatype { if (value is HttpPostedFile || value is HttpPostedFileBase) { - var filename = value is HttpPostedFile + var postedFileName = value is HttpPostedFile ? ((HttpPostedFile)value).FileName : ((HttpPostedFileBase)value).FileName; - var name = IOHelper.SafeFileName(filename.Substring(filename.LastIndexOf(IOHelper.DirSepChar) + 1, filename.Length - filename.LastIndexOf(IOHelper.DirSepChar) - 1).ToLower()); + var name = IOHelper.SafeFileName(postedFileName.Substring(postedFileName.LastIndexOf(IOHelper.DirSepChar) + 1, postedFileName.Length - postedFileName.LastIndexOf(IOHelper.DirSepChar) - 1).ToLower()); var fileStream = value is HttpPostedFile ? ((HttpPostedFile)value).InputStream From bc99a6f6c007b283bd9cd4350d29c5be430f81bd Mon Sep 17 00:00:00 2001 From: Morten Christensen Date: Mon, 28 Jan 2013 15:37:18 -0100 Subject: [PATCH 10/16] Possible fix for U4-1466 which surfaced with the Standard MVC Starterkit package. --- src/umbraco.cms/businesslogic/Content.cs | 2 +- src/umbraco.cms/businesslogic/web/Document.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index 9ec92fcdf8..012b2759ef 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -157,7 +157,7 @@ namespace umbraco.cms.businesslogic { if (_contentTypeIcon == null && this.ContentType != null) _contentTypeIcon = this.ContentType.IconUrl; - return _contentTypeIcon; + return _contentTypeIcon ?? string.Empty; } set { diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 92f9defa4e..47f6c43fc4 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1353,6 +1353,12 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Obsolete", false)] protected override void setupNode() { + if (Id == -1 || Id == -20) + { + base.setupNode(); + return; + } + var content = Version == Guid.Empty ? ApplicationContext.Current.Services.ContentService.GetById(Id) : ApplicationContext.Current.Services.ContentService.GetByVersion(Version); From 93bdfc891526056991534c7756d7e2c405a509fb Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 28 Jan 2013 17:13:18 +0000 Subject: [PATCH 11/16] Fixed U4-1562 YSOD saving a Slider property editor set to DB type Nvarchar. If the Int DbType isn't selected, the DataType will save as string/Nvarchar. --- .../Slider/SliderControl.cs | 7 + .../Slider/SliderDataType.cs | 2 +- .../Slider/SliderOptions.cs | 140 +++++++++--------- .../Slider/SliderPrevalueEditor.cs | 40 ++--- 4 files changed, 98 insertions(+), 91 deletions(-) diff --git a/src/umbraco.editorControls/Slider/SliderControl.cs b/src/umbraco.editorControls/Slider/SliderControl.cs index b8c4fb5006..a1a060c604 100644 --- a/src/umbraco.editorControls/Slider/SliderControl.cs +++ b/src/umbraco.editorControls/Slider/SliderControl.cs @@ -174,6 +174,13 @@ namespace umbraco.editorControls.Slider (hasMultipleValues ? "{ allow: ',' }" : string.Empty)); var javascript = string.Concat(""); writer.WriteLine(javascript); + + if (this.Options.EnableRange || !string.IsNullOrEmpty(this.Options.RangeValue)) + { + // add CSS to override the default style for '.ui-slider-range' (which is used for the DateTime Picker) + var css = string.Format("", this.ClientID); + writer.WriteLine(css); + } } } } \ No newline at end of file diff --git a/src/umbraco.editorControls/Slider/SliderDataType.cs b/src/umbraco.editorControls/Slider/SliderDataType.cs index ccd7ecc6e9..5ad6919821 100644 --- a/src/umbraco.editorControls/Slider/SliderDataType.cs +++ b/src/umbraco.editorControls/Slider/SliderDataType.cs @@ -149,7 +149,7 @@ namespace umbraco.editorControls.Slider } else { - this.Data.Value = value1; + this.Data.Value = value1.ToString(); } } } diff --git a/src/umbraco.editorControls/Slider/SliderOptions.cs b/src/umbraco.editorControls/Slider/SliderOptions.cs index 4d628d2779..b7c84a7bf1 100644 --- a/src/umbraco.editorControls/Slider/SliderOptions.cs +++ b/src/umbraco.editorControls/Slider/SliderOptions.cs @@ -3,90 +3,90 @@ using umbraco.cms.businesslogic.datatype; namespace umbraco.editorControls.Slider { - /// - /// The options for the Slider data-type. - /// - public class SliderOptions : AbstractOptions - { - /// - /// Initializes a new instance of the class. - /// - public SliderOptions() - : base() - { - } + /// + /// The options for the Slider data-type. + /// + public class SliderOptions : AbstractOptions + { + /// + /// Initializes a new instance of the class. + /// + public SliderOptions() + : base() + { + } - /// - /// Initializes a new instance of the class. - /// - /// if set to true [loads defaults]. - public SliderOptions(bool loadDefaults) - : base(loadDefaults) - { - } + /// + /// Initializes a new instance of the class. + /// + /// if set to true [loads defaults]. + public SliderOptions(bool loadDefaults) + : base(loadDefaults) + { + } - /// - /// Gets or sets a value indicating whether [enable range]. - /// - /// true if [enable range]; otherwise, false. - [DefaultValue(false)] - public bool EnableRange { get; set; } + /// + /// Gets or sets a value indicating whether [enable range]. + /// + /// true if [enable range]; otherwise, false. + [DefaultValue(false)] + public bool EnableRange { get; set; } - /// - /// Gets or sets a value indicating whether [enable step]. - /// - /// true if [enable step]; otherwise, false. - [DefaultValue(true)] - public bool EnableStep { get; set; } + /// + /// Gets or sets a value indicating whether [enable step]. + /// + /// true if [enable step]; otherwise, false. + [DefaultValue(true)] + public bool EnableStep { get; set; } - /// - /// Gets or sets the max value. - /// - /// The max value. - [DefaultValue(100)] + /// + /// Gets or sets the max value. + /// + /// The max value. + [DefaultValue(100)] public double MaxValue { get; set; } - /// - /// Gets or sets the min value. - /// - /// The min value. - [DefaultValue(0)] + /// + /// Gets or sets the min value. + /// + /// The min value. + [DefaultValue(0)] public double MinValue { get; set; } - /// - /// Gets or sets the orientation. - /// - /// The orientation. - [DefaultValue("hortizontal")] - public string Orientation { get; set; } + /// + /// Gets or sets the orientation. + /// + /// The orientation. + [DefaultValue("hortizontal")] + public string Orientation { get; set; } - /// - /// Gets or sets the range value. - /// - /// The range value. - [DefaultValue("")] - public string RangeValue { get; set; } + /// + /// Gets or sets the range value. + /// + /// The range value. + [DefaultValue("")] + public string RangeValue { get; set; } - /// - /// Gets or sets the step. - /// - /// The step. - [DefaultValue(5)] + /// + /// Gets or sets the step. + /// + /// The step. + [DefaultValue(5)] public double StepValue { get; set; } //public int StepValue { get; set; } - /// - /// Gets or sets the value. - /// - /// The value. - [DefaultValue(50)] + /// + /// Gets or sets the value. + /// + /// The value. + [DefaultValue(50)] public double Value { get; set; } - /// - /// Gets or sets the second value. - /// - /// The second value. - [DefaultValue(0)] + /// + /// Gets or sets the second value. + /// + /// The second value. + [DefaultValue(0)] public double Value2 { get; set; } /// @@ -94,5 +94,5 @@ namespace umbraco.editorControls.Slider /// [DefaultValue(DBTypes.Integer)] public DBTypes DBType { get; set; } - } + } } diff --git a/src/umbraco.editorControls/Slider/SliderPrevalueEditor.cs b/src/umbraco.editorControls/Slider/SliderPrevalueEditor.cs index fd95b67a9d..706ffee468 100644 --- a/src/umbraco.editorControls/Slider/SliderPrevalueEditor.cs +++ b/src/umbraco.editorControls/Slider/SliderPrevalueEditor.cs @@ -220,41 +220,41 @@ namespace umbraco.editorControls.Slider var javascriptMethod = string.Format( @" $('#{0}').click(function(){{ - var disable = !$(this).attr('checked'); - $('#{1},#{3}').attr('disabled', disable); - $('#{6}').val(disable && !checkDecimals() ? 'Integer' : 'Nvarchar'); - if(!disable) disable = $('#{1}').val() != ''; + var disable = !$(this).attr('checked'); + $('#{1},#{3}').attr('disabled', disable); + $('#{6}').val(disable && !checkDecimals() ? 'Integer' : 'Nvarchar'); + if(!disable) disable = $('#{1}').val() != ''; }}); $('#{1}').change(function(){{ - var disable = $(this).val() != ''; - $('#{3}').attr('disabled', disable); + var disable = $(this).val() != ''; + $('#{3}').attr('disabled', disable); }}); $('#{4}').click(function(){{ - var disable = !$(this).attr('checked'); - $('#{5}').attr('disabled', disable); + var disable = !$(this).attr('checked'); + $('#{5}').attr('disabled', disable); }}); $('#{6}').change(function(){{ - var disable = $(this).val() == 'Integer'; + var disable = $(this).val() == 'Integer'; if (checkDecimals() && disable) {{ $('#{6}').val('Nvarchar'); alert('Please remove decimal points below if you wish to use the Integer datatype'); }} else {{ - $('#{0}').removeAttr('checked'); - $('#{1},#{3}').attr('disabled', disable); + $('#{0}').removeAttr('checked'); + $('#{1},#{3}').attr('disabled', disable); }} }}); $('.slider-numeric').keydown(function(event) {{ - // Allow only backspace and delete - if ( event.keyCode == 46 || event.keyCode == 8 || ($(this).hasClass('slider-decimal') && (event.keyCode == 110 || event.keyCode == 190))) {{ - // let it happen, don't do anything - }} else {{ - // Ensure that it is a number and stop the keypress - if ( (event.keyCode < 48 || event.keyCode > 57 ) && (event.keyCode < 96 || event.keyCode > 105 ) ) {{ - event.preventDefault(); - }} - }} + // Allow only backspace and delete + if ( event.keyCode == 46 || event.keyCode == 8 || ($(this).hasClass('slider-decimal') && (event.keyCode == 110 || event.keyCode == 190))) {{ + // let it happen, don't do anything + }} else {{ + // Ensure that it is a number and stop the keypress + if ( (event.keyCode < 48 || event.keyCode > 57 ) && (event.keyCode < 96 || event.keyCode > 105 ) ) {{ + event.preventDefault(); + }} + }} }}); $('.slider-numeric').keyup(function(event) {{ if ($('#{6}').val() != 'Nvarchar' && checkDecimals()) {{ From df1cfaba281f2bc08f425c376fa1a8d6d9a96a4f Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 29 Jan 2013 07:50:55 +0600 Subject: [PATCH 12/16] Applies patch for #U4-1536 --- src/Umbraco.Web/Mvc/RenderMvcController.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs index ac65d28d3d..62ecb2ded3 100644 --- a/src/Umbraco.Web/Mvc/RenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -60,6 +60,14 @@ namespace Umbraco.Web.Mvc get { return ApplicationContext.DatabaseContext; } } + /// + /// Returns the Current published content item for rendering the content + /// + protected IPublishedContent CurrentPage + { + get { return PublishedContentRequest.PublishedContent; } + } + //TODO: make this protected once we make PublishedContentRequest not internal after we figure out what it should actually contain /// /// Returns the current PublishedContentRequest From 7ffb3cfb7ffa7dc7678f9f118cf467db6e73e673 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 29 Jan 2013 08:35:09 +0600 Subject: [PATCH 13/16] Fixes: #U4-1458 - due to a null check on PublishedContentRequest on UmbracoContext --- src/Umbraco.Web/Templates/TemplateRenderer.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Templates/TemplateRenderer.cs b/src/Umbraco.Web/Templates/TemplateRenderer.cs index ce2da96730..0967f0c360 100644 --- a/src/Umbraco.Web/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web/Templates/TemplateRenderer.cs @@ -1,5 +1,7 @@ using System; +using System.Globalization; using System.IO; +using System.Linq; using System.Web.Compilation; using System.Web.Mvc; using System.Web.Routing; @@ -8,6 +10,7 @@ using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.Routing; using umbraco; +using umbraco.cms.businesslogic.language; namespace Umbraco.Web.Templates { @@ -61,8 +64,21 @@ namespace Umbraco.Web.Templates return; } - //set the culture to the same as is currently rendering - contentRequest.Culture = _umbracoContext.PublishedContentRequest.Culture; + //in some cases the UmbracoContext will not have a PublishedContentRequest assigned to it if we are not in the + //execution of a front-end rendered page. In this case set the culture to the default. + //set the culture to the same as is currently rendering + if (_umbracoContext.PublishedContentRequest == null) + { + var defaultLanguage = Language.GetAllAsList().FirstOrDefault(); + contentRequest.Culture = defaultLanguage == null + ? CultureInfo.CurrentUICulture + : new CultureInfo(defaultLanguage.CultureAlias); + } + else + { + contentRequest.Culture = _umbracoContext.PublishedContentRequest.Culture; + } + //set the doc that was found by id contentRequest.PublishedContent = doc; //set the template, either based on the AltTemplate found or the standard template of the doc From a2b5b1343287cedb615c07956077eeb65da338b6 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 29 Jan 2013 08:38:34 +0600 Subject: [PATCH 14/16] Fixes missing 'using' reference --- src/Umbraco.Web/Mvc/RenderMvcController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web/Mvc/RenderMvcController.cs b/src/Umbraco.Web/Mvc/RenderMvcController.cs index 62ecb2ded3..558d98aabb 100644 --- a/src/Umbraco.Web/Mvc/RenderMvcController.cs +++ b/src/Umbraco.Web/Mvc/RenderMvcController.cs @@ -3,6 +3,7 @@ using System.IO; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Web.Routing; From 65980e8c765da27acba8b3c3162a2bd0ae6bc37c Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 29 Jan 2013 09:45:12 +0600 Subject: [PATCH 15/16] Fixes: #U4-1546 - Moves IApplicationEventHandler to Umbraco.Core and creates UmbracoApplicationBase object in the Core. Changes signatures of IApplicationEventHandler methods to accept an UmbracoApplicationBase object instead of UmbracoApplication. This allows us to execute all IApplicationEventHandler's outside of the web context. Added unit test to support booting the application outside of the web context. --- src/Umbraco.Core/CoreBootManager.cs | 54 +++++++- .../IApplicationEventHandler.cs | 15 ++- .../ApplicationEventsResolver.cs | 3 +- src/Umbraco.Core/PluginManager.cs | 9 ++ src/Umbraco.Core/Umbraco.Core.csproj | 3 + src/Umbraco.Core/UmbracoApplicationBase.cs | 119 ++++++++++++++++++ .../BootManagers/CoreBootManagerTests.cs | 93 ++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/CacheHelperExtensions.cs | 14 +-- src/Umbraco.Web/LegacyScheduledTasks.cs | 26 ++-- src/Umbraco.Web/Umbraco.Web.csproj | 2 - src/Umbraco.Web/UmbracoApplication.cs | 99 +-------------- src/Umbraco.Web/WebBootManager.cs | 61 +++------ .../umbraco/Search/ExamineEvents.cs | 8 +- .../PluginManagerExtensions.cs | 10 -- 15 files changed, 330 insertions(+), 187 deletions(-) rename src/{Umbraco.Web => Umbraco.Core}/IApplicationEventHandler.cs (60%) rename src/{Umbraco.Web => Umbraco.Core/ObjectResolution}/ApplicationEventsResolver.cs (90%) create mode 100644 src/Umbraco.Core/UmbracoApplicationBase.cs create mode 100644 src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index eda2cf494c..9884fab1ce 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -30,10 +30,21 @@ namespace Umbraco.Core private bool _isInitialized = false; private bool _isStarted = false; private bool _isComplete = false; - + private readonly UmbracoApplicationBase _umbracoApplication; protected ApplicationContext ApplicationContext { get; private set; } - public virtual IBootManager Initialize() + protected UmbracoApplicationBase UmbracoApplication + { + get { return _umbracoApplication; } + } + + public CoreBootManager(UmbracoApplicationBase umbracoApplication) + { + if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); + _umbracoApplication = umbracoApplication; + } + + public virtual IBootManager Initialize() { if (_isInitialized) throw new InvalidOperationException("The boot manager has already been initialized"); @@ -56,15 +67,41 @@ namespace Umbraco.Core //initialize the DatabaseContext dbContext.Initialize(); + InitializeApplicationEventsResolver(); + InitializeResolvers(); + + //now we need to call the initialize methods + ApplicationEventsResolver.Current.ApplicationEventHandlers + .ForEach(x => x.OnApplicationInitialized(UmbracoApplication, ApplicationContext)); + _isInitialized = true; return this; } + /// + /// Special method to initialize the ApplicationEventsResolver and any modifications required for it such + /// as adding custom types to the resolver. + /// + protected virtual void InitializeApplicationEventsResolver() + { + //find and initialize the application startup handlers, we need to initialize this resolver here because + //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to + //events and to call their events during bootup. + //ApplicationStartupHandler.RegisterHandlers(); + //... and set the special flag to let us resolve before frozen resolution + ApplicationEventsResolver.Current = new ApplicationEventsResolver( + PluginManager.Current.ResolveApplicationStartupHandlers()) + { + CanResolveBeforeFrozen = true + }; + } + /// - /// Fires after initialization and calls the callback to allow for customizations to occur + /// Fires after initialization and calls the callback to allow for customizations to occur & + /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called /// /// /// @@ -78,6 +115,10 @@ namespace Umbraco.Core afterStartup(ApplicationContext.Current); } + //call OnApplicationStarting of each application events handler + ApplicationEventsResolver.Current.ApplicationEventHandlers + .ForEach(x => x.OnApplicationStarting(UmbracoApplication, ApplicationContext)); + _isStarted = true; return this; @@ -104,8 +145,15 @@ namespace Umbraco.Core afterComplete(ApplicationContext.Current); } + //call OnApplicationStarting of each application events handler + ApplicationEventsResolver.Current.ApplicationEventHandlers + .ForEach(x => x.OnApplicationStarted(UmbracoApplication, ApplicationContext)); + _isComplete = true; + // we're ready to serve content! + ApplicationContext.IsReady = true; + return this; } diff --git a/src/Umbraco.Web/IApplicationEventHandler.cs b/src/Umbraco.Core/IApplicationEventHandler.cs similarity index 60% rename from src/Umbraco.Web/IApplicationEventHandler.cs rename to src/Umbraco.Core/IApplicationEventHandler.cs index 63c34d1589..e382aa8568 100644 --- a/src/Umbraco.Web/IApplicationEventHandler.cs +++ b/src/Umbraco.Core/IApplicationEventHandler.cs @@ -1,7 +1,6 @@ -using Umbraco.Core; using umbraco.interfaces; -namespace Umbraco.Web +namespace Umbraco.Core { /// /// Custom IApplicationStartupHandler that auto subscribes to the applications events @@ -11,23 +10,23 @@ namespace Umbraco.Web /// /// ApplicationContext is created and other static objects that require initialization have been setup /// - /// + /// /// - void OnApplicationInitialized(UmbracoApplication httpApplication, ApplicationContext applicationContext); + void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext); /// /// All resolvers have been initialized but resolution is not frozen so they can be modified in this method /// - /// + /// /// - void OnApplicationStarting(UmbracoApplication httpApplication, ApplicationContext applicationContext); + void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext); /// /// Bootup is completed, this allows you to perform any other bootup logic required for the application. /// Resolution is frozen so now they can be used to resolve instances. /// - /// + /// /// - void OnApplicationStarted(UmbracoApplication httpApplication, ApplicationContext applicationContext); + void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext); } } \ No newline at end of file diff --git a/src/Umbraco.Web/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs similarity index 90% rename from src/Umbraco.Web/ApplicationEventsResolver.cs rename to src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index 93edaae441..7f5b6f6365 100644 --- a/src/Umbraco.Web/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.ObjectResolution; using umbraco.interfaces; -namespace Umbraco.Web +namespace Umbraco.Core.ObjectResolution { /// /// A resolver to return all IApplicationEvents objects diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index 4f1f18c2ab..f0514af628 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -396,6 +396,15 @@ namespace Umbraco.Core private readonly HashSet _types = new HashSet(); private IEnumerable _assemblies; + /// + /// Returns all available IApplicationStartupHandler objects + /// + /// + internal IEnumerable ResolveApplicationStartupHandlers() + { + return ResolveTypes(); + } + /// /// Returns all classes attributed with XsltExtensionAttribute attribute /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 96e43ad6a7..2d8ea1de40 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -137,6 +137,7 @@ + @@ -195,6 +196,7 @@ + @@ -650,6 +652,7 @@ + diff --git a/src/Umbraco.Core/UmbracoApplicationBase.cs b/src/Umbraco.Core/UmbracoApplicationBase.cs new file mode 100644 index 0000000000..df241c2bd3 --- /dev/null +++ b/src/Umbraco.Core/UmbracoApplicationBase.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Hosting; +using Umbraco.Core.Logging; + +namespace Umbraco.Core +{ + + /// + /// The abstract class for the Umbraco HttpApplication + /// + /// + /// This is exposed in the core so that we can have the IApplicationEventHandler in the core project so that + /// IApplicationEventHandler's can fire/execute outside of the web contenxt (i.e. in console applications) + /// + public abstract class UmbracoApplicationBase : System.Web.HttpApplication + { + + public static event EventHandler ApplicationStarting; + public static event EventHandler ApplicationStarted; + + /// + /// Boots up the Umbraco application + /// + internal void StartApplication(object sender, EventArgs e) + { + //boot up the application + GetBootManager() + .Initialize() + .Startup(appContext => OnApplicationStarting(sender, e)) + .Complete(appContext => OnApplicationStarted(sender, e)); + } + + /// + /// Initializes the Umbraco application + /// + /// + /// + protected void Application_Start(object sender, EventArgs e) + { + StartApplication(sender, e); + } + + /// + /// Developers can override this method to modify objects on startup + /// + /// + /// + protected virtual void OnApplicationStarting(object sender, EventArgs e) + { + if (ApplicationStarting != null) + ApplicationStarting(sender, e); + } + + /// + /// Developers can override this method to do anything they need to do once the application startup routine is completed. + /// + /// + /// + protected virtual void OnApplicationStarted(object sender, EventArgs e) + { + if (ApplicationStarted != null) + ApplicationStarted(sender, e); + } + + /// + /// A method that can be overridden to invoke code when the application has an error. + /// + /// + /// + protected virtual void OnApplicationError(object sender, EventArgs e) + { + + } + + protected void Application_Error(object sender, EventArgs e) + { + // Code that runs when an unhandled error occurs + + // Get the exception object. + var exc = Server.GetLastError(); + + // Ignore HTTP errors + if (exc.GetType() == typeof(HttpException)) + { + return; + } + + LogHelper.Error("An unhandled exception occurred", exc); + + OnApplicationError(sender, e); + } + + /// + /// A method that can be overridden to invoke code when the application shuts down. + /// + /// + /// + protected virtual void OnApplicationEnd(object sender, EventArgs e) + { + + } + + protected void Application_End(object sender, EventArgs e) + { + if (SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted) + { + LogHelper.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); + } + OnApplicationEnd(sender, e); + } + + protected abstract IBootManager GetBootManager(); + + } +} diff --git a/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs b/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs new file mode 100644 index 0000000000..92212506ba --- /dev/null +++ b/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Tests.BootManagers +{ + [TestFixture] + public class CoreBootManagerTests + { + + private TestApp _testApp; + + [SetUp] + public void Setup() + { + _testApp = new TestApp(); + } + + /// + /// test application using a CoreBootManager instance to boot + /// + public class TestApp : UmbracoApplicationBase + { + protected override IBootManager GetBootManager() + { + return new TestBootManager(this); + } + } + + /// + /// Test boot manager to add a custom application event handler + /// + public class TestBootManager : CoreBootManager + { + public TestBootManager(UmbracoApplicationBase umbracoApplication) + : base(umbracoApplication) + { + } + + protected override void InitializeApplicationEventsResolver() + { + //create an empty resolver so we can add our own custom ones (don't type find) + ApplicationEventsResolver.Current = new ApplicationEventsResolver( + Enumerable.Empty()) + { + CanResolveBeforeFrozen = true + }; + ApplicationEventsResolver.Current.AddType(); + } + } + + /// + /// test event handler + /// + public class TestApplicationEventHandler : IApplicationEventHandler + { + public static bool Initialized = false; + public static bool Starting = false; + public static bool Started = false; + + public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + Initialized = true; + } + + public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + Starting = true; + } + + public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + Started = true; + } + } + + [Test] + public void Handle_IApplicationEventHandler_Objects_Outside_Web_Context() + { + _testApp.StartApplication(_testApp, new EventArgs()); + + Assert.IsTrue(TestApplicationEventHandler.Initialized); + Assert.IsTrue(TestApplicationEventHandler.Starting); + Assert.IsTrue(TestApplicationEventHandler.Started); + } + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a729aacabb..814bdb938e 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -123,6 +123,7 @@ + diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs index 6e81bd844d..9ecc14e785 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -24,31 +24,31 @@ namespace Umbraco.Web /// public sealed class CacheHelperApplicationEventListener : IApplicationEventHandler { - public void OnApplicationInitialized(UmbracoApplication httpApplication, ApplicationContext applicationContext) + public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { - if (ApplicationContext.Current != null) + if (applicationContext != null) { //bind to events to clear the cache, after publish, after media save and after member save Document.AfterPublish += (sender, args) => - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + applicationContext.ApplicationCache.ClearPartialViewCache(); global::umbraco.cms.businesslogic.media.Media.AfterSave += (sender, args) => - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + applicationContext.ApplicationCache.ClearPartialViewCache(); global::umbraco.cms.businesslogic.member.Member.AfterSave += (sender, args) => - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + applicationContext.ApplicationCache.ClearPartialViewCache(); } } - public void OnApplicationStarting(UmbracoApplication httpApplication, ApplicationContext applicationContext) + public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { } - public void OnApplicationStarted(UmbracoApplication httpApplication, ApplicationContext applicationContext) + public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { } } diff --git a/src/Umbraco.Web/LegacyScheduledTasks.cs b/src/Umbraco.Web/LegacyScheduledTasks.cs index 1ebf9d7903..f79b383b06 100644 --- a/src/Umbraco.Web/LegacyScheduledTasks.cs +++ b/src/Umbraco.Web/LegacyScheduledTasks.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Web; using System.Web.Caching; +using Umbraco.Core; using Umbraco.Core.Logging; using global::umbraco.BusinessLogic; @@ -17,17 +18,20 @@ namespace Umbraco.Web internal sealed class LegacyScheduledTasks : IApplicationEventHandler { - Timer pingTimer; - Timer publishingTimer; - CacheItemRemovedCallback OnCacheRemove; + Timer _pingTimer; + Timer _publishingTimer; + CacheItemRemovedCallback _onCacheRemove; - public void OnApplicationInitialized(UmbracoApplication httpApplication, Core.ApplicationContext applicationContext) + public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, Core.ApplicationContext applicationContext) { // nothing yet } - public void OnApplicationStarting(UmbracoApplication httpApplication, Core.ApplicationContext applicationContext) - { + public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, Core.ApplicationContext applicationContext) + { + if (umbracoApplication.Context == null) + return; + // time to setup the tasks // these are the legacy tasks @@ -35,16 +39,16 @@ namespace Umbraco.Web // of course we should have a proper scheduler, see #U4-809 // ping/keepalive - pingTimer = new Timer(new TimerCallback(global::umbraco.presentation.keepAliveService.PingUmbraco), httpApplication.Context, 60000, 300000); + _pingTimer = new Timer(new TimerCallback(global::umbraco.presentation.keepAliveService.PingUmbraco), umbracoApplication.Context, 60000, 300000); // (un)publishing _and_ also run scheduled tasks (!) - publishingTimer = new Timer(new TimerCallback(global::umbraco.presentation.publishingService.CheckPublishing), httpApplication.Context, 30000, 60000); + _publishingTimer = new Timer(new TimerCallback(global::umbraco.presentation.publishingService.CheckPublishing), umbracoApplication.Context, 30000, 60000); // log scrubbing AddTask(LOG_SCRUBBER_TASK_NAME, GetLogScrubbingInterval()); } - public void OnApplicationStarted(UmbracoApplication httpApplication, Core.ApplicationContext applicationContext) + public void OnApplicationStarted(UmbracoApplicationBase httpApplication, Core.ApplicationContext applicationContext) { // nothing } @@ -88,10 +92,10 @@ namespace Umbraco.Web private void AddTask(string name, int seconds) { - OnCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved); + _onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved); HttpRuntime.Cache.Insert(name, seconds, null, DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration, - CacheItemPriority.NotRemovable, OnCacheRemove); + CacheItemPriority.NotRemovable, _onCacheRemove); } public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5ce59b214e..24cc484f92 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -245,7 +245,6 @@ Properties\SolutionInfo.cs - @@ -290,7 +289,6 @@ ASPXCodeBehind - diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs index c3d32c2ce2..7a47847b5e 100644 --- a/src/Umbraco.Web/UmbracoApplication.cs +++ b/src/Umbraco.Web/UmbracoApplication.cs @@ -15,99 +15,12 @@ namespace Umbraco.Web /// /// The Umbraco global.asax class /// - public class UmbracoApplication : System.Web.HttpApplication + public class UmbracoApplication : UmbracoApplicationBase { - public UmbracoApplication() - { - _bootManager = new WebBootManager(this); - } - - private readonly IBootManager _bootManager; - - public static event EventHandler ApplicationStarting; - public static event EventHandler ApplicationStarted; - - /// - /// Initializes the Umbraco application - /// - /// - /// - protected void Application_Start(object sender, EventArgs e) - { - //boot up the application - _bootManager - .Initialize() - .Startup(appContext => OnApplicationStarting(sender, e)) - .Complete(appContext => OnApplicationStarted(sender, e)); - } - - /// - /// Developers can override this method to modify objects on startup - /// - /// - /// - protected virtual void OnApplicationStarting(object sender, EventArgs e) - { - if (ApplicationStarting != null) - ApplicationStarting(sender, e); - } - - /// - /// Developers can override this method to do anything they need to do once the application startup routine is completed. - /// - /// - /// - protected virtual void OnApplicationStarted(object sender, EventArgs e) - { - if (ApplicationStarted != null) - ApplicationStarted(sender, e); - } - - /// - /// A method that can be overridden to invoke code when the application has an error. - /// - /// - /// - protected virtual void OnApplicationError(object sender, EventArgs e) - { - - } - - protected void Application_Error(object sender, EventArgs e) - { - // Code that runs when an unhandled error occurs - - // Get the exception object. - var exc = Server.GetLastError(); - - // Ignore HTTP errors - if (exc.GetType() == typeof(HttpException)) - { - return; - } - - LogHelper.Error("An unhandled exception occurred", exc); - - OnApplicationError(sender, e); - } - - /// - /// A method that can be overridden to invoke code when the application shuts down. - /// - /// - /// - protected virtual void OnApplicationEnd(object sender, EventArgs e) - { - - } - - protected void Application_End(object sender, EventArgs e) - { - if (SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted) - { - LogHelper.Info("Application shutdown. Reason: " + HostingEnvironment.ShutdownReason); - } - OnApplicationEnd(sender, e); - } + + protected override IBootManager GetBootManager() + { + return new WebBootManager(this); + } } } diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 58992253ff..2127f0f014 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Dynamics; +using Umbraco.Core.ObjectResolution; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Dictionary; using Umbraco.Web.Media; @@ -27,9 +28,8 @@ namespace Umbraco.Web public class WebBootManager : CoreBootManager { private readonly bool _isForTesting; - private readonly UmbracoApplication _umbracoApplication; - public WebBootManager(UmbracoApplication umbracoApplication) + public WebBootManager(UmbracoApplicationBase umbracoApplication) : this(umbracoApplication, false) { @@ -40,11 +40,10 @@ namespace Umbraco.Web /// /// /// - internal WebBootManager(UmbracoApplication umbracoApplication, bool isForTesting) + internal WebBootManager(UmbracoApplicationBase umbracoApplication, bool isForTesting) + : base(umbracoApplication) { - _isForTesting = isForTesting; - _umbracoApplication = umbracoApplication; - if (umbracoApplication == null) throw new ArgumentNullException("umbracoApplication"); + _isForTesting = isForTesting; } /// @@ -71,43 +70,18 @@ namespace Umbraco.Web //set model binder ModelBinders.Binders.Add(new KeyValuePair(typeof(RenderModel), new RenderModelBinder())); - - //find and initialize the application startup handlers, we need to initialize this resolver here because - //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to - //events and to call their events during bootup. - //ApplicationStartupHandler.RegisterHandlers(); - //... and set the special flag to let us resolve before frozen resolution - ApplicationEventsResolver.Current = new ApplicationEventsResolver( - PluginManager.Current.ResolveApplicationStartupHandlers()) - { - CanResolveBeforeFrozen = true - }; - //add the internal types since we don't want to mark these public - ApplicationEventsResolver.Current.AddType(); - ApplicationEventsResolver.Current.AddType(); - - //now we need to call the initialize methods - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => x.OnApplicationInitialized(_umbracoApplication, ApplicationContext)); - return this; } - /// - /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called - /// - /// - /// - public override IBootManager Startup(Action afterStartup) - { - base.Startup(afterStartup); - - //call OnApplicationStarting of each application events handler - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => x.OnApplicationStarting(_umbracoApplication, ApplicationContext)); - - return this; - } + /// + /// Adds custom types to the ApplicationEventsResolver + /// + protected override void InitializeApplicationEventsResolver() + { + base.InitializeApplicationEventsResolver(); + ApplicationEventsResolver.Current.AddType(); + ApplicationEventsResolver.Current.AddType(); + } /// /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called @@ -121,13 +95,6 @@ namespace Umbraco.Web base.Complete(afterComplete); - //call OnApplicationStarting of each application events handler - ApplicationEventsResolver.Current.ApplicationEventHandlers - .ForEach(x => x.OnApplicationStarted(_umbracoApplication, ApplicationContext)); - - // we're ready to serve content! - ApplicationContext.IsReady = true; - return this; } diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Search/ExamineEvents.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Search/ExamineEvents.cs index f122e2013b..1f1471bc7e 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Search/ExamineEvents.cs @@ -15,20 +15,20 @@ namespace umbraco.presentation.umbraco.Search public class ExamineEvents : IApplicationEventHandler { - public void OnApplicationInitialized(UmbracoApplication httpApplication, ApplicationContext applicationContext) + public void OnApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { } - public void OnApplicationStarting(UmbracoApplication httpApplication, ApplicationContext applicationContext) + public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { } /// /// Once the app has booted, then bind to the events /// - /// + /// /// - public void OnApplicationStarted(UmbracoApplication httpApplication, ApplicationContext applicationContext) + public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { //do not continue if the app context or database is not ready if (!applicationContext.IsConfigured || !applicationContext.DatabaseContext.IsDatabaseConfigured) diff --git a/src/umbraco.businesslogic/PluginManagerExtensions.cs b/src/umbraco.businesslogic/PluginManagerExtensions.cs index da8fdea46b..7a61414315 100644 --- a/src/umbraco.businesslogic/PluginManagerExtensions.cs +++ b/src/umbraco.businesslogic/PluginManagerExtensions.cs @@ -10,16 +10,6 @@ namespace umbraco.businesslogic /// public static class PluginManagerExtensions { - /// - /// Returns all available IApplicationStartupHandler objects - /// - /// - /// - internal static IEnumerable ResolveApplicationStartupHandlers(this PluginManager resolver) - { - return resolver.ResolveTypes(); - } - /// /// Returns all available IApplication in application /// From 9ae962492aa15c11e71f86c59583eefec0d8353b Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 29 Jan 2013 10:11:06 +0600 Subject: [PATCH 16/16] Imports patch for PublishWithResult not being thread safe, removes the giant unnused SQL constants (we have them in history) --- src/umbraco.cms/businesslogic/web/Document.cs | 66 ++----------------- 1 file changed, 5 insertions(+), 61 deletions(-) diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 47f6c43fc4..a4c23ab6aa 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Xml; using Umbraco.Core; using Umbraco.Core.Models; @@ -110,66 +111,8 @@ namespace umbraco.cms.businesslogic.web #endregion #region Constants and Static members - // NH: Modified to support SQL CE 4 (doesn't support nested selects) - private const string m_SQLOptimizedSingle = @" - Select - CASE WHEN (childrenTable.total>0) THEN childrenTable.total ELSE 0 END as Children, - CASE WHEN (publishedTable.publishedTotal>0) THEN publishedTable.publishedTotal ELSE 0 END as Published, - cmsContentType.isContainer, - cmsContentVersion.VersionId, - cmsContentVersion.versionDate, - contentTypeNode.uniqueId as ContentTypeGuid, - cmsContent.ContentType, cmsContentType.icon, cmsContentType.alias, cmsContentType.thumbnail, cmsContentType.description, cmsContentType.nodeId as contentTypeId, - published, documentUser, coalesce(templateId, cmsDocumentType.templateNodeId) as templateId, cmsDocument.text as DocumentText, releaseDate, expireDate, updateDate, - umbracoNode.createDate, umbracoNode.trashed, umbracoNode.parentId, umbracoNode.nodeObjectType, umbracoNode.nodeUser, umbracoNode.level, umbracoNode.path, umbracoNode.sortOrder, umbracoNode.uniqueId, umbracoNode.text - from - umbracoNode - inner join cmsContentVersion on cmsContentVersion.contentID = umbracoNode.id - inner join cmsDocument on cmsDocument.versionId = cmsContentVersion.versionId - inner join cmsContent on cmsDocument.nodeId = cmsContent.NodeId - inner join cmsContentType on cmsContentType.nodeId = cmsContent.ContentType - inner join umbracoNode contentTypeNode on contentTypeNode.id = cmsContentType.nodeId - left join cmsDocumentType on cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1 - /* SQL CE support */ - left outer join (select count(id) as total, parentId from umbracoNode where parentId = @id group by parentId) as childrenTable on childrenTable.parentId = umbracoNode.id - left outer join (select Count(published) as publishedTotal, nodeId from cmsDocument where published = 1 And nodeId = @id group by nodeId) as publishedTable on publishedTable.nodeId = umbracoNode.id - /* end SQL CE support */ - where umbracoNode.nodeObjectType = @nodeObjectType AND {0} - order by {1} - "; - - // NH: Had to modify this for SQL CE 4. Only change is that the "coalesce(publishCheck.published,0) as published" didn't work in SQL CE 4 - // because there's already a column called published. I've changed it to isPublished and updated the other places - // - // zb-00010 #29443 : removed the following lines + added constraint on cmsDocument.newest in where clause (equivalent + handles duplicate dates) - // inner join (select contentId, max(versionDate) as versionDate from cmsContentVersion group by contentId) temp - // on cmsContentVersion.contentId = temp.contentId and cmsContentVersion.versionDate = temp.versionDate - private const string m_SQLOptimizedMany = @" - select count(children.id) as children, cmsContentType.isContainer, umbracoNode.id, umbracoNode.uniqueId, umbracoNode.level, umbracoNode.parentId, - cmsDocument.documentUser, coalesce(cmsDocument.templateId, cmsDocumentType.templateNodeId) as templateId, - umbracoNode.path, umbracoNode.sortOrder, coalesce(publishCheck.published,0) as isPublished, umbracoNode.createDate, - cmsDocument.text, cmsDocument.updateDate, cmsContentVersion.versionDate, cmsDocument.releaseDate, cmsDocument.expireDate, cmsContentType.icon, cmsContentType.alias, - cmsContentType.thumbnail, cmsContentType.description, cmsContentType.nodeId as contentTypeId, - umbracoNode.nodeUser - from umbracoNode - left join umbracoNode children on children.parentId = umbracoNode.id - inner join cmsContent on cmsContent.nodeId = umbracoNode.id - inner join cmsContentType on cmsContentType.nodeId = cmsContent.contentType - inner join cmsContentVersion on cmsContentVersion.contentId = umbracoNode.id - inner join cmsDocument on cmsDocument.versionId = cmsContentversion.versionId - left join cmsDocument publishCheck on publishCheck.nodeId = cmsContent.nodeID and publishCheck.published = 1 - left join cmsDocumentType on cmsDocumentType.contentTypeNodeId = cmsContent.contentType and cmsDocumentType.IsDefault = 1 - where umbracoNode.nodeObjectType = @nodeObjectType AND cmsDocument.newest = 1 AND {0} - group by - cmsContentType.isContainer, umbracoNode.id, umbracoNode.uniqueId, umbracoNode.level, umbracoNode.parentId, cmsDocument.documentUser, - cmsDocument.templateId, cmsDocumentType.templateNodeId, umbracoNode.path, umbracoNode.sortOrder, - coalesce(publishCheck.published,0), umbracoNode.createDate, cmsDocument.text, - cmsContentType.icon, cmsContentType.alias, cmsContentType.thumbnail, cmsContentType.description, - cmsContentType.nodeId, cmsDocument.updateDate, cmsContentVersion.versionDate, cmsDocument.releaseDate, cmsDocument.expireDate, umbracoNode.nodeUser - order by {1} - "; - - private const string m_SQLOptimizedForPreview = @" + + private const string SqlOptimizedForPreview = @" select umbracoNode.id, umbracoNode.parentId, umbracoNode.level, umbracoNode.sortOrder, cmsDocument.versionId, cmsPreviewXml.xml from cmsDocument inner join umbracoNode on umbracoNode.id = cmsDocument.nodeId inner join cmsPreviewXml on cmsPreviewXml.nodeId = cmsDocument.nodeId and cmsPreviewXml.versionId = cmsDocument.versionId @@ -850,6 +793,7 @@ namespace umbraco.cms.businesslogic.web /// /// The usercontext under which the action are performed /// True if the publishing succeed. Possible causes for not publishing is if an event aborts the publishing + [MethodImpl(MethodImplOptions.Synchronized)] [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentService.Publish()", false)] public bool PublishWithResult(User u) { @@ -1322,7 +1266,7 @@ namespace umbraco.cms.businesslogic.web string pathExp = childrenOnly ? Path + ",%" : Path; - IRecordsReader dr = SqlHelper.ExecuteReader(String.Format(m_SQLOptimizedForPreview, pathExp)); + IRecordsReader dr = SqlHelper.ExecuteReader(String.Format(SqlOptimizedForPreview, pathExp)); while (dr.Read()) nodes.Add(new CMSPreviewNode(dr.GetInt("id"), dr.GetGuid("versionId"), dr.GetInt("parentId"), dr.GetShort("level"), dr.GetInt("sortOrder"), dr.GetString("xml"))); dr.Close();