diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index dbfd446c39..105d006656 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -472,6 +472,11 @@ namespace Umbraco.Core string message; var database = new UmbracoDatabase(_connectionString, ProviderName); + + // If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks + // for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive. + + /* var supportsCaseInsensitiveQueries = SqlSyntaxContext.SqlSyntaxProvider.SupportsCaseInsensitiveQueries(database); if (supportsCaseInsensitiveQueries == false) { @@ -484,8 +489,9 @@ namespace Umbraco.Core return new Result { Message = message, Success = false, Percentage = "15" }; } + */ - message = GetResultMessageForMySql(supportsCaseInsensitiveQueries); + message = GetResultMessageForMySql(); var schemaResult = ValidateDatabaseSchema(); var installedVersion = schemaResult.DetermineInstalledVersion(); @@ -536,9 +542,9 @@ namespace Umbraco.Core LogHelper.Info("Database upgrade started"); var database = new UmbracoDatabase(_connectionString, ProviderName); - var supportsCaseInsensitiveQueries = SqlSyntaxContext.SqlSyntaxProvider.SupportsCaseInsensitiveQueries(database); + //var supportsCaseInsensitiveQueries = SqlSyntaxContext.SqlSyntaxProvider.SupportsCaseInsensitiveQueries(database); - var message = GetResultMessageForMySql(supportsCaseInsensitiveQueries); + var message = GetResultMessageForMySql(); var schemaResult = ValidateDatabaseSchema(); var installedVersion = schemaResult.DetermineInstalledVersion(); @@ -565,6 +571,23 @@ namespace Umbraco.Core } } + private string GetResultMessageForMySql() + { + if (SqlSyntaxContext.SqlSyntaxProvider.GetType() == typeof(MySqlSyntaxProvider)) + { + return "

 

Congratulations, the database step ran successfully!

" + + "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + + "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + + "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + + "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + + "
lower_case_table_names=1

" + + "

For more technical information on case sensitivity in MySQL, have a look at " + + "the documentation on the subject

"; + } + return string.Empty; + } + + /* private string GetResultMessageForMySql(bool? supportsCaseInsensitiveQueries) { if (supportsCaseInsensitiveQueries == null) @@ -588,7 +611,7 @@ namespace Umbraco.Core "the documentation on the subject

