From f48fb5c499ce75a9f4e8985ef96116e6755674d0 Mon Sep 17 00:00:00 2001 From: Brian Powell Date: Tue, 3 Dec 2013 15:44:34 -0500 Subject: [PATCH 1/8] Fix MySQL migrations. --- .../Migrations/Syntax/Delete/DeleteBuilder.cs | 24 +++++++-------- .../Expressions/DeleteConstraintExpression.cs | 29 ++++++++++++++++++- .../Expressions/DeleteForeignKeyExpression.cs | 4 +-- .../AlterTagRelationsTable.cs | 14 ++++----- .../SqlSyntax/MySqlSyntaxProvider.cs | 24 +++++++-------- 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs index 03ceaa6a4e..4865182134 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteColumnFromTableSyntax Column(string columnName) { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteColumnExpression { ColumnNames = { columnName } } : new DeleteColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders) { ColumnNames = { columnName } }; _context.Expressions.Add(expression); @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteForeignKeyFromTableSyntax ForeignKey() { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteForeignKeyExpression() : new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders); _context.Expressions.Add(expression); @@ -45,7 +45,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteForeignKeyOnTableSyntax ForeignKey(string foreignKeyName) { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteForeignKeyExpression { ForeignKey = { Name = foreignKeyName } } : new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders) { ForeignKey = { Name = foreignKeyName } }; _context.Expressions.Add(expression); @@ -68,17 +68,17 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteIndexForTableSyntax Index(string indexName) { - var expression = new DeleteIndexExpression {Index = {Name = indexName}}; + var expression = new DeleteIndexExpression { Index = { Name = indexName } }; _context.Expressions.Add(expression); return new DeleteIndexBuilder(expression); } public IDeleteConstraintOnTableSyntax PrimaryKey(string primaryKeyName) { - var expression = new DeleteConstraintExpression(ConstraintType.PrimaryKey) - { - Constraint = { ConstraintName = primaryKeyName } - }; + var expression = new DeleteConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, ConstraintType.PrimaryKey) + { + Constraint = { ConstraintName = primaryKeyName } + }; _context.Expressions.Add(expression); return new DeleteConstraintBuilder(expression); } @@ -86,16 +86,16 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteConstraintOnTableSyntax UniqueConstraint(string constraintName) { var expression = new DeleteConstraintExpression(ConstraintType.Unique) - { - Constraint = { ConstraintName = constraintName } - }; + { + Constraint = { ConstraintName = constraintName } + }; _context.Expressions.Add(expression); return new DeleteConstraintBuilder(expression); } public IDeleteDefaultConstraintOnTableSyntax DefaultConstraint() { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteDefaultConstraintExpression() : new DeleteDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders); _context.Expressions.Add(expression); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs index 1f20f6bbb9..39f250532d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs @@ -10,13 +10,40 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions Constraint = new ConstraintDefinition(type); } + public DeleteConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ConstraintType type) + : base(current, databaseProviders) + { + Constraint = new ConstraintDefinition(type); + } + public ConstraintDefinition Constraint { get; private set; } public override string ToString() { - return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, + // Test for MySQL primary key situation. + if (CurrentDatabaseProvider == DatabaseProviders.MySql) + { + if (Constraint.IsPrimaryKeyConstraint) + { + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(Constraint.TableName), + "PRIMARY KEY", + ""); + } + else + { + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(Constraint.TableName), + "FOREIGN KEY", + ""); + } + } + else + { + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(Constraint.TableName), SqlSyntaxContext.SqlSyntaxProvider.GetQuotedName(Constraint.ConstraintName)); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs index df79f7c8f7..75eb5d1d1f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions if (ForeignKey.ForeignTable == null) throw new ArgumentNullException("Table name not specified, ensure you have appended the OnTable extension. Format should be Delete.ForeignKey(KeyName).OnTable(TableName)"); - if(CurrentDatabaseProvider == DatabaseProviders.MySql) + if (CurrentDatabaseProvider == DatabaseProviders.MySql) { //MySql naming "convention" for foreignkeys, which aren't explicitly named if (string.IsNullOrEmpty(ForeignKey.Name)) @@ -35,7 +35,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(ForeignKey.ForeignTable), - "FOREIGN KEY ", + "FOREIGN KEY", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedName(ForeignKey.Name)); } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs index 7b23e22504..4a549c7af3 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs @@ -26,11 +26,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven //create a new col which we will make a foreign key, but first needs to be populated with data. Alter.Table("cmsTagRelationship").AddColumn("propertyTypeId").AsInt32().Nullable(); + //drop the foreign key on umbracoNode. Must drop foreign key first before primary key can be removed in MySql. + Delete.ForeignKey("FK_cmsTagRelationship_umbracoNode_id").OnTable("cmsTagRelationship"); + //we need to drop the primary key Delete.PrimaryKey("PK_cmsTagRelationship").FromTable("cmsTagRelationship"); - - //drop the foreign key on umbracoNode - Delete.ForeignKey("FK_cmsTagRelationship_umbracoNode_id").OnTable("cmsTagRelationship"); } private void Upgrade() @@ -57,9 +57,9 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven var propertyTypes = propertyTypeIdRef.Where(x => x.NodeId == tr.NodeId).ToArray(); if (propertyTypes.Length == 0) { - LogHelper.Warn("There was no cmsContent reference for cmsTagRelationship for nodeId " + LogHelper.Warn("There was no cmsContent reference for cmsTagRelationship for nodeId " + tr.NodeId + - ". The new tag system only supports tags with references to content in the cmsContent and cmsPropertyType tables. This row will be deleted: " + ". The new tag system only supports tags with references to content in the cmsContent and cmsPropertyType tables. This row will be deleted: " + string.Format("nodeId: {0}, tagId: {1}", tr.NodeId, tr.TagId)); Delete.FromTable("cmsTagRelationship").Row(new { nodeId = tr.NodeId, tagId = tr.TagId }); } @@ -92,7 +92,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven Alter.Table("cmsTagRelationship").AlterColumn("propertyTypeId").AsInt32().NotNullable(); //we need to re-add the new primary key on all 3 columns - Create.PrimaryKey("PK_cmsTagRelationship").OnTable("cmsTagRelationship").Columns(new[] {"nodeId", "propertyTypeId", "tagId"}); + Create.PrimaryKey("PK_cmsTagRelationship").OnTable("cmsTagRelationship").Columns(new[] { "nodeId", "propertyTypeId", "tagId" }); //now we need to add a foreign key to the propertyTypeId column and change it's constraints Create.ForeignKey("FK_cmsTagRelationship_cmsPropertyType") @@ -102,7 +102,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSeven .PrimaryColumn("id") .OnDelete(Rule.None) .OnUpdate(Rule.None); - + //now we need to add a foreign key to the nodeId column to cmsContent (intead of the original umbracoNode) Create.ForeignKey("FK_cmsTagRelationship_cmsContent") .FromTable("cmsTagRelationship") diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 52ea11e572..8f466e3829 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax TimeColumnDefinition = "time"; DecimalColumnDefinition = "decimal(38,6)"; GuidColumnDefinition = "char(36)"; - + InitColumnTypeMap(); DefaultValueFormat = "DEFAULT '{0}'"; @@ -48,7 +48,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var items = db.Fetch( "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", - new {TableSchema = db.Connection.Database}); + new { TableSchema = db.Connection.Database }); list = items.Select(x => x.TABLE_NAME).Cast().ToList(); } finally @@ -67,7 +67,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax 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}); + new { TableSchema = db.Connection.Database }); list = items.Select( item => @@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var items = db.Fetch( "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", - new {TableSchema = db.Connection.Database}); + new { TableSchema = db.Connection.Database }); list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); } finally @@ -109,7 +109,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax 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}); + new { TableSchema = db.Connection.Database }); list = items.Select( item => @@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_NAME = @TableName AND " + "TABLE_SCHEMA = @TableSchema", - new {TableName = tableName, TableSchema = db.Connection.Database}); + new { TableName = tableName, TableSchema = db.Connection.Database }); } finally @@ -213,7 +213,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax return string.Format(CreateIndex, GetQuotedName(name), - GetQuotedTableName(index.TableName), + GetQuotedTableName(index.TableName), columns); } @@ -290,7 +290,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax { get { - throw new NotSupportedException("Default constraints are not supported in MySql"); + return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; } } @@ -303,7 +303,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } - public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1}{2}"; } } + public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } @@ -318,11 +318,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax db.OpenSharedConnection(); // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); - - if(lowerCaseTableNames.Any()) + + if (lowerCaseTableNames.Any()) supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; } - catch(Exception ex) + catch (Exception ex) { Logging.LogHelper.Error("Error querying for lower_case support", ex); } From d36fc65baab0e08d60039d0f7a01396473de68ff Mon Sep 17 00:00:00 2001 From: Brian Powell Date: Wed, 4 Dec 2013 10:50:38 -0500 Subject: [PATCH 2/8] Fixed MySQL issue. --- src/Umbraco.Core/Services/UserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 1a6695fd5c..a8fd6187ed 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -166,7 +166,7 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); var sql = new Sql(); - sql.Select("app").From().Where("[user] = @userID", new {userID = user.Id}); + sql.Select("app").From().Where("user = @userID", new {userID = user.Id}); return uow.Database.Fetch(sql); } From 4cb4ff0617976f64bc0dd8435da5499650f3c9c9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Dec 2013 10:26:32 +1100 Subject: [PATCH 3/8] Fix MySQL migrations. Conflicts: src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSeven/AlterTagRelationsTable.cs --- .../Migrations/Syntax/Delete/DeleteBuilder.cs | 24 +++++++-------- .../Expressions/DeleteConstraintExpression.cs | 29 ++++++++++++++++++- .../Expressions/DeleteForeignKeyExpression.cs | 4 +-- .../SqlSyntax/MySqlSyntaxProvider.cs | 24 +++++++-------- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs index 03ceaa6a4e..4865182134 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/DeleteBuilder.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteColumnFromTableSyntax Column(string columnName) { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteColumnExpression { ColumnNames = { columnName } } : new DeleteColumnExpression(_context.CurrentDatabaseProvider, _databaseProviders) { ColumnNames = { columnName } }; _context.Expressions.Add(expression); @@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteForeignKeyFromTableSyntax ForeignKey() { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteForeignKeyExpression() : new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders); _context.Expressions.Add(expression); @@ -45,7 +45,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteForeignKeyOnTableSyntax ForeignKey(string foreignKeyName) { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteForeignKeyExpression { ForeignKey = { Name = foreignKeyName } } : new DeleteForeignKeyExpression(_context.CurrentDatabaseProvider, _databaseProviders) { ForeignKey = { Name = foreignKeyName } }; _context.Expressions.Add(expression); @@ -68,17 +68,17 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteIndexForTableSyntax Index(string indexName) { - var expression = new DeleteIndexExpression {Index = {Name = indexName}}; + var expression = new DeleteIndexExpression { Index = { Name = indexName } }; _context.Expressions.Add(expression); return new DeleteIndexBuilder(expression); } public IDeleteConstraintOnTableSyntax PrimaryKey(string primaryKeyName) { - var expression = new DeleteConstraintExpression(ConstraintType.PrimaryKey) - { - Constraint = { ConstraintName = primaryKeyName } - }; + var expression = new DeleteConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders, ConstraintType.PrimaryKey) + { + Constraint = { ConstraintName = primaryKeyName } + }; _context.Expressions.Add(expression); return new DeleteConstraintBuilder(expression); } @@ -86,16 +86,16 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete public IDeleteConstraintOnTableSyntax UniqueConstraint(string constraintName) { var expression = new DeleteConstraintExpression(ConstraintType.Unique) - { - Constraint = { ConstraintName = constraintName } - }; + { + Constraint = { ConstraintName = constraintName } + }; _context.Expressions.Add(expression); return new DeleteConstraintBuilder(expression); } public IDeleteDefaultConstraintOnTableSyntax DefaultConstraint() { - var expression = _databaseProviders == null + var expression = _databaseProviders == null ? new DeleteDefaultConstraintExpression() : new DeleteDefaultConstraintExpression(_context.CurrentDatabaseProvider, _databaseProviders); _context.Expressions.Add(expression); diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs index 1f20f6bbb9..39f250532d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteConstraintExpression.cs @@ -10,13 +10,40 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions Constraint = new ConstraintDefinition(type); } + public DeleteConstraintExpression(DatabaseProviders current, DatabaseProviders[] databaseProviders, ConstraintType type) + : base(current, databaseProviders) + { + Constraint = new ConstraintDefinition(type); + } + public ConstraintDefinition Constraint { get; private set; } public override string ToString() { - return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, + // Test for MySQL primary key situation. + if (CurrentDatabaseProvider == DatabaseProviders.MySql) + { + if (Constraint.IsPrimaryKeyConstraint) + { + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(Constraint.TableName), + "PRIMARY KEY", + ""); + } + else + { + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(Constraint.TableName), + "FOREIGN KEY", + ""); + } + } + else + { + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(Constraint.TableName), SqlSyntaxContext.SqlSyntaxProvider.GetQuotedName(Constraint.ConstraintName)); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs index df79f7c8f7..75eb5d1d1f 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Delete/Expressions/DeleteForeignKeyExpression.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions if (ForeignKey.ForeignTable == null) throw new ArgumentNullException("Table name not specified, ensure you have appended the OnTable extension. Format should be Delete.ForeignKey(KeyName).OnTable(TableName)"); - if(CurrentDatabaseProvider == DatabaseProviders.MySql) + if (CurrentDatabaseProvider == DatabaseProviders.MySql) { //MySql naming "convention" for foreignkeys, which aren't explicitly named if (string.IsNullOrEmpty(ForeignKey.Name)) @@ -35,7 +35,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Delete.Expressions return string.Format(SqlSyntaxContext.SqlSyntaxProvider.DeleteConstraint, SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(ForeignKey.ForeignTable), - "FOREIGN KEY ", + "FOREIGN KEY", SqlSyntaxContext.SqlSyntaxProvider.GetQuotedName(ForeignKey.Name)); } diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs index 52ea11e572..8f466e3829 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/MySqlSyntaxProvider.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax TimeColumnDefinition = "time"; DecimalColumnDefinition = "decimal(38,6)"; GuidColumnDefinition = "char(36)"; - + InitColumnTypeMap(); DefaultValueFormat = "DEFAULT '{0}'"; @@ -48,7 +48,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var items = db.Fetch( "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @TableSchema", - new {TableSchema = db.Connection.Database}); + new { TableSchema = db.Connection.Database }); list = items.Select(x => x.TABLE_NAME).Cast().ToList(); } finally @@ -67,7 +67,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax 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}); + new { TableSchema = db.Connection.Database }); list = items.Select( item => @@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax var items = db.Fetch( "SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = @TableSchema", - new {TableSchema = db.Connection.Database}); + new { TableSchema = db.Connection.Database }); list = items.Select(item => new Tuple(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList(); } finally @@ -109,7 +109,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax 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}); + new { TableSchema = db.Connection.Database }); list = items.Select( item => @@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax db.ExecuteScalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_NAME = @TableName AND " + "TABLE_SCHEMA = @TableSchema", - new {TableName = tableName, TableSchema = db.Connection.Database}); + new { TableName = tableName, TableSchema = db.Connection.Database }); } finally @@ -213,7 +213,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax return string.Format(CreateIndex, GetQuotedName(name), - GetQuotedTableName(index.TableName), + GetQuotedTableName(index.TableName), columns); } @@ -290,7 +290,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax { get { - throw new NotSupportedException("Default constraints are not supported in MySql"); + return "ALTER TABLE {0} ALTER COLUMN {1} DROP DEFAULT"; } } @@ -303,7 +303,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax public override string CreateForeignKeyConstraint { get { return "ALTER TABLE {0} ADD FOREIGN KEY ({1}) REFERENCES {2} ({3}){4}{5}"; } } - public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1}{2}"; } } + public override string DeleteConstraint { get { return "ALTER TABLE {0} DROP {1} {2}"; } } public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } @@ -318,11 +318,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax db.OpenSharedConnection(); // Need 4 @ signs as it is regarded as a parameter, @@ escapes it once, @@@@ escapes it twice var lowerCaseTableNames = db.Fetch("SELECT @@@@Global.lower_case_table_names"); - - if(lowerCaseTableNames.Any()) + + if (lowerCaseTableNames.Any()) supportsCaseInsensitiveQueries = lowerCaseTableNames.First() == 1; } - catch(Exception ex) + catch (Exception ex) { Logging.LogHelper.Error("Error querying for lower_case support", ex); } From 9d9070c979498237c5fcbe966cf64451c2b701f3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Dec 2013 10:56:49 +1100 Subject: [PATCH 4/8] fixes user service query to work with mssql and mysql --- src/Umbraco.Core/Services/UserService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index a8fd6187ed..ec7c957a49 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -166,7 +166,8 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); var sql = new Sql(); - sql.Select("app").From().Where("user = @userID", new {userID = user.Id}); + sql.Select("app").From() + .Where(dto => dto.UserId == (int)user.Id); return uow.Database.Fetch(sql); } From 77592499637e96d6e19a7e9abf520297d9bee466 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Dec 2013 11:22:16 +1100 Subject: [PATCH 5/8] Fixes: U4-2967 Inline macros error out when trying to access the Model --- .../RazorCore/RazorMacroEngine.cs | 13 ++ .../Resources/Strings.Designer.cs | 78 +++++++++++ .../Resources/Strings.resx | 124 ++++++++++++++++++ .../umbraco.MacroEngines.csproj | 14 ++ 4 files changed, 229 insertions(+) create mode 100644 src/umbraco.MacroEngines/Resources/Strings.Designer.cs create mode 100644 src/umbraco.MacroEngines/Resources/Strings.resx diff --git a/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs b/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs index 0eb1f8ce30..03c9d9e9b8 100644 --- a/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs +++ b/src/umbraco.MacroEngines/RazorCore/RazorMacroEngine.cs @@ -7,6 +7,7 @@ using System.Web; using System.Web.Compilation; using System.Web.WebPages; using Umbraco.Core; +using umbraco.MacroEngines.Resources; using umbraco.cms.businesslogic.macro; using umbraco.interfaces; using Umbraco.Core.IO; @@ -16,6 +17,7 @@ namespace umbraco.MacroEngines public class RazorMacroEngine : IMacroEngine, IMacroEngineResultStatus { public const string RazorTempDirectory = "~/App_Data/TEMP/Razor/"; + private const string RazorWebConfig = "~/App_Data/TEMP/Razor/web.config"; public string GetVirtualPathFromPhysicalPath(string physicalPath) { string rootpath = HttpContext.Current.Server.MapPath("~/"); @@ -40,6 +42,7 @@ namespace umbraco.MacroEngines var relativePath = RazorTempDirectory + fileName; var physicalPath = IOHelper.MapPath(relativePath); var physicalDirectoryPath = IOHelper.MapPath(RazorTempDirectory); + var webconfig = IOHelper.MapPath(RazorWebConfig); if (skipIfFileExists && File.Exists(physicalPath)) return relativePath; @@ -47,6 +50,16 @@ namespace umbraco.MacroEngines File.Delete(physicalPath); if (!Directory.Exists(physicalDirectoryPath)) Directory.CreateDirectory(physicalDirectoryPath); + + //Ensure the correct razor web.config is there + if (File.Exists(webconfig) == false) + { + using (var writer = File.CreateText(webconfig)) + { + writer.Write(Strings.WebConfig); + } + } + using (var file = new StreamWriter(physicalPath, false, Encoding.UTF8)) { file.Write(razorSyntax); diff --git a/src/umbraco.MacroEngines/Resources/Strings.Designer.cs b/src/umbraco.MacroEngines/Resources/Strings.Designer.cs new file mode 100644 index 0000000000..553c0b9460 --- /dev/null +++ b/src/umbraco.MacroEngines/Resources/Strings.Designer.cs @@ -0,0 +1,78 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace umbraco.MacroEngines.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("umbraco.MacroEngines.Resources.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <?xml version="1.0"?> + ///<configuration> + /// + /// <configSections> + /// <sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"> + /// <section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" /> + /// <section name="page [rest of string was truncated]";. + /// + internal static string WebConfig { + get { + return ResourceManager.GetString("WebConfig", resourceCulture); + } + } + } +} diff --git a/src/umbraco.MacroEngines/Resources/Strings.resx b/src/umbraco.MacroEngines/Resources/Strings.resx new file mode 100644 index 0000000000..e3244825c1 --- /dev/null +++ b/src/umbraco.MacroEngines/Resources/Strings.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\..\umbraco.web.ui\macroscripts\web.config;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj index b2c315dfad..6dec8e9ee7 100644 --- a/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj +++ b/src/umbraco.MacroEngines/umbraco.MacroEngines.csproj @@ -180,6 +180,11 @@ + + True + True + Strings.resx + @@ -216,8 +221,17 @@ + + Resources\Web.config + + + + ResXFileCodeGenerator + Strings.Designer.cs + + From 8327b8aa0e5e9a6d001e427de7fa257e87af8471 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Dec 2013 11:52:47 +1100 Subject: [PATCH 6/8] Completes: U4-3792 Move appSettings, connectionStrings back to web.config --- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 13 -------- .../config/appSettings.Release.config | 24 --------------- .../config/connectionStrings.Release.config | 6 ---- src/Umbraco.Web.UI/web.Template.Debug.config | 27 +++++++++++++---- src/Umbraco.Web.UI/web.Template.config | 30 +++++++++++++++++-- 5 files changed, 50 insertions(+), 50 deletions(-) delete mode 100644 src/Umbraco.Web.UI/config/appSettings.Release.config delete mode 100644 src/Umbraco.Web.UI/config/connectionStrings.Release.config diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index ad0f25d42e..2f055e73e7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -647,11 +647,6 @@ 404handlers.config - - - Designer - appSettings.config - ClientDependency.config Designer @@ -662,10 +657,6 @@ BaseRestExtensions.config - - - connectionStrings.config - log4net.config @@ -2654,10 +2645,6 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" - - - - diff --git a/src/Umbraco.Web.UI/config/appSettings.Release.config b/src/Umbraco.Web.UI/config/appSettings.Release.config deleted file mode 100644 index 916c6e8aa1..0000000000 --- a/src/Umbraco.Web.UI/config/appSettings.Release.config +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI/config/connectionStrings.Release.config b/src/Umbraco.Web.UI/config/connectionStrings.Release.config deleted file mode 100644 index 94978d6f65..0000000000 --- a/src/Umbraco.Web.UI/config/connectionStrings.Release.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 3a1bd9b273..c3f68892ec 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -13,7 +13,6 @@ The transforms in this file only apply to debugging, not to the web.config that will be released, see web.Template.Release.config for that - 2012-11-08 SJ - Add Medium trust for everybody so we'll see any MedTrust related errors early on --> @@ -46,11 +45,29 @@ - - + + + + + + + + + + + + + + + + + - - + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 5f0f0d95f2..98dbc1c133 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -29,8 +29,34 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + From dc7c7304adba5380ff099cb58611abddb2975e72 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Dec 2013 14:06:53 +1100 Subject: [PATCH 7/8] Fixes: U4-3505 Sometimes macro parameter types are not resolved during startup --- src/Umbraco.Core/PluginManager.cs | 34 +++++++++++++++++-------- src/Umbraco.Tests/PluginManagerTests.cs | 26 +++++++++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index 2d00d19ee2..22d8f5bb5c 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -309,7 +309,7 @@ namespace Umbraco.Core //return false but specify this exception type so we can detect it if (typeElement == null) - return Attempt>.Fail(new CachedPluginNotFoundInFile()); + return Attempt>.Fail(new CachedPluginNotFoundInFileException()); //return success return Attempt.Succeed(typeElement.Elements("add") @@ -671,8 +671,15 @@ namespace Umbraco.Core { //check if the TypeList already exists, if so return it, if not we'll create it var typeList = _types.SingleOrDefault(x => x.IsTypeList(resolutionType)); + + //need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 + if (cacheResult && typeList != null) + { + LogHelper.Debug("Existing typeList found for {0} with resolution type {1}", () => typeof(T), () => resolutionType); + } + //if we're not caching the result then proceed, or if the type list doesn't exist then proceed - if (!cacheResult || typeList == null) + if (cacheResult == false || typeList == null) { //upgrade to a write lock since we're adding to the collection readLock.UpgradeToWriteLock(); @@ -681,15 +688,17 @@ namespace Umbraco.Core //we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory). //if assemblies have not changed and the cache file actually exists, then proceed to try to lookup by the cache file. - if (!HaveAssembliesChanged && File.Exists(GetPluginListFilePath())) + if (HaveAssembliesChanged == false && File.Exists(GetPluginListFilePath())) { var fileCacheResult = TryGetCachedPluginsFromFile(resolutionType); //here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan //in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed //so in this instance there will never be a result. - if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFile) + if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFileException) { + LogHelper.Debug("Tried to find typelist for type {0} and resolution {1} in file cache but the type was not found so loading types by assembly scan ", () => typeof(T), () => resolutionType); + //we don't have a cache for this so proceed to look them up by scanning LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); } @@ -717,20 +726,22 @@ namespace Umbraco.Core break; } } - if (!successfullyLoadedFromCache) + if (successfullyLoadedFromCache == false) { //we need to manually load by scanning if loading from the file was not successful. LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); } else { - LogHelper.Debug("Loaded plugin types " + typeof(T).FullName + " from persisted cache"); + LogHelper.Debug("Loaded plugin types {0} with resolution {1} from persisted cache", () => typeof(T), () => resolutionType); } } } } else { + LogHelper.Debug("Assembly changes detected, loading types {0} for resolution {1} by assembly scan", () => typeof(T), () => resolutionType); + //we don't have a cache for this so proceed to look them up by scanning LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); } @@ -739,7 +750,10 @@ namespace Umbraco.Core if (cacheResult) { //add the type list to the collection - _types.Add(typeList); + var added = _types.Add(typeList); + + LogHelper.Debug("Caching of typelist for type {0} and resolution {1} was successful = {2}", () => typeof(T), () => resolutionType, () => added); + } } typesFound = typeList.GetTypes().ToList(); @@ -863,14 +877,14 @@ namespace Umbraco.Core } /// - /// Returns true if the current TypeList is of the same type and of the same type + /// Returns true if the current TypeList is of the same lookup type /// /// /// /// public override bool IsTypeList(TypeResolutionKind resolutionType) { - return _resolutionType == resolutionType && (typeof(T)).IsType(); + return _resolutionType == resolutionType && (typeof(T)) == typeof(TLookup); } public override IEnumerable GetTypes() @@ -883,7 +897,7 @@ namespace Umbraco.Core /// This class is used simply to determine that a plugin was not found in the cache plugin list with the specified /// TypeResolutionKind. /// - internal class CachedPluginNotFoundInFile : Exception + internal class CachedPluginNotFoundInFileException : Exception { } diff --git a/src/Umbraco.Tests/PluginManagerTests.cs b/src/Umbraco.Tests/PluginManagerTests.cs index f9f8a7f18a..ea244c06eb 100644 --- a/src/Umbraco.Tests/PluginManagerTests.cs +++ b/src/Umbraco.Tests/PluginManagerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; @@ -9,8 +10,10 @@ using NUnit.Framework; using SqlCE4Umbraco; using Umbraco.Core; using Umbraco.Core.IO; +using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Web; +using Umbraco.Web.PropertyEditors; using umbraco; using umbraco.DataLayer; using umbraco.MacroEngines; @@ -333,6 +336,29 @@ namespace Umbraco.Tests var types = PluginManager.Current.ResolveXsltExtensions(); Assert.AreEqual(3, types.Count()); } + + /// + /// This demonstrates this issue: http://issues.umbraco.org/issue/U4-3505 - the TypeList was returning a list of assignable types + /// not explicit types which is sort of ideal but is confusing so we'll do it the less confusing way. + /// + [Test] + public void TypeList_Resolves_Explicit_Types() + { + var types = new HashSet(); + + var propEditors = new PluginManager.TypeList(PluginManager.TypeResolutionKind.FindAllTypes); + propEditors.AddType(typeof (LabelPropertyEditor)); + types.Add(propEditors); + + var found = types.SingleOrDefault(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes)); + + Assert.IsNotNull(found); + + //This should not find a type list of this type + var shouldNotFind = types.SingleOrDefault(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes)); + + Assert.IsNull(shouldNotFind); + } [XsltExtension("Blah.Blah")] public class MyXsltExtension From 2eb5c088258fd0ff1c4cb975da476583ce7208a8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Dec 2013 15:01:58 +1100 Subject: [PATCH 8/8] Fixing: U4-3686 Umbraco 7 - Rich Text Editor and Macro Issues + fixed up the per-controller webapi configuration and more fixes to loading propertyeditors/param editors, this saves a assembly scan. --- src/Umbraco.Core/Macros/MacroTagParser.cs | 33 ++++++++++-- src/Umbraco.Core/PluginManager.cs | 12 ++--- src/Umbraco.Core/Umbraco.Core.csproj | 3 ++ src/Umbraco.Core/packages.config | 1 + src/Umbraco.Tests/Macros/MacroParserTests.cs | 17 ++++++ .../Editors/AuthenticationController.cs | 11 +--- .../UmbracoAuthorizedJsonController.cs | 13 +---- .../Trees/ApplicationTreeController.cs | 13 +---- src/Umbraco.Web/Trees/TreeControllerBase.cs | 11 +--- src/Umbraco.Web/UmbracoHelper.cs | 53 +++++++++---------- .../WebApi/HttpControllerContextExtensions.cs | 42 ++++++++++----- src/Umbraco.Web/umbraco.presentation/macro.cs | 4 +- 12 files changed, 118 insertions(+), 95 deletions(-) diff --git a/src/Umbraco.Core/Macros/MacroTagParser.cs b/src/Umbraco.Core/Macros/MacroTagParser.cs index 236a184cc0..94eb6a69bc 100644 --- a/src/Umbraco.Core/Macros/MacroTagParser.cs +++ b/src/Umbraco.Core/Macros/MacroTagParser.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; +using HtmlAgilityPack; namespace Umbraco.Core.Macros { @@ -10,7 +11,7 @@ namespace Umbraco.Core.Macros /// internal class MacroTagParser { - private static readonly Regex MacroRteContent = new Regex(@"(.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); private static readonly Regex MacroPersistedFormat = new Regex(@"(<\?UMBRACO_MACRO macroAlias=[""'](\w+?)[""'].+?)(?:/>|>.*?)", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); /// @@ -88,7 +89,33 @@ namespace Umbraco.Core.Macros /// internal static string FormatRichTextContentForPersistence(string rteContent) { - return MacroRteContent.Replace(rteContent, match => + if (string.IsNullOrEmpty(rteContent)) + { + return string.Empty; + } + + var html = new HtmlDocument(); + html.LoadHtml(rteContent); + + //get all the comment nodes we want + var commentNodes = html.DocumentNode.SelectNodes("//comment()[contains(., ' with the comment node itself. + foreach (var c in commentNodes) + { + var div = c.ParentNode; + var divContainer = div.ParentNode; + divContainer.ReplaceChild(c, div); + } + + var parsed = html.DocumentNode.OuterHtml; + + //now replace all the with nothing + return MacroRteContent.Replace(parsed, match => { if (match.Groups.Count >= 3) { @@ -96,7 +123,7 @@ namespace Umbraco.Core.Macros return match.Groups[2].Value; } //replace with nothing if we couldn't find the syntax for whatever reason - return ""; + return string.Empty; }); } diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index 22d8f5bb5c..d3305af031 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -459,24 +459,24 @@ namespace Umbraco.Core private readonly HashSet _types = new HashSet(); private IEnumerable _assemblies; - /// - /// Returns all found property editors + /// Returns all found property editors (based on the resolved Iparameter editors - this saves a scan) /// internal IEnumerable ResolvePropertyEditors() { //return all proeprty editor types found except for the base property editor type - return ResolveTypes().ToArray() - .Except(new[] {typeof (PropertyEditor)}); + return ResolveTypes() + .Where(x => x.IsType()) + .Except(new[] { typeof(PropertyEditor) }); } /// - /// Returns all found parameter editors + /// Returns all found parameter editors (which includes property editors) /// internal IEnumerable ResolveParameterEditors() { //return all paramter editor types found except for the base property editor type - return ResolveTypes().ToArray() + return ResolveTypes() .Except(new[] { typeof(ParameterEditor), typeof(PropertyEditor) }); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3aa3a3d5bc..880673ef6e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -44,6 +44,9 @@ ..\packages\AutoMapper.3.0.0\lib\net40\AutoMapper.Net4.dll + + ..\packages\HtmlAgilityPack.1.4.6\lib\Net45\HtmlAgilityPack.dll + False ..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll diff --git a/src/Umbraco.Core/packages.config b/src/Umbraco.Core/packages.config index 7ad8fc5ac1..cc673f88a1 100644 --- a/src/Umbraco.Core/packages.config +++ b/src/Umbraco.Core/packages.config @@ -1,6 +1,7 @@  + diff --git a/src/Umbraco.Tests/Macros/MacroParserTests.cs b/src/Umbraco.Tests/Macros/MacroParserTests.cs index 56fc4f2062..78dd51e855 100644 --- a/src/Umbraco.Tests/Macros/MacroParserTests.cs +++ b/src/Umbraco.Tests/Macros/MacroParserTests.cs @@ -158,5 +158,22 @@ asdfsdf ".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); } + + [Test] + public void Format_RTE_Data_For_Persistence_Custom_Single_Entry() + { + var content = @"
+
+
+

null

+
+
1089
+
+
"; + var result = MacroTagParser.FormatRichTextContentForPersistence(content); + + Assert.AreEqual(@"", result); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 780302f959..d9452e23fe 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -28,18 +28,9 @@ namespace Umbraco.Web.Editors ///
[PluginController("UmbracoApi")] [ValidationFilter] + [AngularJsonOnlyConfiguration] public class AuthenticationController : UmbracoApiController { - - /// - /// Remove the xml formatter... only support JSON! - /// - /// - protected override void Initialize(HttpControllerContext controllerContext) - { - base.Initialize(controllerContext); - controllerContext.EnsureJsonOutputOnly(); - } /// /// This is a special method that will return the current users' remaining session seconds, the reason diff --git a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs index cd8b9f6084..e60abe0859 100644 --- a/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs +++ b/src/Umbraco.Web/Editors/UmbracoAuthorizedJsonController.cs @@ -12,6 +12,7 @@ namespace Umbraco.Web.Editors /// methods that are not called by Angular or don't contain a valid csrf header will NOT work. /// [ValidateAngularAntiForgeryToken] + [AngularJsonOnlyConfiguration] public abstract class UmbracoAuthorizedJsonController : UmbracoAuthorizedApiController { protected UmbracoAuthorizedJsonController() @@ -22,17 +23,5 @@ namespace Umbraco.Web.Editors { } - /// - /// Remove the xml formatter... only support JSON! - /// - /// - protected override void Initialize(HttpControllerContext controllerContext) - { - base.Initialize(controllerContext); - controllerContext.EnsureJsonOutputOnly(); - } - - - } } \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/ApplicationTreeController.cs b/src/Umbraco.Web/Trees/ApplicationTreeController.cs index 2355b0c443..1b90ae0dd8 100644 --- a/src/Umbraco.Web/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web/Trees/ApplicationTreeController.cs @@ -20,21 +20,10 @@ using umbraco; namespace Umbraco.Web.Trees { - + [AngularJsonOnlyConfiguration] [PluginController("UmbracoTrees")] public class ApplicationTreeController : UmbracoAuthorizedApiController { - - /// - /// Remove the xml formatter... only support JSON! - /// - /// - protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext) - { - base.Initialize(controllerContext); - controllerContext.EnsureJsonOutputOnly(); - } - /// /// Returns the tree nodes for an application /// diff --git a/src/Umbraco.Web/Trees/TreeControllerBase.cs b/src/Umbraco.Web/Trees/TreeControllerBase.cs index 3271b6124e..ebf4275c1f 100644 --- a/src/Umbraco.Web/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web/Trees/TreeControllerBase.cs @@ -15,18 +15,9 @@ namespace Umbraco.Web.Trees /// A base controller reference for non-attributed trees (un-registered). Developers should inherit from /// TreeController. /// + [AngularJsonOnlyConfiguration] public abstract class TreeControllerBase : UmbracoAuthorizedApiController { - /// - /// Remove the xml formatter... only support JSON! - /// - /// - protected override void Initialize(global::System.Web.Http.Controllers.HttpControllerContext controllerContext) - { - base.Initialize(controllerContext); - controllerContext.EnsureJsonOutputOnly(); - } - /// /// The method called to render the contents of the tree structure /// diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 0275ba2ce5..44fc11feb8 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -976,35 +976,32 @@ namespace Umbraco.Web { var doc = new HtmlDocument(); doc.LoadHtml("

" + html + "

"); - using (var ms = new MemoryStream()) - { - var targets = new List(); + var targets = new List(); - var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); - if (nodes != null) - { - foreach (var node in nodes) - { - //is element - if (node.NodeType != HtmlNodeType.Element) continue; - var filterAllTags = (tags == null || !tags.Any()); - if (filterAllTags || tags.Any(tag => string.Equals(tag, node.Name, StringComparison.CurrentCultureIgnoreCase))) - { - targets.Add(node); - } - } - foreach (var target in targets) - { - HtmlNode content = doc.CreateTextNode(target.InnerText); - target.ParentNode.ReplaceChild(content, target); - } - } - else - { - return new HtmlString(html); - } - return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml); - } + var nodes = doc.DocumentNode.FirstChild.SelectNodes(".//*"); + if (nodes != null) + { + foreach (var node in nodes) + { + //is element + if (node.NodeType != HtmlNodeType.Element) continue; + var filterAllTags = (tags == null || !tags.Any()); + if (filterAllTags || tags.Any(tag => string.Equals(tag, node.Name, StringComparison.CurrentCultureIgnoreCase))) + { + targets.Add(node); + } + } + foreach (var target in targets) + { + HtmlNode content = doc.CreateTextNode(target.InnerText); + target.ParentNode.ReplaceChild(content, target); + } + } + else + { + return new HtmlString(html); + } + return new HtmlString(doc.DocumentNode.FirstChild.InnerHtml); } public string Coalesce(params object[] args) diff --git a/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs b/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs index 710e795226..f8f8203f8d 100644 --- a/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpControllerContextExtensions.cs @@ -96,22 +96,38 @@ namespace Umbraco.Web.WebApi jsonFormatter.SerializerSettings.Converters.Add(new CustomDateTimeConvertor("yyyy-MM-dd HH:mm:ss")); } - /// - /// Removes the xml formatter so it only outputs angularized json (with the json vulnerability prefix added) - /// - /// - internal static void EnsureJsonOutputOnly(this HttpControllerContext controllerContext) - { + ///// + ///// Removes the xml formatter so it only outputs angularized json (with the json vulnerability prefix added) + ///// + ///// + //internal static void EnsureJsonOutputOnly(this HttpControllerContext controllerContext) + //{ + // controllerContext.Configuration.Formatters = new MediaTypeFormatterCollection(); + + // //remove all json/xml formatters then add our custom one + // var toRemove = controllerContext.Configuration.Formatters.Where(t => (t is JsonMediaTypeFormatter) || (t is XmlMediaTypeFormatter)).ToList(); + // foreach (var r in toRemove) + // { + // controllerContext.Configuration.Formatters.Remove(r); + // } + // controllerContext.Configuration.Formatters.Add(new AngularJsonMediaTypeFormatter()); + //} + } + + /// + /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. + /// + public class AngularJsonOnlyConfigurationAttribute : Attribute, IControllerConfiguration + { + public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) + { //remove all json/xml formatters then add our custom one - for (var i = 0; i < controllerContext.Configuration.Formatters.Count;i++) + var toRemove = controllerSettings.Formatters.Where(t => (t is JsonMediaTypeFormatter) || (t is XmlMediaTypeFormatter)).ToList(); + foreach (var r in toRemove) { - if ((controllerContext.Configuration.Formatters[i] is JsonMediaTypeFormatter) - || (controllerContext.Configuration.Formatters[i] is XmlMediaTypeFormatter)) - { - controllerContext.Configuration.Formatters.RemoveAt(i); - } + controllerSettings.Formatters.Remove(r); } - controllerContext.Configuration.Formatters.Add(new AngularJsonMediaTypeFormatter()); + controllerSettings.Formatters.Add(new AngularJsonMediaTypeFormatter()); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index d9b3e83747..5934b6e7e7 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -796,7 +796,9 @@ namespace umbraco { if (attributes.ContainsKey(mp.Key.ToLower())) { - mp.Value = attributes[mp.Key.ToLower()].ToString(); + var item = attributes[mp.Key.ToLower()]; + + mp.Value = item == null ? string.Empty : item.ToString(); } else {