diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs index 275cc61425..092de4d6d6 100644 --- a/src/Umbraco.Core/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/ContentVariationExtensions.cs @@ -1,82 +1,162 @@ -using Umbraco.Core.Models; +using System; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core { /// - /// Provides extension methods for various enumerations. + /// Provides extension methods for content variations. /// public static class ContentVariationExtensions { /// - /// Determines whether a variation has all flags set. + /// Determines whether the content type is invariant. /// - public static bool Has(this ContentVariation variation, ContentVariation values) - => (variation & values) == values; + public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing(); /// - /// Determines whether a variation has at least a flag set. + /// Determines whether the content type varies by culture. /// - public static bool HasAny(this ContentVariation variation, ContentVariation values) - => (variation & values) != ContentVariation.Unknown; + /// And then it could also vary by segment. + public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); /// - /// Determines whether a variation does not support culture variations + /// Determines whether the content type varies by segment. /// - /// - /// - public static bool DoesNotSupportCulture(this ContentVariation variation) + /// And then it could also vary by culture. + public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the property type is invariant. + /// + public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing(); + + /// + /// Determines whether the property type varies by culture. + /// + /// And then it could also vary by segment. + public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by segment. + /// + /// And then it could also vary by culture. + public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the content type is invariant. + /// + public static bool VariesByNothing(this PublishedContentType contentType) => contentType.Variations.VariesByNothing(); + + /// + /// Determines whether the content type varies by culture. + /// + /// And then it could also vary by segment. + public static bool VariesByCulture(this PublishedContentType contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the content type varies by segment. + /// + /// And then it could also vary by culture. + public static bool VariesBySegment(this PublishedContentType contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this PublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether a variation is invariant. + /// + public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing; + + /// + /// Determines whether a variation varies by culture. + /// + /// And then it could also vary by segment. + public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0; + + /// + /// Determines whether a variation varies by segment. + /// + /// And then it could also vary by culture. + public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0; + + /// + /// Determines whether a variation varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) > 0; + + /// + /// Validates that a combination of culture and segment is valid for the variation. + /// + /// The variation. + /// The culture. + /// The segment. + /// A value indicating whether to perform exact validation. + /// A value indicating whether to support wildcards. + /// A value indicating whether to throw a when the combination is invalid. + /// True if the combination is valid; otherwise false. + /// + /// When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then + /// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is + /// Culture, an invariant combination is ok. + /// Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type. + /// Both and can be "*" to indicate "all of them". + /// + /// Occurs when the combination is invalid, and is true. + public static bool ValidateVariation(this ContentVariation variation, string culture, string segment, bool exact, bool wildcards, bool throwIfInvalid) { - return !variation.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment); - } + culture = culture.NullOrWhiteSpaceAsNull(); + segment = segment.NullOrWhiteSpaceAsNull(); - /// - /// Determines whether a variation does support culture variations - /// - /// - /// - public static bool DoesSupportCulture(this ContentVariation variation) - { - return variation.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment); - } + bool Validate(bool variesBy, string value) + { + if (variesBy) + { + // varies by + // in exact mode, the value cannot be null (but it can be a wildcard) + // in !wildcards mode, the value cannot be a wildcard (but it can be null) + if ((exact && value == null) || (!wildcards && value == "*")) + return false; + } + else + { + // does not vary by value + // the value cannot have a value + // unless wildcards and it's "*" + if (value != null && (!wildcards || value != "*")) + return false; + } - /// - /// Determines whether a variation does not support invariant variations - /// - /// - /// - public static bool DoesNotSupportInvariant(this ContentVariation variation) - { - return !variation.HasAny(ContentVariation.InvariantNeutral | ContentVariation.InvariantSegment); - } + return true; + } - /// - /// Determines whether a variation does support invariant variations - /// - /// - /// - public static bool DoesSupportInvariant(this ContentVariation variation) - { - return variation.HasAny(ContentVariation.InvariantNeutral | ContentVariation.InvariantSegment); - } + if (!Validate(variation.VariesByCulture(), culture)) + { + if (throwIfInvalid) + throw new NotSupportedException($"Culture value \"{culture ?? ""}\" is invalid."); + return false; + } - /// - /// Determines whether a variation does not support segment variations - /// - /// - /// - public static bool DoesNotSupportSegment(this ContentVariation variation) - { - return !variation.HasAny(ContentVariation.InvariantSegment | ContentVariation.CultureSegment); - } + if (!Validate(variation.VariesBySegment(), segment)) + { + if (throwIfInvalid) + throw new NotSupportedException($"Segment value \"{segment ?? ""}\" is invalid."); + return false; + } - /// - /// Determines whether a variation does not support neutral variations - /// - /// - /// - public static bool DoesNotSupportNeutral(this ContentVariation variation) - { - return !variation.HasAny(ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral); + return true; } } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index 8063ba9f46..7c02f7ad75 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -156,10 +156,10 @@ namespace Umbraco.Core.Migrations.Install private void CreateContentTypeData() { - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user", Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 532, NodeId = 1031, Alias = Constants.Conventions.MediaTypes.Folder, Icon = "icon-folder", Thumbnail = "icon-folder", IsContainer = false, AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 533, NodeId = 1032, Alias = Constants.Conventions.MediaTypes.Image, Icon = "icon-picture", Thumbnail = "icon-picture", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 534, NodeId = 1033, Alias = Constants.Conventions.MediaTypes.File, Icon = "icon-document", Thumbnail = "icon-document", AllowAtRoot = true, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.ContentType, "pk", false, new ContentTypeDto { PrimaryKey = 531, NodeId = 1044, Alias = Constants.Conventions.MemberTypes.DefaultAlias, Icon = "icon-user", Thumbnail = "icon-user", Variations = (byte) ContentVariation.Nothing }); } private void CreateUserData() @@ -212,23 +212,23 @@ namespace Umbraco.Core.Migrations.Install private void CreatePropertyTypeData() { - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.DataTypes.DefaultMediaListView, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 6, UniqueId = 6.ToGuid(), DataTypeId = 1043, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.File, Name = "Upload image", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 7, UniqueId = 7.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Width, Name = "Width", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 8, UniqueId = 8.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Height, Name = "Height", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 9, UniqueId = 9.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 10, UniqueId = 10.ToGuid(), DataTypeId = -92, ContentTypeId = 1032, PropertyTypeGroupId = 3, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.DataTypes.DefaultMediaListView, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); //membership property types - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); - _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.InvariantNeutral }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 30, UniqueId = 30.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsApproved, Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 2, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 31, UniqueId = 31.ToGuid(), DataTypeId = -49, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.IsLockedOut, Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 3, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 32, UniqueId = 32.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLockoutDate, Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 4, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 33, UniqueId = 33.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastLoginDate, Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 5, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); + _database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 34, UniqueId = 34.ToGuid(), DataTypeId = Constants.DataTypes.LabelDateTime, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.LastPasswordChangeDate, Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 6, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing }); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index b7a77b10ce..698b77f8bf 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -121,9 +121,10 @@ namespace Umbraco.Core.Migrations.Upgrade Chain("{083A9894-903D-41B7-B6B3-9EAF2D4CCED0}"); Chain("{42097524-0F8C-482C-BD79-AC7407D8A028}"); Chain("{3608CD41-792A-4E9A-A97D-42A5E797EE31}"); + Chain("{608A02B8-B1A1-4C24-8955-0B95DB1F567E}"); // must chain to v8 final state (see at end of file) - Chain("{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); + Chain("{1350617A-4930-4D61-852F-E3AA9E692173}"); // UPGRADE FROM 7, MORE RECENT @@ -221,10 +222,13 @@ namespace Umbraco.Core.Migrations.Upgrade //Chain("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}"); // stephan added that one - need a path to final state Add("{65D6B71C-BDD5-4A2E-8D35-8896325E9151}", "{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); + // 8.0.0 + Chain("{1350617A-4930-4D61-852F-E3AA9E692173}"); + // FINAL STATE - MUST MATCH LAST ONE ABOVE ! // whenever this changes, update all references in this file! - Add(string.Empty, "{4CACE351-C6B9-4F0C-A6BA-85A02BBD39E4}"); + Add(string.Empty, "{1350617A-4930-4D61-852F-E3AA9E692173}"); } } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs new file mode 100644 index 0000000000..23c835a327 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/ContentVariationMigration.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class ContentVariationMigration : MigrationBase + { + public ContentVariationMigration(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + byte GetNewValue(byte oldValue) + { + switch (oldValue) + { + case 0: // Unknown + case 1: // InvariantNeutral + return 0; // Unknown + case 2: // CultureNeutral + case 3: // CultureNeutral | InvariantNeutral + return 1; // Culture + case 4: // InvariantSegment + case 5: // InvariantSegment | InvariantNeutral + return 2; // Segment + case 6: // InvariantSegment | CultureNeutral + case 7: // InvariantSegment | CultureNeutral | InvariantNeutral + case 8: // CultureSegment + case 9: // CultureSegment | InvariantNeutral + case 10: // CultureSegment | CultureNeutral + case 11: // CultureSegment | CultureNeutral | InvariantNeutral + case 12: // etc + case 13: + case 14: + case 15: + return 3; // Culture | Segment + default: + throw new NotSupportedException($"Invalid value {oldValue}."); + } + } + + var propertyTypes = Database.Fetch(Sql().Select().From()); + foreach (var dto in propertyTypes) + { + dto.Variations = GetNewValue(dto.Variations); + Database.Update(dto); + } + + var contentTypes = Database.Fetch(Sql().Select().From()); + foreach (var dto in contentTypes) + { + dto.Variations = GetNewValue(dto.Variations); + Database.Update(dto); + } + } + } +} diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index e671b45968..10869c62da 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -21,7 +21,8 @@ namespace Umbraco.Core.Models private DateTime? _releaseDate; private DateTime? _expireDate; private Dictionary _publishInfos; - private HashSet _edited; + private Dictionary _publishInfosOrig; + private HashSet _editedCultures; private static readonly Lazy Ps = new Lazy(); @@ -188,123 +189,118 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public IContentType ContentType => _contentType; + /// [IgnoreDataMember] - public DateTime? PublishDate { get; internal set; } + public DateTime? PublishDate { get; internal set; } // set by persistence + /// [IgnoreDataMember] - public int? PublisherId { get; internal set; } + public int? PublisherId { get; internal set; } // set by persistence + /// [IgnoreDataMember] - public ITemplate PublishTemplate { get; internal set; } + public ITemplate PublishTemplate { get; internal set; } // set by persistence + /// [IgnoreDataMember] - public string PublishName { get; internal set; } + public string PublishName { get; internal set; } // set by persistence - // sets publish infos - // internal for repositories - // clear by clearing name - internal void SetPublishInfos(string culture, string name, DateTime date) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullOrEmptyException(nameof(name)); + /// + [IgnoreDataMember] + public IEnumerable EditedCultures => CultureNames.Keys.Where(IsCultureEdited); - // this is the only place where we set PublishName (apart from factories etc), and we must ensure - // that we do have an invariant name, as soon as we have a variant name, else we would end up not - // being able to publish - and not being able to change the name, as PublishName is readonly. - // see also: DocumentRepository.EnsureInvariantNameValues() - which deals with Name. - // see also: U4-11286 - if (culture == null || string.IsNullOrEmpty(PublishName)) - { - PublishName = name; - PublishDate = date; - } + /// + [IgnoreDataMember] + public IEnumerable PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty(); - if (culture != null) - { - // private method, assume that culture is valid + /// + public bool IsCulturePublished(string culture) + => _publishInfos != null && _publishInfos.ContainsKey(culture); - if (_publishInfos == null) - _publishInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + public bool WasCulturePublished(string culture) + => _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture); - _publishInfos[culture] = (name, date); - } - } + /// + public bool IsCultureEdited(string culture) + => !IsCulturePublished(culture) || (_editedCultures != null && _editedCultures.Contains(culture)); /// [IgnoreDataMember] - public IReadOnlyDictionary PublishCultureNames => _publishInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; + public IReadOnlyDictionary PublishNames => _publishInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; /// public string GetPublishName(string culture) { - if (culture == null) return PublishName; + if (culture.IsNullOrWhiteSpace()) return PublishName; + if (!ContentTypeBase.VariesByCulture()) return null; if (_publishInfos == null) return null; return _publishInfos.TryGetValue(culture, out var infos) ? infos.Name : null; } - // clears a publish name - private void ClearPublishName(string culture) + /// + public DateTime? GetPublishDate(string culture) { - if (culture == null) - { - PublishName = null; - return; - } - - if (_publishInfos == null) return; - _publishInfos.Remove(culture); - if (_publishInfos.Count == 0) - _publishInfos = null; + if (culture.IsNullOrWhiteSpace()) return PublishDate; + if (!ContentTypeBase.VariesByCulture()) return null; + if (_publishInfos == null) return null; + return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null; } - // clears all publish names - private void ClearPublishNames() + // internal for repository + internal void SetPublishInfo(string culture, string name, DateTime date) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullOrEmptyException(nameof(name)); + + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + if (_publishInfos == null) + _publishInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + + _publishInfos[culture] = (name, date); + } + + private void ClearPublishInfos() { - PublishName = null; _publishInfos = null; } - /// - public bool IsCulturePublished(string culture) - => !string.IsNullOrWhiteSpace(GetPublishName(culture)); - - /// - public DateTime GetCulturePublishDate(string culture) + private void ClearPublishInfo(string culture) { - if (_publishInfos != null && _publishInfos.TryGetValue(culture, out var infos)) - return infos.Date; - throw new InvalidOperationException($"Culture \"{culture}\" is not published."); - } + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); - /// - public IEnumerable PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty(); - - /// - public bool IsCultureEdited(string culture) - { - return string.IsNullOrWhiteSpace(GetPublishName(culture)) || (_edited != null && _edited.Contains(culture)); + if (_publishInfos == null) return; + _publishInfos.Remove(culture); + if (_publishInfos.Count == 0) _publishInfos = null; } // sets a publish edited internal void SetCultureEdited(string culture) { - if (_edited == null) - _edited = new HashSet(StringComparer.OrdinalIgnoreCase); - _edited.Add(culture); + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + if (_editedCultures == null) + _editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); + _editedCultures.Add(culture); } // sets all publish edited internal void SetCultureEdited(IEnumerable cultures) { - _edited = new HashSet(cultures, StringComparer.OrdinalIgnoreCase); + if (cultures == null) + { + _editedCultures = null; + } + else + { + var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase); + _editedCultures = editedCultures.Count > 0 ? editedCultures : null; + } } - /// - public IEnumerable EditedCultures => CultureNames.Keys.Where(IsCultureEdited); - - /// - public IEnumerable AvailableCultures => CultureNames.Keys; - [IgnoreDataMember] public int PublishedVersionId { get; internal set; } @@ -312,251 +308,73 @@ namespace Umbraco.Core.Models public bool Blueprint { get; internal set; } /// - internal virtual bool TryPublishAllValues() + public virtual bool PublishCulture(string culture = "*") { - // the values we want to publish should be valid - if (ValidateAllProperties().Any()) - return false; //fixme this should return an attempt with error results + culture = culture.NullOrWhiteSpaceAsNull(); - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - if (string.IsNullOrWhiteSpace(Name)) - throw new InvalidOperationException($"Cannot publish invariant culture without a name."); - PublishName = Name; - var now = DateTime.Now; - foreach (var (culture, name) in CultureNames) - { - if (string.IsNullOrWhiteSpace(name)) - return false; //fixme this should return an attempt with error results - - SetPublishInfos(culture, name, now); - } - - // property.PublishAllValues only deals with supported variations (if any) - foreach (var property in Properties) - property.PublishAllValues(); - - _publishedState = PublishedState.Publishing; - return true; - } - - /// - public virtual bool TryPublishValues(string culture = null, string segment = null) - { - // the variation should be supported by the content type - ContentType.ValidateVariation(culture, segment, throwIfInvalid: true); + // the variation should be supported by the content type properties + // if the content type is invariant, only '*' and 'null' is ok + // if the content type varies, everything is ok because some properties may be invariant + if (!ContentType.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\"."); // the values we want to publish should be valid - if (ValidateProperties(culture, segment).Any()) - return false; //fixme this should return an attempt with error results - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - if (segment == null) - { - var name = GetName(culture); - if (string.IsNullOrWhiteSpace(name)) - return false; //fixme this should return an attempt with error results - - SetPublishInfos(culture, name, DateTime.Now); - } - - // property.PublishValue throws on invalid variation, so filter them out - foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(culture, segment, throwIfInvalid: false))) - property.PublishValue(culture, segment); - - _publishedState = PublishedState.Publishing; - return true; - } - - /// - internal virtual bool PublishCultureValues(string culture = null) - { - //fixme - needs API review as this is not used apart from in tests - - // the values we want to publish should be valid - if (ValidatePropertiesForCulture(culture).Any()) + if (ValidateProperties(culture).Any()) return false; - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - var name = GetName(culture); - if (string.IsNullOrWhiteSpace(name)) - throw new InvalidOperationException($"Cannot publish {culture ?? "invariant"} culture without a name."); - SetPublishInfos(culture, name, DateTime.Now); + var alsoInvariant = false; + if (culture == "*") // all cultures + { + foreach (var c in AvailableCultures) + { + var name = GetCultureName(c); + if (string.IsNullOrWhiteSpace(name)) + return false; + SetPublishInfo(c, name, DateTime.Now); + } + } + else // one single culture + { + var name = GetCultureName(culture); + if (string.IsNullOrWhiteSpace(name)) + return false; + SetPublishInfo(culture, name, DateTime.Now); + alsoInvariant = true; // we also want to publish invariant values + } - // property.PublishCultureValues only deals with supported variations (if any) + // property.PublishValues only publishes what is valid, variation-wise foreach (var property in Properties) - property.PublishCultureValues(culture); + { + property.PublishValues(culture); + if (alsoInvariant) + property.PublishValues(null); + } _publishedState = PublishedState.Publishing; return true; } /// - public virtual void ClearAllPublishedValues() + public virtual void UnpublishCulture(string culture = "*") { - // property.ClearPublishedAllValues only deals with supported variations (if any) - foreach (var property in Properties) - property.ClearPublishedAllValues(); + culture = culture.NullOrWhiteSpaceAsNull(); - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - ClearPublishNames(); + // the variation should be supported by the content type properties + if (!ContentType.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\"."); + + if (culture == "*") // all cultures + ClearPublishInfos(); + else // one single culture + ClearPublishInfo(culture); + + // property.PublishValues only publishes what is valid, variation-wise + foreach (var property in Properties) + property.UnpublishValues(culture); _publishedState = PublishedState.Publishing; } - /// - public virtual void ClearPublishedValues(string culture = null, string segment = null) - { - // the variation should be supported by the content type - ContentType.ValidateVariation(culture, segment, throwIfInvalid: true); - - // property.ClearPublishedValue throws on invalid variation, so filter them out - foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(culture, segment, throwIfInvalid: false))) - property.ClearPublishedValue(culture, segment); - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - ClearPublishName(culture); - - _publishedState = PublishedState.Publishing; - } - - /// - public virtual void ClearCulturePublishedValues(string culture = null) - { - // property.ClearPublishedCultureValues only deals with supported variations (if any) - foreach (var property in Properties) - property.ClearPublishedCultureValues(culture); - - // Name and PublishName are managed by the repository, but Names and PublishNames - // must be managed here as they depend on the existing / supported variations. - ClearPublishName(culture); - - _publishedState = PublishedState.Publishing; - } - - private bool CopyingFromSelf(IContent other) - { - // copying from the same Id and VersionPk - return Id == other.Id && VersionId == other.VersionId; - } - - /// - public virtual void CopyAllValues(IContent other) - { - if (other.ContentTypeId != ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); - - // we could copy from another document entirely, - // or from another version of the same document, - // in which case there is a special case. - var published = CopyingFromSelf(other); - - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - - // clear all existing properties - foreach (var property in Properties) - foreach (var pvalue in property.Values) - if (property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - property.SetValue(null, pvalue.Culture, pvalue.Segment); - - // copy other properties - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) - { - var alias = otherProperty.PropertyType.Alias; - foreach (var pvalue in otherProperty.Values) - { - if (!otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - continue; - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - SetValue(alias, value, pvalue.Culture, pvalue.Segment); - } - } - - // copy names - ClearNames(); - foreach (var (culture, name) in other.CultureNames) - SetName(name, culture); - Name = other.Name; - } - - /// - public virtual void CopyValues(IContent other, string culture = null, string segment = null) - { - if (other.ContentTypeId != ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); - - var published = CopyingFromSelf(other); - - // segment is invariant in comparisons - segment = segment?.ToLowerInvariant(); - - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - - // clear all existing properties - foreach (var property in Properties) - { - if (!property.PropertyType.ValidateVariation(culture, segment, false)) - continue; - - foreach (var pvalue in property.Values) - if (pvalue.Culture.InvariantEquals(culture) && pvalue.Segment.InvariantEquals(segment)) - property.SetValue(null, pvalue.Culture, pvalue.Segment); - } - - // copy other properties - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) - { - if (!otherProperty.PropertyType.ValidateVariation(culture, segment, false)) - continue; - - var alias = otherProperty.PropertyType.Alias; - SetValue(alias, otherProperty.GetValue(culture, segment, published), culture, segment); - } - - // copy name - SetName(other.GetName(culture), culture); - } - - /// - public virtual void CopyCultureValues(IContent other, string culture = null) - { - if (other.ContentTypeId != ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); - - var published = CopyingFromSelf(other); - - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - - // clear all existing properties - foreach (var property in Properties) - foreach (var pvalue in property.Values) - if (pvalue.Culture.InvariantEquals(culture) && property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - property.SetValue(null, pvalue.Culture, pvalue.Segment); - - // copy other properties - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) - { - var alias = otherProperty.PropertyType.Alias; - foreach (var pvalue in otherProperty.Values) - { - if (pvalue.Culture != culture || !otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) - continue; - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - SetValue(alias, value, pvalue.Culture, pvalue.Segment); - } - } - - // copy name - SetName(other.GetName(culture), culture); - } - /// /// Changes the for the current content object /// @@ -598,6 +416,11 @@ namespace Umbraco.Core.Models // take care of the published state _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; + + // take care of publish infos + _publishInfosOrig = _publishInfos == null + ? null + : new Dictionary(_publishInfos, StringComparer.OrdinalIgnoreCase); } /// @@ -630,7 +453,6 @@ namespace Umbraco.Core.Models clone.EnableChangeTracking(); return clone; - } } } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 04fbe269f8..29e36829d2 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -56,7 +56,7 @@ namespace Umbraco.Core.Models Id = 0; // no identity VersionId = 0; // no versions - SetName(name, culture); + SetCultureName(name, culture); _contentTypeId = contentType.Id; _properties = properties ?? throw new ArgumentNullException(nameof(properties)); @@ -139,104 +139,101 @@ namespace Umbraco.Core.Models #region Cultures + // notes - common rules + // - setting a variant value on an invariant content type throws + // - getting a variant value on an invariant content type returns null + // - setting and getting the invariant value is always possible + // - setting a null value clears the value + + /// + public IEnumerable AvailableCultures + => _cultureInfos?.Select(x => x.Key) ?? Enumerable.Empty(); + + /// + public bool IsCultureAvailable(string culture) + => _cultureInfos != null && _cultureInfos.ContainsKey(culture); + /// [DataMember] public virtual IReadOnlyDictionary CultureNames => _cultureInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames; - // sets culture infos - // internal for repositories - // clear by clearing name - internal void SetCultureInfos(string culture, string name, DateTime date) - { - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullOrEmptyException(nameof(name)); - - if (culture == null) - { - Name = name; - return; - } - - // private method, assume that culture is valid - - if (_cultureInfos == null) - _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); - - _cultureInfos[culture] = (name, date); - } - /// - public virtual void SetName(string name, string culture) + public virtual string GetCultureName(string culture) { - if (string.IsNullOrWhiteSpace(name)) - { - ClearName(culture); - return; - } - - if (culture == null) - { - Name = name; - return; - } - - if (ContentTypeBase.Variations.DoesNotSupportCulture()) - throw new NotSupportedException("Content type does not support varying name by culture."); - - if (_cultureInfos == null) - _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); - - _cultureInfos[culture] = (name, DateTime.Now); - OnPropertyChanged(Ps.Value.NamesSelector); - } - - /// - public virtual string GetName(string culture) - { - if (culture == null) return Name; + if (culture.IsNullOrWhiteSpace()) return Name; + if (!ContentTypeBase.VariesByCulture()) return null; if (_cultureInfos == null) return null; return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null; } /// - public bool IsCultureAvailable(string culture) - => !string.IsNullOrWhiteSpace(GetName(culture)); - - private void ClearName(string culture) + public DateTime? GetCultureDate(string culture) { - if (culture == null) - { - Name = null; - return; - } - - if (ContentTypeBase.Variations.DoesNotSupportCulture()) - throw new NotSupportedException("Content type does not support varying name by culture."); - - if (_cultureInfos == null) return; - if (!_cultureInfos.ContainsKey(culture)) - throw new InvalidOperationException($"Cannot unpublish culture {culture}, the document contains only cultures {string.Join(", ", _cultureInfos.Keys)}"); - - _cultureInfos.Remove(culture); - if (_cultureInfos.Count == 0) - _cultureInfos = null; + if (culture.IsNullOrWhiteSpace()) return null; + if (!ContentTypeBase.VariesByCulture()) return null; + if (_cultureInfos == null) return null; + return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null; } - protected virtual void ClearNames() + /// + public virtual void SetCultureName(string name, string culture) { - if (ContentTypeBase.Variations.DoesNotSupportCulture()) - throw new NotSupportedException("Content type does not support varying name by culture."); + if (ContentTypeBase.VariesByCulture()) // set on variant content type + { + if (culture.IsNullOrWhiteSpace()) // invariant is ok + { + Name = name; // may be null + } + else if (name.IsNullOrWhiteSpace()) // clear + { + ClearCultureInfo(culture); + } + else // set + { + SetCultureInfo(culture, name, DateTime.Now); + } + } + else // set on invariant content type + { + if (!culture.IsNullOrWhiteSpace()) // invariant is NOT ok + throw new NotSupportedException("Content type does not vary by culture."); + Name = name; // may be null + } + } + + protected void ClearCultureInfos() + { _cultureInfos = null; OnPropertyChanged(Ps.Value.NamesSelector); } - /// - public DateTime GetCultureDate(string culture) + protected void ClearCultureInfo(string culture) { - if (_cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos)) - return infos.Date; - throw new InvalidOperationException($"Culture \"{culture}\" is not available."); + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + if (_cultureInfos == null) return; + _cultureInfos.Remove(culture); + if (_cultureInfos.Count == 0) + _cultureInfos = null; + OnPropertyChanged(Ps.Value.NamesSelector); + } + + // internal for repository + internal void SetCultureInfo(string culture, string name, DateTime date) + { + if (name.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(name)); + + if (culture.IsNullOrWhiteSpace()) + throw new ArgumentNullOrEmptyException(nameof(culture)); + + if (_cultureInfos == null) + _cultureInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + + _cultureInfos[culture.ToLowerInvariant()] = (name, date); + OnPropertyChanged(Ps.Value.NamesSelector); } #endregion @@ -305,45 +302,113 @@ namespace Umbraco.Core.Models #endregion + #region Copy + + /// + public virtual void CopyFrom(IContent other, string culture = "*") + { + if (other.ContentTypeId != ContentTypeId) + throw new InvalidOperationException("Cannot copy values from a different content type."); + + culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + // if the content type is invariant, only '*' and 'null' is ok + // if the content type varies, everything is ok because some properties may be invariant + if (!ContentTypeBase.SupportsPropertyVariation(culture, "*", true)) + throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentTypeBase.Alias}\" with variation \"{ContentTypeBase.Variations}\"."); + + // copying from the same Id and VersionPk + var copyingFromSelf = Id == other.Id && VersionId == other.VersionId; + var published = copyingFromSelf; + + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + + // clear all existing properties for the specified culture + foreach (var property in Properties) + { + // each property type may or may not support the variation + if (!property.PropertyType.SupportsVariation(culture, "*", wildcards: true)) + continue; + + foreach (var pvalue in property.Values) + if (property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) && + (culture == "*" || pvalue.Culture.InvariantEquals(culture))) + { + property.SetValue(null, pvalue.Culture, pvalue.Segment); + } + } + + // copy properties from 'other' + var otherProperties = other.Properties; + foreach (var otherProperty in otherProperties) + { + if (!otherProperty.PropertyType.SupportsVariation(culture, "*", wildcards: true)) + continue; + + var alias = otherProperty.PropertyType.Alias; + foreach (var pvalue in otherProperty.Values) + { + if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) && + (culture == "*" || pvalue.Culture.InvariantEquals(culture))) + { + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + SetValue(alias, value, pvalue.Culture, pvalue.Segment); + } + } + } + + // copy names, too + + if (culture == "*") + ClearCultureInfos(); + + if (culture == null || culture == "*") + Name = other.Name; + + foreach (var (otherCulture, otherName) in other.CultureNames) + { + if (culture == "*" || culture == otherCulture) + SetCultureName(otherName, otherCulture); + } + } + + #endregion + #region Validation - internal virtual Property[] ValidateAllProperties() - { - //fixme - needs API review as this is not used apart from in tests - - return Properties.Where(x => !x.IsAllValid()).ToArray(); - } - /// - public bool IsValid(string culture = null, string segment = null) + public bool IsValid(string culture = "*") { - var name = GetName(culture); - if (name.IsNullOrWhiteSpace()) return false; - return ValidateProperties(culture, segment).Length == 0; - } + culture = culture.NullOrWhiteSpaceAsNull(); - /// - public virtual Property[] ValidateProperties(string culture = null, string segment = null) - { - return Properties.Where(x => + if (culture == null) { - if (!culture.IsNullOrWhiteSpace() && x.PropertyType.Variations.DoesNotSupportCulture()) - return false; //has a culture, this prop is only culture invariant, ignore - if (culture.IsNullOrWhiteSpace() && x.PropertyType.Variations.DoesNotSupportInvariant()) - return false; //no culture, this prop is only culture variant, ignore - if (!segment.IsNullOrWhiteSpace() && x.PropertyType.Variations.DoesNotSupportSegment()) - return false; //has segment, this prop is only segment neutral, ignore - if (segment.IsNullOrWhiteSpace() && x.PropertyType.Variations.DoesNotSupportNeutral()) - return false; //no segment, this prop is only non segment neutral, ignore - return !x.IsValid(culture, segment); - }).ToArray(); + if (Name.IsNullOrWhiteSpace()) return false; + return ValidateProperties(null).Length == 0; + } + + if (culture != "*") + { + var name = GetCultureName(culture); + if (name.IsNullOrWhiteSpace()) return false; + return ValidateProperties(culture).Length == 0; + } + + // 'all cultures' + // those that have a name are ok, those without a name... we don't validate + return AvailableCultures.All(c => ValidateProperties(c).Length == 0); } - internal virtual Property[] ValidatePropertiesForCulture(string culture = null) + /// + public virtual Property[] ValidateProperties(string culture = "*") { - //fixme - needs API review as this is not used apart from in tests + var alsoInvariant = culture != null && culture != "*"; - return Properties.Where(x => !x.IsCultureValid(culture)).ToArray(); + return Properties.Where(x => // select properties... + x.PropertyType.SupportsVariation(culture, "*", true) && // that support the variation + (!x.IsValid(culture) || (alsoInvariant && !x.IsValid(null)))) // and are not valid + .ToArray(); } #endregion diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index f1b61f424a..6f90b5201d 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -46,7 +46,7 @@ namespace Umbraco.Core.Models _propertyTypes = new PropertyTypeCollection(IsPublishing); _propertyTypes.CollectionChanged += PropertyTypesChanged; - _variations = ContentVariation.InvariantNeutral; + _variations = ContentVariation.Nothing; } protected ContentTypeBase(IContentTypeBase parent) @@ -67,7 +67,7 @@ namespace Umbraco.Core.Models _propertyTypes = new PropertyTypeCollection(IsPublishing); _propertyTypes.CollectionChanged += PropertyTypesChanged; - _variations = ContentVariation.InvariantNeutral; + _variations = ContentVariation.Nothing; } /// @@ -201,33 +201,22 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _variations, Ps.Value.VaryBy); } - /// - /// Validates that a variation is valid for the content type. - /// - public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) + /// + public bool SupportsVariation(string culture, string segment, bool wildcards = false) { - ContentVariation variation; - if (culture != null) - { - variation = segment != null - ? ContentVariation.CultureSegment - : ContentVariation.CultureNeutral; - } - else if (segment != null) - { - variation = ContentVariation.InvariantSegment; - } - else - { - variation = ContentVariation.InvariantNeutral; - } - if (!Variations.Has(variation)) - { - if (throwIfInvalid) - throw new NotSupportedException($"Variation {variation} is invalid for content type \"{Alias}\"."); - return false; - } - return true; + // exact validation: cannot accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + return Variations.ValidateVariation(culture, segment, true, wildcards, false); + } + + /// + public bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false) + { + // non-exact validation: can accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + return Variations.ValidateVariation(culture, segment, false, true, false); } /// diff --git a/src/Umbraco.Core/Models/ContentVariation.cs b/src/Umbraco.Core/Models/ContentVariation.cs index 5b775b52b9..486f0e54f2 100644 --- a/src/Umbraco.Core/Models/ContentVariation.cs +++ b/src/Umbraco.Core/Models/ContentVariation.cs @@ -3,34 +3,35 @@ namespace Umbraco.Core.Models { /// - /// Indicates the values accepted by a property. + /// Indicates how values can vary. /// + /// + /// Values can vary by nothing, or culture, or segment, or both. + /// Varying by culture implies that each culture version of a document can + /// be available or not, and published or not, individually. Varying by segment + /// is a property-level thing. + /// [Flags] public enum ContentVariation : byte { /// - /// Unknown. + /// Values do not vary. /// - Unknown = 0, + Nothing = 0, /// - /// Accepts values for the invariant culture and the neutral segment. + /// Values vary by culture. /// - InvariantNeutral = 1, + Culture = 1, /// - /// Accepts values for a specified culture and the neutral segment. + /// Values vary by segment. /// - CultureNeutral = 2, + Segment = 2, /// - /// Accepts values for the invariant culture and a specified segment. + /// Values vary by culture and segment. /// - InvariantSegment = 4, - - /// - /// Accepts values for a specified culture and a specified segment. - /// - CultureSegment = 8 + CultureAndSegment = Culture | Segment } } diff --git a/src/Umbraco.Core/Models/EntityExtensions.cs b/src/Umbraco.Core/Models/EntityExtensions.cs index fa7d9b6110..5ef68e99ea 100644 --- a/src/Umbraco.Core/Models/EntityExtensions.cs +++ b/src/Umbraco.Core/Models/EntityExtensions.cs @@ -4,15 +4,6 @@ namespace Umbraco.Core.Models { public static class EntityExtensions { - - /// - /// Determines whether the entity was just created and persisted. - /// - public static bool IsNewEntity(this IRememberBeingDirty entity) - { - return entity.WasPropertyDirty("Id"); - } - /// /// Gets additional data. /// diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 13797425ed..9e79a75e25 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -81,7 +81,7 @@ namespace Umbraco.Core.Models ContentStatus Status { get; } /// - /// Gets a value indicating whether a given culture is published. + /// Gets a value indicating whether a culture is published. /// /// /// A culture becomes published whenever values for this culture are published, @@ -90,10 +90,18 @@ namespace Umbraco.Core.Models /// bool IsCulturePublished(string culture); + /// + /// Gets a value indicating whether a culture was published. + /// + /// + /// Mirrors whenever the content item is saved. + /// + bool WasCulturePublished(string culture); + /// /// Gets the date a culture was published. /// - DateTime GetCulturePublishDate(string culture); + DateTime? GetPublishDate(string culture); /// /// Gets a value indicated whether a given culture is edited. @@ -121,12 +129,7 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot get the invariant /// name, which must be get via the property. /// - IReadOnlyDictionary PublishCultureNames { get; } - - /// - /// Gets the available cultures. - /// - IEnumerable AvailableCultures { get; } + IReadOnlyDictionary PublishNames { get; } /// /// Gets the published cultures. @@ -162,67 +165,22 @@ namespace Umbraco.Core.Models IContent DeepCloneWithResetIdentities(); /// - /// Publishes all values. + /// Registers a culture to be published. /// - /// A value indicating whether the values could be published. + /// A value indicating whether the culture can be published. /// - /// The document must then be published via the content service. - /// Values are not published if they are not valid. + /// Fails if values cannot be published, e.g. if some values are not valid. + /// Publishing must be finalized via the content service SavePublishing method. /// - //fixme return an Attempt with some error results if it doesn't work - //fixme - needs API review as this is not used apart from in tests - //bool TryPublishAllValues(); + // fixme - should return an attempt with error results + bool PublishCulture(string culture = "*"); /// - /// Publishes values. + /// Registers a culture to be unpublished. /// - /// A value indicating whether the values could be published. /// - /// The document must then be published via the content service. - /// Values are not published if they are not valid. + /// Unpublishing must be finalized via the content service SavePublishing method. /// - //fixme return an Attempt with some error results if it doesn't work - bool TryPublishValues(string culture = null, string segment = null); - - /// - /// Publishes the culture/any values. - /// - /// A value indicating whether the values could be published. - /// - /// The document must then be published via the content service. - /// Values are not published if they are not valie. - /// - //fixme - needs API review as this is not used apart from in tests - //bool PublishCultureValues(string culture = null); - - /// - /// Clears all published values. - /// - void ClearAllPublishedValues(); - - /// - /// Clears published values. - /// - void ClearPublishedValues(string culture = null, string segment = null); - - /// - /// Clears the culture/any published values. - /// - void ClearCulturePublishedValues(string culture = null); - - /// - /// Copies values from another document. - /// - void CopyAllValues(IContent other); - - /// - /// Copies values from another document. - /// - void CopyValues(IContent other, string culture = null, string segment = null); - - /// - /// Copies culture/any values from another document. - /// - void CopyCultureValues(IContent other, string culture = null); + void UnpublishCulture(string culture = "*"); } } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 1605c1da01..3c56b2c737 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -29,45 +29,64 @@ namespace Umbraco.Core.Models int VersionId { get; } /// - /// Sets the name of the content item for a specified language. + /// Sets the name of the content item for a specified culture. /// /// - /// When is null, sets the invariant - /// language, which sets the property. + /// When is null, sets the invariant + /// culture name, which sets the property. + /// When is not null, throws if the content + /// type does not vary by culture. /// - void SetName(string value, string culture); + void SetCultureName(string value, string culture); /// /// Gets the name of the content item for a specified language. /// /// - /// When is null, gets the invariant - /// language, which is the value of the property. + /// When is null, gets the invariant + /// culture name, which is the value of the property. + /// When is not null, and the content type + /// does not vary by culture, returns null. /// - string GetName(string culture); + string GetCultureName(string culture); /// /// Gets the names of the content item. /// /// - /// Because a dictionary key cannot be null this cannot get the invariant - /// name, which must be get or set via the property. + /// Because a dictionary key cannot be null this cannot contain the invariant + /// culture name, which must be get or set via the property. /// IReadOnlyDictionary CultureNames { get; } + /// + /// Gets the available cultures. + /// + /// + /// Cannot contain the invariant culture, which is always available. + /// + IEnumerable AvailableCultures { get; } + /// /// Gets a value indicating whether a given culture is available. /// /// /// A culture becomes available whenever the content name for this culture is /// non-null, and it becomes unavailable whenever the content name is null. + /// Returns false for the invariant culture, in order to be consistent + /// with , even though the invariant culture is + /// always available. /// bool IsCultureAvailable(string culture); /// /// Gets the date a culture was created. /// - DateTime GetCultureDate(string culture); + /// + /// When is null, returns null. + /// If the specified culture is not available, returns null. + /// + DateTime? GetCultureDate(string culture); /// /// List of properties, which make up all the data available for this Content object @@ -97,47 +116,40 @@ namespace Umbraco.Core.Models /// /// Gets the value of a Property /// + /// Values 'null' and 'empty' are equivalent for culture and segment. object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); /// /// Gets the typed value of a Property /// + /// Values 'null' and 'empty' are equivalent for culture and segment. TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); /// /// Sets the (edited) value of a Property /// + /// Values 'null' and 'empty' are equivalent for culture and segment. void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null); + /// + /// Copies values from another document. + /// + void CopyFrom(IContent other, string culture = "*"); + + // fixme validate published cultures? + /// /// Checks if the content and property values are valid in order to be persisted. /// - /// - /// - /// - bool IsValid(string culture = null, string segment = null); + /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor + /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. + bool IsValid(string culture = "*"); /// - /// Gets a value indicating if all properties values are valid. + /// Validates the content item's properties. /// - //fixme - needs API review as this is not used apart from in tests - //Property[] ValidateAllProperties(); - - /// - /// Validates the content item's properties for the provided culture/segment - /// - /// - /// - /// - /// - /// This will not perform validation for properties that do not match the required ContentVariation based on the culture/segment values provided - /// - Property[] ValidateProperties(string culture = null, string segment = null); - - /// - /// Gets a value indicating if the culture properties values are valid. - /// - //fixme - needs API review as this is not used apart from in tests - //Property[] ValidatePropertiesForCulture(string culture = null); + /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor + /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. + Property[] ValidateProperties(string culture = "*"); } } diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index df171d5efc..ef5988344e 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -49,9 +49,31 @@ namespace Umbraco.Core.Models ContentVariation Variations { get; set; } /// - /// Validates that a variation is valid for the content type. + /// Validates that a combination of culture and segment is valid for the content type. /// - bool ValidateVariation(string culture, string segment, bool throwIfInvalid); + /// The culture. + /// The segment. + /// A value indicating whether wilcards are supported. + /// True if the combination is valid; otherwise false. + /// + /// The combination must match the content type variation exactly. For instance, if the content type varies by culture, + /// then an invariant culture would be invalid. + /// + bool SupportsVariation(string culture, string segment, bool wildcards = false); + + /// + /// Validates that a combination of culture and segment is valid for the content type properties. + /// + /// The culture. + /// The segment. + /// A value indicating whether wilcards are supported. + /// True if the combination is valid; otherwise false. + /// + /// The combination must be valid for properties of the content type. For instance, if the content type varies by culture, + /// then an invariant culture is valid, because some properties may be invariant. On the other hand, if the content type is invariant, + /// then a variant culture is invalid, because no property could possibly vary by culture. + /// + bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false); /// /// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index ac6a9b09f0..c0dd97ff87 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -16,44 +16,83 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Property : EntityBase { + // _values contains all property values, including the invariant-neutral value private List _values = new List(); + + // _pvalue contains the invariant-neutral property value private PropertyValue _pvalue; + + // _vvalues contains the (indexed) variant property values private Dictionary _vvalues; private static readonly Lazy Ps = new Lazy(); + /// + /// Initializes a new instance of the class. + /// protected Property() { } + /// + /// Initializes a new instance of the class. + /// public Property(PropertyType propertyType) { PropertyType = propertyType; } + /// + /// Initializes a new instance of the class. + /// public Property(int id, PropertyType propertyType) { Id = id; PropertyType = propertyType; } + /// + /// Represents a property value. + /// public class PropertyValue { private string _culture; private string _segment; + /// + /// Gets or sets the culture of the property. + /// + /// The culture is either null (invariant) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. public string Culture { get => _culture; - internal set => _culture = value?.ToLowerInvariant(); + internal set => _culture = value.IsNullOrWhiteSpace() ? null : value.ToLowerInvariant(); } + + /// + /// Gets or sets the segment of the property. + /// + /// The segment is either null (neutral) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. public string Segment { get => _segment; internal set => _segment = value?.ToLowerInvariant(); } + + /// + /// Gets or sets the edited value of the property. + /// public object EditedValue { get; internal set; } + + /// + /// Gets or sets the published value of the property. + /// public object PublishedValue { get; internal set; } + /// + /// Clones the property value. + /// public PropertyValue Clone() => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; } @@ -101,7 +140,7 @@ namespace Umbraco.Core.Models { // make sure we filter out invalid variations // make sure we leave _vvalues null if possible - _values = value.Where(x => PropertyType.ValidateVariation(x.Culture, x.Segment, false)).ToList(); + _values = value.Where(x => PropertyType.SupportsVariation(x.Culture, x.Segment)).ToList(); _pvalue = _values.FirstOrDefault(x => x.Culture == null && x.Segment == null); _vvalues = _values.Count > (_pvalue == null ? 0 : 1) ? _values.Where(x => x != _pvalue).ToDictionary(x => new CompositeNStringNStringKey(x.Culture, x.Segment), x => x) @@ -135,7 +174,11 @@ namespace Umbraco.Core.Models /// public object GetValue(string culture = null, string segment = null, bool published = false) { - if (!PropertyType.ValidateVariation(culture, segment, false)) return null; + // ensure null or whitespace are nulls + culture = culture.NullOrWhiteSpaceAsNull(); + segment = segment.NullOrWhiteSpaceAsNull(); + + if (!PropertyType.SupportsVariation(culture, segment)) return null; if (culture == null && segment == null) return GetPropertyValue(_pvalue, published); if (_vvalues == null) return null; return _vvalues.TryGetValue(new CompositeNStringNStringKey(culture, segment), out var pvalue) @@ -154,97 +197,56 @@ namespace Umbraco.Core.Models // internal - must be invoked by the content item // does *not* validate the value - content item must validate first - internal void PublishAllValues() + internal void PublishValues(string culture = "*", string segment = "*") { - // if invariant-neutral is supported, publish invariant-neutral - if (PropertyType.ValidateVariation(null, null, false)) - PublishPropertyValue(_pvalue); + culture = culture.NullOrWhiteSpaceAsNull(); + segment = segment.NullOrWhiteSpaceAsNull(); - // publish everything not invariant-neutral that is supported - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - PublishPropertyValue(pvalue); - } + // if invariant or all, and invariant-neutral is supported, publish invariant-neutral + if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null)) + PublishValue(_pvalue); + + // then deal with everything that varies + if (_vvalues == null) return; + + // get the property values that are still relevant (wrt the property type variation), + // and match the specified culture and segment (or anything when '*'). + var pvalues = _vvalues.Where(x => + PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok + (culture == "*" || x.Value.Culture.InvariantEquals(culture)) && // the culture matches + (segment == "*" || x.Value.Segment.InvariantEquals(segment))) // the segment matches + .Select(x => x.Value); + + foreach (var pvalue in pvalues) + PublishValue(pvalue); } // internal - must be invoked by the content item - // does *not* validate the value - content item must validate first - internal void PublishValue(string culture = null, string segment = null) + internal void UnpublishValues(string culture = "*", string segment = "*") { - PropertyType.ValidateVariation(culture, segment, true); + culture = culture.NullOrWhiteSpaceAsNull(); + segment = segment.NullOrWhiteSpaceAsNull(); - var (pvalue, _) = GetPValue(culture, segment, false); - if (pvalue == null) return; - PublishPropertyValue(pvalue); + // if invariant or all, and invariant-neutral is supported, publish invariant-neutral + if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null)) + UnpublishValue(_pvalue); + + // then deal with everything that varies + if (_vvalues == null) return; + + // get the property values that are still relevant (wrt the property type variation), + // and match the specified culture and segment (or anything when '*'). + var pvalues = _vvalues.Where(x => + PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok + (culture == "*" || x.Value.Culture.InvariantEquals(culture)) && // the culture matches + (segment == "*" || x.Value.Segment.InvariantEquals(segment))) // the segment matches + .Select(x => x.Value); + + foreach (var pvalue in pvalues) + UnpublishValue(pvalue); } - // internal - must be invoked by the content item - // does *not* validate the value - content item must validate first - internal void PublishCultureValues(string culture = null) - { - // if invariant and invariant-neutral is supported, publish invariant-neutral - if (culture == null && PropertyType.ValidateVariation(null, null, false)) - PublishPropertyValue(_pvalue); - - // publish everything not invariant-neutral that matches the culture and is supported - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => x.Value.Culture.InvariantEquals(culture)) - .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - PublishPropertyValue(pvalue); - } - } - - // internal - must be invoked by the content item - internal void ClearPublishedAllValues() - { - if (PropertyType.ValidateVariation(null, null, false)) - ClearPublishedPropertyValue(_pvalue); - - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - ClearPublishedPropertyValue(pvalue); - } - } - - // internal - must be invoked by the content item - internal void ClearPublishedValue(string culture = null, string segment = null) - { - PropertyType.ValidateVariation(culture, segment, true); - var (pvalue, _) = GetPValue(culture, segment, false); - if (pvalue == null) return; - ClearPublishedPropertyValue(pvalue); - } - - // internal - must be invoked by the content item - internal void ClearPublishedCultureValues(string culture = null) - { - if (culture == null && PropertyType.ValidateVariation(null, null, false)) - ClearPublishedPropertyValue(_pvalue); - - if (_vvalues != null) - { - var pvalues = _vvalues - .Where(x => x.Value.Culture.InvariantEquals(culture)) - .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) - .Select(x => x.Value); - foreach (var pvalue in pvalues) - ClearPublishedPropertyValue(pvalue); - } - } - - private void PublishPropertyValue(PropertyValue pvalue) + private void PublishValue(PropertyValue pvalue) { if (pvalue == null) return; @@ -255,7 +257,7 @@ namespace Umbraco.Core.Models DetectChanges(pvalue.EditedValue, origValue, Ps.Value.ValuesSelector, Ps.Value.PropertyValueComparer, false); } - private void ClearPublishedPropertyValue(PropertyValue pvalue) + private void UnpublishValue(PropertyValue pvalue) { if (pvalue == null) return; @@ -271,7 +273,12 @@ namespace Umbraco.Core.Models /// public void SetValue(object value, string culture = null, string segment = null) { - PropertyType.ValidateVariation(culture, segment, true); + culture = culture.NullOrWhiteSpaceAsNull(); + segment = segment.NullOrWhiteSpaceAsNull(); + + if (!PropertyType.SupportsVariation(culture, segment)) + throw new NotSupportedException($"Variation \"{culture??""},{segment??""}\" is not supported by the property type."); + var (pvalue, change) = GetPValue(culture, segment, true); var origValue = pvalue.EditedValue; @@ -332,67 +339,40 @@ namespace Umbraco.Core.Models } /// - /// Gets a value indicating whether all properties are valid. + /// Gets a value indicating whether the property has valid values. /// - /// - internal bool IsAllValid() + internal bool IsValid(string culture = "*", string segment = "*") { - //fixme - needs API review as this is not used apart from in tests + culture = culture.NullOrWhiteSpaceAsNull(); + segment = segment.NullOrWhiteSpaceAsNull(); - // invariant-neutral is supported, validate invariant-neutral - // includes mandatory validation - if (PropertyType.ValidateVariation(null, null, false) && !IsValidValue(_pvalue)) return false; + // if validating invariant/neutral, and it is supported, validate + // (including ensuring that the value exists, if mandatory) + if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null)) + if (!IsValidValue(_pvalue?.EditedValue)) + return false; + + // if validating only invariant/neutral, we are good + if (culture == null && segment == null) + return true; + + // if nothing else to validate, we are good + if ((culture == null || culture == "*") && (segment == null || segment == "*") && !PropertyType.VariesByCulture()) + return true; - // either invariant-neutral is not supported, or it is valid // for anything else, validate the existing values (including mandatory), // but we cannot validate mandatory globally (we don't know the possible cultures and segments) - if (_vvalues == null) return true; + if (_vvalues == null) return culture == "*" || IsValidValue(null); - var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) + var pvalues = _vvalues.Where(x => + PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok + (culture == "*" || x.Value.Culture.InvariantEquals(culture)) && // the culture matches + (segment == "*" || x.Value.Segment.InvariantEquals(segment))) // the segment matches .Select(x => x.Value) - .ToArray(); + .ToList(); - return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); - } - - /// - /// Gets a value indicating whether the culture/any values are valid. - /// - /// An invalid value can be saved, but only valid values can be published. - internal bool IsCultureValid(string culture) - { - //fixme - needs API review as this is not used apart from in tests - - // culture-neutral is supported, validate culture-neutral - // includes mandatory validation - if (PropertyType.ValidateVariation(culture, null, false) && !IsValidValue(GetValue(culture))) - return false; - - // either culture-neutral is not supported, or it is valid - // for anything non-neutral, validate the existing values (including mandatory), - // but we cannot validate mandatory globally (we don't know the possible segments) - - if (_vvalues == null) return true; - - var pvalues = _vvalues - .Where(x => x.Value.Culture.InvariantEquals(culture)) - .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) - .Select(x => x.Value) - .ToArray(); - - return pvalues.Length == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); - } - - /// - /// Gets a value indicating whether the value is valid. - /// - /// An invalid value can be saved, but only valid values can be published. - public bool IsValid(string culture = null, string segment = null) - { - // single value -> validates mandatory - return IsValidValue(GetValue(culture, segment)); + return pvalues.Count == 0 || pvalues.All(x => IsValidValue(x.EditedValue)); } /// diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 587af74aa2..3d5fac2077 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -47,7 +47,7 @@ namespace Umbraco.Core.Models _propertyEditorAlias = dataType.EditorAlias; _valueStorageType = dataType.DatabaseType; - _variations = ContentVariation.InvariantNeutral; + _variations = ContentVariation.Nothing; } /// @@ -84,7 +84,7 @@ namespace Umbraco.Core.Models _valueStorageType = valueStorageType; _forceValueStorageType = forceValueStorageType; _alias = propertyTypeAlias == null ? null : SanitizeAlias(propertyTypeAlias); - _variations = ContentVariation.InvariantNeutral; + _variations = ContentVariation.Nothing; } private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); @@ -224,32 +224,17 @@ namespace Umbraco.Core.Models } /// - /// Validates that a variation is valid for the property type. + /// Determines whether the property type supports a combination of culture and segment. /// - public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) + /// The culture. + /// The segment. + /// A value indicating whether wildcards are valid. + public bool SupportsVariation(string culture, string segment, bool wildcards = false) { - ContentVariation variation; - if (culture != null) - { - variation = segment != null - ? ContentVariation.CultureSegment - : ContentVariation.CultureNeutral; - } - else if (segment != null) - { - variation = ContentVariation.InvariantSegment; - } - else - { - variation = ContentVariation.InvariantNeutral; - } - if (!Variations.Has(variation)) - { - if (throwIfInvalid) - throw new NotSupportedException($"Variation {variation} is invalid for property type \"{Alias}\"."); - return false; - } - return true; + // exact validation: cannot accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + return Variations.ValidateVariation(culture, segment, true, wildcards, false); } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index bc403b904d..e611ded6c8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -81,7 +81,7 @@ namespace Umbraco.Core.Models.PublishedContent foreach ((var alias, (var dataTypeId, var editorAlias)) in BuiltinMemberProperties) { if (aliases.Contains(alias)) continue; - propertyTypes.Add(factory.CreatePropertyType(this, alias, dataTypeId, ContentVariation.InvariantNeutral)); + propertyTypes.Add(factory.CreatePropertyType(this, alias, dataTypeId, ContentVariation.Nothing)); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 3ba908b9bf..5de5842eda 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -32,13 +32,13 @@ namespace Umbraco.Core.Models.PublishedContent } // for tests - internal PublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.InvariantNeutral) + internal PublishedContentType CreateContentType(int id, string alias, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing) { return new PublishedContentType(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations); } // for tests - internal PublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.InvariantNeutral) + internal PublishedContentType CreateContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations = ContentVariation.Nothing) { return new PublishedContentType(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations); } @@ -50,13 +50,13 @@ namespace Umbraco.Core.Models.PublishedContent } /// - public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.InvariantNeutral) + public PublishedPropertyType CreatePropertyType(PublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); } // for tests - internal PublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.InvariantNeutral) + internal PublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing) { return new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); } diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index 5dc4a280e6..7469222ab0 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core.Models.PublishedContent public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { - if (propertyType.Variations != ContentVariation.InvariantNeutral) + if (propertyType.Variations != ContentVariation.Nothing) throw new ArgumentException("Property types with variations are not supported here.", nameof(propertyType)); _sourceValue = sourceValue; diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index c3b4b0d24c..ec364c7c6a 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -159,8 +159,7 @@ namespace Umbraco.Core.Persistence.Factories /// public static DocumentDto BuildDto(IContent entity, Guid objectType) { - var contentBase = (Content) entity; - var contentDto = BuildContentDto(contentBase, objectType); + var contentDto = BuildContentDto(entity, objectType); var dto = new DocumentDto { @@ -170,7 +169,7 @@ namespace Umbraco.Core.Persistence.Factories ExpiresDate = entity.ExpireDate, ContentDto = contentDto, - DocumentVersionDto = BuildDocumentVersionDto(contentBase, contentDto) + DocumentVersionDto = BuildDocumentVersionDto(entity, contentDto) }; return dto; @@ -181,14 +180,13 @@ namespace Umbraco.Core.Persistence.Factories /// public static MediaDto BuildDto(IMedia entity) { - var contentBase = (Models.Media) entity; - var contentDto = BuildContentDto(contentBase, Constants.ObjectTypes.Media); + var contentDto = BuildContentDto(entity, Constants.ObjectTypes.Media); var dto = new MediaDto { NodeId = entity.Id, ContentDto = contentDto, - MediaVersionDto = BuildMediaVersionDto(contentBase, contentDto) + MediaVersionDto = BuildMediaVersionDto(entity, contentDto) }; return dto; @@ -199,8 +197,7 @@ namespace Umbraco.Core.Persistence.Factories /// public static MemberDto BuildDto(IMember entity) { - var member = (Member) entity; - var contentDto = BuildContentDto(member, Constants.ObjectTypes.Member); + var contentDto = BuildContentDto(entity, Constants.ObjectTypes.Member); var dto = new MemberDto { @@ -210,12 +207,12 @@ namespace Umbraco.Core.Persistence.Factories Password = entity.RawPasswordValue, ContentDto = contentDto, - ContentVersionDto = BuildContentVersionDto(member, contentDto) + ContentVersionDto = BuildContentVersionDto(entity, contentDto) }; return dto; } - private static ContentDto BuildContentDto(ContentBase entity, Guid objectType) + private static ContentDto BuildContentDto(IContentBase entity, Guid objectType) { var dto = new ContentDto { @@ -228,7 +225,7 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - private static NodeDto BuildNodeDto(ContentBase entity, Guid objectType) + private static NodeDto BuildNodeDto(IContentBase entity, Guid objectType) { var dto = new NodeDto { @@ -250,7 +247,7 @@ namespace Umbraco.Core.Persistence.Factories // always build the current / VersionPk dto // we're never going to build / save old versions (which are immutable) - private static ContentVersionDto BuildContentVersionDto(ContentBase entity, ContentDto contentDto) + private static ContentVersionDto BuildContentVersionDto(IContentBase entity, ContentDto contentDto) { var dto = new ContentVersionDto { @@ -269,7 +266,7 @@ namespace Umbraco.Core.Persistence.Factories // always build the current / VersionPk dto // we're never going to build / save old versions (which are immutable) - private static DocumentVersionDto BuildDocumentVersionDto(Content entity, ContentDto contentDto) + private static DocumentVersionDto BuildDocumentVersionDto(IContent entity, ContentDto contentDto) { var dto = new DocumentVersionDto { @@ -283,7 +280,7 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - private static MediaVersionDto BuildMediaVersionDto(Models.Media entity, ContentDto contentDto) + private static MediaVersionDto BuildMediaVersionDto(IMedia entity, ContentDto contentDto) { // try to get a path from the string being stored for media // fixme - only considering umbracoFile ?! diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index b0e3e2dc7d..3bea84e619 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -99,13 +99,7 @@ namespace Umbraco.Core.Persistence.Factories { if (property.PropertyType.IsPublishing) { - // fixme - // why only CultureNeutral? - // then, the tree can only show when a CultureNeutral value has been modified, but not when - // a CultureSegment has been modified, so if I edit some french/mobile thing, the tree will - // NOT tell me that I have changes? - - var editingCultures = property.PropertyType.Variations.Has(ContentVariation.CultureNeutral); + var editingCultures = property.PropertyType.VariesByCulture(); if (editingCultures && editedCultures == null) editedCultures = new HashSet(StringComparer.OrdinalIgnoreCase); // publishing = deal with edit and published values diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index f2a91c11a1..093723cea5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -232,7 +232,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(IContent entity) { - //fixme - stop doing this just so we have access to AddingEntity + // fixme - stop doing this - sort out IContent vs Content + // however, it's not just so we have access to AddingEntity + // there are tons of things at the end of the methods, that can only work with a true Content + // and basically, the repository requires a Content, not an IContent var content = (Content) entity; content.AddingEntity(); @@ -242,11 +245,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (entity.Template == null) entity.Template = entity.ContentType.DefaultTemplate; - // sanitize names: ensure we have an invariant name, and names are unique-ish - // (well, only invariant name is unique at the moment) - EnsureUniqueVariationNames(entity); - EnsureInvariantNameValues(entity, publishing); - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name); + // sanitize names + SanitizeNames(content, publishing); // ensure that strings don't contain characters that are invalid in xml // fixme - do we really want to keep doing this here? @@ -295,7 +295,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto; contentVersionDto.NodeId = nodeDto.NodeId; contentVersionDto.Current = !publishing; - contentVersionDto.Text = publishing ? content.PublishName : content.Name; + contentVersionDto.Text = content.Name; Database.Insert(contentVersionDto); content.VersionId = contentVersionDto.Id; @@ -312,7 +312,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.PublishedVersionId = content.VersionId; contentVersionDto.Id = 0; contentVersionDto.Current = true; - contentVersionDto.Text = content.PublishName; + contentVersionDto.Text = content.Name; Database.Insert(contentVersionDto); content.VersionId = contentVersionDto.Id; @@ -326,8 +326,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); - // name also impacts 'edited' - if (content.PublishName != content.Name) + // if !publishing, we may have a new name != current publish name, + // also impacts 'edited' + if (!publishing && content.PublishName != content.Name) edited = true; // persist the document dto @@ -340,7 +341,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(dto); // persist the variations - if (content.ContentType.Variations.DoesSupportCulture()) + if (content.ContentType.VariesByCulture()) { // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) @@ -355,8 +356,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // refresh content - if (editedCultures != null) - content.SetCultureEdited(editedCultures); + content.SetCultureEdited(editedCultures); // trigger here, before we reset Published etc OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity)); @@ -369,6 +369,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.Published = true; content.PublishTemplate = content.Template; content.PublisherId = content.WriterId; + content.PublishName = content.Name; content.PublishDate = content.UpdateDate; SetEntityTags(entity, _tagRepository); @@ -378,6 +379,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.Published = false; content.PublishTemplate = null; content.PublisherId = null; + content.PublishName = null; content.PublishDate = null; ClearEntityTags(entity, _tagRepository); @@ -400,6 +402,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IContent entity) { + // fixme - stop doing this - sort out IContent vs Content + // however, it's not just so we have access to AddingEntity + // there are tons of things at the end of the methods, that can only work with a true Content + // and basically, the repository requires a Content, not an IContent var content = (Content) entity; // check if we need to make any database changes at all @@ -423,11 +429,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Execute(Sql().Update(u => u.Set(x => x.Published, false)).Where(x => x.Id == content.PublishedVersionId)); } - // sanitize names: ensure we have an invariant name, and names are unique-ish - // (well, only invariant name is unique at the moment) - EnsureUniqueVariationNames(entity); - EnsureInvariantNameValues(entity, publishing); - entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id); + // sanitize names + SanitizeNames(content, publishing); // ensure that strings don't contain characters that are invalid in xml // fixme - do we really want to keep doing this here? @@ -460,7 +463,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { documentVersionDto.Published = true; // now published contentVersionDto.Current = false; // no more current - contentVersionDto.Text = content.PublishName; + contentVersionDto.Text = content.Name; } Database.Update(contentVersionDto); Database.Update(documentVersionDto); @@ -491,11 +494,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); - // name also impacts 'edited' - if (content.PublishName != content.Name) + // if !publishing, we may have a new name != current publish name, + // also impacts 'edited' + if (!publishing && content.PublishName != content.Name) edited = true; - if (content.ContentType.Variations.DoesSupportCulture()) + if (content.ContentType.VariesByCulture()) { // names also impact 'edited' foreach (var (culture, name) in content.CultureNames) @@ -511,7 +515,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var deleteDocumentVariations = Sql().Delete().Where(x => x.NodeId == content.Id); Database.Execute(deleteDocumentVariations); - // fixme is we'd like to use the native NPoco InsertBulk here but it causes problems (not sure exaclty all scenarios) but by using SQL Server and updating a variants name will cause: Unable to cast object of type 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'. + // fixme NPoco InsertBulk issue? + // we should use the native NPoco InsertBulk here but it causes problems (not sure exaclty all scenarios) + // but by using SQL Server and updating a variants name will cause: Unable to cast object of type + // 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'. // (same in PersistNewItem above) // insert content variations @@ -522,8 +529,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // refresh content - if (editedCultures != null) - content.SetCultureEdited(editedCultures); + content.SetCultureEdited(editedCultures); // update the document dto // at that point, when un/publishing, the entity still has its old Published value @@ -550,6 +556,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.Published = true; content.PublishTemplate = content.Template; content.PublisherId = content.WriterId; + content.PublishName = content.Name; content.PublishDate = content.UpdateDate; SetEntityTags(entity, _tagRepository); @@ -559,6 +566,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.Published = false; content.PublishTemplate = null; content.PublisherId = null; + content.PublishName = null; content.PublishDate = null; ClearEntityTags(entity, _tagRepository); @@ -904,7 +912,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // set variations, if varying - temps = temps.Where(x => x.ContentType.Variations.DoesSupportCulture()).ToList(); + temps = temps.Where(x => x.ContentType.VariesByCulture()).ToList(); if (temps.Count > 0) { // load all variations for all documents from database, in one query @@ -939,7 +947,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement content.Properties = properties[dto.DocumentVersionDto.Id]; // set variations, if varying - if (contentType.Variations.DoesSupportCulture()) + if (contentType.VariesByCulture()) { var contentVariations = GetContentVariations(ltemp); var documentVariations = GetDocumentVariations(ltemp); @@ -955,10 +963,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { if (contentVariations.TryGetValue(content.VersionId, out var contentVariation)) foreach (var v in contentVariation) - content.SetCultureInfos(v.Culture, v.Name, v.Date); + content.SetCultureInfo(v.Culture, v.Name, v.Date); if (content.PublishedVersionId > 0 && contentVariations.TryGetValue(content.PublishedVersionId, out contentVariation)) foreach (var v in contentVariation) - content.SetPublishInfos(v.Culture, v.Name, v.Date); + content.SetPublishInfo(v.Culture, v.Name, v.Date); if (documentVariations.TryGetValue(content.Id, out var documentVariation)) foreach (var v in documentVariation.Where(x => x.Edited)) content.SetCultureEdited(v.Culture); @@ -1038,7 +1046,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Culture = culture, Name = name, - Date = content.GetCultureDate(culture) + Date = content.GetCultureDate(culture) ?? DateTime.MinValue // we *know* there is a value }; // if not publishing, we're just updating the 'current' (non-published) version, @@ -1046,14 +1054,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (!publishing) yield break; // create dtos for the 'published' version, for published cultures (those having a name) - foreach (var (culture, name) in content.PublishCultureNames) + foreach (var (culture, name) in content.PublishNames) yield return new ContentVersionCultureVariationDto { VersionId = content.PublishedVersionId, LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Culture = culture, Name = name, - Date = content.GetCulturePublishDate(culture) + Date = content.GetPublishDate(culture) ?? DateTime.MinValue // we *know* there is a value }; } @@ -1084,90 +1092,103 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Utilities - /// - /// Ensures that the Name/PublishName properties are filled in and validates if all names are null - /// - private void EnsureInvariantNameValues(IContent content, bool publishing) + private void SanitizeNames(Content content, bool publishing) { - // here we have to ensure we have names and publish names, and to try and fix the situation if we have no name, see also: U4-11286 + // a content item *must* have an invariant name, and invariant published name + // else we just cannot write the invariant rows (node, content version...) to the database - // invariant content must have an invariant name - if (content.ContentType.Variations.DoesNotSupportCulture() && string.IsNullOrWhiteSpace(content.Name)) - throw new InvalidOperationException("Cannot save content with an empty name."); + // ensure that we have an invariant name + // invariant content = must be there already, else throw + // variant content = update with default culture or anything really + EnsureInvariantNameExists(content); - // variant content must have at least one variant name - if (content.ContentType.Variations.DoesSupportCulture()) + // ensure that that invariant name is unique + EnsureInvariantNameIsUnique(content); + + // and finally, + // ensure that each culture has a unique node name + // no published name = not published + // else, it needs to be unique + EnsureVariantNamesAreUnique(content, publishing); + } + + private void EnsureInvariantNameExists(Content content) + { + if (content.ContentType.VariesByCulture()) { + // content varies by culture + // then it must have at least a variant name, else it makes no sense if (content.CultureNames.Count == 0) throw new InvalidOperationException("Cannot save content with an empty name."); - // cannot save with an empty invariant name, - // if invariant name is missing, derive it from variant names - // fixme should we always sync the invariant name with the default culture name when updating? - if (string.IsNullOrWhiteSpace(content.Name)) - { - var defaultCulture = LanguageRepository.GetDefaultIsoCode(); - if (defaultCulture != null && content.CultureNames.TryGetValue(defaultCulture, out var cultureName)) - content.Name = cultureName; - else - content.Name = content.CultureNames.First().Value; // only option is to take the first - } + // and then, we need to set the invariant name implicitely, + // using the default culture if it has a name, otherwise anything we can + var defaultCulture = LanguageRepository.GetDefaultIsoCode(); + content.Name = defaultCulture != null && content.CultureNames.TryGetValue(defaultCulture, out var cultureName) + ? cultureName + : content.CultureNames.First().Value; } - - // cannot publish without an invariant name - if (publishing && string.IsNullOrWhiteSpace(content.PublishName)) + else { - // no variant name = error - if (content.PublishCultureNames.Count == 0) - throw new InvalidOperationException("Cannot publish content with an empty name."); - - // else... we cannot deal with it here because PublishName is readonly, so in reality, PublishName - // should not be null because it should have been set when preparing the content for publish. - // see also: Content.SetPublishInfos() - it deals with PublishName + // content is invariant, and invariant content must have an explicit invariant name + if (string.IsNullOrWhiteSpace(content.Name)) + throw new InvalidOperationException("Cannot save content with an empty name."); } } + private void EnsureInvariantNameIsUnique(Content content) + { + content.Name = EnsureUniqueNodeName(content.ParentId, content.Name, content.Id); + } + protected override string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) { return EnsureUniqueNaming == false ? nodeName : base.EnsureUniqueNodeName(parentId, nodeName, id); } - private SqlTemplate SqlEnsureUniqueVariationNames => SqlContext.Templates.Get("Umbraco.Core.DomainRepository.EnsureUniqueVariationNames", tsql => tsql + private SqlTemplate SqlEnsureVariantNamesAreUnique => SqlContext.Templates.Get("Umbraco.Core.DomainRepository.EnsureVariantNamesAreUnique", tsql => tsql .Select(x => x.Id, x => x.Name, x => x.LanguageId) .From() - .InnerJoin() - .On(x => x.Id, x => x.VersionId) - .InnerJoin() - .On(x => x.NodeId, x => x.NodeId) + .InnerJoin().On(x => x.Id, x => x.VersionId) + .InnerJoin().On(x => x.NodeId, x => x.NodeId) .Where(x => x.Current == SqlTemplate.Arg("current")) .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.ParentId == SqlTemplate.Arg("parentId") && x.NodeId != SqlTemplate.Arg("id")) .OrderBy(x => x.LanguageId)); - private void EnsureUniqueVariationNames(IContent content) + private void EnsureVariantNamesAreUnique(Content content, bool publishing) { - if (!EnsureUniqueNaming || content.CultureNames.Count == 0) return; + if (!EnsureUniqueNaming || !content.ContentType.VariesByCulture() || content.CultureNames.Count == 0) return; - //Get all culture names at the same level - - var sql = SqlEnsureUniqueVariationNames.Sql(true, NodeObjectTypeId, content.ParentId, content.Id); + // get names per culture, at same level (ie all siblings) + var sql = SqlEnsureVariantNamesAreUnique.Sql(true, NodeObjectTypeId, content.ParentId, content.Id); var names = Database.Fetch(sql) .GroupBy(x => x.LanguageId) .ToDictionary(x => x.Key, x => x); if (names.Count == 0) return; - foreach(var n in content.CultureNames) + // note: the code below means we are going to unique-ify every culture names, regardless + // of whether the name has changed (ie the culture has been updated) - some saving culture + // fr-FR could cause culture en-UK name to change - not sure that is clean + + foreach(var (culture, name) in content.CultureNames) { - var langId = LanguageRepository.GetIdByIsoCode(n.Key); + var langId = LanguageRepository.GetIdByIsoCode(culture); if (!langId.HasValue) continue; - if (names.TryGetValue(langId.Value, out var cultureNames)) - { - var otherLangNames = cultureNames.Select(x => new SimilarNodeName { Id = x.Id, Name = x.Name }); - var uniqueName = SimilarNodeName.GetUniqueName(otherLangNames, 0, n.Value); - content.SetName(uniqueName, n.Key); - } + if (!names.TryGetValue(langId.Value, out var cultureNames)) continue; + + // get a unique name + var otherNames = cultureNames.Select(x => new SimilarNodeName { Id = x.Id, Name = x.Name }); + var uniqueName = SimilarNodeName.GetUniqueName(otherNames, 0, name); + + if (uniqueName == content.GetCultureName(culture)) continue; + + // update the name, and the publish name if published + content.SetCultureName(uniqueName, culture); + if (publishing && content.PublishNames.ContainsKey(culture)) + content.SetPublishInfo(culture, uniqueName, DateTime.Now); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 75dbeca559..340eecb538 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -905,7 +905,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.Edited = dto.Edited; entity.Published = dto.Published; - if (dto.Variations.Has(ContentVariation.CultureNeutral) && dto.VariationInfo != null && dto.VariationInfo.Count > 0) + if (dto.Variations.VariesByCulture() && dto.VariationInfo != null && dto.VariationInfo.Count > 0) { var variantInfo = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (var info in dto.VariationInfo) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index db2e1124a2..740015683a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -52,7 +52,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement sql.OrderBy(dto => dto.Id); // get languages - var languages = Database.Fetch(sql).Select(ConvertFromDto).ToList(); + var languages = Database.Fetch(sql).Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); + + // fix inconsistencies: there has to be a default language, and it has to be mandatory + var defaultLanguage = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage) ?? languages.First(); + defaultLanguage.IsDefaultVariantLanguage = true; + defaultLanguage.Mandatory = true; // initialize the code-id map lock (_codeIdMap) diff --git a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs index b429a142e7..1d2d62b929 100644 --- a/src/Umbraco.Core/Publishing/ScheduledPublisher.cs +++ b/src/Umbraco.Core/Publishing/ScheduledPublisher.cs @@ -41,8 +41,8 @@ namespace Umbraco.Core.Publishing try { d.ReleaseDate = null; - d.TryPublishValues(); // fixme variants? - var result = _contentService.SaveAndPublish(d, _userService.GetProfileById(d.WriterId).Id); + d.PublishCulture(); // fixme variants? + var result = _contentService.SaveAndPublish(d, userId: _userService.GetProfileById(d.WriterId).Id); _logger.Debug(() => $"Result of publish attempt: {result.Result}"); if (result.Success == false) { diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 98767f2aa2..93f475f7a6 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -258,13 +258,21 @@ namespace Umbraco.Core.Runtime logger.Debug("No local version, need to install Umbraco."); _state.Level = RuntimeLevel.Install; } - else if (localVersion != codeVersion) + else if (localVersion < codeVersion) { // there *is* a local version, but it does not match the code version // need to upgrade - logger.Debug(() => $"Local version \"{localVersion}\" != code version \"{codeVersion}\", need to upgrade Umbraco."); + logger.Debug(() => $"Local version \"{localVersion}\" < code version \"{codeVersion}\", need to upgrade Umbraco."); _state.Level = RuntimeLevel.Upgrade; } + else if (localVersion > codeVersion) + { + logger.Warn(() => $"Local version \"{localVersion}\" > code version \"{codeVersion}\", downgrading is not supported."); + _state.Level = RuntimeLevel.BootFailed; + + // in fact, this is bad enough that we want to throw + throw new BootFailedException($"Local version \"{localVersion}\" > code version \"{codeVersion}\", downgrading is not supported."); + } else if (databaseFactory.Configured == false) { // local version *does* match code version, but the database is not configured diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index edc10353c9..2e788382fe 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -348,23 +348,48 @@ namespace Umbraco.Core.Services /// /// Saves and publishes a document. /// - /// Property values must first be published at document level. - PublishResult SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); + /// + /// By default, publishes all variations of the document, but it is possible to specify a culture to be published. + /// When a culture is being published, it includes all varying values along with all invariant values. For + /// anything more complicated, see . + /// The document is *always* saved, even when publishing fails. + /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor + /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. + /// + PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = 0, bool raiseEvents = true); + + /// + /// Saves and publishes a publishing document. + /// + /// + /// A publishing document is a document with values that are being published, i.e. + /// that have been published or cleared via and + /// . + /// The document is *always* saved, even when publishing fails. + /// + PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true); /// /// Saves and publishes a document branch. /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = null, string segment = null, int userId = 0); + IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = 0); /// /// Saves and publishes a document branch. /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, Func editing, Func publishValues, int userId = 0); + IEnumerable SaveAndPublishBranch(IContent content, bool force, Func editing, Func publishCultures, int userId = 0); /// - /// Unpublishes a document or optionally unpublishes a culture and/or segment for the document. + /// Unpublishes a document. /// - UnpublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0); + /// + /// By default, unpublishes the document as a whole, but it is possible to specify a culture to be + /// unpublished. Depending on whether that culture is mandatory, and other cultures remain published, + /// the document as a whole may or may not remain published. + /// If the content type is variant, then culture can be either '*' or an actual culture, but neither null nor + /// empty. If the content type is invariant, then culture can be either '*' or null or empty. + /// + UnpublishResult Unpublish(IContent content, string culture = "*", int userId = 0); /// /// Gets a value indicating whether a document is path-publishable. diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 0c0015019a..e953a6073e 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.IO; @@ -862,7 +863,7 @@ namespace Umbraco.Core.Services.Implement { var publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) - throw new InvalidOperationException("Cannot save a (un)publishing, use the dedicated (un)publish method."); + throw new InvalidOperationException("Cannot save (un)publishing content, use the dedicated SavePublished method."); var evtMsgs = EventMessagesFactory.Get(); @@ -875,8 +876,6 @@ namespace Umbraco.Core.Services.Implement return OperationResult.Cancel(evtMsgs); } - var isNew = content.IsNewEntity(); - scope.WriteLock(Constants.Locks.ContentTree); if (content.HasIdentity == false) @@ -890,9 +889,9 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); } - var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode; + var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); + Audit(AuditType.Save, "Saved by user", userId, content.Id); scope.Complete(); } @@ -914,8 +913,7 @@ namespace Umbraco.Core.Services.Implement return OperationResult.Cancel(evtMsgs); } - var treeChanges = contentsA.Select(x => new TreeChange(x, - x.IsNewEntity() ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode)); + var treeChanges = contentsA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)); scope.WriteLock(Constants.Locks.ContentTree); foreach (var content in contentsA) @@ -933,7 +931,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); } scope.Events.Dispatch(TreeChanged, this, treeChanges.ToEventArgs()); - Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); + Audit(AuditType.Save, "Bulk-saved by user", userId == -1 ? 0 : userId, Constants.System.Root); scope.Complete(); } @@ -942,21 +940,169 @@ namespace Umbraco.Core.Services.Implement } /// - public PublishResult SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) + public PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = 0, bool raiseEvents = true) { var evtMsgs = EventMessagesFactory.Get(); - PublishResult result; - if (((Content) content).PublishedState != PublishedState.Publishing && content.Published) + var publishedState = content.PublishedState; + if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) + throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(SavePublishing)} method."); + + // cannot accept invariant (null or empty) culture for variant content type + // cannot accept a specific culture for invariant content type (but '*' is ok) + if (content.ContentType.VariesByCulture()) { - // already published, and values haven't changed - i.e. not changing anything - // nothing to do - // fixme - unless we *want* to bump dates? - return new PublishResult(PublishResultType.SuccessAlready, evtMsgs, content); + if (culture.IsNullOrWhiteSpace()) + throw new NotSupportedException("Invariant culture is not supported by variant content types."); } + else + { + if (!culture.IsNullOrWhiteSpace() && culture != "*") + throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); + } + + // if culture is specific, first publish the invariant values, then publish the culture itself. + // if culture is '*', then publish them all (including variants) + + // explicitely SaveAndPublish a specific culture also publishes invariant values + if (!culture.IsNullOrWhiteSpace() && culture != "*") + { + // publish the invariant values + var publishInvariant = content.PublishCulture(null); + if (!publishInvariant) + return new PublishResult(PublishResultType.FailedContentInvalid, evtMsgs, content); + } + + // publish the culture(s) + var publishCulture = content.PublishCulture(culture); + if (!publishCulture) + return new PublishResult(PublishResultType.FailedContentInvalid, evtMsgs, content); + + // finally, "save publishing" + // what happens next depends on whether the content can be published or not + return SavePublishing(content, userId, raiseEvents); + } + + /// + public UnpublishResult Unpublish(IContent content, string culture = "*", int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + culture = culture.NullOrWhiteSpaceAsNull(); + + var publishedState = content.PublishedState; + if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) + throw new InvalidOperationException($"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(SavePublishing)} method."); + + // cannot accept invariant (null or empty) culture for variant content type + // cannot accept a specific culture for invariant content type (but '*' is ok) + if (content.ContentType.VariesByCulture()) + { + if (culture == null) + throw new NotSupportedException("Invariant culture is not supported by variant content types."); + } + else + { + if (culture != null && culture != "*") + throw new NotSupportedException($"Culture \"{culture}\" is not supported by invariant content types."); + } + + // if the content is not published, nothing to do + if (!content.Published) + return new UnpublishResult(UnpublishResultType.SuccessAlready, evtMsgs, content); + + // all cultures = unpublish whole + if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null)) + { + ((Content) content).PublishedState = PublishedState.Unpublishing; + } + else + { + // if the culture we want to unpublish was already unpublished, nothing to do + if (!content.WasCulturePublished(culture)) + return new UnpublishResult(UnpublishResultType.SuccessAlready, evtMsgs, content); + + // unpublish the culture + content.UnpublishCulture(culture); + } + + // finally, "save publishing" + // what happens next depends on whether the content can be published or not + using (var scope = ScopeProvider.CreateScope()) + { + var saved = SavePublishing(content, userId); + if (saved.Success) + { + UnpublishResultType result; + if (culture == "*" || culture == null) + { + Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id); + result = UnpublishResultType.Success; + } + else + { + Audit(AuditType.UnPublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id); + if (!content.Published) + Audit(AuditType.UnPublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id); + result = content.Published ? UnpublishResultType.SuccessCulture : UnpublishResultType.SuccessMandatoryCulture; + } + scope.Complete(); + return new UnpublishResult(result, evtMsgs, content); + } + + // failed - map result + var r = saved.Result == PublishResultType.FailedCancelledByEvent + ? UnpublishResultType.FailedCancelledByEvent + : UnpublishResultType.Failed; + return new UnpublishResult(r, evtMsgs, content); + } + } + + /// + public PublishResult SavePublishing(IContent content, int userId = 0, bool raiseEvents = true) + { + var evtMsgs = EventMessagesFactory.Get(); + PublishResult publishResult = null; + UnpublishResult unpublishResult = null; + + // nothing set = republish it all + if (content.PublishedState != PublishedState.Publishing && content.PublishedState != PublishedState.Unpublishing) + ((Content) content).PublishedState = PublishedState.Publishing; + + // state here is either Publishing or Unpublishing + var publishing = content.PublishedState == PublishedState.Publishing; + var unpublishing = content.PublishedState == PublishedState.Unpublishing; using (var scope = ScopeProvider.CreateScope()) { + // is the content going to end up published, or unpublished? + if (publishing && content.ContentType.VariesByCulture()) + { + var publishedCultures = content.PublishedCultures.ToList(); + var cannotBePublished = publishedCultures.Count == 0; // no published cultures = cannot be published + if (!cannotBePublished) + { + var mandatoryCultures = _languageRepository.GetMany().Where(x => x.Mandatory).Select(x => x.IsoCode); + cannotBePublished = mandatoryCultures.Any(x => !publishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase)); // missing mandatory culture = cannot be published + } + + if (cannotBePublished) + { + publishing = false; + unpublishing = content.Published; // if not published yet, nothing to do + + // we may end up in a state where we won't publish nor unpublish + // keep going, though, as we want to save anways + } + } + + var isNew = !content.HasIdentity; + var changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch; + var previouslyPublished = content.HasIdentity && content.Published; + + scope.WriteLock(Constants.Locks.ContentTree); + + // always save var saveEventArgs = new SaveEventArgs(content, evtMsgs); if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, "Saving")) { @@ -964,183 +1110,118 @@ namespace Umbraco.Core.Services.Implement return new PublishResult(PublishResultType.FailedCancelledByEvent, evtMsgs, content); } - var isNew = content.IsNewEntity(); - var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode; - var previouslyPublished = content.HasIdentity && content.Published; + if (publishing) + { + // ensure that the document can be published, and publish + // handling events, business rules, etc + // note: StrategyPublish flips the PublishedState to Publishing! + publishResult = StrategyCanPublish(scope, content, userId, /*checkPath:*/ true, evtMsgs); + if (publishResult.Success) + publishResult = StrategyPublish(scope, content, /*canPublish:*/ true, userId, evtMsgs); + if (!publishResult.Success) + ((Content) content).Published = content.Published; // reset published state = save unchanged + } - scope.WriteLock(Constants.Locks.ContentTree); + if (unpublishing) + { + var newest = GetById(content.Id); // ensure we have the newest version - in scope + if (content.VersionId != newest.VersionId) // but use the original object if it's already the newest version + content = newest; - // ensure that the document can be published, and publish - // handling events, business rules, etc - result = StrategyCanPublish(scope, content, userId, /*checkPath:*/ true, evtMsgs); - if (result.Success) - result = StrategyPublish(scope, content, /*canPublish:*/ true, userId, evtMsgs); + if (content.Published) + { + // ensure that the document can be unpublished, and unpublish + // handling events, business rules, etc + // note: StrategyUnpublish flips the PublishedState to Unpublishing! + unpublishResult = StrategyCanUnpublish(scope, content, userId, evtMsgs); + if (unpublishResult.Success) + unpublishResult = StrategyUnpublish(scope, content, true, userId, evtMsgs); + if (!unpublishResult.Success) + ((Content) content).Published = content.Published; // reset published state = save unchanged + } + else + { + // already unpublished - optimistic concurrency collision, really, + // and I am not sure at all what we should do, better die fast, else + // we may end up corrupting the db + throw new InvalidOperationException("Concurrency collision."); + } + } - // save - always, even if not publishing (this is SaveAndPublish) + // save, always if (content.HasIdentity == false) content.CreatorId = userId; content.WriterId = userId; - // if not going to publish, must reset the published state - if (!result.Success) - ((Content) content).Published = content.Published; - + // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing _documentRepository.Save(content); - if (raiseEvents) // always + // raise the Saved event, always + if (raiseEvents) { saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs, "Saved"); } - if (result.Success == false) + if (unpublishing) // we have tried to unpublish { + if (unpublishResult.Success) // and succeeded, trigger events + { + // events and audit + scope.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false), "UnPublished"); + scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); + Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id); + scope.Complete(); + return new PublishResult(PublishResultType.Success, evtMsgs, content); + } + + // or, failed scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); scope.Complete(); // compete the save - return result; + return new PublishResult(PublishResultType.FailedToUnpublish, evtMsgs, content); // bah } - if (isNew == false && previouslyPublished == false) - changeType = TreeChangeTypes.RefreshBranch; // whole branch - - // invalidate the node/branch - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); - - scope.Events.Dispatch(Published, this, new PublishEventArgs(content, false, false), "Published"); - - // if was not published and now is... descendants that were 'published' (but - // had an unpublished ancestor) are 're-published' ie not explicitely published - // but back as 'published' nevertheless - if (isNew == false && previouslyPublished == false && HasChildren(content.Id)) + if (publishing) // we have tried to publish { - var descendants = GetPublishedDescendantsLocked(content).ToArray(); - scope.Events.Dispatch(Published, this, new PublishEventArgs(descendants, false, false), "Published"); - } - - Audit(AuditType.Publish, "Save and Publish performed by user", userId, content.Id); - - scope.Complete(); - } - - return result; - } - - /// - public UnpublishResult Unpublish(IContent content, string culture = null, string segment = null, int userId = 0) - { - var evtMsgs = EventMessagesFactory.Get(); - - using (var scope = ScopeProvider.CreateScope()) - { - scope.WriteLock(Constants.Locks.ContentTree); - - var tryUnpublishVariation = TryUnpublishVariationInternal(scope, content, evtMsgs, culture, segment, userId); - - if (tryUnpublishVariation) return tryUnpublishVariation.Result; - - //continue the normal Unpublish operation to unpublish the entire content item - var result = UnpublishInternal(scope, content, evtMsgs, userId); - - //not succesful, so return it - if (!result.Success) return result; - - //else check if there was a status returned from TryUnpublishVariationInternal and if so use that - return tryUnpublishVariation.Result ?? result; - } - } - - /// - /// Used to unpublish the requested variant if possible - /// - /// - /// - /// - /// - /// - /// - /// - /// A successful attempt if a variant was unpublished and it wasn't the last, else a failed result if it's an invariant document or if it's the last. - /// A failed result indicates that a normal unpublish operation will need to be performed. - /// - private Attempt TryUnpublishVariationInternal(IScope scope, IContent content, EventMessages evtMsgs, string culture, string segment, int userId) - { - if (!culture.IsNullOrWhiteSpace() || !segment.IsNullOrWhiteSpace()) - { - //now we need to check if this is not the last culture/segment that is published - if (!culture.IsNullOrWhiteSpace()) - { - //capture before we clear the values - var publishedCultureCount = content.PublishedCultures.Count(); - - //we need to unpublish everything if this is a mandatory language - var unpublishAll = _languageRepository.GetMany().Where(x => x.Mandatory).Select(x => x.IsoCode).Contains(culture, StringComparer.InvariantCultureIgnoreCase); - - content.ClearPublishedValues(culture, segment); - //now we just publish with the name cleared - SaveAndPublish(content, userId); - Audit(AuditType.UnPublish, $"UnPublish variation culture: {culture ?? string.Empty}, segment: {segment ?? string.Empty} performed by user", userId, content.Id); - - //We don't need to unpublish all and this is not the last one, so complete the scope and return - if (!unpublishAll && publishedCultureCount > 1) + if (publishResult.Success) // and succeeded, trigger events { + if (isNew == false && previouslyPublished == false) + changeType = TreeChangeTypes.RefreshBranch; // whole branch + + // invalidate the node/branch + scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); + scope.Events.Dispatch(Published, this, new PublishEventArgs(content, false, false), "Published"); + + // if was not published and now is... descendants that were 'published' (but + // had an unpublished ancestor) are 're-published' ie not explicitely published + // but back as 'published' nevertheless + if (isNew == false && previouslyPublished == false && HasChildren(content.Id)) + { + var descendants = GetPublishedDescendantsLocked(content).ToArray(); + scope.Events.Dispatch(Published, this, new PublishEventArgs(descendants, false, false), "Published"); + } + + Audit(AuditType.Publish, "Published by user", userId, content.Id); scope.Complete(); - return Attempt.Succeed(new UnpublishResult(UnpublishResultType.SuccessVariant, evtMsgs, content)); + return publishResult; } - if (unpublishAll) - { - //return a fail with a custom status here so the normal unpublish operation takes place but the result will be this flag - return Attempt.Fail(new UnpublishResult(UnpublishResultType.SuccessMandatoryCulture, evtMsgs, content)); - } + // or, failed + scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); + scope.Complete(); // compete the save + return publishResult; } - if (!segment.IsNullOrWhiteSpace()) - { - //TODO: When segments are supported, implement this, a discussion needs to happen about what how a segment is 'published' or not - // since currently this is very different from a culture. - throw new NotImplementedException("Segments are currently not supported in Umbraco"); - } + // both publishing and unpublishing are false + // this means that we wanted to publish, in a variant scenario, a document that + // was not published yet, and we could not, due to cultures issues + // + // raise event (we saved), report + + scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, changeType).ToEventArgs()); + scope.Complete(); // compete the save + return new PublishResult(PublishResultType.FailedByCulture, evtMsgs, content); } - - //This is either a non variant document or this is the last one or this was a mandatory variant, so return a failed result which indicates that a normal unpublish operation needs to also take place - return Attempt.Fail(); - } - - /// - /// Performs the logic to unpublish an entire document - /// - /// - /// - /// - /// - /// - private UnpublishResult UnpublishInternal(IScope scope, IContent content, EventMessages evtMsgs, int userId) - { - var newest = GetById(content.Id); // ensure we have the newest version - if (content.VersionId != newest.VersionId) // but use the original object if it's already the newest version - content = newest; - if (content.Published == false) - { - scope.Complete(); - return new UnpublishResult(UnpublishResultType.SuccessAlready, evtMsgs, content); // already unpublished - } - - // strategy - // fixme should we still complete the uow? don't want to rollback here! - var attempt = StrategyCanUnpublish(scope, content, userId, evtMsgs); - if (attempt.Success == false) return attempt; // causes rollback - attempt = StrategyUnpublish(scope, content, true, userId, evtMsgs); - if (attempt.Success == false) return attempt; // causes rollback - - content.WriterId = userId; - _documentRepository.Save(content); - - scope.Events.Dispatch(UnPublished, this, new PublishEventArgs(content, false, false), "UnPublished"); - scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.RefreshBranch).ToEventArgs()); - Audit(AuditType.UnPublish, "UnPublish performed by user", userId, content.Id); - - scope.Complete(); - return new UnpublishResult(evtMsgs, content); } /// @@ -1156,8 +1237,8 @@ namespace Umbraco.Core.Services.Implement try { d.ReleaseDate = null; - d.TryPublishValues(); // fixme variants? - result = SaveAndPublish(d, d.WriterId); + d.PublishCulture(); // fixme variants? + result = SaveAndPublish(d, userId: d.WriterId); if (result.Success == false) Logger.Error($"Failed to publish document id={d.Id}, reason={result.Result}."); } @@ -1189,19 +1270,22 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = null, string segment = null, int userId = 0) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = 0) { - segment = segment?.ToLowerInvariant(); + // note: EditedValue and PublishedValue are objects here, so it is important to .Equals() + // and not to == them, else we would be comparing references, and that is a bad thing - bool IsEditing(IContent c, string l, string s) - => c.Properties.Any(x => x.Values.Where(y => y.Culture == l && y.Segment == s).Any(y => y.EditedValue != y.PublishedValue)); + bool IsEditing(IContent c, string l) + => c.PublishName != c.Name || + c.PublishedCultures.Any(x => c.GetCultureName(x) != c.GetPublishName(x)) || + c.Properties.Any(x => x.Values.Where(y => culture == "*" || y.Culture == l).Any(y => !y.EditedValue.Equals(y.PublishedValue))); - return SaveAndPublishBranch(content, force, document => IsEditing(document, culture, segment), document => document.TryPublishValues(culture, segment), userId); + return SaveAndPublishBranch(content, force, document => IsEditing(document, culture), document => document.PublishCulture(culture), userId); } /// public IEnumerable SaveAndPublishBranch(IContent document, bool force, - Func editing, Func publishValues, int userId = 0) + Func editing, Func publishCultures, int userId = 0) { var evtMsgs = EventMessagesFactory.Get(); var results = new List(); @@ -1221,7 +1305,7 @@ namespace Umbraco.Core.Services.Implement throw new InvalidOperationException("Do not publish values when publishing branches."); // deal with the branch root - if it fails, abort - var result = SaveAndPublishBranchOne(scope, document, editing, publishValues, true, publishedDocuments, evtMsgs, userId); + var result = SaveAndPublishBranchOne(scope, document, editing, publishCultures, true, publishedDocuments, evtMsgs, userId); results.Add(result); if (!result.Success) return results; @@ -1241,7 +1325,7 @@ namespace Umbraco.Core.Services.Implement // no need to check path here, // 1. because we know the parent is path-published (we just published it) // 2. because it would not work as nothing's been written out to the db until the uow completes - result = SaveAndPublishBranchOne(scope, d, editing, publishValues, false, publishedDocuments, evtMsgs, userId); + result = SaveAndPublishBranchOne(scope, d, editing, publishCultures, false, publishedDocuments, evtMsgs, userId); results.Add(result); if (result.Success) continue; @@ -1251,7 +1335,7 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(TreeChanged, this, new TreeChange(document, TreeChangeTypes.RefreshBranch).ToEventArgs()); scope.Events.Dispatch(Published, this, new PublishEventArgs(publishedDocuments, false, false), "Published"); - Audit(AuditType.Publish, "SaveAndPublishBranch performed by user", userId, document.Id); + Audit(AuditType.Publish, "Branch published by user", userId, document.Id); scope.Complete(); } @@ -1320,7 +1404,7 @@ namespace Umbraco.Core.Services.Implement DeleteLocked(scope, content); scope.Events.Dispatch(TreeChanged, this, new TreeChange(content, TreeChangeTypes.Remove).ToEventArgs()); - Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); + Audit(AuditType.Delete, "Deleted by user", userId, content.Id); scope.Complete(); } @@ -1388,7 +1472,7 @@ namespace Umbraco.Core.Services.Implement deleteRevisionsEventArgs.CanCancel = false; scope.Events.Dispatch(DeletedVersions, this, deleteRevisionsEventArgs); - Audit(AuditType.Delete, "Delete Content by version date performed by user", userId, Constants.System.Root); + Audit(AuditType.Delete, "Delete (by version date) by user", userId, Constants.System.Root); scope.Complete(); } @@ -1425,7 +1509,7 @@ namespace Umbraco.Core.Services.Implement _documentRepository.DeleteVersion(versionId); scope.Events.Dispatch(DeletedVersions, this, new DeleteRevisionsEventArgs(id, false,/* specificVersion:*/ versionId)); - Audit(AuditType.Delete, "Delete Content by version performed by user", userId, Constants.System.Root); + Audit(AuditType.Delete, "Delete (by version) by user", userId, Constants.System.Root); scope.Complete(); } @@ -1470,7 +1554,7 @@ namespace Umbraco.Core.Services.Implement moveEventArgs.CanCancel = false; moveEventArgs.MoveInfoCollection = moveInfo; scope.Events.Dispatch(Trashed, this, moveEventArgs, nameof(Trashed)); - Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); + Audit(AuditType.Move, "Moved to Recycle Bin by user", userId, content.Id); scope.Complete(); } @@ -1542,7 +1626,7 @@ namespace Umbraco.Core.Services.Implement moveEventArgs.MoveInfoCollection = moveInfo; moveEventArgs.CanCancel = false; scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved)); - Audit(AuditType.Move, "Move Content performed by user", userId, content.Id); + Audit(AuditType.Move, "Moved by user", userId, content.Id); scope.Complete(); } @@ -1639,7 +1723,7 @@ namespace Umbraco.Core.Services.Implement recycleBinEventArgs.RecycleBinEmptiedSuccessfully = true; // oh my?! scope.Events.Dispatch(EmptiedRecycleBin, this, recycleBinEventArgs); scope.Events.Dispatch(TreeChanged, this, deleted.Select(x => new TreeChange(x, TreeChangeTypes.Remove)).ToEventArgs()); - Audit(AuditType.Delete, "Empty Content Recycle Bin performed by user", 0, Constants.System.RecycleBinContent); + Audit(AuditType.Delete, "Recycle Bin emptied by user", 0, Constants.System.RecycleBinContent); scope.Complete(); } diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index cca7eb8fb7..f2d873f2ca 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -768,8 +768,6 @@ namespace Umbraco.Core.Services.Implement if (string.IsNullOrWhiteSpace(media.Name)) throw new ArgumentException("Media has no name.", nameof(media)); - var isNew = media.IsNewEntity(); - scope.WriteLock(Constants.Locks.MediaTree); if (media.HasIdentity == false) media.CreatorId = userId; @@ -780,7 +778,7 @@ namespace Umbraco.Core.Services.Implement saveEventArgs.CanCancel = false; scope.Events.Dispatch(Saved, this, saveEventArgs); } - var changeType = isNew ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode; + var changeType = TreeChangeTypes.RefreshNode; scope.Events.Dispatch(TreeChanged, this, new TreeChange(media, changeType).ToEventArgs()); Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); @@ -810,7 +808,7 @@ namespace Umbraco.Core.Services.Implement return OperationResult.Attempt.Cancel(evtMsgs); } - var treeChanges = mediasA.Select(x => new TreeChange(x, x.IsNewEntity() ? TreeChangeTypes.RefreshBranch : TreeChangeTypes.RefreshNode)); + var treeChanges = mediasA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)); scope.WriteLock(Constants.Locks.MediaTree); foreach (var media in mediasA) diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 367a870896..b41e7739ba 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -12,10 +12,10 @@ namespace Umbraco.Core.Services /// public static class LocalizedTextServiceExtensions { - public static string Localize(this ILocalizedTextService manager, params string[] keyParts) + public static string Localize(this ILocalizedTextService manager, string area, string key) { - var key = string.Join("/", keyParts); - return manager.Localize(key, Thread.CurrentThread.CurrentUICulture); + var fullKey = string.Join("/", area, key); + return manager.Localize(fullKey, Thread.CurrentThread.CurrentUICulture); } /// diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs index b4bfe078b7..15b2f503c7 100644 --- a/src/Umbraco.Core/Services/PublishResultType.cs +++ b/src/Umbraco.Core/Services/PublishResultType.cs @@ -18,7 +18,7 @@ /// The item was already published. /// SuccessAlready = 1, - + /// /// The operation failed. /// @@ -58,8 +58,23 @@ FailedContentInvalid = Failed | 6, /// - /// The document could not be published because it does not have published values. + /// Cannot republish a document that hasn't been published. /// - FailedNoPublishedValues = Failed | 7 + FailedNoPublishedValues = Failed | 7, // in ContentService.StrategyCanPublish - fixme weird + + /// + /// Some mandatory cultures are missing, or are not valid. + /// + FailedCannotPublish = Failed | 8, // in ContentController.PublishInternal - fixme // FailedByCulture? + + /// + /// Publishing changes triggered an unpublishing, due to missing mandatory cultures, and unpublishing failed. + /// + FailedToUnpublish = Failed | 9, // in ContentService.SavePublishing + + /// + /// Some mandatory cultures are missing. + /// + FailedByCulture = Failed | 10, // in ContentService.SavePublishing } } diff --git a/src/Umbraco.Core/Services/UnpublishResultType.cs b/src/Umbraco.Core/Services/UnpublishResultType.cs index 010c37d7a5..e61e786a05 100644 --- a/src/Umbraco.Core/Services/UnpublishResultType.cs +++ b/src/Umbraco.Core/Services/UnpublishResultType.cs @@ -18,7 +18,7 @@ /// /// The specified variant was unpublished, the content item itself remains published. /// - SuccessVariant = 2, + SuccessCulture = 2, /// /// The specified variant was a mandatory culture therefore it was unpublished and the content item itself is unpublished diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index a2f5727ae4..0799c2c9a8 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1504,7 +1504,7 @@ namespace Umbraco.Core /// /// Turns an null-or-whitespace string into a null string. /// - public static string NullEmpty(this string text) + public static string NullOrWhiteSpaceAsNull(this string text) => string.IsNullOrWhiteSpace(text) ? null : text; } } diff --git a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs index 09feeff338..9472ff4823 100644 --- a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Strings if (content.HasProperty(Constants.Conventions.Content.UrlName)) source = (content.GetValue(Constants.Conventions.Content.UrlName, culture) ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(source)) - source = content.GetName(culture); + source = content.GetCultureName(culture); return source; } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 728e507c05..c9f40e28dd 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1097,6 +1097,7 @@ + @@ -1519,4 +1520,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Tests/Integration/ContentEventsTests.cs b/src/Umbraco.Tests/Integration/ContentEventsTests.cs index 73f95c44a7..03539d2273 100644 --- a/src/Umbraco.Tests/Integration/ContentEventsTests.cs +++ b/src/Umbraco.Tests/Integration/ContentEventsTests.cs @@ -108,17 +108,14 @@ namespace Umbraco.Tests.Integration private IContent CreateBranch() { var content1 = MockedContent.CreateSimpleContent(_contentType, "Content1"); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); // 2 (published) // .1 (published) // .2 (not published) var content2 = MockedContent.CreateSimpleContent(_contentType, "Content2", content1); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); var content21 = MockedContent.CreateSimpleContent(_contentType, "Content21", content2); - content21.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content21); var content22 = MockedContent.CreateSimpleContent(_contentType, "Content22", content2); ServiceContext.ContentService.Save(content22); @@ -137,12 +134,10 @@ namespace Umbraco.Tests.Integration // .1 (published) // .2 (not published) var content4 = MockedContent.CreateSimpleContent(_contentType, "Content4", content1); - content4.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content4); content4.Name = "Content4X"; ServiceContext.ContentService.Save(content4); var content41 = MockedContent.CreateSimpleContent(_contentType, "Content41", content4); - content41.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content41); var content42 = MockedContent.CreateSimpleContent(_contentType, "Content42", content4); ServiceContext.ContentService.Save(content42); @@ -151,10 +146,8 @@ namespace Umbraco.Tests.Integration // .1 (published) // .2 (not published) var content5 = MockedContent.CreateSimpleContent(_contentType, "Content5", content1); - content5.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content5); var content51 = MockedContent.CreateSimpleContent(_contentType, "Content51", content5); - content51.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content51); var content52 = MockedContent.CreateSimpleContent(_contentType, "Content52", content5); ServiceContext.ContentService.Save(content52); @@ -165,6 +158,69 @@ namespace Umbraco.Tests.Integration #endregion + #region Validate Setup + + [Test] + public void CreatedBranchIsOk() + { + var content1 = CreateBranch(); + + var children1 = Children(content1).ToArray(); + + var content2 = children1[0]; + var children2 = Children(content2).ToArray(); + var content21 = children2[0]; + var content22 = children2[1]; + + var content3 = children1[1]; + var children3 = Children(content3).ToArray(); + var content31 = children3[0]; + var content32 = children3[1]; + + var content4 = children1[2]; + var children4 = Children(content4).ToArray(); + var content41 = children4[0]; + var content42 = children4[1]; + + var content5 = children1[3]; + var children5 = Children(content5).ToArray(); + var content51 = children5[0]; + var content52 = children5[1]; + + Assert.IsTrue(content1.Published); + Assert.IsFalse(content1.Edited); + + Assert.IsTrue(content2.Published); + Assert.IsFalse(content2.Edited); + Assert.IsTrue(content21.Published); + Assert.IsFalse(content21.Edited); + Assert.IsFalse(content22.Published); + Assert.IsTrue(content22.Edited); + + Assert.IsFalse(content3.Published); + Assert.IsTrue(content3.Edited); + Assert.IsFalse(content31.Published); + Assert.IsTrue(content31.Edited); + Assert.IsFalse(content32.Published); + Assert.IsTrue(content32.Edited); + + Assert.IsTrue(content4.Published); + Assert.IsTrue(content4.Edited); + Assert.IsTrue(content41.Published); + Assert.IsFalse(content41.Edited); + Assert.IsFalse(content42.Published); + Assert.IsTrue(content42.Edited); + + Assert.IsFalse(content5.Published); + Assert.IsTrue(content5.Edited); + Assert.IsTrue(content51.Published); + Assert.IsFalse(content51.Edited); + Assert.IsFalse(content52.Published); + Assert.IsTrue(content52.Edited); + } + + #endregion + #region Events tracer private class EventInstance @@ -441,7 +497,6 @@ namespace Umbraco.Tests.Integration var content = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -478,7 +533,6 @@ namespace Umbraco.Tests.Integration var content = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -515,7 +569,6 @@ namespace Umbraco.Tests.Integration var content = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -555,7 +608,6 @@ namespace Umbraco.Tests.Integration ResetEvents(); content.Name = "changed"; - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); Assert.AreEqual(2, _msgCount); @@ -576,12 +628,10 @@ namespace Umbraco.Tests.Integration var content = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); content.Name = "changed"; - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); Assert.AreEqual(2, _msgCount); @@ -590,7 +640,7 @@ namespace Umbraco.Tests.Integration var m = 0; Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content.Id}.p+p", _events[i++].ToString()); m++; - Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshNode/{content.Id}", _events[i++].ToString()); + Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content.Id}", _events[i++].ToString()); } [Test] @@ -608,7 +658,6 @@ namespace Umbraco.Tests.Integration ResetEvents(); content.Name = "changed"; - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); Assert.AreEqual(2, _msgCount); @@ -620,35 +669,6 @@ namespace Umbraco.Tests.Integration Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content.Id}", _events[i++].ToString()); } - [Test] - public void PublishPublishedContent() - { - // rule: when a content is published, - // - repository : refresh (p) - // - published page cache :: refresh - // note: whenever the published cache is refreshed, subscribers must - // assume that the unpublished cache is also refreshed, with the same - // values, and deal with it. - - var content = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); - Assert.IsNotNull(content); - content.TryPublishValues(); - ServiceContext.ContentService.SaveAndPublish(content); - - ResetEvents(); - content.Name = "changed"; - content.TryPublishValues(); - ServiceContext.ContentService.SaveAndPublish(content); - - Assert.AreEqual(2, _msgCount); - Assert.AreEqual(2, _events.Count); - var i = 0; - var m = 0; - Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content.Id}.p+p", _events[i++].ToString()); - m++; - Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshNode/{content.Id}", _events[i++].ToString()); - } - [Test] public void UnpublishContent() { @@ -658,7 +678,6 @@ namespace Umbraco.Tests.Integration var content = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -682,7 +701,6 @@ namespace Umbraco.Tests.Integration var content = ServiceContext.ContentService.GetRootContent().FirstOrDefault(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); content.Name = "changed"; ServiceContext.ContentService.Save(content); @@ -743,7 +761,6 @@ namespace Umbraco.Tests.Integration ServiceContext.ContentService.Unpublish(content1); ResetEvents(); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); Assert.AreEqual(2, _msgCount); @@ -775,32 +792,32 @@ namespace Umbraco.Tests.Integration var content1 = CreateBranch(); ServiceContext.ContentService.Unpublish(content1); - ResetEvents(); - ServiceContext.ContentService.SaveAndPublishBranch(content1, false); + // branch is: - Assert.AreEqual(6, _msgCount); - Assert.AreEqual(6, _events.Count); + ResetEvents(); + ServiceContext.ContentService.SaveAndPublishBranch(content1, force: false); // force = false, don't publish unpublished items + + foreach (var e in _events) + Console.WriteLine(e); + + Assert.AreEqual(3, _msgCount); + Assert.AreEqual(3, _events.Count); var i = 0; var m = 0; var content1C = Children(content1).ToArray(); var content2C = Children(content1C[0]).ToArray(); var content4C = Children(content1C[2]).ToArray(); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.u+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p+p", _events[i++].ToString()); - Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content4C[0].Id}.p+p", _events[i++].ToString()); - m++; + + // force:false => only republish the root node + nodes that are edited + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.u+p", _events[i++].ToString()); // content1 was unpublished, now published + + // change: only content4 shows here, because it has changes - others don't need to be published + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p+p", _events[i++].ToString()); // content1/content2 + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p+p", _events[i++].ToString()); // content1/content4 + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p+p", _events[i++].ToString()); // content1/content2/content21 + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p+p", _events[i++].ToString()); // content1/content4/content41 + Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); // repub content1 - /* - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished/{1}", m, content1C[0].Id), _events[i++].ToString()); // repub content1.content2 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content1C[2].Id), _events[i++].ToString()); // repub content1.content4 - var c = ServiceContext.ContentService.GetPublishedVersion(((ContentCacheRefresher.JsonPayload)_events[i - 1].EventArgs).Id); - Assert.IsTrue(c.Published); // get the published one - Assert.AreEqual("Content4X", c.Name); // published has new name - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished/{1}", m, content2C[0].Id), _events[i++].ToString()); // repub content1.content2.content21 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished/{1}", m, content4C[0].Id), _events[i].ToString()); // repub content1.content4.content41 - */ } [Test] @@ -812,10 +829,13 @@ namespace Umbraco.Tests.Integration ServiceContext.ContentService.Unpublish(content1); ResetEvents(); - ServiceContext.ContentService.SaveAndPublishBranch(content1, true); + ServiceContext.ContentService.SaveAndPublishBranch(content1, force: true); // force = true, also publish unpublished items - Assert.AreEqual(14, _msgCount); - Assert.AreEqual(14, _events.Count); + foreach (var e in _events) + Console.WriteLine(e); + + Assert.AreEqual(10, _msgCount); + Assert.AreEqual(10, _events.Count); var i = 0; var m = 0; var content1C = Children(content1).ToArray(); @@ -823,43 +843,26 @@ namespace Umbraco.Tests.Integration var content3C = Children(content1C[1]).ToArray(); var content4C = Children(content1C[2]).ToArray(); var content5C = Children(content1C[3]).ToArray(); + + // force:true => all nodes are republished, refreshing all nodes - but only with changes - published w/out changes are not repub Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1.Id}.u+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p+p", _events[i++].ToString()); + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[0].Id}.p+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[1].Id}.u+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[2].Id}.p+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content1C[3].Id}.u+p", _events[i++].ToString()); // remember: ordered by level, sortOrder - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p+p", _events[i++].ToString()); + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[0].Id}.p+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[0].Id}.u+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p+p", _events[i++].ToString()); - Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p+p", _events[i++].ToString()); + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[0].Id}.p+p", _events[i++].ToString()); + //Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[0].Id}.p+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content2C[1].Id}.u+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content3C[1].Id}.u+p", _events[i++].ToString()); Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content4C[1].Id}.u+p", _events[i++].ToString()); - Assert.AreEqual($"{m:000}: ContentRepository/Refresh/{content5C[1].Id}.u+p", _events[i++].ToString()); - m++; + Assert.AreEqual($"{m++:000}: ContentRepository/Refresh/{content5C[1].Id}.u+p", _events[i++].ToString()); + Assert.AreEqual($"{m:000}: ContentCacheRefresher/RefreshBranch/{content1.Id}", _events[i++].ToString()); // repub content1 - /* - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished/{1}", m, content1C[0].Id), _events[i++].ToString()); // repub content1.content2 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content1C[1].Id), _events[i++].ToString()); // repub content1.content3 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content1C[2].Id), _events[i++].ToString()); // repub content1.content4 - var c = ServiceContext.ContentService.GetPublishedVersion(((ContentCacheRefresher.JsonPayload)_events[i - 1].EventArgs).Id); - Assert.IsTrue(c.Published); // get the published one - Assert.AreEqual("Content4X", c.Name); // published has new name - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content1C[3].Id), _events[i++].ToString()); // repub content1.content5 - - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished/{1}", m, content2C[0].Id), _events[i++].ToString()); // repub content1.content2.content21 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content3C[0].Id), _events[i++].ToString()); // repub content1.content3.content31 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished/{1}", m, content4C[0].Id), _events[i++].ToString()); // repub content1.content4.content41 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished/{1}", m, content5C[0].Id), _events[i++].ToString()); // repub content1.content5.content51 - - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content2C[1].Id), _events[i++].ToString()); // repub content1.content2.content22 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content3C[1].Id), _events[i++].ToString()); // repub content1.content3.content32 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content4C[1].Id), _events[i++].ToString()); // repub content1.content4.content42 - Assert.AreEqual(string.Format("{0:000}: ContentCacheRefresher/RefreshPublished,Refresh/{1}", m, content5C[1].Id), _events[i].ToString()); // repub content1.content5.content52 - */ } #endregion @@ -986,7 +989,6 @@ namespace Umbraco.Tests.Integration var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -1010,7 +1012,6 @@ namespace Umbraco.Tests.Integration var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ServiceContext.ContentService.MoveToRecycleBin(content); @@ -1035,7 +1036,6 @@ namespace Umbraco.Tests.Integration var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); content.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content); @@ -1206,7 +1206,6 @@ namespace Umbraco.Tests.Integration { var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -1226,7 +1225,6 @@ namespace Umbraco.Tests.Integration { var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); content.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content); @@ -1248,11 +1246,9 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ServiceContext.ContentService.Unpublish(content1); @@ -1334,7 +1330,6 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); @@ -1356,7 +1351,6 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); content1.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content1); @@ -1382,7 +1376,6 @@ namespace Umbraco.Tests.Integration Assert.IsNotNull(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ResetEvents(); @@ -1404,11 +1397,9 @@ namespace Umbraco.Tests.Integration Assert.IsNotNull(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); var content3 = CreateContent(); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); ServiceContext.ContentService.Unpublish(content2); @@ -1429,11 +1420,9 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ResetEvents(); @@ -1453,15 +1442,12 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); var content3 = CreateContent(content2.Id); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); ServiceContext.ContentService.Unpublish(content2); @@ -1482,13 +1468,11 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); content1.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ResetEvents(); @@ -1508,17 +1492,14 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); content1.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content1); var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); var content3 = CreateContent(content2.Id); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); ServiceContext.ContentService.Unpublish(content2); @@ -1539,16 +1520,13 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ServiceContext.ContentService.Unpublish(content1); var content3 = CreateContent(); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); ResetEvents(); @@ -1568,20 +1546,16 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ServiceContext.ContentService.Unpublish(content1); var content3 = CreateContent(); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); var content4 = CreateContent(content3.Id); Assert.IsNotNull(content4); - content4.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content4); ServiceContext.ContentService.Unpublish(content3); @@ -1602,18 +1576,15 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); content2.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content2); ServiceContext.ContentService.Unpublish(content1); var content3 = CreateContent(); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); ResetEvents(); @@ -1633,22 +1604,18 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); content2.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content2); ServiceContext.ContentService.Unpublish(content1); var content3 = CreateContent(); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); var content4 = CreateContent(content3.Id); Assert.IsNotNull(content4); - content4.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content4); ServiceContext.ContentService.Unpublish(content3); @@ -1669,11 +1636,9 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ServiceContext.ContentService.Unpublish(content1); var content3 = CreateContent(); @@ -1696,11 +1661,9 @@ namespace Umbraco.Tests.Integration { var content1 = CreateContent(); Assert.IsNotNull(content1); - content1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content1); var content2 = CreateContent(content1.Id); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); content2.Properties.First().SetValue("changed"); ServiceContext.ContentService.Save(content2); @@ -1780,7 +1743,6 @@ namespace Umbraco.Tests.Integration var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ResetEvents(); @@ -1834,11 +1796,9 @@ namespace Umbraco.Tests.Integration var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); var content3 = CreateContent(content2.Id); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); ServiceContext.ContentService.Unpublish(content2); @@ -1893,7 +1853,6 @@ namespace Umbraco.Tests.Integration var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); ServiceContext.ContentService.Move(content1, content2.Id); @@ -2003,11 +1962,9 @@ namespace Umbraco.Tests.Integration var content2 = CreateContent(); Assert.IsNotNull(content2); - content2.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content2); var content3 = CreateContent(content2.Id); Assert.IsNotNull(content3); - content3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content3); ServiceContext.ContentService.Unpublish(content2); @@ -2083,7 +2040,6 @@ namespace Umbraco.Tests.Integration { var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -2103,7 +2059,6 @@ namespace Umbraco.Tests.Integration { var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); var content2 = CreateContent(); Assert.IsNotNull(content2); @@ -2126,7 +2081,6 @@ namespace Umbraco.Tests.Integration { var content = CreateBranch(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); ResetEvents(); @@ -2168,17 +2122,14 @@ namespace Umbraco.Tests.Integration { var content = CreateContent(); Assert.IsNotNull(content); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); var v1 = content.VersionId; content.Properties.First().SetValue("changed"); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); var v2 = content.VersionId; content.Properties.First().SetValue("again"); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); var v3 = content.VersionId; @@ -2187,7 +2138,7 @@ namespace Umbraco.Tests.Integration Console.WriteLine(v3); ResetEvents(); - content.CopyValues(ServiceContext.ContentService.GetVersion(v2)); + content.CopyFrom(ServiceContext.ContentService.GetVersion(v2)); ServiceContext.ContentService.Save(content); Assert.AreEqual(2, _msgCount); @@ -2212,12 +2163,10 @@ namespace Umbraco.Tests.Integration Assert.IsFalse(content.IsPropertyDirty("Published")); Assert.IsFalse(content.WasPropertyDirty("Published")); - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); Assert.IsFalse(content.IsPropertyDirty("Published")); Assert.IsTrue(content.WasPropertyDirty("Published")); // has just been published - content.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(content); Assert.IsFalse(content.IsPropertyDirty("Published")); Assert.IsFalse(content.WasPropertyDirty("Published")); // was published already diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index 044f8fa0cd..19da19bcf0 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -2,12 +2,11 @@ using LightInject; using Moq; using NUnit.Framework; -using Umbraco.Core.Cache; +using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; @@ -59,6 +58,55 @@ namespace Umbraco.Tests.Models }); } + [Test] + public void ValidateVariationTests() + { + void Assert4A(ContentVariation v, string c, string s, bool xx) + { + Assert4B(v, c, s, xx, xx, xx, xx); + } + + void Assert4B(ContentVariation v, string c, string s, bool ew, bool nn, bool en, bool nw) + { + Assert.AreEqual(ew, v.ValidateVariation(c, s, true, true, false)); + Assert.AreEqual(nn, v.ValidateVariation(c, s, false, false, false)); + Assert.AreEqual(en, v.ValidateVariation(c, s, true, false, false)); + Assert.AreEqual(nw, v.ValidateVariation(c, s, false, true, false)); + } + + // always support invariant,neutral + Assert4A(ContentVariation.Nothing, null, null, true); + + // never support culture and/or segment + Assert4A(ContentVariation.Nothing, "culture", null, false); + Assert4A(ContentVariation.Nothing, null, "segment", false); + Assert4A(ContentVariation.Nothing, "culture", "segment", false); + + // support '*' only when wildcards are supported + Assert4B(ContentVariation.Nothing, "*", null, true, false, false, true); + Assert4B(ContentVariation.Nothing, null, "*", true, false, false, true); + Assert4B(ContentVariation.Nothing, "*", "*", true, false, false, true); + + + // support invariant if not exact + Assert4B(ContentVariation.Culture, null, null, false, true, false, true); + + // support invariant if not exact, '*' when wildcards are supported + Assert4B(ContentVariation.Culture, "*", null, true, false, false, true); + Assert4B(ContentVariation.Culture, null, "*", false, false, false, true); + Assert4B(ContentVariation.Culture, "*", "*", true, false, false, true); + + // never support segment + Assert4A(ContentVariation.Culture, null, "segment", false); + Assert4A(ContentVariation.Culture, "culture", "segment", false); + Assert4A(ContentVariation.Culture, "*", "segment", false); + + Assert4B(ContentVariation.Culture, null, "*", false, false, false, true); + Assert4B(ContentVariation.Culture, "culture", "*", true, false, false, true); + + // could do the same with .Segment, and .CultureAndSegment + } + [Test] public void PropertyTests() { @@ -75,7 +123,7 @@ namespace Umbraco.Tests.Models Assert.AreEqual("a", prop.GetValue(published: true)); // illegal, 'cos non-publishing - Assert.Throws(() => prop.PublishValue()); + Assert.Throws(() => prop.PublishValues()); // change propertyType.IsPublishing = true; @@ -91,7 +139,7 @@ namespace Umbraco.Tests.Models // can publish value // and get edited and published values - prop.PublishValue(); + prop.PublishValues(); Assert.AreEqual("a", prop.GetValue()); Assert.AreEqual("a", prop.GetValue(published: true)); @@ -102,57 +150,57 @@ namespace Umbraco.Tests.Models Assert.AreEqual("a", prop.GetValue(published: true)); // can clear value - prop.ClearPublishedValue(); + prop.UnpublishValues(); Assert.AreEqual("b", prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); - // change - propertyType.Variations |= ContentVariation.CultureNeutral; + // change - now we vary by culture + propertyType.Variations |= ContentVariation.Culture; // can set value // and get values prop.SetValue("c", langFr); - Assert.AreEqual("b", prop.GetValue()); + Assert.IsNull(prop.GetValue()); // there is no invariant value anymore Assert.IsNull(prop.GetValue(published: true)); Assert.AreEqual("c", prop.GetValue(langFr)); Assert.IsNull(prop.GetValue(langFr, published: true)); // can publish value // and get edited and published values - prop.PublishValue(langFr); - Assert.AreEqual("b", prop.GetValue()); + prop.PublishValues(langFr); + Assert.IsNull(prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); Assert.AreEqual("c", prop.GetValue(langFr)); Assert.AreEqual("c", prop.GetValue(langFr, published: true)); // can clear all - prop.ClearPublishedAllValues(); - Assert.AreEqual("b", prop.GetValue()); + prop.UnpublishValues("*"); + Assert.IsNull(prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); Assert.AreEqual("c", prop.GetValue(langFr)); Assert.IsNull(prop.GetValue(langFr, published: true)); // can publish all - prop.PublishAllValues(); - Assert.AreEqual("b", prop.GetValue()); - Assert.AreEqual("b", prop.GetValue(published: true)); + prop.PublishValues("*"); + Assert.IsNull(prop.GetValue()); + Assert.IsNull(prop.GetValue(published: true)); Assert.AreEqual("c", prop.GetValue(langFr)); Assert.AreEqual("c", prop.GetValue(langFr, published: true)); // same for culture - prop.ClearPublishedCultureValues(langFr); + prop.UnpublishValues(langFr); Assert.AreEqual("c", prop.GetValue(langFr)); Assert.IsNull(prop.GetValue(langFr, published: true)); - prop.PublishCultureValues(langFr); + prop.PublishValues(langFr); Assert.AreEqual("c", prop.GetValue(langFr)); Assert.AreEqual("c", prop.GetValue(langFr, published: true)); - prop.ClearPublishedCultureValues(); - Assert.AreEqual("b", prop.GetValue()); + prop.UnpublishValues(); // does not throw, internal, content item throws + Assert.IsNull(prop.GetValue()); + Assert.IsNull(prop.GetValue(published: true)); + prop.PublishValues(); // does not throw, internal, content item throws + Assert.IsNull(prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); - prop.PublishCultureValues(); - Assert.AreEqual("b", prop.GetValue()); - Assert.AreEqual("b", prop.GetValue(published: true)); } [Test] @@ -165,23 +213,23 @@ namespace Umbraco.Tests.Models const string langUk = "en-UK"; // throws if the content type does not support the variation - Assert.Throws(() => content.SetName("name-fr", langFr)); + Assert.Throws(() => content.SetCultureName("name-fr", langFr)); // now it will work - contentType.Variations = ContentVariation.CultureNeutral; + contentType.Variations = ContentVariation.Culture; // invariant name works content.Name = "name"; - Assert.AreEqual("name", content.GetName(null)); - content.SetName("name2", null); + Assert.AreEqual("name", content.GetCultureName(null)); + content.SetCultureName("name2", null); Assert.AreEqual("name2", content.Name); - Assert.AreEqual("name2", content.GetName(null)); + Assert.AreEqual("name2", content.GetCultureName(null)); // variant names work - content.SetName("name-fr", langFr); - content.SetName("name-uk", langUk); - Assert.AreEqual("name-fr", content.GetName(langFr)); - Assert.AreEqual("name-uk", content.GetName(langUk)); + content.SetCultureName("name-fr", langFr); + content.SetCultureName("name-uk", langUk); + Assert.AreEqual("name-fr", content.GetCultureName(langFr)); + Assert.AreEqual("name-uk", content.GetCultureName(langUk)); // variant dictionary of names work Assert.AreEqual(2, content.CultureNames.Count); @@ -215,7 +263,7 @@ namespace Umbraco.Tests.Models // can publish value // and get edited and published values - Assert.IsTrue(content.TryPublishValues()); + Assert.IsTrue(content.PublishCulture()); Assert.AreEqual("a", content.GetValue("prop")); Assert.AreEqual("a", content.GetValue("prop", published: true)); @@ -226,76 +274,76 @@ namespace Umbraco.Tests.Models Assert.AreEqual("a", content.GetValue("prop", published: true)); // can clear value - content.ClearPublishedValues(); + content.UnpublishCulture(); Assert.AreEqual("b", content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); - // change - contentType.Variations |= ContentVariation.CultureNeutral; - propertyType.Variations |= ContentVariation.CultureNeutral; + // change - now we vary by culture + contentType.Variations |= ContentVariation.Culture; + propertyType.Variations |= ContentVariation.Culture; // can set value // and get values content.SetValue("prop", "c", langFr); - Assert.AreEqual("b", content.GetValue("prop")); + Assert.IsNull(content.GetValue("prop")); // there is no invariant value anymore Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.IsNull(content.GetValue("prop", langFr, published: true)); // can publish value // and get edited and published values - Assert.IsFalse(content.TryPublishValues(langFr)); // no name - content.SetName("name-fr", langFr); - Assert.IsTrue(content.TryPublishValues(langFr)); - Assert.AreEqual("b", content.GetValue("prop")); + Assert.IsFalse(content.PublishCulture(langFr)); // no name + content.SetCultureName("name-fr", langFr); + Assert.IsTrue(content.PublishCulture(langFr)); + Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); // can clear all - content.ClearAllPublishedValues(); - Assert.AreEqual("b", content.GetValue("prop")); + content.UnpublishCulture("*"); + Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.IsNull(content.GetValue("prop", langFr, published: true)); // can publish all - Assert.IsTrue(content.TryPublishAllValues()); - Assert.AreEqual("b", content.GetValue("prop")); - Assert.AreEqual("b", content.GetValue("prop", published: true)); + Assert.IsTrue(content.PublishCulture("*")); + Assert.IsNull(content.GetValue("prop")); + Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); // same for culture - content.ClearCulturePublishedValues(langFr); + content.UnpublishCulture(langFr); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.IsNull(content.GetValue("prop", langFr, published: true)); - content.PublishCultureValues(langFr); + content.PublishCulture(langFr); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); - content.ClearCulturePublishedValues(); - Assert.AreEqual("b", content.GetValue("prop")); + content.UnpublishCulture(); // clears invariant props if any + Assert.IsNull(content.GetValue("prop")); + Assert.IsNull(content.GetValue("prop", published: true)); + content.PublishCulture(); // publishes invariant props if any + Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); - content.PublishCultureValues(); - Assert.AreEqual("b", content.GetValue("prop")); - Assert.AreEqual("b", content.GetValue("prop", published: true)); var other = new Content("other", -1, contentType) { Id = 2, VersionId = 1 }; - other.SetValue("prop", "o"); + Assert.Throws(() => other.SetValue("prop", "o")); // don't even try other.SetValue("prop", "o1", langFr); // can copy other's edited value - content.CopyAllValues(other); - Assert.AreEqual("o", content.GetValue("prop")); - Assert.AreEqual("b", content.GetValue("prop", published: true)); + content.CopyFrom(other); + Assert.IsNull(content.GetValue("prop")); + Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("o1", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); // can copy self's published value - content.CopyAllValues(content); - Assert.AreEqual("b", content.GetValue("prop")); - Assert.AreEqual("b", content.GetValue("prop", published: true)); + content.CopyFrom(content); + Assert.IsNull(content.GetValue("prop")); + Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); } @@ -305,31 +353,33 @@ namespace Umbraco.Tests.Models { const string langFr = "fr-FR"; - var contentType = new ContentType(-1) { Alias = "contentType" }; - contentType.Variations |= ContentVariation.CultureNeutral; //supports both variant/invariant + // content type varies by Culture + // prop1 varies by Culture + // prop2 is invariant + + var contentType = new ContentType(-1) { Alias = "contentType" }; + contentType.Variations |= ContentVariation.Culture; + + var variantPropType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop1", Variations = ContentVariation.Culture, Mandatory = true }; + var invariantPropType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop2", Variations = ContentVariation.Nothing, Mandatory = true}; - //In real life, a property cannot be both invariant + variant and be mandatory. If this happens validation will always fail when doing TryPublishValues since the invariant value will always be empty. - //so here we are only setting properties to one or the other, not both - var variantPropType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop1", Variations = ContentVariation.CultureNeutral, Mandatory = true }; - var invariantPropType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop2", Variations = ContentVariation.InvariantNeutral, Mandatory = true}; - contentType.AddPropertyType(variantPropType); contentType.AddPropertyType(invariantPropType); var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; - content.SetName("hello", langFr); + content.SetCultureName("hello", langFr); - Assert.IsFalse(content.TryPublishValues(langFr)); //will fail because prop1 is mandatory + Assert.IsFalse(content.PublishCulture(langFr)); // fails because prop1 is mandatory content.SetValue("prop1", "a", langFr); - Assert.IsTrue(content.TryPublishValues(langFr)); + Assert.IsTrue(content.PublishCulture(langFr)); Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true)); //this will be null because we tried to publish values for a specific culture but this property is invariant Assert.IsNull(content.GetValue("prop2", published: true)); - Assert.IsFalse(content.TryPublishValues()); //will fail because prop2 is mandatory + Assert.IsFalse(content.PublishCulture()); // fails because prop2 is mandatory content.SetValue("prop2", "b"); - Assert.IsTrue(content.TryPublishValues()); + Assert.IsTrue(content.PublishCulture()); Assert.AreEqual("b", content.GetValue("prop2", published: true)); } @@ -346,42 +396,43 @@ namespace Umbraco.Tests.Models var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; - contentType.Variations |= ContentVariation.CultureNeutral; - propertyType.Variations |= ContentVariation.CultureNeutral; + // change - now we vary by culture + contentType.Variations |= ContentVariation.Culture; + propertyType.Variations |= ContentVariation.Culture; - content.SetValue("prop", "a"); + Assert.Throws(() => content.SetValue("prop", "a")); // invariant = no content.SetValue("prop", "a-fr", langFr); content.SetValue("prop", "a-uk", langUk); content.SetValue("prop", "a-es", langEs); // cannot publish without a name - Assert.IsFalse(content.TryPublishValues(langFr)); + Assert.IsFalse(content.PublishCulture(langFr)); // works with a name // and then FR is available, and published - content.SetName("name-fr", langFr); - Assert.IsTrue(content.TryPublishValues(langFr)); + content.SetCultureName("name-fr", langFr); + Assert.IsTrue(content.PublishCulture(langFr)); // now UK is available too - content.SetName("name-uk", langUk); + content.SetCultureName("name-uk", langUk); // test available, published Assert.IsTrue(content.IsCultureAvailable(langFr)); Assert.IsTrue(content.IsCulturePublished(langFr)); Assert.AreEqual("name-fr", content.GetPublishName(langFr)); - Assert.AreNotEqual(DateTime.MinValue, content.GetCulturePublishDate(langFr)); + Assert.AreNotEqual(DateTime.MinValue, content.GetPublishDate(langFr)); Assert.IsFalse(content.IsCultureEdited(langFr)); // once published, edited is *wrong* until saved Assert.IsTrue(content.IsCultureAvailable(langUk)); Assert.IsFalse(content.IsCulturePublished(langUk)); Assert.IsNull(content.GetPublishName(langUk)); - Assert.Throws(() => content.GetCulturePublishDate(langUk)); // not published! + Assert.IsNull(content.GetPublishDate(langUk)); // not published Assert.IsTrue(content.IsCultureEdited(langEs)); // not published, so... edited Assert.IsFalse(content.IsCultureAvailable(langEs)); Assert.IsFalse(content.IsCulturePublished(langEs)); Assert.IsNull(content.GetPublishName(langEs)); - Assert.Throws(() => content.GetCulturePublishDate(langEs)); // not published! + Assert.IsNull(content.GetPublishDate(langEs)); // not published! Assert.IsTrue(content.IsCultureEdited(langEs)); // not published, so... edited // cannot test IsCultureEdited here - as that requires the content service and repository @@ -432,7 +483,7 @@ namespace Umbraco.Tests.Models Assert.IsFalse(prop.IsValid()); // can publish, even though invalid - prop.PublishValue(); + prop.PublishValues(); } } } diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs index 0afc581f83..21a75b2e24 100644 --- a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs +++ b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs @@ -187,13 +187,13 @@ namespace Umbraco.Tests.Persistence.NPocoTests contentTypeService.Save(contentType); var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content1.TryPublishValues(); + content1.PublishCulture(); contentService.SaveAndPublish(content1); id2 = content1.Id; var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.TryPublishValues(); + content2.PublishCulture(); contentService.SaveAndPublish(content2); id3 = content2.Id; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 983cccbdb9..17232d89ef 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Persistence.Repositories // publish = new edit version content1.SetValue("title", "title"); - ((Content)content1).TryPublishValues(); + ((Content)content1).PublishCulture(); ((Content)content1).PublishedState = PublishedState.Publishing; repository.Save(content1); @@ -202,7 +202,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(false, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // publish = version - ((Content)content1).TryPublishValues(); + ((Content)content1).PublishCulture(); ((Content)content1).PublishedState = PublishedState.Publishing; repository.Save(content1); @@ -238,7 +238,7 @@ namespace Umbraco.Tests.Persistence.Repositories // publish = new version content1.Name = "name-4"; content1.SetValue("title", "title-4"); - ((Content)content1).TryPublishValues(); + ((Content)content1).PublishCulture(); ((Content)content1).PublishedState = PublishedState.Publishing; repository.Save(content1); @@ -648,7 +648,7 @@ namespace Umbraco.Tests.Persistence.Repositories // publish them all foreach (var content in result) { - content.TryPublishValues(); + content.PublishCulture(); repository.Save(content); } @@ -683,22 +683,24 @@ namespace Umbraco.Tests.Persistence.Repositories [Test] public void GetPagedResultsByQuery_With_Variant_Names() { - //2x content types, one invariant, one variant - + // one invariant content type named "umbInvariantTextPage" + // var invariantCt = MockedContentTypes.CreateSimpleContentType("umbInvariantTextpage", "Invariant Textpage"); - invariantCt.Variations = ContentVariation.InvariantNeutral; - foreach (var p in invariantCt.PropertyTypes) p.Variations = ContentVariation.InvariantNeutral; + invariantCt.Variations = ContentVariation.Nothing; + foreach (var p in invariantCt.PropertyTypes) p.Variations = ContentVariation.Nothing; ServiceContext.FileService.SaveTemplate(invariantCt.DefaultTemplate); // else, FK violation on contentType! ServiceContext.ContentTypeService.Save(invariantCt); + // one variant (by culture) content type named "umbVariantTextPage" + // with properties, every 2nd one being variant (by culture), the other being invariant + // var variantCt = MockedContentTypes.CreateSimpleContentType("umbVariantTextpage", "Variant Textpage"); - variantCt.Variations = ContentVariation.CultureNeutral; + variantCt.Variations = ContentVariation.Culture; var propTypes = variantCt.PropertyTypes.ToList(); for (var i = 0; i < propTypes.Count; i++) { var p = propTypes[i]; - //every 2nd one is variant - p.Variations = i % 2 == 0 ? ContentVariation.CultureNeutral : ContentVariation.InvariantNeutral; + p.Variations = i % 2 == 0 ? ContentVariation.Culture : ContentVariation.Nothing; } ServiceContext.FileService.SaveTemplate(variantCt.DefaultTemplate); // else, FK violation on contentType! ServiceContext.ContentTypeService.Save(variantCt); @@ -711,6 +713,8 @@ namespace Umbraco.Tests.Persistence.Repositories var root = MockedContent.CreateSimpleContent(invariantCt); ServiceContext.ContentService.Save(root); + var children = new List(); + for (var i = 0; i < 25; i++) { var isInvariant = i % 2 == 0; @@ -732,20 +736,30 @@ namespace Umbraco.Tests.Persistence.Repositories } ServiceContext.ContentService.Save(child); + children.Add(child); } + var child1 = children[1]; + Assert.IsTrue(child1.ContentType.VariesByCulture()); + Assert.IsTrue(child1.Name.StartsWith("VAR")); + Assert.IsTrue(child1.GetCultureName("en-US").StartsWith("VAR")); + var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) { var repository = CreateRepository((IScopeAccessor)provider, out _); - var query = scope.SqlContext.Query().Where(x => x.ParentId == root.Id); + var child = repository.Get(children[1].Id); // 1 is variant + Assert.IsTrue(child.ContentType.VariesByCulture()); + Assert.IsTrue(child.Name.StartsWith("VAR")); + Assert.IsTrue(child.GetCultureName("en-US").StartsWith("VAR")); try { scope.Database.AsUmbracoDatabase().EnableSqlTrace = true; scope.Database.AsUmbracoDatabase().EnableSqlCount = true; + var query = scope.SqlContext.Query().Where(x => x.ParentId == root.Id); var result = repository.GetPage(query, 0, 20, out var totalRecords, "UpdateDate", Direction.Ascending, true); Assert.AreEqual(25, totalRecords); @@ -761,7 +775,7 @@ namespace Umbraco.Tests.Persistence.Repositories foreach (var p in r.Properties) { //ensure there is a value for the correct variant/invariant property - var value = p.GetValue(p.PropertyType.Variations.Has(ContentVariation.InvariantNeutral) ? null : "en-US"); + var value = p.GetValue(p.PropertyType.Variations.VariesByNothing() ? null : "en-US"); Assert.IsNotNull(value); } } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 2703cd448d..492f1f7dc0 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -84,8 +84,8 @@ namespace Umbraco.Tests.PublishedContent dataType }; - var propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral }; - var contentType = new ContentType(-1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral }; + var propertyType = new PropertyType("Umbraco.Void.Editor", ValueStorageType.Nvarchar) { Alias = "prop", DataTypeId = 3, Variations = ContentVariation.Culture }; + var contentType = new ContentType(-1) { Id = 2, Alias = "alias-ct", Variations = ContentVariation.Culture }; contentType.AddPropertyType(propertyType); var contentTypes = new[] @@ -195,16 +195,16 @@ namespace Umbraco.Tests.PublishedContent // but, // if the content type / property type does not vary, then it's all invariant again // modify the content type and property type, notify the snapshot service - contentType.Variations = ContentVariation.InvariantNeutral; - propertyType.Variations = ContentVariation.InvariantNeutral; + contentType.Variations = ContentVariation.Nothing; + propertyType.Variations = ContentVariation.Nothing; snapshotService.Notify(new[] { new ContentTypeCacheRefresher.JsonPayload("IContentType", publishedContent.ContentType.Id, ContentTypeChangeTypes.RefreshMain) }); // get a new snapshot (nothing changed in the old one), get the published content again var anotherSnapshot = snapshotService.CreatePublishedSnapshot(previewToken: null); var againContent = anotherSnapshot.Content.GetById(1); - Assert.AreEqual(ContentVariation.InvariantNeutral, againContent.ContentType.Variations); - Assert.AreEqual(ContentVariation.InvariantNeutral, againContent.ContentType.GetPropertyType("prop").Variations); + Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.Variations); + Assert.AreEqual(ContentVariation.Nothing, againContent.ContentType.GetPropertyType("prop").Variations); // now, "no culture" means "invariant" Assert.AreEqual("It Works1!", againContent.Name); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index 4f895243b2..a640423515 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -98,7 +98,7 @@ namespace Umbraco.Tests.PublishedContent var doc = GetContent(true, 1); //change a doc type alias var c = (TestPublishedContent) doc.Children.ElementAt(0); - c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.InvariantNeutral); + c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var dt = doc.ChildrenAsTable(Current.Services, "Child"); @@ -175,7 +175,7 @@ namespace Umbraco.Tests.PublishedContent new RawValueProperty(factory.CreatePropertyType("property3", 1), d, "value" + (indexVals + 2))); } - d.ContentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.InvariantNeutral); + d.ContentType = new PublishedContentType(22, contentTypeAlias, PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); return d; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index 66f7871a4b..df7e1d9d09 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -81,7 +81,7 @@ namespace Umbraco.Tests.PublishedContent pc.Setup(content => content.Path).Returns("-1,1"); pc.Setup(content => content.Parent).Returns(() => null); pc.Setup(content => content.Properties).Returns(new Collection()); - pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.InvariantNeutral)); + pc.Setup(content => content.ContentType).Returns(new PublishedContentType(22, "anything", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing)); return pc; } } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 4f63533693..756b775e46 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -330,11 +330,11 @@ namespace Umbraco.Tests.PublishedContent } public AutoPublishedContentType(int id, string alias, IEnumerable propertyTypes) - : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.InvariantNeutral) + : base(id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, ContentVariation.Nothing) { } public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) - : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.InvariantNeutral) + : base(id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, ContentVariation.Nothing) { } public override PublishedPropertyType GetPropertyType(string alias) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs index 032f15f5d1..4059cb1858 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs @@ -22,12 +22,12 @@ namespace Umbraco.Tests.Routing var properties = new[] { - new PublishedPropertyType("umbracoUrlAlias", Constants.DataTypes.Textbox, false, ContentVariation.InvariantNeutral, + new PublishedPropertyType("umbracoUrlAlias", Constants.DataTypes.Textbox, false, ContentVariation.Nothing, new PropertyValueConverterCollection(Enumerable.Empty()), Mock.Of(), Mock.Of()), }; - _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.InvariantNeutral); + _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); } protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs index 6b5eedc743..0c1f89f430 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -20,12 +20,12 @@ namespace Umbraco.Tests.Routing var properties = new[] { - new PublishedPropertyType("umbracoUrlAlias", Constants.DataTypes.Textbox, false, ContentVariation.InvariantNeutral, + new PublishedPropertyType("umbracoUrlAlias", Constants.DataTypes.Textbox, false, ContentVariation.Nothing, new PropertyValueConverterCollection(Enumerable.Empty()), Mock.Of(), Mock.Of()), }; - _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.InvariantNeutral); + _publishedContentType = new PublishedContentType(0, "Doc", PublishedItemType.Content, Enumerable.Empty(), properties, ContentVariation.Nothing); } protected override PublishedContentType GetPublishedContentTypeByAlias(string alias) diff --git a/src/Umbraco.Tests/Routing/UrlProviderTests.cs b/src/Umbraco.Tests/Routing/UrlProviderTests.cs index d3cd25ae92..2f1e4e3476 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderTests.cs @@ -167,8 +167,7 @@ namespace Umbraco.Tests.Routing var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), - ContentVariation.CultureNeutral); + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); var publishedContentCache = new Mock(); @@ -216,8 +215,7 @@ namespace Umbraco.Tests.Routing var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), - ContentVariation.CultureNeutral); + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); var publishedContentCache = new Mock(); @@ -274,8 +272,7 @@ namespace Umbraco.Tests.Routing var requestMock = Mock.Get(_umbracoSettings.RequestHandler); requestMock.Setup(x => x.UseDomainPrefixes).Returns(false); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), - ContentVariation.CultureNeutral); + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Culture); var publishedContent = new TestPublishedContent(contentType, 1234, Guid.NewGuid(), new Dictionary(), false); var publishedContentCache = new Mock(); diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index 627d95ea29..c6bd0fdb88 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -186,7 +186,7 @@ namespace Umbraco.Tests.Routing { new DefaultUrlProvider(settings.RequestHandler, Logger, globalSettings.Object, new SiteDomainHelper()) }, globalSettings:globalSettings.Object); - + SetDomains1(); var currentUri = new Uri(currentUrl); @@ -415,7 +415,9 @@ namespace Umbraco.Tests.Routing var result = umbracoContext.UrlProvider.GetOtherUrls(100111).ToArray(); - Assert.AreEqual(2, result.Count()); + foreach (var x in result) Console.WriteLine(x); + + Assert.AreEqual(2, result.Length); Assert.IsTrue(result.Contains("http://domain1a.com/en/1001-1-1/")); Assert.IsTrue(result.Contains("http://domain1b.com/en/1001-1-1/")); } diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index d59fe0bb51..cf3285cd7e 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -152,7 +152,6 @@ namespace Umbraco.Tests.Scoping using (var scope = ScopeProvider.CreateScope()) { - item.TryPublishValues(); Current.Services.ContentService.SaveAndPublish(item); scope.Complete(); } @@ -167,7 +166,6 @@ namespace Umbraco.Tests.Scoping using (var scope = ScopeProvider.CreateScope()) { item.Name = "changed"; - item.TryPublishValues(); Current.Services.ContentService.SaveAndPublish(item); if (complete) diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 9de2012dce..5f4e653735 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -123,10 +123,8 @@ namespace Umbraco.Tests.Scoping using (var scope = ScopeProvider.CreateScope()) { - item.TryPublishValues(); Current.Services.ContentService.SaveAndPublish(item); // should create an xml clone item.Name = "changed"; - item.TryPublishValues(); Current.Services.ContentService.SaveAndPublish(item); // should re-use the xml clone // this should never change @@ -230,13 +228,11 @@ namespace Umbraco.Tests.Scoping using (var scope = ScopeProvider.CreateScope()) { - item.TryPublishValues(); Current.Services.ContentService.SaveAndPublish(item); for (var i = 0; i < count; i++) { var temp = new Content("content_" + i, -1, contentType); - temp.TryPublishValues(); Current.Services.ContentService.SaveAndPublish(temp); ids[i] = temp.Id; } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 50a535d029..d5003674af 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -209,16 +209,16 @@ namespace Umbraco.Tests.Services { var contentService = ServiceContext.ContentService; var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1); - root.TryPublishValues(); + root.PublishCulture(); Assert.IsTrue(contentService.SaveAndPublish(root).Success); var content = contentService.CreateAndSave("Test", -1, "umbTextpage", 0); - content.TryPublishValues(); + content.PublishCulture(); Assert.IsTrue(contentService.SaveAndPublish(content).Success); var hierarchy = CreateContentHierarchy().OrderBy(x => x.Level).ToArray(); contentService.Save(hierarchy, 0); foreach (var c in hierarchy) { - c.TryPublishValues(); + c.PublishCulture(); Assert.IsTrue(contentService.SaveAndPublish(c).Success); } @@ -268,7 +268,6 @@ namespace Umbraco.Tests.Services // Assert - content.TryPublishValues(); Assert.IsTrue(contentService.SaveAndPublish(content).Success); } @@ -283,7 +282,6 @@ namespace Umbraco.Tests.Services for (var i = 0; i < 20; i++) { content.SetValue("bodyText", "hello world " + Guid.NewGuid()); - content.TryPublishValues(); contentService.SaveAndPublish(content); } @@ -422,12 +420,10 @@ namespace Umbraco.Tests.Services var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.TryPublishValues(); contentService.SaveAndPublish(content2); // verify @@ -463,7 +459,7 @@ namespace Umbraco.Tests.Services allTags = tagService.GetAllContentTags(); Assert.AreEqual(4, allTags.Count()); - content1.TryPublishValues(); + content1.PublishCulture(); contentService.SaveAndPublish(content1); Assert.IsTrue(content1.Published); @@ -493,12 +489,10 @@ namespace Umbraco.Tests.Services var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" }); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.TryPublishValues(); contentService.SaveAndPublish(content2); // verify @@ -534,9 +528,9 @@ namespace Umbraco.Tests.Services allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content1.TryPublishValues(); + content1.PublishCulture(); contentService.SaveAndPublish(content1); - content2.TryPublishValues(); + content2.PublishCulture(); contentService.SaveAndPublish(content2); // tags are back @@ -565,12 +559,12 @@ namespace Umbraco.Tests.Services var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "plus" }); - content1.TryPublishValues(); + content1.PublishCulture(); contentService.SaveAndPublish(content1); var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1.Id); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.TryPublishValues(); + content2.PublishCulture(); contentService.SaveAndPublish(content2); // verify @@ -605,7 +599,7 @@ namespace Umbraco.Tests.Services allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content1.TryPublishValues(); + content1.PublishCulture(); contentService.SaveAndPublish(content1); Assert.IsTrue(content1.Published); @@ -643,12 +637,10 @@ namespace Umbraco.Tests.Services var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" }); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.TryPublishValues(); contentService.SaveAndPublish(content2); contentService.Unpublish(content1); @@ -664,7 +656,7 @@ namespace Umbraco.Tests.Services var allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content2.TryPublishValues(); + content2.PublishCulture(); contentService.SaveAndPublish(content2); tags = tagService.GetTagsForEntity(content2.Id); @@ -690,12 +682,12 @@ namespace Umbraco.Tests.Services var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags", "bam" }); - content1.TryPublishValues(); + content1.PublishCulture(); contentService.SaveAndPublish(content1); var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", content1); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.TryPublishValues(); + content2.PublishCulture(); contentService.SaveAndPublish(content2); contentService.Unpublish(content1); @@ -711,7 +703,7 @@ namespace Umbraco.Tests.Services var allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content1.TryPublishValues(); + content1.PublishCulture(); contentService.SaveAndPublish(content1); tags = tagService.GetTagsForEntity(content2.Id); @@ -795,7 +787,6 @@ namespace Umbraco.Tests.Services // create a content with tags and publish var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1); content.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content.TryPublishValues(); contentService.SaveAndPublish(content); // edit tags and save @@ -835,7 +826,6 @@ namespace Umbraco.Tests.Services // Act content.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content.TryPublishValues(); contentService.SaveAndPublish(content); // Assert @@ -866,12 +856,10 @@ namespace Umbraco.Tests.Services contentTypeService.Save(contentType); var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1); content.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content.TryPublishValues(); contentService.SaveAndPublish(content); // Act content.AssignTags("tags", new[] { "another", "world" }, merge: true); - content.TryPublishValues(); contentService.SaveAndPublish(content); // Assert @@ -902,12 +890,10 @@ namespace Umbraco.Tests.Services contentTypeService.Save(contentType); var content = MockedContent.CreateSimpleContent(contentType, "Tagged content", -1); content.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content.TryPublishValues(); contentService.SaveAndPublish(content); // Act content.RemoveTags("tags", new[] { "some", "world" }); - content.TryPublishValues(); contentService.SaveAndPublish(content); // Assert @@ -1090,7 +1076,6 @@ namespace Umbraco.Tests.Services var parent = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 2); Assert.IsFalse(parent.Published); - parent.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(parent); // publishing parent, so Text Page 2 can be updated. var content = contentService.GetById(NodeDto.NodeIdSeed + 4); @@ -1103,7 +1088,6 @@ namespace Umbraco.Tests.Services content.Name = "Text Page 2 Updated"; content.SetValue("author", "Jane Doe"); - content.TryPublishValues(); contentService.SaveAndPublish(content); // publishes the current version, creates a version var version2 = content.VersionId; @@ -1111,7 +1095,6 @@ namespace Umbraco.Tests.Services content.Name = "Text Page 2 ReUpdated"; content.SetValue("author", "Bob Hope"); - content.TryPublishValues(); contentService.SaveAndPublish(content); // publishes again, creates a version var version3 = content.VersionId; @@ -1178,11 +1161,9 @@ namespace Umbraco.Tests.Services // Arrange var contentService = ServiceContext.ContentService; var root = contentService.GetById(NodeDto.NodeIdSeed + 2); - root.TryPublishValues(); contentService.SaveAndPublish(root); var content = contentService.GetById(NodeDto.NodeIdSeed + 4); content.ExpireDate = DateTime.Now.AddSeconds(1); - content.TryPublishValues(); contentService.SaveAndPublish(content); // Act @@ -1232,8 +1213,7 @@ namespace Umbraco.Tests.Services // Arrange var contentService = ServiceContext.ContentService; var content = contentService.GetById(NodeDto.NodeIdSeed + 2); - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + var published = contentService.SaveAndPublish(content, userId: 0); using (var scope = ScopeProvider.CreateScope()) { @@ -1255,15 +1235,30 @@ namespace Umbraco.Tests.Services } [Test] - public void Can_Publish_Content() + public void Can_Publish_Content_1() { // Arrange var contentService = ServiceContext.ContentService; var content = contentService.GetById(NodeDto.NodeIdSeed + 2); // Act - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + content.PublishCulture(); + var published = contentService.SavePublishing(content, 0); + + // Assert + Assert.That(published.Success, Is.True); + Assert.That(content.Published, Is.True); + } + + [Test] + public void Can_Publish_Content_2() + { + // Arrange + var contentService = ServiceContext.ContentService; + var content = contentService.GetById(NodeDto.NodeIdSeed + 2); + + // Act + var published = contentService.SaveAndPublish(content, userId: 0); // Assert Assert.That(published.Success, Is.True); @@ -1276,7 +1271,7 @@ namespace Umbraco.Tests.Services // Arrange var contentService = ServiceContext.ContentService; var parent = contentService.Create("parent", -1, "umbTextpage"); - parent.TryPublishValues(); + contentService.SaveAndPublish(parent); var content = contentService.Create("child", parent, "umbTextpage"); contentService.Save(content); @@ -1302,8 +1297,8 @@ namespace Umbraco.Tests.Services Assert.AreEqual("Home", content.Name); content.Name = "foo"; - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + content.PublishCulture(); + var published = contentService.SavePublishing(content, 0); Assert.That(published.Success, Is.True); Assert.That(content.Published, Is.True); @@ -1343,17 +1338,14 @@ namespace Umbraco.Tests.Services var parent = contentService.GetById(parentId); - var parentCanPublishValues = parent.TryPublishValues(); var parentPublished = contentService.SaveAndPublish(parent); // parent can publish values // and therefore can be published - Assert.IsTrue(parentCanPublishValues); Assert.IsTrue(parentPublished.Success); Assert.IsTrue(parent.Published); - var contentCanPublishValues = content.TryPublishValues(); - var contentPublished = contentService.SaveAndPublish(content); + var contentCanPublishValues = content.PublishCulture(); // content cannot publish values because they are invalid Assert.IsFalse(contentCanPublishValues); @@ -1361,8 +1353,9 @@ namespace Umbraco.Tests.Services // and therefore cannot be published, // because it did not have a published version at all + var contentPublished = contentService.SaveAndPublish(content); Assert.IsFalse(contentPublished.Success); - Assert.AreEqual(PublishResultType.FailedNoPublishedValues, contentPublished.Result); + Assert.AreEqual(PublishResultType.FailedContentInvalid, contentPublished.Result); Assert.IsFalse(content.Published); } @@ -1428,12 +1421,12 @@ namespace Umbraco.Tests.Services contentService.Save(content); var parent = contentService.GetById(NodeDto.NodeIdSeed + 2); - parent.TryPublishValues(); - var parentPublished = contentService.SaveAndPublish(parent, 0);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' + parent.PublishCulture(); + var parentPublished = contentService.SavePublishing(parent, 0);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' // Act - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + content.PublishCulture(); + var published = contentService.SavePublishing(content, 0); // Assert Assert.That(parentPublished.Success, Is.True); @@ -1451,12 +1444,12 @@ namespace Umbraco.Tests.Services contentService.Save(content, 0); var parent = contentService.GetById(NodeDto.NodeIdSeed + 2); - parent.TryPublishValues(); - var parentPublished = contentService.SaveAndPublish(parent, 0);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' + parent.PublishCulture(); + var parentPublished = contentService.SavePublishing(parent, 0);//Publish root Home node to enable publishing of 'NodeDto.NodeIdSeed + 3' // Act - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + content.PublishCulture(); + var published = contentService.SavePublishing(content, 0); // Assert Assert.That(parentPublished.Success, Is.True); @@ -1488,8 +1481,8 @@ namespace Umbraco.Tests.Services var content = contentService.GetById(NodeDto.NodeIdSeed + 5); // Act - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + content.PublishCulture(); + var published = contentService.SavePublishing(content, 0); // Assert Assert.That(published.Success, Is.False); @@ -1506,8 +1499,8 @@ namespace Umbraco.Tests.Services content.SetValue("author", "Barack Obama"); // Act - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + content.PublishCulture(); + var published = contentService.SavePublishing(content, 0); // Assert Assert.That(content.HasIdentity, Is.True); @@ -1531,15 +1524,15 @@ namespace Umbraco.Tests.Services content.SetValue("author", "Barack Obama"); // Act - content.TryPublishValues(); - var published = contentService.SaveAndPublish(content, 0); + content.PublishCulture(); + var published = contentService.SavePublishing(content, 0); var childContent = contentService.Create("Child", content.Id, "umbTextpage", 0); // Reset all identity properties childContent.Id = 0; childContent.Path = null; ((Content)childContent).ResetIdentity(); - childContent.TryPublishValues(); - var childPublished = contentService.SaveAndPublish(childContent, 0); + childContent.PublishCulture(); + var childPublished = contentService.SavePublishing(childContent, 0); // Assert Assert.That(content.HasIdentity, Is.True); @@ -1557,12 +1550,10 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var root = contentService.GetById(NodeDto.NodeIdSeed + 2); - root.TryPublishValues(); var rootPublished = contentService.SaveAndPublish(root); var content = contentService.GetById(NodeDto.NodeIdSeed + 4); content.Properties["title"].SetValue(content.Properties["title"].GetValue() + " Published"); - content.TryPublishValues(); var contentPublished = contentService.SaveAndPublish(content); var publishedVersion = content.VersionId; @@ -1862,14 +1853,14 @@ namespace Umbraco.Tests.Services content1.PropertyValues(obj); content1.ResetDirtyProperties(false); ServiceContext.ContentService.Save(content1, 0); - content1.TryPublishValues(); - Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content1, 0).Success); + content1.PublishCulture(); + Assert.IsTrue(ServiceContext.ContentService.SavePublishing(content1, 0).Success); var content2 = MockedContent.CreateBasicContent(contentType); content2.PropertyValues(obj); content2.ResetDirtyProperties(false); ServiceContext.ContentService.Save(content2, 0); - content2.TryPublishValues(); - Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content2, 0).Success); + content2.PublishCulture(); + Assert.IsTrue(ServiceContext.ContentService.SavePublishing(content2, 0).Success); var editorGroup = ServiceContext.UserService.GetUserGroupByAlias("editor"); editorGroup.StartContentId = content1.Id; @@ -2026,7 +2017,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual(0, contentTags.Length); // publish - content.TryPublishValues(); contentService.SaveAndPublish(content); // now tags have been set (published) @@ -2043,7 +2033,6 @@ namespace Umbraco.Tests.Services Assert.AreEqual(0, copiedTags.Length); // publish - copy.TryPublishValues(); contentService.SaveAndPublish(copy); // now tags have been set (published) @@ -2062,7 +2051,6 @@ namespace Umbraco.Tests.Services var parent = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 2); Assert.IsFalse(parent.Published); - parent.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(parent); // publishing parent, so Text Page 2 can be updated. var content = contentService.GetById(NodeDto.NodeIdSeed + 4); @@ -2079,7 +2067,6 @@ namespace Umbraco.Tests.Services // non published = edited Assert.IsTrue(content.Edited); - content.TryPublishValues(); contentService.SaveAndPublish(content); // new version var version2 = content.VersionId; Assert.AreNotEqual(version1, version2); @@ -2104,7 +2091,6 @@ namespace Umbraco.Tests.Services content.Name = "Text Page 2 ReReUpdated"; - content.TryPublishValues(); contentService.SaveAndPublish(content); // new version var version3 = content.VersionId; Assert.AreNotEqual(version2, version3); @@ -2124,7 +2110,7 @@ namespace Umbraco.Tests.Services // rollback all values to version1 var rollback = contentService.GetById(NodeDto.NodeIdSeed + 4); var rollto = contentService.GetVersion(version1); - rollback.CopyValues(rollto); + rollback.CopyFrom(rollto); rollback.Name = rollto.Name; // must do it explicitely contentService.Save(rollback); @@ -2146,7 +2132,7 @@ namespace Umbraco.Tests.Services // special because... current has edits... this really is equivalent to rolling back to version2 var rollback2 = contentService.GetById(NodeDto.NodeIdSeed + 4); var rollto2 = contentService.GetVersion(version3); - rollback2.CopyValues(rollto2); + rollback2.CopyFrom(rollto2); rollback2.Name = rollto2.PublishName; // must do it explicitely AND must pick the publish one! contentService.Save(rollback2); @@ -2160,7 +2146,6 @@ namespace Umbraco.Tests.Services content = contentService.GetById(content.Id); Assert.AreEqual("Text Page 2 ReReUpdated", content.Name); Assert.AreEqual("Jane Doe", content.GetValue("author")); - content.TryPublishValues(); contentService.SaveAndPublish(content); Assert.IsFalse(content.Edited); content.Name = "Xxx"; @@ -2168,7 +2153,7 @@ namespace Umbraco.Tests.Services contentService.Save(content); Assert.IsTrue(content.Edited); rollto = contentService.GetVersion(content.VersionId); - content.CopyValues(rollto); + content.CopyFrom(rollto); content.Name = rollto.PublishName; // must do it explicitely AND must pick the publish one! contentService.Save(content); Assert.IsFalse(content.Edited); @@ -2278,7 +2263,6 @@ namespace Umbraco.Tests.Services Assert.IsFalse(scope.Database.Exists(content.Id)); } - content.TryPublishValues(); contentService.SaveAndPublish(content); using (var scope = ScopeProvider.CreateScope()) @@ -2420,7 +2404,6 @@ namespace Umbraco.Tests.Services // becomes Published, !Edited // creates a new version // can get published property values - content.TryPublishValues(); contentService.SaveAndPublish(content); Assert.IsTrue(content.Published); @@ -2507,7 +2490,7 @@ namespace Umbraco.Tests.Services var languageService = ServiceContext.LocalizationService; var langUk = new Language("en-UK") { IsDefaultVariantLanguage = true }; - var langFr = new Language("fr-FR"); + var langFr = new Language("fr-FR"); languageService.Save(langFr); languageService.Save(langUk); @@ -2515,15 +2498,15 @@ namespace Umbraco.Tests.Services var contentTypeService = ServiceContext.ContentTypeService; var contentType = contentTypeService.Get("umbTextpage"); - contentType.Variations = ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral; - contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar, "prop") { Variations = ContentVariation.CultureNeutral }); + contentType.Variations = ContentVariation.Culture; + contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar, "prop") { Variations = ContentVariation.Culture }); contentTypeService.Save(contentType); var contentService = ServiceContext.ContentService; var content = new Content(null, -1, contentType); - content.SetName("name-us", langUk.IsoCode); - content.SetName("name-fr", langFr.IsoCode); + content.SetCultureName("name-us", langUk.IsoCode); + content.SetCultureName("name-fr", langFr.IsoCode); contentService.Save(content); //the name will be set to the default culture variant name @@ -2550,26 +2533,26 @@ namespace Umbraco.Tests.Services var contentTypeService = ServiceContext.ContentTypeService; var contentType = contentTypeService.Get("umbTextpage"); - contentType.Variations = ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral; - contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar, "prop") { Variations = ContentVariation.CultureNeutral }); + contentType.Variations = ContentVariation.Culture; contentTypeService.Save(contentType); var contentService = ServiceContext.ContentService; var content = new Content(null, -1, contentType); - content.SetName("root", langUk.IsoCode); + content.SetCultureName("root", langUk.IsoCode); contentService.Save(content); for (var i = 0; i < 5; i++) { var child = new Content(null, content, contentType); - child.SetName("child", langUk.IsoCode); + child.SetCultureName("child", langUk.IsoCode); contentService.Save(child); - Assert.AreEqual("child" + (i == 0 ? "" : " (" + (i).ToString() + ")"), child.GetName(langUk.IsoCode)); + + Assert.AreEqual("child" + (i == 0 ? "" : " (" + i + ")"), child.GetCultureName(langUk.IsoCode)); //Save it again to ensure that the unique check is not performed again against it's own name contentService.Save(child); - Assert.AreEqual("child" + (i == 0 ? "" : " (" + (i).ToString() + ")"), child.GetName(langUk.IsoCode)); + Assert.AreEqual("child" + (i == 0 ? "" : " (" + i + ")"), child.GetCultureName(langUk.IsoCode)); } } @@ -2578,6 +2561,8 @@ namespace Umbraco.Tests.Services { var languageService = ServiceContext.LocalizationService; + //var langFr = new Language("fr-FR") { IsDefaultVariantLanguage = true }; + var langXx = new Language("pt-PT") { IsDefaultVariantLanguage = true }; var langFr = new Language("fr-FR"); var langUk = new Language("en-UK"); var langDe = new Language("de-DE"); @@ -2588,31 +2573,29 @@ namespace Umbraco.Tests.Services var contentTypeService = ServiceContext.ContentTypeService; - // fixme - // contentType.Variations is InvariantNeutral | CultureNeutral - // propertyType.Variations can only be a subset of contentType.Variations - ie cannot *add* anything - // (at least, we should validate this) - // but then, - // if the contentType supports InvariantNeutral | CultureNeutral, - // the propertyType should support InvariantNeutral, or both, but not solely CultureNeutral? - // but does this mean that CultureNeutral implies InvariantNeutral? - // can a contentType *not* support InvariantNeutral? - var contentType = contentTypeService.Get("umbTextpage"); - contentType.Variations = ContentVariation.InvariantNeutral | ContentVariation.CultureNeutral; - contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar, "prop") { Variations = ContentVariation.CultureNeutral }); + contentType.Variations = ContentVariation.Culture; + contentType.AddPropertyType(new PropertyType(Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Nvarchar, "prop") { Variations = ContentVariation.Culture }); + // fixme add test w/ an invariant prop contentTypeService.Save(contentType); var contentService = ServiceContext.ContentService; - var content = contentService.Create("Home US", - 1, "umbTextpage"); + var content = contentService.Create("Home US", -1, "umbTextpage"); + + // creating content with a name but no culture - will set the invariant name + // but, because that content is variant, as soon as we save, we'll need to + // replace the invariant name with whatever we have in cultures - always + // + // in fact, that would throw, because there is no name + //contentService.Save(content); // act content.SetValue("author", "Barack Obama"); content.SetValue("prop", "value-fr1", langFr.IsoCode); content.SetValue("prop", "value-uk1", langUk.IsoCode); - content.SetName("name-fr", langFr.IsoCode); - content.SetName("name-uk", langUk.IsoCode); + content.SetCultureName("name-fr", langFr.IsoCode); // and then we can save + content.SetCultureName("name-uk", langUk.IsoCode); contentService.Save(content); // content has been saved, @@ -2620,9 +2603,9 @@ namespace Umbraco.Tests.Services var content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US", content2.Name); - Assert.AreEqual("name-fr", content2.GetName(langFr.IsoCode)); - Assert.AreEqual("name-uk", content2.GetName(langUk.IsoCode)); + Assert.AreEqual("name-fr", content2.Name); // got the default culture name when saved + Assert.AreEqual("name-fr", content2.GetCultureName(langFr.IsoCode)); + Assert.AreEqual("name-uk", content2.GetCultureName(langUk.IsoCode)); Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.IsoCode)); Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode)); @@ -2647,9 +2630,9 @@ namespace Umbraco.Tests.Services // act - content.TryPublishValues(langFr.IsoCode); - content.TryPublishValues(langUk.IsoCode); - contentService.SaveAndPublish(content); + content.PublishCulture(langFr.IsoCode); + content.PublishCulture(langUk.IsoCode); + contentService.SavePublishing(content); // both FR and UK have been published, // and content has been published, @@ -2657,9 +2640,9 @@ namespace Umbraco.Tests.Services content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US", content2.Name); - Assert.AreEqual("name-fr", content2.GetName(langFr.IsoCode)); - Assert.AreEqual("name-uk", content2.GetName(langUk.IsoCode)); + Assert.AreEqual("name-fr", content2.Name); // got the default culture name when saved + Assert.AreEqual("name-fr", content2.GetCultureName(langFr.IsoCode)); + Assert.AreEqual("name-uk", content2.GetCultureName(langUk.IsoCode)); // we haven't published InvariantNeutral, but a document cannot be published without an invariant name, // so when we tried and published for the first time above the french culture, the french name was used @@ -2686,29 +2669,28 @@ namespace Umbraco.Tests.Services AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, false), (langUk, false), (langDe, true)); AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, false), (langUk, false), (langDe, true)); - AssertPerCulture(content, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw - AssertPerCulture(content2, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw + AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw + AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw // note that content and content2 culture published dates might be slightly different due to roundtrip to database // act - content.TryPublishValues(); contentService.SaveAndPublish(content); // now it has publish name for invariant neutral content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US", content2.PublishName); + Assert.AreEqual("name-fr", content2.PublishName); // act - content.SetName("Home US2", null); - content.SetName("name-fr2", langFr.IsoCode); - content.SetName("name-uk2", langUk.IsoCode); + content.SetCultureName("Home US2", null); + content.SetCultureName("name-fr2", langFr.IsoCode); + content.SetCultureName("name-uk2", langUk.IsoCode); content.SetValue("author", "Barack Obama2"); content.SetValue("prop", "value-fr2", langFr.IsoCode); content.SetValue("prop", "value-uk2", langUk.IsoCode); @@ -2719,11 +2701,11 @@ namespace Umbraco.Tests.Services content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.IsoCode)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.IsoCode)); + Assert.AreEqual("name-fr2", content2.Name); // got the default culture name when saved + Assert.AreEqual("name-fr2", content2.GetCultureName(langFr.IsoCode)); + Assert.AreEqual("name-uk2", content2.GetCultureName(langUk.IsoCode)); - Assert.AreEqual("Home US", content2.PublishName); + Assert.AreEqual("name-fr", content2.PublishName); Assert.AreEqual("name-fr", content2.GetPublishName(langFr.IsoCode)); Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); @@ -2747,26 +2729,27 @@ namespace Umbraco.Tests.Services AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); - AssertPerCulture(content, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw - AssertPerCulture(content2, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw + AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw + AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langFr, false), (langUk, false)); // DE would throw // act // cannot just 'save' since we are changing what's published! - content.ClearPublishedValues(langFr.IsoCode); - contentService.SaveAndPublish(content); + content.UnpublishCulture(langFr.IsoCode); + contentService.SavePublishing(content); // content has been published, // the french culture is gone + // (only if french is not mandatory, else everything would be gone!) content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.IsoCode)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.IsoCode)); + Assert.AreEqual("name-fr2", content2.Name); // got the default culture name when saved + Assert.AreEqual("name-fr2", content2.GetCultureName(langFr.IsoCode)); + Assert.AreEqual("name-uk2", content2.GetCultureName(langUk.IsoCode)); - Assert.AreEqual("Home US", content2.PublishName); + Assert.AreEqual("name-fr2", content2.PublishName); Assert.IsNull(content2.GetPublishName(langFr.IsoCode)); Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); @@ -2790,8 +2773,8 @@ namespace Umbraco.Tests.Services AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); - AssertPerCulture(content, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw - AssertPerCulture(content2, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw // act @@ -2812,11 +2795,11 @@ namespace Umbraco.Tests.Services Assert.IsFalse(content2.Published); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.IsoCode)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.IsoCode)); + Assert.AreEqual("name-fr2", content2.Name); // got the default culture name when saved + Assert.AreEqual("name-fr2", content2.GetCultureName(langFr.IsoCode)); + Assert.AreEqual("name-uk2", content2.GetCultureName(langUk.IsoCode)); - Assert.AreEqual("Home US", content2.PublishName); // not null, see note above + Assert.AreEqual("name-fr2", content2.PublishName); // not null, see note above Assert.IsNull(content2.GetPublishName(langFr.IsoCode)); Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); // not null, see note above @@ -2837,13 +2820,15 @@ namespace Umbraco.Tests.Services AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); - AssertPerCulture(content, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw - AssertPerCulture(content2, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw // act - contentService.SaveAndPublish(content); + // that HAS to be SavePublishing, because SaveAndPublish would just republish everything! + + contentService.SavePublishing(content); // content has been re-published, // everything is back to what it was before being unpublished @@ -2852,11 +2837,11 @@ namespace Umbraco.Tests.Services Assert.IsTrue(content2.Published); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.IsoCode)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.IsoCode)); + Assert.AreEqual("name-fr2", content2.Name); // got the default culture name when saved + Assert.AreEqual("name-fr2", content2.GetCultureName(langFr.IsoCode)); + Assert.AreEqual("name-uk2", content2.GetCultureName(langUk.IsoCode)); - Assert.AreEqual("Home US", content2.PublishName); + Assert.AreEqual("name-fr2", content2.PublishName); Assert.IsNull(content2.GetPublishName(langFr.IsoCode)); Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); @@ -2877,14 +2862,14 @@ namespace Umbraco.Tests.Services AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, true), (langDe, true)); - AssertPerCulture(content, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw - AssertPerCulture(content2, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw // act - content.TryPublishValues(langUk.IsoCode); - contentService.SaveAndPublish(content); + content.PublishCulture(langUk.IsoCode); + contentService.SavePublishing(content); content2 = contentService.GetById(content.Id); @@ -2900,18 +2885,22 @@ namespace Umbraco.Tests.Services AssertPerCulture(content, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, false), (langDe, true)); AssertPerCulture(content2, (x, c) => x.IsCultureEdited(c), (langFr, true), (langUk, false), (langDe, true)); - AssertPerCulture(content, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw - AssertPerCulture(content2, (x, c) => x.GetCulturePublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw + AssertPerCulture(content2, (x, c) => x.GetPublishDate(c) == DateTime.MinValue, (langUk, false)); // FR, DE would throw // act - content.SetName("name-uk3", langUk.IsoCode); + content.SetCultureName("name-uk3", langUk.IsoCode); contentService.Save(content); content2 = contentService.GetById(content.Id); + // note that the 'edited' flags only change once saved - not immediately + // but they change, on what's being saved, and when getting it back + // changing the name = edited! + Assert.IsTrue(content.IsCultureEdited(langUk.IsoCode)); Assert.IsTrue(content2.IsCultureEdited(langUk.IsoCode)); } diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs index b21f96a4bf..18c62c49cb 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceTests.cs @@ -88,7 +88,6 @@ namespace Umbraco.Tests.Services var contentType = contentTypes[index]; var contentItem = MockedContent.CreateSimpleContent(contentType, "MyName_" + index + "_" + i, parentId); ServiceContext.ContentService.Save(contentItem); - contentItem.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(contentItem); parentId = contentItem.Id; @@ -189,7 +188,6 @@ namespace Umbraco.Tests.Services var contentType = contentTypes[index]; var contentItem = MockedContent.CreateSimpleContent(contentType, "MyName_" + index + "_" + i, parentId); ServiceContext.ContentService.Save(contentItem); - contentItem.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(contentItem); parentId = contentItem.Id; } @@ -225,19 +223,16 @@ namespace Umbraco.Tests.Services var root = MockedContent.CreateSimpleContent(contentType1, "Root", -1); ServiceContext.ContentService.Save(root); - root.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(root); var level1 = MockedContent.CreateSimpleContent(contentType2, "L1", root.Id); ServiceContext.ContentService.Save(level1); - level1.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(level1); for (int i = 0; i < 2; i++) { var level3 = MockedContent.CreateSimpleContent(contentType3, "L2" + i, level1.Id); ServiceContext.ContentService.Save(level3); - level3.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(level3); } @@ -267,7 +262,6 @@ namespace Umbraco.Tests.Services ServiceContext.FileService.SaveTemplate(contentType1.DefaultTemplate); ServiceContext.ContentTypeService.Save(contentType1); IContent contentItem = MockedContent.CreateTextpageContent(contentType1, "Testing", -1); - contentItem.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(contentItem); var initProps = contentItem.Properties.Count; var initPropTypes = contentItem.PropertyTypes.Count(); @@ -297,14 +291,12 @@ namespace Umbraco.Tests.Services var contentItems1 = MockedContent.CreateTextpageContent(contentType1, -1, 10).ToArray(); foreach (var x in contentItems1) { - x.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(x); } var contentItems2 = MockedContent.CreateTextpageContent(contentType2, -1, 5).ToArray(); foreach (var x in contentItems2) { - x.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(x); } @@ -362,7 +354,6 @@ namespace Umbraco.Tests.Services var contentItems1 = MockedContent.CreateTextpageContent(contentType1, -1, 10).ToArray(); foreach (var x in contentItems1) { - x.TryPublishValues(); ServiceContext.ContentService.SaveAndPublish(x); } var alias = contentType1.PropertyTypes.First().Alias; @@ -496,7 +487,6 @@ namespace Umbraco.Tests.Services // Act var homeDoc = cs.Create("Home Page", -1, contentTypeAlias); - homeDoc.TryPublishValues(); cs.SaveAndPublish(homeDoc); // Assert diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 627adff6d5..1db653f4ab 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -451,19 +451,18 @@ namespace Umbraco.Tests.Services { var service = ServiceContext.EntityService; - var alias = "test" + Guid.NewGuid(); var contentType = MockedContentTypes.CreateSimpleContentType(alias, alias, false); - contentType.Variations = ContentVariation.CultureNeutral; + contentType.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(contentType); var c1 = MockedContent.CreateSimpleContent(contentType, "Test", -1); - c1.SetName("Test - FR", _langFr.IsoCode); - c1.SetName("Test - ES", _langEs.IsoCode); + c1.SetCultureName("Test - FR", _langFr.IsoCode); + c1.SetCultureName("Test - ES", _langEs.IsoCode); ServiceContext.ContentService.Save(c1); var result = service.Get(c1.Id, UmbracoObjectTypes.Document); - Assert.AreEqual("Test", result.Name); + Assert.AreEqual("Test - FR", result.Name); // got name from default culture Assert.IsNotNull(result as IDocumentEntitySlim); var doc = (IDocumentEntitySlim)result; var cultureNames = doc.CultureNames; @@ -477,11 +476,11 @@ namespace Umbraco.Tests.Services var service = ServiceContext.EntityService; var contentType = MockedContentTypes.CreateSimpleContentType("test1", "Test1", false); - contentType.Variations = ContentVariation.CultureNeutral; + contentType.Variations = ContentVariation.Culture; ServiceContext.ContentTypeService.Save(contentType); var root = MockedContent.CreateSimpleContent(contentType); - root.SetName("Root", _langFr.IsoCode); // else cannot save + root.SetCultureName("Root", _langFr.IsoCode); // else cannot save ServiceContext.ContentService.Save(root); for (int i = 0; i < 10; i++) @@ -489,12 +488,12 @@ namespace Umbraco.Tests.Services var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); if (i % 2 == 0) { - c1.SetName("Test " + i + " - FR", _langFr.IsoCode); - c1.SetName("Test " + i + " - ES", _langEs.IsoCode); + c1.SetCultureName("Test " + i + " - FR", _langFr.IsoCode); + c1.SetCultureName("Test " + i + " - ES", _langEs.IsoCode); } else { - c1.SetName("Test", _langFr.IsoCode); // else cannot save + c1.SetCultureName("Test", _langFr.IsoCode); // else cannot save } ServiceContext.ContentService.Save(c1); } diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index c0daaa8fb9..664d15fc71 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -249,7 +249,7 @@ namespace Umbraco.Tests.Services var result = new List(); ServiceContext.ContentTypeService.Save(contentType1); IContent lastParent = MockedContent.CreateSimpleContent(contentType1); - lastParent.TryPublishValues(); + lastParent.PublishCulture(); ServiceContext.ContentService.SaveAndPublish(lastParent); result.Add(lastParent); //create 20 deep @@ -263,7 +263,7 @@ namespace Umbraco.Tests.Services //only publish evens if (j % 2 == 0) { - content.TryPublishValues(); + content.PublishCulture(); ServiceContext.ContentService.SaveAndPublish(content); } else diff --git a/src/Umbraco.Tests/Services/TagServiceTests.cs b/src/Umbraco.Tests/Services/TagServiceTests.cs index c24c729bf8..49aad03efb 100644 --- a/src/Umbraco.Tests/Services/TagServiceTests.cs +++ b/src/Umbraco.Tests/Services/TagServiceTests.cs @@ -36,21 +36,17 @@ namespace Umbraco.Tests.Services IContent content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "cow", "pig", "goat" }); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); // change content1.AssignTags("tags", new[] { "elephant" }, true); content1.RemoveTags("tags", new[] { "cow" }); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); // more changes content1.AssignTags("tags", new[] { "mouse" }, true); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); content1.RemoveTags("tags", new[] { "mouse" }); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); // get it back @@ -88,17 +84,14 @@ namespace Umbraco.Tests.Services var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "cow", "pig", "goat" }); - content1.TryPublishValues(); contentService.SaveAndPublish(content1); var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); content2.AssignTags("tags", new[] { "cow", "pig" }); - content2.TryPublishValues(); contentService.SaveAndPublish(content2); var content3 = MockedContent.CreateSimpleContent(contentType, "Tagged content 3", -1); content3.AssignTags("tags", new[] { "cow" }); - content3.TryPublishValues(); contentService.SaveAndPublish(content3); // Act diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 053705ca4d..0faf1537b3 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -23,7 +23,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs { // handle context culture if (culture == null) - culture = VariationContextAccessor?.VariationContext.Culture; + culture = VariationContextAccessor?.VariationContext?.Culture; // no invariant culture infos if (culture == "" || Cultures == null) return null; diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index f21a953ae7..e79e504a69 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -83,8 +83,7 @@ namespace Umbraco.Tests.Testing.TestingTests var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, umbracoContext.VariationContextAccessor); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), - ContentVariation.InvariantNeutral); + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = Mock.Of(); Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType); diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index 585e9a17d6..dedf04488c 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -59,7 +59,7 @@ namespace Umbraco.Tests.UmbracoExamine m.CreateDate == (DateTime)x.Attribute("createDate") && m.UpdateDate == (DateTime)x.Attribute("updateDate") && m.Name == (string)x.Attribute("nodeName") && - m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && + m.GetCultureName(It.IsAny()) == (string)x.Attribute("nodeName") && m.Path == (string)x.Attribute("path") && m.Properties == new PropertyCollection() && m.ContentType == Mock.Of(mt => @@ -103,7 +103,7 @@ namespace Umbraco.Tests.UmbracoExamine m.CreateDate == (DateTime) x.Attribute("createDate") && m.UpdateDate == (DateTime) x.Attribute("updateDate") && m.Name == (string) x.Attribute("nodeName") && - m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && + m.GetCultureName(It.IsAny()) == (string)x.Attribute("nodeName") && m.Path == (string) x.Attribute("path") && m.Properties == new PropertyCollection() && m.ContentType == Mock.Of(mt => diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index e08c6c5f05..2d440b8453 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.UmbracoExamine m.CreateDate == (DateTime)x.Attribute("createDate") && m.UpdateDate == (DateTime)x.Attribute("updateDate") && m.Name == (string)x.Attribute("nodeName") && - m.GetName(It.IsAny()) == (string)x.Attribute("nodeName") && + m.GetCultureName(It.IsAny()) == (string)x.Attribute("nodeName") && m.Path == (string)x.Attribute("path") && m.Properties == new PropertyCollection() && m.Published == true && diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index c6f7d8551e..0a9638fc30 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -82,7 +82,7 @@ namespace Umbraco.Tests.Web var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.InvariantNeutral); + var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = Mock.Of(); Mock.Get(publishedContent).Setup(x => x.Id).Returns(1234); Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index b33e27d048..c9e4af4062 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -1,19 +1,22 @@
- +
- + @@ -23,12 +26,12 @@ - +
- +
-
- +
+
- +
{{item.timestampFormatted}}
- +
{{ item.comment }}
- +
@@ -84,8 +87,8 @@
- - + +
@@ -96,15 +99,15 @@
- + - +
- +
@@ -115,12 +118,12 @@
{{node.releaseDateDay}} {{node.releaseDateTime}}
- Set date - + Set date +
- + Clear date @@ -140,7 +143,7 @@
- +
{{node.removeDateMonth}} {{node.removeDateYear}}
{{node.removeDateDayNumber}}
@@ -159,7 +162,7 @@
- + @@ -200,7 +203,7 @@ {{ node.key }} - +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html index 9f4043db39..9d060090ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umb-media-node-info.html @@ -5,16 +5,16 @@ -