"; } return string.Empty; - } + }*/ private Attempt CheckReadyForInstall() { diff --git a/src/Umbraco.Core/Dynamics/PropertyResult.cs b/src/Umbraco.Core/Dynamics/PropertyResult.cs index d1007f8bb7..60a699969e 100644 --- a/src/Umbraco.Core/Dynamics/PropertyResult.cs +++ b/src/Umbraco.Core/Dynamics/PropertyResult.cs @@ -4,14 +4,14 @@ using System.Web; namespace Umbraco.Core.Dynamics { - internal class PropertyResult : IPublishedProperty, IHtmlString + internal class PropertyResult : IPublishedContentProperty, IHtmlString { - private readonly IPublishedProperty _source; + private readonly IPublishedContentProperty _source; private readonly string _alias; private readonly object _value; private readonly PropertyResultType _type; - internal PropertyResult(IPublishedProperty source, PropertyResultType type) + internal PropertyResult(IPublishedContentProperty source, PropertyResultType type) { if (source == null) throw new ArgumentNullException("source"); diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index d5069e27b3..fc10c818ef 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -5,6 +5,7 @@ using System.Runtime.Serialization; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Strings; namespace Umbraco.Core.Models { @@ -35,7 +36,7 @@ namespace Umbraco.Core.Models base.Path = path; ParentId = -1; _name = name; //.Replace("/", ".").Replace("\\", ""); // why? that's just the name! - _alias = alias.ToSafeAlias(); + _alias = alias.ToCleanString(CleanStringType.UnderscoreAlias); } [DataMember] diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs index 7ad88f0739..58a0a47802 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaCreation.cs @@ -175,6 +175,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial .Where(x => x.IsNullOrWhiteSpace() == false).ToList(); //Add valid and invalid foreign key differences to the result object + // We'll need to do invariant contains with case insensitivity because foreign key, primary key, and even index naming w/ MySQL is not standardized + // In theory you could have: FK_ or fk_ ...or really any standard that your development department (or developer) chooses to use. foreach (var unknown in unknownConstraintsInDatabase) { if (foreignKeysInSchema.InvariantContains(unknown) || primaryKeysInSchema.InvariantContains(unknown) || indexesInSchema.InvariantContains(unknown)) diff --git a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs index 9151ffd598..33595e3905 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Initial/DatabaseSchemaResult.cs @@ -65,7 +65,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial return new Version(4, 7, 0); } - return new Version(4, 9, 0); + return new Version(4, 8, 0); } //if the error is for umbracoServer diff --git a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs index cacfd87fbd..f9bf78ae2a 100644 --- a/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs +++ b/src/Umbraco.Core/Persistence/Migrations/MigrationRunner.cs @@ -46,20 +46,20 @@ namespace Umbraco.Core.Persistence.Migrations { LogHelper.Info("Initializing database migrations"); - var foundMigrations = MigrationResolver.Current.Migrations.ToArray(); + var foundMigrations = MigrationResolver.Current.Migrations.ToArray(); //filter all non-schema migrations var migrations = isUpgrade ? OrderedUpgradeMigrations(foundMigrations).ToList() : OrderedDowngradeMigrations(foundMigrations).ToList(); - + //SD: Why do we want this? if (Migrating.IsRaisedEventCancelled(new MigrationEventArgs(migrations, _currentVersion, _targetVersion, true), this)) return false; //Loop through migrations to generate sql var migrationContext = InitializeMigrations(migrations, database, databaseProvider, isUpgrade); - + try { ExecuteMigrations(migrationContext, database); @@ -86,8 +86,8 @@ namespace Umbraco.Core.Persistence.Migrations return true; } - private void ExecuteMigrations(IMigrationContext context, Database database) - { + private void ExecuteMigrations(IMigrationContext context, Database database) + { //Transactional execution of the sql that was generated from the found migrations using (var transaction = database.GetTransaction()) { @@ -108,13 +108,13 @@ namespace Umbraco.Core.Persistence.Migrations transaction.Complete(); } - } + } internal MigrationContext InitializeMigrations(List migrations, Database database, DatabaseProviders databaseProvider, bool isUpgrade = true) - { + { //Loop through migrations to generate sql var context = new MigrationContext(databaseProvider, database); - + foreach (var migration in migrations) { var baseMigration = migration as MigrationBase; @@ -148,7 +148,7 @@ namespace Umbraco.Core.Persistence.Migrations } return context; - } + } /// /// Filters and orders migrations based on the migrations listed and the currently configured version and the target installation version @@ -158,17 +158,17 @@ namespace Umbraco.Core.Persistence.Migrations internal IEnumerable OrderedUpgradeMigrations(IEnumerable foundMigrations) { var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where migrationAttribute.TargetVersion > _currentVersion && - migrationAttribute.TargetVersion <= _targetVersion && + migrationAttribute.TargetVersion <= _targetVersion && migrationAttribute.ProductName == _productName && //filter if the migration specifies a minimum current version for which to execute (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending - select migration).Distinct(); + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder ascending + select migration).Distinct(); return migrations; } @@ -180,17 +180,17 @@ namespace Umbraco.Core.Persistence.Migrations public IEnumerable OrderedDowngradeMigrations(IEnumerable foundMigrations) { var migrations = (from migration in foundMigrations - let migrationAttributes = migration.GetType().GetCustomAttributes(false) - from migrationAttribute in migrationAttributes - where migrationAttribute != null - where + let migrationAttributes = migration.GetType().GetCustomAttributes(false) + from migrationAttribute in migrationAttributes + where migrationAttribute != null + where migrationAttribute.TargetVersion > _currentVersion && - migrationAttribute.TargetVersion <= _targetVersion && + migrationAttribute.TargetVersion <= _targetVersion && migrationAttribute.ProductName == _productName && //filter if the migration specifies a minimum current version for which to execute (migrationAttribute.MinimumCurrentVersion == null || _currentVersion >= migrationAttribute.MinimumCurrentVersion) - orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending - select migration).Distinct(); + orderby migrationAttribute.TargetVersion, migrationAttribute.SortOrder descending + select migration).Distinct(); return migrations; } diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs index a972b7eeb0..027cecca9d 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero { + //see: http://issues.umbraco.org/issue/U4-4430 [Migration("7.1.0", 0, GlobalSettings.UmbracoMigrationName)] [Migration("6.2.0", 0, GlobalSettings.UmbracoMigrationName)] @@ -15,15 +16,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) { var constraints = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); - - //This should be 2 because this table has 2 keys - if (constraints.Count(x => x.Item1.InvariantEquals("cmsContentType2ContentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) - { - Create.PrimaryKey("PK_cmsContentType2ContentType") - .OnTable("cmsContentType2ContentType") - .Columns(new[] {"parentContentTypeId", "childContentTypeId"}); - } - + //This should be 2 because this table has 2 keys if (constraints.Count(x => x.Item1.InvariantEquals("cmsContentTypeAllowedContentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) { diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs new file mode 100644 index 0000000000..65e440a1da --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSixTwoZero/AssignMissingPrimaryForMySqlKeys2.cs @@ -0,0 +1,34 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSixTwoZero +{ + //We have to target this specifically to ensure this DOES NOT execute if upgrading from a version previous to 6.0, + // this is because when the 6.0.0 migrations are executed, this primary key get's created so if this migration is also executed + // we will get exceptions because it is trying to create the PK two times. + [Migration("6.0.0", "6.2.0", 0, GlobalSettings.UmbracoMigrationName)] + public class AssignMissingPrimaryForMySqlKeys2 : MigrationBase + { + public override void Up() + { + if (Context.CurrentDatabaseProvider == DatabaseProviders.MySql) + { + var constraints = SqlSyntaxContext.SqlSyntaxProvider.GetConstraintsPerColumn(Context.Database).Distinct().ToArray(); + + //This should be 2 because this table has 2 keys + if (constraints.Count(x => x.Item1.InvariantEquals("cmsContentType2ContentType") && x.Item3.InvariantEquals("PRIMARY")) == 0) + { + Create.PrimaryKey("PK_cmsContentType2ContentType") + .OnTable("cmsContentType2ContentType") + .Columns(new[] {"parentContentTypeId", "childContentTypeId"}); + } + } + } + + public override void Down() + { + //don't do anything, these keys should have always existed! + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index ae5e4f9010..b38b19a2d8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -158,12 +158,12 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM umbracoRelation WHERE childId = @Id", "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", "DELETE FROM umbracoDomains WHERE domainRootStructureID = @Id", - "DELETE FROM cmsDocument WHERE NodeId = @Id", + "DELETE FROM cmsDocument WHERE nodeId = @Id", "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeID = @Id", - "DELETE FROM cmsContent WHERE NodeId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; return list; diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index ec5f810152..26df451f62 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -153,7 +153,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", "DELETE FROM cmsDocumentType WHERE contentTypeNodeId = @Id", - "DELETE FROM cmsContentType WHERE NodeId = @Id", + "DELETE FROM cmsContentType WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; return list; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 3d6f242ecf..789a583510 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -142,12 +142,12 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM umbracoRelation WHERE parentId = @Id", "DELETE FROM umbracoRelation WHERE childId = @Id", "DELETE FROM cmsTagRelationship WHERE nodeId = @Id", - "DELETE FROM cmsDocument WHERE NodeId = @Id", + "DELETE FROM cmsDocument WHERE nodeId = @Id", "DELETE FROM cmsPropertyData WHERE contentNodeId = @Id", "DELETE FROM cmsPreviewXml WHERE nodeId = @Id", "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeID = @Id", - "DELETE FROM cmsContent WHERE NodeId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; return list; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 18ff7e8e4c..95221f37bf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsContentType2ContentType WHERE childContentTypeId = @Id", "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", - "DELETE FROM cmsContentType WHERE NodeId = @Id", + "DELETE FROM cmsContentType WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; return list; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 8e051e77e5..0d6fc90016 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -120,7 +120,8 @@ namespace Umbraco.Core.Persistence.Repositories group.AddingEntity(); var dto = _modelFactory.BuildDto(group); var o = Database.IsNew(dto) ? Convert.ToInt32(Database.Insert(dto)) : Database.Update(dto); - + group.Id = dto.NodeId; //Set Id on entity to ensure an Id is set + //Update with new correct path and id dto.Path = string.Concat("-1,", dto.NodeId); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 8f588dcaee..595fcb033d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -189,8 +189,8 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsMember2MemberGroup WHERE Member = @Id", "DELETE FROM cmsMember WHERE nodeId = @Id", "DELETE FROM cmsContentVersion WHERE ContentId = @Id", - "DELETE FROM cmsContentXml WHERE nodeID = @Id", - "DELETE FROM cmsContent WHERE NodeId = @Id", + "DELETE FROM cmsContentXml WHERE nodeId = @Id", + "DELETE FROM cmsContent WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; return list; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 2ea9814f26..0e91cc3c9e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -149,7 +149,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsPropertyType WHERE contentTypeId = @Id", "DELETE FROM cmsPropertyTypeGroup WHERE contenttypeNodeId = @Id", "DELETE FROM cmsMemberType WHERE NodeId = @Id", - "DELETE FROM cmsContentType WHERE NodeId = @Id", + "DELETE FROM cmsContentType WHERE nodeId = @Id", "DELETE FROM umbracoNode WHERE id = @Id" }; return list; diff --git a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs index 258d26bc91..91cea2ed18 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RecycleBinRepository.cs @@ -86,12 +86,12 @@ namespace Umbraco.Core.Persistence.Repositories FormatDeleteStatement("umbracoRelation", "childId"), FormatDeleteStatement("cmsTagRelationship", "nodeId"), FormatDeleteStatement("umbracoDomains", "domainRootStructureID"), - FormatDeleteStatement("cmsDocument", "NodeId"), + FormatDeleteStatement("cmsDocument", "nodeId"), FormatDeleteStatement("cmsPropertyData", "contentNodeId"), FormatDeleteStatement("cmsPreviewXml", "nodeId"), FormatDeleteStatement("cmsContentVersion", "ContentId"), - FormatDeleteStatement("cmsContentXml", "nodeID"), - FormatDeleteStatement("cmsContent", "NodeId"), + FormatDeleteStatement("cmsContentXml", "nodeId"), + FormatDeleteStatement("cmsContent", "nodeId"), "UPDATE umbracoNode SET parentID = '-20' WHERE trashed = '1' AND nodeObjectType = @NodeObjectType", "DELETE FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType" }; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 63042f0284..34775df456 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -136,7 +136,7 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsTask WHERE parentUserId = @Id", "DELETE FROM umbracoUser2NodePermission WHERE userId = @Id", "DELETE FROM umbracoUser2NodeNotify WHERE userId = @Id", - "DELETE FROM umbracoUserLogins WHERE userId = @Id", + "DELETE FROM umbracoUserLogins WHERE userID = @Id", "DELETE FROM umbracoUser2app WHERE " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("user") + "=@Id", "DELETE FROM umbracoUser WHERE id = @Id" }; diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 9a8106d633..475d5d185b 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -943,11 +943,12 @@ namespace Umbraco.Core.Services return ImportDictionaryItems(dictionaryItemElementList, languages, raiseEvents); } - private IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, List languages, bool raiseEvents) + private IEnumerable ImportDictionaryItems(XElement dictionaryItemElementList, List languages, bool raiseEvents, Guid? parentId = null) { var items = new List(); foreach (var dictionaryItemElement in dictionaryItemElementList.Elements("DictionaryItem")) - items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, raiseEvents)); + items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, raiseEvents, parentId)); + if (raiseEvents) ImportedDictionaryItem.RaiseEvent(new ImportEventArgs(items, dictionaryItemElementList, false), this); @@ -955,7 +956,7 @@ namespace Umbraco.Core.Services return items; } - private IEnumerable ImportDictionaryItem(XElement dictionaryItemElement, List languages, bool raiseEvents) + private IEnumerable ImportDictionaryItem(XElement dictionaryItemElement, List languages, bool raiseEvents, Guid? parentId) { var items = new List(); @@ -964,10 +965,11 @@ namespace Umbraco.Core.Services if (_localizationService.DictionaryItemExists(key)) dictionaryItem = GetAndUpdateDictionaryItem(key, dictionaryItemElement, languages); else - dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages); + dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages, parentId); _localizationService.Save(dictionaryItem); items.Add(dictionaryItem); - items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, raiseEvents)); + + items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, raiseEvents, dictionaryItem.Key)); return items; } @@ -981,9 +983,9 @@ namespace Umbraco.Core.Services return dictionaryItem; } - private static DictionaryItem CreateNewDictionaryItem(string key, XElement dictionaryItemElement, List languages) + private static DictionaryItem CreateNewDictionaryItem(string key, XElement dictionaryItemElement, List languages, Guid? parentId) { - var dictionaryItem = new DictionaryItem(key); + var dictionaryItem = parentId.HasValue ? new DictionaryItem(parentId.Value, key) : new DictionaryItem(key); var translations = new List(); foreach (var valueElement in dictionaryItemElement.Elements("Value")) diff --git a/src/Umbraco.Core/Strings/CleanStringType.cs b/src/Umbraco.Core/Strings/CleanStringType.cs index ea9603ec6f..0e0a7c9908 100644 --- a/src/Umbraco.Core/Strings/CleanStringType.cs +++ b/src/Umbraco.Core/Strings/CleanStringType.cs @@ -92,7 +92,7 @@ namespace Umbraco.Core.Strings /// /// Flag mask for role. /// - RoleMask = UrlSegment | Alias | FileName | ConvertCase, + RoleMask = UrlSegment | Alias | UnderscoreAlias | FileName | ConvertCase, /// /// Url role. @@ -112,6 +112,12 @@ namespace Umbraco.Core.Strings /// /// ConvertCase role. /// - ConvertCase = 0x080000 + ConvertCase = 0x080000, + + /// + /// UnderscoreAlias role. + /// + /// This is Alias + leading underscore. + UnderscoreAlias = 0x100000 } } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 84233e59bf..7c29a7aeef 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -175,6 +175,12 @@ namespace Umbraco.Core.Strings : (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, BreakTermsOnUpper = false + }).WithConfig(CleanStringType.UnderscoreAlias, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, + BreakTermsOnUpper = false }).WithConfig(CleanStringType.ConvertCase, new Config { PreFilter = null, diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d68ca62ddc..5c1e79f449 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -342,6 +342,7 @@ + @@ -373,6 +374,7 @@ + @@ -955,7 +957,6 @@ - diff --git a/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs index dd28e559bb..d7aca12bfa 100644 --- a/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs +++ b/src/Umbraco.Tests/Migrations/MigrationRunnerTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Migrations { var runner = new MigrationRunner(new Version(4, 0, 0), new Version(6, 0, 0), "Test"); - var migrations = runner.OrderedUpgradeMigrations(new List {new MultiMigration()}); + var migrations = runner.OrderedUpgradeMigrations(new List { new MultiMigration() }); var ctx = runner.InitializeMigrations( //new List {new DoRunMigration(), new DoNotRunMigration()}, @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Migrations Assert.AreEqual(1, ctx.Expressions.Count()); } - + [Migration("6.0.0", 1, "Test")] [Migration("5.0.0", 1, "Test")] private class MultiMigration : MigrationBase diff --git a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs index a730b184c4..bec18c23c3 100644 --- a/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs +++ b/src/Umbraco.Tests/Services/Importing/PackageImportTests.cs @@ -414,6 +414,32 @@ namespace Umbraco.Tests.Services.Importing AssertDictionaryItem("Child", expectedNorwegianChildValue, "nb-NO"); } + [Test] + public void PackagingService_Can_Import_Nested_DictionaryItems() + { + // Arrange + const string parentKey = "Parent"; + const string childKey = "Child"; + + var newPackageXml = XElement.Parse(ImportResources.Dictionary_Package); + var dictionaryItemsElement = newPackageXml.Elements("DictionaryItems").First(); + + AddLanguages(); + + // Act + var dictionaryItems = ServiceContext.PackagingService.ImportDictionaryItems(dictionaryItemsElement); + + // Assert + Assert.That(ServiceContext.LocalizationService.DictionaryItemExists(parentKey), "DictionaryItem parentKey does not exist"); + Assert.That(ServiceContext.LocalizationService.DictionaryItemExists(childKey), "DictionaryItem childKey does not exist"); + + var parentDictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey(parentKey); + var childDictionaryItem = ServiceContext.LocalizationService.GetDictionaryItemByKey(childKey); + + Assert.That(parentDictionaryItem.ParentId, Is.Not.EqualTo(childDictionaryItem.ParentId)); + Assert.That(childDictionaryItem.ParentId, Is.EqualTo(parentDictionaryItem.Key)); + } + [Test] public void PackagingService_WhenExistingDictionaryKey_ImportsNewChildren() { diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 8cf3fd5d13..dee2c47751 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -121,7 +121,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache try { //by default use the InternalSearcher - return eMgr.IndexProviderCollection["InternalIndexer"]; + var indexer = eMgr.IndexProviderCollection["InternalIndexer"]; + if (indexer.IndexerData.IncludeNodeTypes.Any() || indexer.IndexerData.ExcludeNodeTypes.Any()) + { + LogHelper.Warn("The InternalIndexer for examine is configured incorrectly, it should not list any include/exclude node types or field names, it should simply be configured as: " + ""); + } + return indexer; } catch (Exception ex) { @@ -177,12 +182,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return ConvertFromSearchResult(results.First()); } } - catch (FileNotFoundException) + catch (FileNotFoundException ex) { //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco //See this thread: http://examine.cdodeplex.com/discussions/264341 //Catch the exception here for the time being, and just fallback to GetMedia //TODO: Need to fix examine in LB scenarios! + LogHelper.Error("Could not load data from Examine index for media", ex); } } @@ -259,7 +265,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var values = new Dictionary {{"nodeName", xpath.GetAttribute("nodeName", "")}}; if (!UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema) { - values.Add("nodeTypeAlias", xpath.Name); + values["nodeTypeAlias"] = xpath.Name; } var result = xpath.SelectChildren(XPathNodeType.Element); @@ -271,13 +277,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //checking for duplicate keys because of the 'nodeTypeAlias' might already be added above. if (!values.ContainsKey(result.Current.Name)) { - values.Add(result.Current.Name, result.Current.Value); + values[result.Current.Name] = result.Current.Value; } while (result.Current.MoveToNextAttribute()) { if (!values.ContainsKey(result.Current.Name)) { - values.Add(result.Current.Name, result.Current.Value); + values[result.Current.Name] = result.Current.Value; } } result.Current.MoveToParent(); @@ -296,7 +302,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache value = result.Current.OuterXml; } } - values.Add(result.Current.Name, value); + values[result.Current.Name] = value; } } @@ -320,48 +326,25 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias) { - if (dd.LoadedFromExamine) - { - //if this is from Examine, lets check if the alias does not exist on the document - if (dd.Properties.All(x => x.PropertyTypeAlias != alias)) - { - //ok it doesn't exist, we might assume now that Examine didn't index this property because the index is not set up correctly - //so before we go loading this from the database, we can check if the alias exists on the content type at all, this information - //is cached so will be quicker to look up. - if (dd.Properties.Any(x => x.PropertyTypeAlias == UmbracoContentIndexer.NodeTypeAliasFieldName)) - { - // so in dd.Properties, there is an IPublishedProperty with property type alias "__NodeTypeAlias" and - // that special property would contain the node type alias, which we use to get "aliases & names". That - // special property is going to be a PropertyResult (with Value == DataValue) and we - // want its value in the most simple way = it is OK to use DataValue here. - var aliasesAndNames = ContentType.GetAliasesAndNames(dd.Properties.First(x => x.PropertyTypeAlias.InvariantEquals(UmbracoContentIndexer.NodeTypeAliasFieldName)).DataValue.ToString()); - if (aliasesAndNames != null) - { - if (!aliasesAndNames.ContainsKey(alias)) - { - //Ok, now we know it doesn't exist on this content type anyways - return null; - } - } - } + //lets check if the alias does not exist on the document. + //NOTE: Examine will not index empty values and we do not output empty XML Elements to the cache - either of these situations + // would mean that the property is missing from the collection whether we are getting the value from Examine or from the library media cache. + if (dd.Properties.All(x => x.PropertyTypeAlias != alias)) + { + return null; + } - //if we've made it here, that means it does exist on the content type but not in examine, we'll need to query the db :( - var media = global::umbraco.library.GetMedia(dd.Id, true); - if (media != null && media.Current != null) - { - media.MoveNext(); - var mediaDoc = ConvertFromXPathNavigator(media.Current); - return mediaDoc.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); - } - } - } - - //We've made it here which means that the value is stored in the Examine index. - //We are going to check for a special field however, that is because in some cases we store a 'Raw' - //value in the index such as for xml/html. - var rawValue = dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(UmbracoContentIndexer.RawFieldPrefix + alias)); - return rawValue - ?? dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + if (dd.LoadedFromExamine) + { + //We are going to check for a special field however, that is because in some cases we store a 'Raw' + //value in the index such as for xml/html. + var rawValue = dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(UmbracoContentIndexer.RawFieldPrefix + alias)); + return rawValue + ?? dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + } + + //if its not loaded from examine, then just return the property + return dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); } /// diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index c272c7ff14..ed1e01f407 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using System.Security; @@ -60,6 +60,7 @@ namespace Umbraco.Web.Search CacheRefresherBase.CacheUpdated += PublishedPageCacheRefresherCacheUpdated; CacheRefresherBase.CacheUpdated += MediaCacheRefresherCacheUpdated; CacheRefresherBase.CacheUpdated += MemberCacheRefresherCacheUpdated; + CacheRefresherBase.CacheUpdated += ContentTypeCacheRefresherCacheUpdated; var contentIndexer = ExamineManager.Instance.IndexProviderCollection["InternalIndexer"] as UmbracoContentIndexer; if (contentIndexer != null) @@ -73,6 +74,24 @@ namespace Umbraco.Web.Search } } + /// + /// This is used to refresh content indexers IndexData based on the DataService whenever a content type is changed since + /// properties may have been added/removed + /// + /// + /// + /// + /// See: http://issues.umbraco.org/issue/U4-4798 + /// + static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e) + { + var indexersToUpdated = ExamineManager.Instance.IndexProviderCollection.OfType(); + foreach (var provider in indexersToUpdated) + { + provider.RefreshIndexerDataFromDataService(); + } + } + static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e) { switch (e.MessageType) diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 702d74346d..bfa57d5ab3 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -254,7 +255,7 @@ namespace Umbraco.Web //NOTE: the value could have html encoded values, so we need to deal with that - macroProps.Add(i.Key.ToLower(), (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); + macroProps.Add(i.Key.ToLowerInvariant(), (i.Value is string) ? HttpUtility.HtmlDecode(i.Value.ToString()) : i.Value); } var macroControl = m.renderMacro(macroProps, umbracoPage.Elements, diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 870522fc6d..2fd71b1458 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -803,9 +803,9 @@ namespace umbraco { foreach (MacroPropertyModel mp in Model.Properties) { - if (attributes.ContainsKey(mp.Key.ToLower())) + if (attributes.ContainsKey(mp.Key.ToLowerInvariant())) { - var item = attributes[mp.Key.ToLower()]; + var item = attributes[mp.Key.ToLowerInvariant()]; mp.Value = item == null ? string.Empty : item.ToString(); } diff --git a/src/UmbracoExamine/Config/IndexSetExtensions.cs b/src/UmbracoExamine/Config/IndexSetExtensions.cs index 76a4d46575..ac432b16ec 100644 --- a/src/UmbracoExamine/Config/IndexSetExtensions.cs +++ b/src/UmbracoExamine/Config/IndexSetExtensions.cs @@ -14,58 +14,63 @@ namespace UmbracoExamine.Config /// public static class IndexSetExtensions { - - private static readonly object Locker = new object(); - internal static IIndexCriteria ToIndexCriteria(this IndexSet set, IDataService svc, IEnumerable indexFieldPolicies) { + + var attributeFields = set.IndexAttributeFields.Cast().ToArray(); + var userFields = set.IndexUserFields.Cast().ToArray(); + var includeNodeTypes = set.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray(); + var excludeNodeTypes = set.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray(); + var parentId = set.IndexParentId; + + //if there are no user fields defined, we'll populate them from the data source (include them all) if (set.IndexUserFields.Count == 0) { - lock (Locker) + //we need to add all user fields to the collection if it is empty (this is the default if none are specified) + var userProps = svc.ContentService.GetAllUserPropertyNames(); + var fields = new List(); + foreach (var u in userProps) { - //we need to add all user fields to the collection if it is empty (this is the default if none are specified) - var userFields = svc.ContentService.GetAllUserPropertyNames(); - foreach (var u in userFields) + var field = new IndexField() { Name = u }; + var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u); + if (policy != null) { - var field = new IndexField() {Name = u}; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == u); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } - set.IndexUserFields.Add(field); + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; } + fields.Add(field); } + userFields = fields.ToArray(); } + //if there are no attribute fields defined, we'll populate them from the data source (include them all) if (set.IndexAttributeFields.Count == 0) { - lock (Locker) + //we need to add all system fields to the collection if it is empty (this is the default if none are specified) + var sysProps = svc.ContentService.GetAllSystemPropertyNames(); + var fields = new List(); + foreach (var s in sysProps) { - //we need to add all system fields to the collection if it is empty (this is the default if none are specified) - var sysFields = svc.ContentService.GetAllSystemPropertyNames(); - foreach (var s in sysFields) + var field = new IndexField() { Name = s }; + var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s); + if (policy != null) { - var field = new IndexField() { Name = s }; - var policy = indexFieldPolicies.FirstOrDefault(x => x.Name == s); - if (policy != null) - { - field.Type = policy.Type; - field.EnableSorting = policy.EnableSorting; - } - set.IndexAttributeFields.Add(field); + field.Type = policy.Type; + field.EnableSorting = policy.EnableSorting; } + fields.Add(field); } + attributeFields = fields.ToArray(); } + return new IndexCriteria( - set.IndexAttributeFields.Cast().ToArray(), - set.IndexUserFields.Cast().ToArray(), - set.IncludeNodeTypes.ToList().Select(x => x.Name).ToArray(), - set.ExcludeNodeTypes.ToList().Select(x => x.Name).ToArray(), - set.IndexParentId); + attributeFields, + userFields, + includeNodeTypes, + excludeNodeTypes, + parentId); } /// diff --git a/src/UmbracoExamine/UmbracoContentIndexer.cs b/src/UmbracoExamine/UmbracoContentIndexer.cs index 9da53a77f2..3f442f5daa 100644 --- a/src/UmbracoExamine/UmbracoContentIndexer.cs +++ b/src/UmbracoExamine/UmbracoContentIndexer.cs @@ -298,6 +298,19 @@ namespace UmbracoExamine base.RebuildIndex(); } + /// + /// Used to refresh the current IndexerData from the data in the DataService. This can be used + /// if there are more properties added/removed from the database + /// + public void RefreshIndexerDataFromDataService() + { + //TODO: This would be much better done if the IndexerData property had read/write locks applied + // to it! Unless we update the base class there's really no way to prevent the IndexerData from being + // changed during an operation that is reading from it. + var newIndexerData = GetIndexerData(IndexSets.Instance.Sets[IndexSetName]); + IndexerData = newIndexerData; + } + /// /// Override this method to strip all html from all user fields before raising the event, then after the event /// ensure our special Path field is added to the collection diff --git a/src/umbraco.cms/businesslogic/template/Template.cs b/src/umbraco.cms/businesslogic/template/Template.cs index 6d09bd37a7..12b59d7138 100644 --- a/src/umbraco.cms/businesslogic/template/Template.cs +++ b/src/umbraco.cms/businesslogic/template/Template.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Strings; using umbraco.DataLayer; using System.Text.RegularExpressions; using System.IO; @@ -205,7 +206,7 @@ namespace umbraco.cms.businesslogic.template { FlushCache(); _oldAlias = _alias; - _alias = value.ToSafeAlias(); + _alias = value.ToCleanString(CleanStringType.UnderscoreAlias); SqlHelper.ExecuteNonQuery("Update cmsTemplate set alias = @alias where NodeId = " + this.Id, SqlHelper.CreateParameter("@alias", _alias)); _templateAliasesInitialized = false; @@ -403,13 +404,13 @@ namespace umbraco.cms.businesslogic.template var node = MakeNew(-1, ObjectType, u.Id, 1, name, Guid.NewGuid()); //ensure unique alias - name = name.ToSafeAlias(); + name = name.ToCleanString(CleanStringType.UnderscoreAlias); if (GetByAlias(name) != null) name = EnsureUniqueAlias(name, 1); //name = name.Replace("/", ".").Replace("\\", ""); //why? ToSafeAlias() already removes those chars if (name.Length > 100) - name = name.Substring(0, 98); // + "..."; // no, these are invalid alias chars + name = name.Substring(0, 95); // + "..."; // no, these are invalid alias chars SqlHelper.ExecuteNonQuery("INSERT INTO cmsTemplate (NodeId, Alias, design, master) VALUES (@nodeId, @alias, @design, @master)", SqlHelper.CreateParameter("@nodeId", node.Id), diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 98e7db6647..098d3c87ed 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -1022,10 +1022,10 @@ namespace umbraco.cms.businesslogic.web return result; } - return Attempt.Fail(); + return new Attempt(false, new PublishStatus(Content, PublishStatusType.FailedCancelledByEvent)); } - return Attempt.Fail(); + return new Attempt(false, new PublishStatus(Content, PublishStatusType.FailedCancelledByEvent)); } ///