diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs
index 275cc61425..03abbcbc9e 100644
--- a/src/Umbraco.Core/ContentVariationExtensions.cs
+++ b/src/Umbraco.Core/ContentVariationExtensions.cs
@@ -1,82 +1,110 @@
-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.
- ///
- public static bool Has(this ContentVariation variation, ContentVariation values)
- => (variation & values) == values;
+ // fixme document
+ public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing();
+ public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture();
+ public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment();
+ public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment();
+
+ public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing();
+ public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture();
+ public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment();
+ public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
+
+ public static bool VariesByNothing(this PublishedContentType contentType) => contentType.Variations.VariesByNothing();
+ public static bool VariesByCulture(this PublishedContentType contentType) => contentType.Variations.VariesByCulture();
+ public static bool VariesBySegment(this PublishedContentType contentType) => contentType.Variations.VariesBySegment();
+ public static bool VariesByCultureAndSegment(this PublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment();
///
- /// Determines whether a variation has at least a flag set.
+ /// Determines whether a variation varies by nothing.
///
- public static bool HasAny(this ContentVariation variation, ContentVariation values)
- => (variation & values) != ContentVariation.Unknown;
+ public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing;
///
- /// Determines whether a variation does not support culture variations
+ /// Determines whether a variation varies by culture.
///
- ///
- ///
- public static bool DoesNotSupportCulture(this ContentVariation variation)
+ public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0;
+
+ ///
+ /// Determines whether a variation varies by segment.
+ ///
+ 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/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index e671b45968..d670d3a588 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -21,7 +21,7 @@ namespace Umbraco.Core.Models
private DateTime? _releaseDate;
private DateTime? _expireDate;
private Dictionary _publishInfos;
- private HashSet _edited;
+ private HashSet _editedCultures;
private static readonly Lazy Ps = new Lazy();
@@ -188,123 +188,136 @@ 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);
-
- _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));
+
+ if (_publishInfos == null) return;
+ _publishInfos.Remove(culture);
+ if (_publishInfos.Count == 0) _publishInfos = null;
}
- ///
- public IEnumerable PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty();
+ // fixme DOCUMENT
+ // sets publish name - used by persistence
+ //internal void UpdatePublishName(string culture, string name)
+ //{
+ // if (string.IsNullOrWhiteSpace(name))
+ // throw new ArgumentNullOrEmptyException(nameof(name));
- ///
- public bool IsCultureEdited(string culture)
- {
- return string.IsNullOrWhiteSpace(GetPublishName(culture)) || (_edited != null && _edited.Contains(culture));
- }
+ // if (culture == null) // fixme that should NOT happen
+ // {
+ // PublishName = name;
+ // }
+ // else
+ // {
+ // // private method, assume that culture is valid
+
+ // if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var pi))
+ // throw new InvalidOperationException("Culture is not published.");
+
+ // _publishInfos[culture] = (name, pi.Date);
+ // }
+ //}
// 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,128 +325,80 @@ namespace Umbraco.Core.Models
public bool Blueprint { get; internal set; }
///
- internal virtual bool TryPublishAllValues()
+ public virtual bool TryPublishValues(string culture = null, string segment = null) // fixme should it default to "*", "*" instead?
{
- // 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();
+ segment = segment.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 (!ContentType.SupportsPropertyVariation(culture, segment, true))
+ throw new NotSupportedException($"Content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\" does not support \"{culture??""},{segment??""}\".");
// 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
+ return false; // fixme - 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)
+ // when explicitely publishing values for a given segment, values for the segment are published,
+ // but that does not change which cultures are published. to publish a culture, publish 'null' or
+ // '*', and then we need to deal with culture names
+ if (segment == null || segment == "*")
{
- var name = GetName(culture);
- if (string.IsNullOrWhiteSpace(name))
- return false; //fixme this should return an attempt with error results
+ // do not deal with invariant culture - it is always published
- SetPublishInfos(culture, name, DateTime.Now);
+ 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 if (!culture.IsNullOrWhiteSpace()) // one single culture
+ {
+ var name = GetCultureName(culture);
+ if (string.IsNullOrWhiteSpace(name))
+ return false;
+ SetPublishInfo(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);
+ // property.PublishValues only publishes what is valid, variation-wise
+ foreach (var property in Properties)
+ property.PublishValues(culture, segment);
_publishedState = PublishedState.Publishing;
return true;
}
///
- internal virtual bool PublishCultureValues(string culture = null)
+ public virtual void ClearPublishedValues(string culture = null, string segment = null) // fixme should it default to "*", "*" instead?
{
- //fixme - needs API review as this is not used apart from in tests
+ culture = culture.NullOrWhiteSpaceAsNull();
+ segment = segment.NullOrWhiteSpaceAsNull();
- // the values we want to publish should be valid
- if (ValidatePropertiesForCulture(culture).Any())
- return false;
+ // the variation should be supported by the content type properties
+ if (!ContentType.SupportsPropertyVariation(culture, segment, true))
+ throw new NotSupportedException($"Content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\" does not support \"{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.
- var name = GetName(culture);
- if (string.IsNullOrWhiteSpace(name))
- throw new InvalidOperationException($"Cannot publish {culture ?? "invariant"} culture without a name.");
- SetPublishInfos(culture, name, DateTime.Now);
+ // when explicitely clearing values for a given segment, values for the segment are cleared,
+ // but that does not change which cultures are published. to unpublish a culture, clear 'null' or
+ // '*', and then we need to deal with culture names
+ if (segment == null || segment == "*")
+ {
+ // do not deal with invariant culture - it cannot be unpublished
+ // fixme - so should we throw instead?
- // property.PublishCultureValues only deals with supported variations (if any)
+ if (culture == "*") // all cultures
+ ClearPublishInfos();
+ else if (!culture.IsNullOrWhiteSpace()) // one single culture
+ ClearPublishInfo(culture);
+ }
+
+ // property.PublishValues only publishes what is valid, variation-wise
foreach (var property in Properties)
- property.PublishCultureValues(culture);
-
- _publishedState = PublishedState.Publishing;
- return true;
- }
-
- ///
- public virtual void ClearAllPublishedValues()
- {
- // property.ClearPublishedAllValues only deals with supported variations (if any)
- foreach (var property in Properties)
- property.ClearPublishedAllValues();
-
- // 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();
-
- _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);
+ property.ClearPublishedValues(culture, segment);
_publishedState = PublishedState.Publishing;
}
@@ -444,6 +409,8 @@ namespace Umbraco.Core.Models
return Id == other.Id && VersionId == other.VersionId;
}
+ // fixme must ALSO refactor copyValues
+
///
public virtual void CopyAllValues(IContent other)
{
@@ -460,7 +427,7 @@ namespace Umbraco.Core.Models
// clear all existing properties
foreach (var property in Properties)
foreach (var pvalue in property.Values)
- if (property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false))
+ if (property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment))
property.SetValue(null, pvalue.Culture, pvalue.Segment);
// copy other properties
@@ -470,7 +437,7 @@ namespace Umbraco.Core.Models
var alias = otherProperty.PropertyType.Alias;
foreach (var pvalue in otherProperty.Values)
{
- if (!otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false))
+ if (!otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment))
continue;
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
SetValue(alias, value, pvalue.Culture, pvalue.Segment);
@@ -478,9 +445,9 @@ namespace Umbraco.Core.Models
}
// copy names
- ClearNames();
+ ClearCultureInfos();
foreach (var (culture, name) in other.CultureNames)
- SetName(name, culture);
+ SetCultureName(name, culture);
Name = other.Name;
}
@@ -500,7 +467,7 @@ namespace Umbraco.Core.Models
// clear all existing properties
foreach (var property in Properties)
{
- if (!property.PropertyType.ValidateVariation(culture, segment, false))
+ if (!property.PropertyType.SupportsVariation(culture, segment))
continue;
foreach (var pvalue in property.Values)
@@ -512,7 +479,7 @@ namespace Umbraco.Core.Models
var otherProperties = other.Properties;
foreach (var otherProperty in otherProperties)
{
- if (!otherProperty.PropertyType.ValidateVariation(culture, segment, false))
+ if (!otherProperty.PropertyType.SupportsVariation(culture, segment))
continue;
var alias = otherProperty.PropertyType.Alias;
@@ -520,7 +487,7 @@ namespace Umbraco.Core.Models
}
// copy name
- SetName(other.GetName(culture), culture);
+ SetCultureName(other.GetCultureName(culture), culture);
}
///
@@ -536,7 +503,7 @@ namespace Umbraco.Core.Models
// 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))
+ if (pvalue.Culture.InvariantEquals(culture) && property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment))
property.SetValue(null, pvalue.Culture, pvalue.Segment);
// copy other properties
@@ -546,7 +513,7 @@ namespace Umbraco.Core.Models
var alias = otherProperty.PropertyType.Alias;
foreach (var pvalue in otherProperty.Values)
{
- if (pvalue.Culture != culture || !otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false))
+ if (pvalue.Culture != culture || !otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment))
continue;
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
SetValue(alias, value, pvalue.Culture, pvalue.Segment);
@@ -554,7 +521,7 @@ namespace Umbraco.Core.Models
}
// copy name
- SetName(other.GetName(culture), culture);
+ SetCultureName(other.GetCultureName(culture), culture);
}
///
diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs
index 04fbe269f8..283054d501 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
@@ -307,17 +304,10 @@ namespace Umbraco.Core.Models
#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)
{
- var name = GetName(culture);
+ var name = GetCultureName(culture);
if (name.IsNullOrWhiteSpace()) return false;
return ValidateProperties(culture, segment).Length == 0;
}
@@ -325,25 +315,10 @@ namespace Umbraco.Core.Models
///
public virtual Property[] ValidateProperties(string culture = null, string segment = null)
{
- return Properties.Where(x =>
- {
- 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();
- }
-
- internal virtual Property[] ValidatePropertiesForCulture(string culture = null)
- {
- //fixme - needs API review as this is not used apart from in tests
-
- return Properties.Where(x => !x.IsCultureValid(culture)).ToArray();
+ return Properties.Where(x => // select properties...
+ x.PropertyType.SupportsVariation(culture, segment, true) && // that support the variation
+ !x.IsValid(culture, segment)) // 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..2759f2e075 100644
--- a/src/Umbraco.Core/Models/ContentVariation.cs
+++ b/src/Umbraco.Core/Models/ContentVariation.cs
@@ -3,34 +3,62 @@
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,
+ CultureAndSegment = Culture | Segment
- ///
- /// Accepts values for a specified culture and a specified segment.
- ///
- CultureSegment = 8
+
+ // fixme - remove once we have a migration for DB values!
+ /////
+ ///// Unknown.
+ /////
+ //Unknown = 0,
+
+ /////
+ ///// Accepts values for the invariant culture and the neutral segment.
+ /////
+ //InvariantNeutral = 1,
+
+ /////
+ ///// Accepts values for a specified culture and the neutral segment.
+ /////
+ //CultureNeutral = 2,
+
+ /////
+ ///// Accepts values for the invariant culture and a specified segment.
+ /////
+ //InvariantSegment = 4,
+
+ /////
+ ///// Accepts values for a specified culture and a specified segment.
+ /////
+ //CultureSegment = 8
}
}
diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs
index 13797425ed..929ea990e5 100644
--- a/src/Umbraco.Core/Models/IContent.cs
+++ b/src/Umbraco.Core/Models/IContent.cs
@@ -93,7 +93,7 @@ namespace Umbraco.Core.Models
///
/// 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 +121,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.
@@ -161,54 +156,77 @@ namespace Umbraco.Core.Models
///
IContent DeepCloneWithResetIdentities();
- ///
- /// Publishes all 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 valid.
- ///
- //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
+ /////
+ ///// Publishes all values.
+ /////
+ ///// A value indicating whether the values could be published.
+ /////
+ ///// Fails if values cannot be published, e.g. if some values are not valid.
+ ///// Sets the property values for all cultures, including the invariant ones.
+ ///// Sets the published name for all culture that are available, thus publishing them all.
+ ///// The document must then be published via the content service SaveAndPublish method.
+ /////
+ //// fixme - should return an attemps with error results
+ //// fixme - needs API review as this is not used apart from in tests << YES but users could use it
//bool TryPublishAllValues();
+ /////
+ ///// Publishes the values for a specified culture and all segments.
+ /////
+ ///// A value indicating whether the values could be published.
+ /////
+ ///// Fails if values cannot be published, e.g. if some values are not valid.
+ ///// Sets the property values for the specified culture, and only the specified culture: must
+ ///// be invoked with a null culture to set the invariant values.
+ ///// Sets the published name for the specified culture, thus publishing the culture.
+ ///// The document must then be published via the content service SaveAndPublish method.
+ /////
+ //// fixme - needs API review as this is not used apart from in tests << NO it is THAT one that we should use for now
+ //// fixme - should return an attemps with error results
+ //// fixme - should it publish the invariant values too? - NO that's done when SaveAndPublish (is it? don't think so) - BUT could we want to avoid it?
+ //bool TryPublishCultureValues(string culture);
+
///
- /// Publishes values.
+ /// Publishes values for a specific culture and segment.
///
/// 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.
+ /// Fails if values cannot be published, e.g. if some values are not valid.
+ /// Sets the property values but not the published name for the specified culture,
+ /// thus not explicitely publishing the culture.
+ /// The document must then be published via the content service SaveAndPublish method.
///
- //fixme return an Attempt with some error results if it doesn't work
+ // fixme - should return an attemps with error results
+ // fixme - publishing for segments is not supported
+ // we don't know whether should it also publish the specified culture?
+ // we don't know how to publish segments but not neutral, etc
+ // what shall we do then?
bool TryPublishValues(string culture = null, string segment = null);
+ /////
+ ///// Clears published values.
+ /////
+ /////
+ ///// Clears the published name for all cultures, thus unpublishing all cultures.
+ /////
+ //void ClearAllPublishedValues();
+
///
- /// Publishes the culture/any values.
+ /// Clears published values for a specified culture and segment.
///
- /// 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.
+ /// Clears the property values but not the published name for the specified culture,
+ /// thus leaving the culture published.
///
- //fixme - needs API review as this is not used apart from in tests
- //bool PublishCultureValues(string culture = null);
+ void ClearPublishedValues(string culture = null, string segment = null); // fixme should NOT use
- ///
- /// 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);
+ /////
+ ///// Clears published values for a specified culture, all segments.
+ /////
+ /////
+ ///// Clears the published name for the specified culture, thus unpublishing the culture.
+ /////
+ //void ClearCulturePublishedValues(string culture = null); // fixme that one should be used!
///
/// Copies values from another document.
@@ -216,12 +234,12 @@ namespace Umbraco.Core.Models
void CopyAllValues(IContent other);
///
- /// Copies values from another document.
+ /// Copies values from another document for a specified culture and segment.
///
void CopyValues(IContent other, string culture = null, string segment = null);
///
- /// Copies culture/any values from another document.
+ /// Copies values from another document for a specified culture, all segments.
///
void CopyCultureValues(IContent other, string culture = null);
}
diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs
index 1605c1da01..deda24cea0 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
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..e1f05a4982 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)
@@ -152,6 +195,8 @@ namespace Umbraco.Core.Models
: pvalue.EditedValue;
}
+ // fixme clear
+ /*
// internal - must be invoked by the content item
// does *not* validate the value - content item must validate first
internal void PublishAllValues()
@@ -161,14 +206,13 @@ namespace Umbraco.Core.Models
PublishPropertyValue(_pvalue);
// 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 (_vvalues == null) return;
+
+ 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);
}
// internal - must be invoked by the content item
@@ -201,7 +245,61 @@ namespace Umbraco.Core.Models
PublishPropertyValue(pvalue);
}
}
+ */
+ // internal - must be invoked by the content item
+ // does *not* validate the value - content item must validate first
+ internal void PublishValues(string culture = null, string segment = null)
+ {
+ culture = culture.NullOrWhiteSpaceAsNull();
+ segment = segment.NullOrWhiteSpaceAsNull();
+
+ // if invariant or all, and invariant-neutral is supported, publish invariant-neutral
+ if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null))
+ PublishPropertyValue(_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) && // 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)
+ PublishPropertyValue(pvalue);
+ }
+
+ // internal - must be invoked by the content item
+ internal void ClearPublishedValues(string culture = null, string segment = null)
+ {
+ culture = culture.NullOrWhiteSpaceAsNull();
+ segment = segment.NullOrWhiteSpaceAsNull();
+
+ // if invariant or all, and invariant-neutral is supported, publish invariant-neutral
+ if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null))
+ ClearPublishedPropertyValue(_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) && // 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)
+ ClearPublishedPropertyValue(pvalue);
+ }
+
+ // fixme clear
+ /*
// internal - must be invoked by the content item
internal void ClearPublishedAllValues()
{
@@ -228,7 +326,7 @@ namespace Umbraco.Core.Models
}
// internal - must be invoked by the content item
- internal void ClearPublishedCultureValues(string culture = null)
+ internal void ClearCulturePublishedValues(string culture = null)
{
if (culture == null && PropertyType.ValidateVariation(null, null, false))
ClearPublishedPropertyValue(_pvalue);
@@ -243,6 +341,7 @@ namespace Umbraco.Core.Models
ClearPublishedPropertyValue(pvalue);
}
}
+ */
private void PublishPropertyValue(PropertyValue pvalue)
{
@@ -271,7 +370,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;
@@ -335,65 +439,69 @@ namespace Umbraco.Core.Models
/// Gets a value indicating whether all properties are valid.
///
///
- internal bool IsAllValid()
+ internal bool IsValid(string culture = null, string segment = null)
{
- //fixme - needs API review as this is not used apart from in tests
+ // 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;
- // invariant-neutral is supported, validate invariant-neutral
- // includes mandatory validation
- if (PropertyType.ValidateVariation(null, null, false) && !IsValidValue(_pvalue)) return false;
+ // if validating only invariant/neutral, we are good
+ if (culture == null && segment == null) 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 == "*" || segment == "*" || 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) && // 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));
+ return pvalues.Count == 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
+ // fixme clear
+ /////
+ ///// 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)
+ //{
- // culture-neutral is supported, validate culture-neutral
- // includes mandatory validation
- if (PropertyType.ValidateVariation(culture, null, false) && !IsValidValue(GetValue(culture)))
- return false;
+ // // 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)
+ // // 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;
+ // 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();
+ // 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));
- }
+ // 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));
- }
+ /////
+ ///// 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));
+ //}
///
/// Boolean indicating whether the passed in value is valid
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..202f51729a 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,7 +326,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var propertyDataDto in propertyDataDtos)
Database.Insert(propertyDataDto);
- // name also impacts 'edited'
+ // if !publishing, we may have a new name != current publish name,
+ // also impacts 'edited'
if (content.PublishName != content.Name)
edited = true;
@@ -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,9 @@ 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 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'.
// (same in PersistNewItem above)
// insert content variations
@@ -522,8 +528,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 +555,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 +565,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 +911,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 +946,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 +962,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 +1045,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 +1053,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 +1091,124 @@ 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);
+
+ // now that we have an invariant name, which is unique,
+ // if publishing, ensure that we have an invariant publish name
+ // invariant content = must be there, else throw - then must follow the invariant name
+ // variant content = update with invariant name
+ // fixme wtf is this we never needed it, PublishName derives from Name when publishing!
+ //if (publishing) EnsureInvariantPublishName(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);
+ }
+
+ //private void EnsureInvariantPublishName(Content content)
+ //{
+ // if (content.ContentType.VariesByCulture())
+ // {
+ // // content varies by culture, reuse name as publish name
+ // content.UpdatePublishName(null, content.Name);
+ // }
+ // else
+ // {
+ // // content is invariant, and invariant content must have an explicit invariant name
+ // if (string.IsNullOrWhiteSpace(content.PublishName))
+ // throw new InvalidOperationException("Cannot save content with an empty name.");
+ // // and then, must follow the name itself
+ // if (content.PublishName != content.Name)
+ // content.UpdatePublishName(null, content.Name);
+ // }
+ //}
+
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)
+ 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)) // fixme but what about those cultures we are NOT publishing NOW?! they shouldn't change their name!
+ 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/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index 2e3fcf8522..4051a26f7f 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -1056,7 +1056,7 @@ namespace Umbraco.Core.Services.Implement
// not varying, or invariant culture
// simply unpublish the document
- if (!content.ContentType.Variations.DoesSupportCulture() || culture.IsNullOrWhiteSpace())
+ if (!content.ContentType.VariesByCulture() || culture.IsNullOrWhiteSpace())
{
var unpublished = UnpublishScoped(scope, content, evtMsgs, userId);
if (unpublished.Success) scope.Complete();
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.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs
index 044f8fa0cd..861dee1eca 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.ClearPublishedValues();
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.ClearPublishedValues("*");
+ 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.ClearPublishedValues(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.ClearPublishedValues(); // 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);
@@ -230,14 +278,14 @@ namespace Umbraco.Tests.Models
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));
@@ -245,57 +293,57 @@ namespace Umbraco.Tests.Models
// can publish value
// and get edited and published values
Assert.IsFalse(content.TryPublishValues(langFr)); // no name
- content.SetName("name-fr", langFr);
+ content.SetCultureName("name-fr", langFr);
Assert.IsTrue(content.TryPublishValues(langFr));
- Assert.AreEqual("b", content.GetValue("prop"));
+ 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.ClearPublishedValues("*");
+ 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.TryPublishValues("*"));
+ 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.ClearPublishedValues(langFr);
Assert.AreEqual("c", content.GetValue("prop", langFr));
Assert.IsNull(content.GetValue("prop", langFr, published: true));
- content.PublishCultureValues(langFr);
+ content.TryPublishValues(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.ClearPublishedValues(); // clears invariant props if any
+ Assert.IsNull(content.GetValue("prop"));
+ Assert.IsNull(content.GetValue("prop", published: true));
+ content.TryPublishValues(); // 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));
+ 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));
+ 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,29 +353,31 @@ 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.TryPublishValues(langFr)); // fails because prop1 is mandatory
content.SetValue("prop1", "a", langFr);
Assert.IsTrue(content.TryPublishValues(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.TryPublishValues()); // fails because prop2 is mandatory
content.SetValue("prop2", "b");
Assert.IsTrue(content.TryPublishValues());
Assert.AreEqual("b", content.GetValue("prop2", published: true));
@@ -346,10 +396,11 @@ 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);
@@ -359,29 +410,29 @@ namespace Umbraco.Tests.Models
// works with a name
// and then FR is available, and published
- content.SetName("name-fr", langFr);
+ content.SetCultureName("name-fr", langFr);
Assert.IsTrue(content.TryPublishValues(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/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs
index 983cccbdb9..805ea2d862 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs
@@ -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/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs
index 50a535d029..92c38cee67 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -2507,7 +2507,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 +2515,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 +2550,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,7 +2578,7 @@ namespace Umbraco.Tests.Services
{
var languageService = ServiceContext.LocalizationService;
- var langFr = new Language("fr-FR");
+ var langFr = new Language("fr-FR") { IsDefaultVariantLanguage = true };
var langUk = new Language("en-UK");
var langDe = new Language("de-DE");
@@ -2588,31 +2588,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 +2618,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));
@@ -2657,9 +2655,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,8 +2684,8 @@ 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
@@ -2701,14 +2699,14 @@ namespace Umbraco.Tests.Services
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 +2717,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,8 +2745,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, (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
@@ -2762,11 +2760,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-fr2", content2.PublishName);
Assert.IsNull(content2.GetPublishName(langFr.IsoCode));
Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode));
@@ -2790,8 +2788,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 +2810,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,8 +2835,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
@@ -2852,11 +2850,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,8 +2875,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
@@ -2900,18 +2898,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/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs
index 6abe5add94..74401c9318 100644
--- a/src/Umbraco.Tests/Services/EntityServiceTests.cs
+++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs
@@ -450,19 +450,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;
@@ -476,11 +475,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++)
@@ -488,12 +487,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/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/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs
index 184c442568..75b22d5404 100644
--- a/src/Umbraco.Web/Editors/ContentController.cs
+++ b/src/Umbraco.Web/Editors/ContentController.cs
@@ -74,7 +74,7 @@ namespace Umbraco.Web.Editors
public bool AllowsCultureVariation()
{
var contentTypes = Services.ContentTypeService.GetAll();
- return contentTypes.Any(contentType => contentType.Variations.DoesSupportCulture());
+ return contentTypes.Any(contentType => contentType.VariesByCulture());
}
///
@@ -310,7 +310,7 @@ namespace Umbraco.Web.Editors
var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName);
mapped.Tabs = mapped.Tabs.Except(new[] { containerTab });
- if (contentType.Variations.Has(ContentVariation.CultureNeutral))
+ if (contentType.VariesByCulture())
{
//Remove all variants except for the default since currently the default must be saved before other variants can be edited
//TODO: Allow for editing all variants at once ... this will be a future task
@@ -709,7 +709,7 @@ namespace Umbraco.Web.Editors
///
private void PublishInternal(ContentItemSave contentItem, ref PublishResult publishStatus, ref bool wasCancelled)
{
- if (!contentItem.PersistedContent.ContentType.Variations.Has(ContentVariation.CultureNeutral))
+ if (!contentItem.PersistedContent.ContentType.VariesByCulture())
{
//its invariant, proceed normally
contentItem.PersistedContent.TryPublishValues();
@@ -1037,13 +1037,13 @@ namespace Umbraco.Web.Editors
private void MapPropertyValues(ContentItemSave contentItem)
{
//Don't update the name if it is empty
- if (contentItem.Name.IsNullOrWhiteSpace() == false)
+ if (!contentItem.Name.IsNullOrWhiteSpace())
{
- //set the name according to the culture settings
- if (contentItem.PersistedContent.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral))
+ if (contentItem.PersistedContent.ContentType.VariesByCulture())
{
- if (contentItem.Culture.IsNullOrWhiteSpace()) throw new InvalidOperationException($"Cannot save a content item that is {ContentVariation.CultureNeutral} with a culture specified");
- contentItem.PersistedContent.SetName(contentItem.Name, contentItem.Culture);
+ if (contentItem.Culture.IsNullOrWhiteSpace())
+ throw new InvalidOperationException($"Cannot set culture name without a culture.");
+ contentItem.PersistedContent.SetCultureName(contentItem.Name, contentItem.Culture);
}
else
{
@@ -1074,7 +1074,7 @@ namespace Umbraco.Web.Editors
}
}
- bool Varies(Property property) => property.PropertyType.Variations.Has(ContentVariation.CultureNeutral);
+ bool Varies(Property property) => property.PropertyType.VariesByCulture();
MapPropertyValues(
contentItem,
@@ -1276,7 +1276,7 @@ namespace Umbraco.Web.Editors
{
//A culture must exist in the mapping context if this content type is CultureNeutral since for a culture variant to be edited,
// the Cuture property of ContentItemDisplay must exist (at least currently).
- if (culture == null && content.ContentType.Variations.Has(ContentVariation.CultureNeutral))
+ if (culture == null && content.ContentType.VariesByCulture())
{
//If a culture is not explicitly sent up, then it means that the user is editing the default variant language.
culture = Services.LocalizationService.GetDefaultLanguageIsoCode();
diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs
index 2e8155e1a7..101fed8a06 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayNameResolver.cs
@@ -14,12 +14,9 @@ namespace Umbraco.Web.Models.Mapping
public string Resolve(IContent source, ContentItemDisplay destination, string destMember, ResolutionContext context)
{
var culture = context.GetCulture();
- if (culture != null && source.ContentType.Variations.Has(ContentVariation.CultureNeutral))
- {
- //return the culture name being requested
- return source.GetName(culture);
- }
- return source.Name;
+ return source.ContentType.VariesByCulture() && culture != null
+ ? source.GetCultureName(culture)
+ : source.Name;
}
}
}
diff --git a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs
index d1673c2a5b..cb6e2938be 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentItemDisplayVariationResolver.cs
@@ -25,7 +25,7 @@ namespace Umbraco.Web.Models.Mapping
public IEnumerable Resolve(IContent source, ContentItemDisplay destination, IEnumerable destMember, ResolutionContext context)
{
- if (!source.ContentType.Variations.Has(Core.Models.ContentVariation.CultureNeutral))
+ if (!source.ContentType.VariesByCulture())
return Enumerable.Empty();
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
@@ -36,7 +36,7 @@ namespace Umbraco.Web.Models.Mapping
{
Language = x,
Mandatory = x.Mandatory,
- Name = source.GetName(x.IsoCode),
+ Name = source.GetCultureName(x.IsoCode),
Exists = source.IsCultureAvailable(x.IsoCode), // segments ??
PublishedState = (source.PublishedState == PublishedState.Unpublished //if the entire document is unpublished, then flag every variant as unpublished
? PublishedState.Unpublished
diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs
index 5353614033..ffcd39856e 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs
@@ -68,11 +68,11 @@ namespace Umbraco.Web.Models.Mapping
var culture = context.GetCulture();
//a culture needs to be in the context for a property type that can vary
- if (culture == null && property.PropertyType.Variations.Has(ContentVariation.CultureNeutral))
+ if (culture == null && property.PropertyType.VariesByCulture())
throw new InvalidOperationException($"No languageId found in mapping operation when one is required for the culture neutral property type {property.PropertyType.Alias}");
//set the culture to null if it's an invariant property type
- culture = !property.PropertyType.Variations.Has(ContentVariation.CultureNeutral) ? null : culture;
+ culture = !property.PropertyType.VariesByCulture() ? null : culture;
// if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return.
result.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture);
diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs
index 2ee9e38ff5..407fd64372 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs
@@ -112,7 +112,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore())
.ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore())
.ForMember(display => display.Notifications, opt => opt.Ignore())
- .ForMember(display => display.AllowCultureVariant, opt => opt.MapFrom(type => type.Variations.HasFlag(ContentVariation.CultureNeutral)))
+ .ForMember(display => display.AllowCultureVariant, opt => opt.MapFrom(type => type.VariesByCulture()))
.AfterMap((source, dest) =>
{
//sync templates
diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs
index fcfe9a47cc..e25568868f 100644
--- a/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs
@@ -13,12 +13,10 @@ namespace Umbraco.Web.Models.Mapping
public ContentVariation Resolve(TSource source, TDestination destination, ContentVariation destMember, ResolutionContext context)
{
//this will always be the case, a content type will always be allowed to be invariant
- var result = ContentVariation.InvariantNeutral;
+ var result = ContentVariation.Nothing;
if (source.AllowCultureVariant)
- {
- result |= ContentVariation.CultureNeutral;
- }
+ result |= ContentVariation.Culture;
return result;
}
diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs
index 63c8c5e97a..fbd7be4ecd 100644
--- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs
@@ -221,7 +221,7 @@ namespace Umbraco.Web.Models.Mapping
SortOrder = p.SortOrder,
ContentTypeId = contentType.Id,
ContentTypeName = contentType.Name,
- AllowCultureVariant = p.Variations.HasFlag(Core.Models.ContentVariation.CultureNeutral)
+ AllowCultureVariant = p.VariesByCulture()
});
}
diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeVariationsResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeVariationsResolver.cs
index 6c0fa9915e..00472a291c 100644
--- a/src/Umbraco.Web/Models/Mapping/PropertyTypeVariationsResolver.cs
+++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeVariationsResolver.cs
@@ -12,12 +12,7 @@ namespace Umbraco.Web.Models.Mapping
{
public ContentVariation Resolve(PropertyTypeBasic source, PropertyType destination, ContentVariation destMember, ResolutionContext context)
{
- //A property type should only be one type of culture variation.
- //If a property type allows both variant and invariant then it generally won't be able to save because validation
- //occurs when performing something like IContent.TryPublishAllValues and it will result in validation problems because
- //the invariant value will not be set since in the UI only the variant values are edited if it supports it.
- var result = source.AllowCultureVariant ? ContentVariation.CultureNeutral : ContentVariation.InvariantNeutral;
- return result;
+ return source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing;
}
}
}
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs
index 39fb005ba3..2d24efdd67 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs
@@ -191,10 +191,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (culture != null && segment != null) return;
// use context values
- // fixme CultureSegment?
var publishedVariationContext = _content.VariationContextAccessor?.VariationContext;
- if (culture == null) culture = _variations.Has(ContentVariation.CultureNeutral) ? publishedVariationContext?.Culture : "";
- if (segment == null) segment = _variations.Has(ContentVariation.CultureNeutral) ? publishedVariationContext?.Segment : "";
+ if (culture == null) culture = _variations.VariesByCulture() ? publishedVariationContext?.Culture : "";
+ if (segment == null) segment = _variations.VariesBySegment() ? publishedVariationContext?.Segment : "";
}
public override object GetValue(string culture = null, string segment = null)
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
index 567201ef6f..f8e8cdb974 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs
@@ -178,7 +178,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
get
{
- if (!ContentType.Variations.Has(ContentVariation.CultureNeutral)) // fixme CultureSegment?
+ if (!ContentType.VariesByCulture())
return _contentData.Name;
var culture = VariationContextAccessor.VariationContext.Culture;
@@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
get
{
- if (!ContentType.Variations.Has(ContentVariation.CultureNeutral)) // fixme CultureSegment?
+ if (!ContentType.VariesByCulture())
return _urlSegment;
var culture = VariationContextAccessor.VariationContext.Culture;
@@ -258,7 +258,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
get
{
- if (!ContentType.Variations.Has(ContentVariation.CultureNeutral)) // fixme CultureSegment?
+ if (!ContentType.VariesByCulture())
return NoCultureInfos;
if (_cultureInfos != null) return _cultureInfos;
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
index fca9458565..34efa7136a 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs
@@ -1188,13 +1188,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
var names = content is IContent document
? (published
- ? document.PublishCultureNames
+ ? document.PublishNames
: document.CultureNames)
: content.CultureNames;
foreach (var (culture, name) in names)
{
- cultureData[culture] = new CultureVariation { Name = name, Date = content.GetCultureDate(culture) };
+ cultureData[culture] = new CultureVariation { Name = name, Date = content.GetCultureDate(culture) ?? DateTime.MinValue };
}
//the dictionary that will be serialized
diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs
index 1d3c747b6e..5e1708f118 100644
--- a/src/Umbraco.Web/PublishedContentExtensions.cs
+++ b/src/Umbraco.Web/PublishedContentExtensions.cs
@@ -71,7 +71,7 @@ namespace Umbraco.Web
public static string GetUrlSegment(this IPublishedContent content, string culture = null)
{
// for invariant content, return the invariant url segment
- if (!content.ContentType.Variations.Has(ContentVariation.CultureNeutral))
+ if (!content.ContentType.VariesByCulture())
return content.UrlSegment;
// content.GetCulture(culture) will use the 'current' culture (via accessor) in case 'culture'
diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs
index 7ea359fa67..9e89459774 100644
--- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs
+++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs
@@ -87,7 +87,7 @@ namespace Umbraco.Web.Routing
while (domainUris == null && n != null) // n is null at root
{
n = n.Parent; // move to parent node
- domainUris = n == null ? null : domainHelper.DomainsForNode(n.Id, current, false);
+ domainUris = n == null ? null : domainHelper.DomainsForNode(n.Id, current, excludeDefault: true);
}
// no domains = exit
diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs
index 67bd27f959..b6d79e788a 100644
--- a/src/Umbraco.Web/Routing/DomainHelper.cs
+++ b/src/Umbraco.Web/Routing/DomainHelper.cs
@@ -135,8 +135,8 @@ namespace Umbraco.Web.Routing
return null;
// sanitize cultures
- culture = culture.NullEmpty();
- defaultCulture = defaultCulture.NullEmpty();
+ culture = culture.NullOrWhiteSpaceAsNull();
+ defaultCulture = defaultCulture.NullOrWhiteSpaceAsNull();
if (uri == null)
{
diff --git a/src/Umbraco.Web/Routing/PublishedRouter.cs b/src/Umbraco.Web/Routing/PublishedRouter.cs
index 1cc487fd99..4c137b2d81 100644
--- a/src/Umbraco.Web/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Web/Routing/PublishedRouter.cs
@@ -295,7 +295,7 @@ namespace Umbraco.Web.Routing
return false;
// invariant - always published
- if (!domainDocument.ContentType.Variations.Has(ContentVariation.CultureNeutral))
+ if (!domainDocument.ContentType.VariesByCulture())
return true;
// variant, ensure that the culture corresponding to the domain's language is published
diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs
index 86fc51fe44..b7e26f894e 100644
--- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs
+++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs
@@ -40,7 +40,7 @@ namespace Umbraco.Web.Routing
// if content is variant, go with the current culture - and that is NOT safe, there may be
// no 'main' url for that culture, deal with it later - otherwise, go with the invariant
// culture, and that is safe.
- var varies = content.ContentType.Variations.DoesSupportCulture();
+ var varies = content.ContentType.VariesByCulture();
var culture = varies ? Thread.CurrentThread.CurrentUICulture.Name : "";
if (content.Published == false)
diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs
index 3e24e037df..4e032b8b25 100644
--- a/src/Umbraco.Web/Trees/ContentTreeController.cs
+++ b/src/Umbraco.Web/Trees/ContentTreeController.cs
@@ -264,7 +264,7 @@ namespace Umbraco.Web.Trees
// for those items that DO support cultures, we need to get the proper name, IF it exists
// otherwise, invariant is fine
- if (docEntity.Variations.Has(Core.Models.ContentVariation.CultureNeutral) &&
+ if (docEntity.Variations.VariesByCulture() &&
docEntity.CultureNames.TryGetValue(culture, out var name) &&
!string.IsNullOrWhiteSpace(name))
{
diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
index da7f723e12..1df1445979 100644
--- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
+++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs
@@ -58,10 +58,10 @@ namespace Umbraco.Web.WebApi.Binders
protected override bool ValidateCultureVariant(ContentItemSave postedItem, HttpActionContext actionContext)
{
var contentType = postedItem.PersistedContent.GetContentType();
- if (contentType.Variations.DoesSupportCulture() && postedItem.Culture.IsNullOrWhiteSpace())
+ if (contentType.VariesByCulture() && postedItem.Culture.IsNullOrWhiteSpace())
{
//we cannot save a content item that is culture variant if no culture was specified in the request!
- actionContext.Response = actionContext.Request.CreateValidationErrorResponse($"No 'Culture' found in request. Cannot save a content item that is of a {Core.Models.ContentVariation.CultureNeutral} content type without a specified culture.");
+ actionContext.Response = actionContext.Request.CreateValidationErrorResponse($"No culture found in request. Cannot save a content item that varies by culture, without a specified culture.");
return false;
}
return true;
diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs
index d61d21700a..a1da34d46e 100644
--- a/src/Umbraco.Web/umbraco.presentation/page.cs
+++ b/src/Umbraco.Web/umbraco.presentation/page.cs
@@ -478,14 +478,14 @@ namespace umbraco
{
get
{
- if (!_inner.ContentType.Variations.HasFlag(ContentVariation.CultureNeutral)) // fixme CultureSegment?
+ if (!_inner.ContentType.VariesByCulture())
return NoCultureInfos;
if (_cultureInfos != null)
return _cultureInfos;
- return _cultureInfos = _inner.PublishCultureNames
- .ToDictionary(x => x.Key, x => new PublishedCultureInfo(x.Key, x.Value, _inner.GetCulturePublishDate(x.Key)));
+ return _cultureInfos = _inner.PublishNames
+ .ToDictionary(x => x.Key, x => new PublishedCultureInfo(x.Key, x.Value, _inner.GetPublishDate(x.Key) ?? DateTime.MinValue));
}
}