diff --git a/src/Umbraco.Core/Collections/CompositeStringStringKey.cs b/src/Umbraco.Core/Collections/CompositeStringStringKey.cs new file mode 100644 index 0000000000..11f123f2d0 --- /dev/null +++ b/src/Umbraco.Core/Collections/CompositeStringStringKey.cs @@ -0,0 +1,41 @@ +using System; + +namespace Umbraco.Core.Collections +{ + /// + /// Represents a composite key of (string, string) for fast dictionaries. + /// + /// + /// The string parts of the key are case-insensitive. + /// Null is a valid value for both parts. + /// + public struct CompositeStringStringKey : IEquatable + { + private readonly string _key1; + private readonly string _key2; + + /// + /// Initializes a new instance of the struct. + /// + public CompositeStringStringKey(string key1, string key2) + { + _key1 = key1?.ToLowerInvariant() ?? "NULL"; + _key2 = key2?.ToLowerInvariant() ?? "NULL"; + } + + public bool Equals(CompositeStringStringKey other) + => _key2 == other._key2 && _key1 == other._key1; + + public override bool Equals(object obj) + => obj is CompositeStringStringKey other && _key2 == other._key2 && _key1 == other._key1; + + public override int GetHashCode() + => _key2.GetHashCode() * 31 + _key1.GetHashCode(); + + public static bool operator ==(CompositeStringStringKey key1, CompositeStringStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; + + public static bool operator !=(CompositeStringStringKey key1, CompositeStringStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; + } +} diff --git a/src/Umbraco.Core/IO/MediaFileSystem.cs b/src/Umbraco.Core/IO/MediaFileSystem.cs index b96b0d54bc..5534936a04 100644 --- a/src/Umbraco.Core/IO/MediaFileSystem.cs +++ b/src/Umbraco.Core/IO/MediaFileSystem.cs @@ -335,35 +335,35 @@ namespace Umbraco.Core.IO // fixme - what's below belongs to the upload property editor, not the media filesystem! - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, int? languageId = null, string segment = null) + public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) { var property = GetProperty(content, propertyTypeAlias); - var oldpath = property.GetValue(languageId, segment) is string svalue ? GetRelativePath(svalue) : null; + var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath); - property.SetValue(GetUrl(filepath), languageId, segment); - SetUploadFile(content, property, filepath, filestream, languageId, segment); + property.SetValue(GetUrl(filepath), culture, segment); + SetUploadFile(content, property, filepath, filestream, culture, segment); } - public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, int? languageId = null, string segment = null) + public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath, string culture = null, string segment = null) { var property = GetProperty(content, propertyTypeAlias); // fixme delete? - var oldpath = property.GetValue(languageId, segment) is string svalue ? GetRelativePath(svalue) : null; + var oldpath = property.GetValue(culture, segment) is string svalue ? GetRelativePath(svalue) : null; if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath) DeleteFile(oldpath); - property.SetValue(GetUrl(filepath), languageId, segment); + property.SetValue(GetUrl(filepath), culture, segment); using (var filestream = OpenFile(filepath)) { - SetUploadFile(content, property, filepath, filestream, languageId, segment); + SetUploadFile(content, property, filepath, filestream, culture, segment); } } // sets a file for the FileUpload property editor // ie generates thumbnails and populates autofill properties - private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, int? languageId = null, string segment = null) + private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream, string culture = null, string segment = null) { // will use filepath for extension, and filestream for length - UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, languageId, segment); + UploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, culture, segment); } #endregion diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index 3fdaf7b3d3..4ab2bf7a7c 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -41,9 +41,9 @@ namespace Umbraco.Core.Media /// /// The content item. /// The property type alias. - /// Variation language. + /// Variation language. /// Variation segment. - public void Reset(IContentBase content, string propertyTypeAlias, int? languageId, string segment) + public void Reset(IContentBase content, string propertyTypeAlias, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); @@ -53,7 +53,7 @@ namespace Umbraco.Core.Media if (autoFillConfig == null) return; // nothing // reset - Reset(content, autoFillConfig, languageId, segment); + Reset(content, autoFillConfig, culture, segment); } /// @@ -61,14 +61,14 @@ namespace Umbraco.Core.Media /// /// The content item. /// The auto-fill configuration. - /// Variation language. + /// Variation language. /// Variation segment. - public void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig, int? languageId, string segment) + public void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); - ResetProperties(content, autoFillConfig, languageId, segment); + ResetProperties(content, autoFillConfig, culture, segment); } /// @@ -77,9 +77,9 @@ namespace Umbraco.Core.Media /// The content item. /// The property type alias. /// The filesystem-relative filepath, or null to clear properties. - /// Variation language. + /// Variation language. /// Variation segment. - public void Populate(IContentBase content, string propertyTypeAlias, string filepath, int? languageId, string segment) + public void Populate(IContentBase content, string propertyTypeAlias, string filepath, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); @@ -92,7 +92,7 @@ namespace Umbraco.Core.Media if (autoFillConfig == null) return; // nothing // populate - Populate(content, autoFillConfig, filepath, languageId, segment); + Populate(content, autoFillConfig, filepath, culture, segment); } /// @@ -102,9 +102,9 @@ namespace Umbraco.Core.Media /// The property type alias. /// The filesystem-relative filepath, or null to clear properties. /// The stream containing the file data. - /// Variation language. + /// Variation language. /// Variation segment. - public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, int? languageId, string segment) + public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (propertyTypeAlias == null) throw new ArgumentNullException(nameof(propertyTypeAlias)); @@ -117,7 +117,7 @@ namespace Umbraco.Core.Media if (autoFillConfig == null) return; // nothing // populate - Populate(content, autoFillConfig, filepath, filestream, languageId, segment); + Populate(content, autoFillConfig, filepath, filestream, culture, segment); } /// @@ -127,9 +127,9 @@ namespace Umbraco.Core.Media /// The auto-fill configuration. /// The filesystem path to the uploaded file. /// The parameter is the path relative to the filesystem. - /// Variation language. + /// Variation language. /// Variation segment. - public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, int? languageId, string segment) + public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); @@ -137,7 +137,7 @@ namespace Umbraco.Core.Media // no file = reset, file = auto-fill if (filepath.IsNullOrWhiteSpace()) { - ResetProperties(content, autoFillConfig, languageId, segment); + ResetProperties(content, autoFillConfig, culture, segment); } else { @@ -148,13 +148,13 @@ namespace Umbraco.Core.Media { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); var size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null; - SetProperties(content, autoFillConfig, size, filestream.Length, extension, languageId, segment); + SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } } catch (Exception ex) { _logger.Error(typeof(UploadAutoFillProperties), $"Could not populate upload auto-fill properties for file \"{filepath}\".", ex); - ResetProperties(content, autoFillConfig, languageId, segment); + ResetProperties(content, autoFillConfig, culture, segment); } } } @@ -166,9 +166,9 @@ namespace Umbraco.Core.Media /// /// The filesystem-relative filepath, or null to clear properties. /// The stream containing the file data. - /// Variation language. + /// Variation language. /// Variation segment. - public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, int? languageId, string segment) + public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); @@ -176,50 +176,50 @@ namespace Umbraco.Core.Media // no file = reset, file = auto-fill if (filepath.IsNullOrWhiteSpace() || filestream == null) { - ResetProperties(content, autoFillConfig, languageId, segment); + ResetProperties(content, autoFillConfig, culture, segment); } else { var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.'); var size = _mediaFileSystem.IsImageFile(extension) ? (Size?)_mediaFileSystem.GetDimensions(filestream) : null; - SetProperties(content, autoFillConfig, size, filestream.Length, extension, languageId, segment); + SetProperties(content, autoFillConfig, size, filestream.Length, extension, culture, segment); } } - private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension, int? languageId, string segment) + private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias].SetValue(size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty, languageId, segment); + content.Properties[autoFillConfig.WidthFieldAlias].SetValue(size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty, culture, segment); if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias].SetValue(size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty, languageId, segment); + content.Properties[autoFillConfig.HeightFieldAlias].SetValue(size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty, culture, segment); if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias].SetValue(length, languageId, segment); + content.Properties[autoFillConfig.LengthFieldAlias].SetValue(length, culture, segment); if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(extension, languageId, segment); + content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(extension, culture, segment); } - private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, int? languageId, string segment) + private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string culture, string segment) { if (content == null) throw new ArgumentNullException(nameof(content)); if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias].SetValue(string.Empty, languageId, segment); + content.Properties[autoFillConfig.WidthFieldAlias].SetValue(string.Empty, culture, segment); if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias].SetValue(string.Empty, languageId, segment); + content.Properties[autoFillConfig.HeightFieldAlias].SetValue(string.Empty, culture, segment); if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias].SetValue(string.Empty, languageId, segment); + content.Properties[autoFillConfig.LengthFieldAlias].SetValue(string.Empty, culture, segment); if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(string.Empty, languageId, segment); + content.Properties[autoFillConfig.ExtensionFieldAlias].SetValue(string.Empty, culture, segment); } } } diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 142d7540c2..1f106124b4 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models private PublishedState _publishedState; private DateTime? _releaseDate; private DateTime? _expireDate; - private Dictionary _publishNames; + private Dictionary _publishNames; private static readonly Lazy Ps = new Lazy(); @@ -201,24 +201,24 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] - public IReadOnlyDictionary PublishNames => _publishNames ?? NoNames; + public IReadOnlyDictionary PublishNames => _publishNames ?? NoNames; /// - public string GetPublishName(int? languageId) + public string GetPublishName(string culture) { - if (languageId == null) return PublishName; + if (culture == null) return PublishName; if (_publishNames == null) return null; - return _publishNames.TryGetValue(languageId.Value, out var name) ? name : null; + return _publishNames.TryGetValue(culture, out var name) ? name : null; } // sets a publish name // internal for repositories - internal void SetPublishName(int? languageId, string name) + internal void SetPublishName(string culture, string name) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name)); - if (languageId == null) + if (culture == null) { PublishName = name; return; @@ -227,22 +227,22 @@ namespace Umbraco.Core.Models // private method, assume that culture is valid if (_publishNames == null) - _publishNames = new Dictionary(); + _publishNames = new Dictionary(StringComparer.OrdinalIgnoreCase); - _publishNames[languageId.Value] = name; + _publishNames[culture] = name; } // clears a publish name - private void ClearPublishName(int? languageId) + private void ClearPublishName(string culture) { - if (languageId == null) + if (culture == null) { PublishName = null; return; } if (_publishNames == null) return; - _publishNames.Remove(languageId.Value); + _publishNames.Remove(culture); if (_publishNames.Count == 0) _publishNames = null; } @@ -255,12 +255,12 @@ namespace Umbraco.Core.Models } /// - public bool IsCultureAvailable(int? languageId) - => !string.IsNullOrWhiteSpace(GetName(languageId)); + public bool IsCultureAvailable(string culture) + => !string.IsNullOrWhiteSpace(GetName(culture)); /// - public bool IsCulturePublished(int? languageId) - => !string.IsNullOrWhiteSpace(GetPublishName(languageId)); + public bool IsCulturePublished(string culture) + => !string.IsNullOrWhiteSpace(GetPublishName(culture)); [IgnoreDataMember] public int PublishedVersionId { get; internal set; } @@ -275,57 +275,72 @@ namespace Umbraco.Core.Models if (ValidateAll().Any()) return false; - // property.PublishAllValues only deals with supported variations (if any) - foreach (var property in Properties) - property.PublishAllValues(); - // Name and PublishName are managed by the repository, but Names and PublishNames // must be managed here as they depend on the existing / supported variations. - PublishName = Name; - foreach (var (languageId, name) in Names) - SetPublishName(languageId, name); + if (string.IsNullOrWhiteSpace(Name)) + throw new InvalidOperationException($"Cannot publish invariant culture without a name."); + PublishName = Name; + foreach (var (culture, name) in Names) + { + if (string.IsNullOrWhiteSpace(name)) + throw new InvalidOperationException($"Cannot publish {culture ?? "invariant"} culture without a name."); + SetPublishName(culture, name); + } + // property.PublishAllValues only deals with supported variations (if any) + foreach (var property in Properties) + property.PublishAllValues(); + _publishedState = PublishedState.Publishing; return true; } /// - public virtual bool PublishValues(int? languageId = null, string segment = null) + public virtual bool PublishValues(string culture = null, string segment = null) { // the variation should be supported by the content type - ContentType.ValidateVariation(languageId, segment, throwIfInvalid: true); + ContentType.ValidateVariation(culture, segment, throwIfInvalid: true); // the values we want to publish should be valid - if (Validate(languageId, segment).Any()) + if (Validate(culture, segment).Any()) return false; - // property.PublishValue throws on invalid variation, so filter them out - foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(languageId, segment, throwIfInvalid: false))) - property.PublishValue(languageId, 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. - SetPublishName(languageId, GetName(languageId)); + if (segment == null) + { + var name = GetName(culture); + if (string.IsNullOrWhiteSpace(name)) + throw new InvalidOperationException($"Cannot publish {culture ?? "invariant"} culture without a name."); + SetPublishName(culture, name); + } + // property.PublishValue throws on invalid variation, so filter them out + foreach (var property in Properties.Where(x => x.PropertyType.ValidateVariation(culture, segment, throwIfInvalid: false))) + property.PublishValue(culture, segment); + _publishedState = PublishedState.Publishing; return true; } /// - public virtual bool PublishCultureValues(int? languageId = null) + public virtual bool PublishCultureValues(string culture = null) { // the values we want to publish should be valid - if (ValidateCulture(languageId).Any()) + if (ValidateCulture(culture).Any()) return false; - // property.PublishCultureValues only deals with supported variations (if any) - foreach (var property in Properties) - property.PublishCultureValues(languageId); - // Name and PublishName are managed by the repository, but Names and PublishNames // must be managed here as they depend on the existing / supported variations. - SetPublishName(languageId, GetName(languageId)); + var name = GetName(culture); + if (string.IsNullOrWhiteSpace(name)) + throw new InvalidOperationException($"Cannot publish {culture ?? "invariant"} culture without a name."); + SetPublishName(culture, name); + // property.PublishCultureValues only deals with supported variations (if any) + foreach (var property in Properties) + property.PublishCultureValues(culture); + _publishedState = PublishedState.Publishing; return true; } @@ -345,32 +360,32 @@ namespace Umbraco.Core.Models } /// - public virtual void ClearPublishedValues(int? languageId = null, string segment = null) + public virtual void ClearPublishedValues(string culture = null, string segment = null) { // the variation should be supported by the content type - ContentType.ValidateVariation(languageId, segment, throwIfInvalid: true); + 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(languageId, segment, throwIfInvalid: false))) - property.ClearPublishedValue(languageId, segment); + 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(languageId); + ClearPublishName(culture); _publishedState = PublishedState.Publishing; } /// - public virtual void ClearCulturePublishedValues(int? languageId = null) + public virtual void ClearCulturePublishedValues(string culture = null) { // property.ClearPublishedCultureValues only deals with supported variations (if any) foreach (var property in Properties) - property.ClearPublishedCultureValues(languageId); + 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(languageId); + ClearPublishName(culture); _publishedState = PublishedState.Publishing; } @@ -397,8 +412,8 @@ namespace Umbraco.Core.Models // clear all existing properties foreach (var property in Properties) foreach (var pvalue in property.Values) - if (property.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) - property.SetValue(null, pvalue.LanguageId, pvalue.Segment); + if (property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) + property.SetValue(null, pvalue.Culture, pvalue.Segment); // copy other properties var otherProperties = other.Properties; @@ -407,10 +422,10 @@ namespace Umbraco.Core.Models var alias = otherProperty.PropertyType.Alias; foreach (var pvalue in otherProperty.Values) { - if (!otherProperty.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) + if (!otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) continue; var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - SetValue(alias, value, pvalue.LanguageId, pvalue.Segment); + SetValue(alias, value, pvalue.Culture, pvalue.Segment); } } @@ -422,7 +437,7 @@ namespace Umbraco.Core.Models } /// - public virtual void CopyValues(IContent other, int? languageId = null, string segment = null) + public virtual void CopyValues(IContent other, string culture = null, string segment = null) { if (other.ContentTypeId != ContentTypeId) throw new InvalidOperationException("Cannot copy values from a different content type."); @@ -437,31 +452,31 @@ namespace Umbraco.Core.Models // clear all existing properties foreach (var property in Properties) { - if (!property.PropertyType.ValidateVariation(languageId, segment, false)) + if (!property.PropertyType.ValidateVariation(culture, segment, false)) continue; foreach (var pvalue in property.Values) - if (pvalue.LanguageId == languageId && pvalue.Segment == segment) - property.SetValue(null, pvalue.LanguageId, pvalue.Segment); + if (pvalue.Culture.InvariantEquals(culture) && pvalue.Segment.InvariantEquals(segment)) + property.SetValue(null, pvalue.Culture, pvalue.Segment); } // copy other properties var otherProperties = other.Properties; foreach (var otherProperty in otherProperties) { - if (!otherProperty.PropertyType.ValidateVariation(languageId, segment, false)) + if (!otherProperty.PropertyType.ValidateVariation(culture, segment, false)) continue; var alias = otherProperty.PropertyType.Alias; - SetValue(alias, otherProperty.GetValue(languageId, segment, published), languageId, segment); + SetValue(alias, otherProperty.GetValue(culture, segment, published), culture, segment); } // copy name - SetName(languageId, other.GetName(languageId)); + SetName(culture, other.GetName(culture)); } /// - public virtual void CopyCultureValues(IContent other, int? languageId = null) + public virtual void CopyCultureValues(IContent other, string culture = null) { if (other.ContentTypeId != ContentTypeId) throw new InvalidOperationException("Cannot copy values from a different content type."); @@ -473,8 +488,8 @@ namespace Umbraco.Core.Models // clear all existing properties foreach (var property in Properties) foreach (var pvalue in property.Values) - if (pvalue.LanguageId == languageId && property.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) - property.SetValue(null, pvalue.LanguageId, pvalue.Segment); + if (pvalue.Culture.InvariantEquals(culture) && property.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) + property.SetValue(null, pvalue.Culture, pvalue.Segment); // copy other properties var otherProperties = other.Properties; @@ -483,15 +498,15 @@ namespace Umbraco.Core.Models var alias = otherProperty.PropertyType.Alias; foreach (var pvalue in otherProperty.Values) { - if (pvalue.LanguageId != languageId || !otherProperty.PropertyType.ValidateVariation(pvalue.LanguageId, pvalue.Segment, false)) + if (pvalue.Culture != culture || !otherProperty.PropertyType.ValidateVariation(pvalue.Culture, pvalue.Segment, false)) continue; var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - SetValue(alias, value, pvalue.LanguageId, pvalue.Segment); + SetValue(alias, value, pvalue.Culture, pvalue.Segment); } } // copy name - SetName(languageId, other.GetName(languageId)); + SetName(culture, other.GetName(culture)); } /// diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 8203cba985..32a26f9dc0 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -18,14 +18,14 @@ namespace Umbraco.Core.Models [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] public abstract class ContentBase : TreeEntityBase, IContentBase { - protected static readonly Dictionary NoNames = new Dictionary(); + protected static readonly Dictionary NoNames = new Dictionary(); private static readonly Lazy Ps = new Lazy(); private int _contentTypeId; protected IContentTypeComposition ContentTypeBase; private int _writerId; private PropertyCollection _properties; - private Dictionary _names; + private Dictionary _names; /// /// Initializes a new instance of the class. @@ -67,7 +67,7 @@ namespace Umbraco.Core.Models public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ContentTypeId); public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo(x => x.Properties); public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo(x => x.WriterId); - public readonly PropertyInfo NamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.Names); + public readonly PropertyInfo NamesSelector = ExpressionHelper.GetPropertyInfo>(x => x.Names); } protected void PropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -123,26 +123,26 @@ namespace Umbraco.Core.Models /// [DataMember] - public virtual IReadOnlyDictionary Names + public virtual IReadOnlyDictionary Names { get => _names ?? NoNames; set { - foreach (var (languageId, name) in value) - SetName(languageId, name); + foreach (var (culture, name) in value) + SetName(culture, name); } } /// - public virtual void SetName(int? languageId, string name) + public virtual void SetName(string culture, string name) { if (string.IsNullOrWhiteSpace(name)) { - ClearName(languageId); + ClearName(culture); return; } - if (languageId == null) + if (culture == null) { Name = name; return; @@ -152,22 +152,22 @@ namespace Umbraco.Core.Models throw new NotSupportedException("Content type does not support varying name by culture."); if (_names == null) - _names = new Dictionary(); + _names = new Dictionary(StringComparer.OrdinalIgnoreCase); - _names[languageId.Value] = name; + _names[culture] = name; OnPropertyChanged(Ps.Value.NamesSelector); } - private void ClearName(int? languageId) + private void ClearName(string culture) { - if (languageId == null) + if (culture == null) { Name = null; return; } if (_names == null) return; - _names.Remove(languageId.Value); + _names.Remove(culture); if (_names.Count == 0) _names = null; } @@ -179,11 +179,11 @@ namespace Umbraco.Core.Models } /// - public virtual string GetName(int? languageId) + public virtual string GetName(string culture) { - if (languageId == null) return Name; + if (culture == null) return Name; if (_names == null) return null; - return _names.TryGetValue(languageId.Value, out var name) ? name : null; + return _names.TryGetValue(culture, out var name) ? name : null; } /// @@ -207,29 +207,29 @@ namespace Umbraco.Core.Models => Properties.Contains(propertyTypeAlias); /// - public virtual object GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false) + public virtual object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) { return Properties.TryGetValue(propertyTypeAlias, out var property) - ? property.GetValue(languageId, segment, published) + ? property.GetValue(culture, segment, published) : null; } /// - public virtual TValue GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false) + public virtual TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false) { if (!Properties.TryGetValue(propertyTypeAlias, out var property)) return default; - var convertAttempt = property.GetValue(languageId, segment, published).TryConvertTo(); + var convertAttempt = property.GetValue(culture, segment, published).TryConvertTo(); return convertAttempt.Success ? convertAttempt.Result : default; } /// - public virtual void SetValue(string propertyTypeAlias, object value, int? languageId = null, string segment = null) + public virtual void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null) { if (Properties.Contains(propertyTypeAlias)) { - Properties[propertyTypeAlias].SetValue(value, languageId, segment); + Properties[propertyTypeAlias].SetValue(value, culture, segment); return; } @@ -238,7 +238,7 @@ namespace Umbraco.Core.Models throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); var property = propertyType.CreateProperty(); - property.SetValue(value, languageId, segment); + property.SetValue(value, culture, segment); Properties.Add(property); } @@ -249,17 +249,17 @@ namespace Umbraco.Core.Models /// /// Sets the posted file value of a property. /// - public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, int? languageId = null, string segment = null) + public virtual void SetValue(string propertyTypeAlias, HttpPostedFile value, string culture = null, string segment = null) { - ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), languageId, segment); + ContentExtensions.SetValue(this, propertyTypeAlias, new HttpPostedFileWrapper(value), culture, segment); } /// /// Sets the posted file value of a property. /// - public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, int? languageId = null, string segment = null) + public virtual void SetValue(string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) { - ContentExtensions.SetValue(this, propertyTypeAlias, value, languageId, segment); + ContentExtensions.SetValue(this, propertyTypeAlias, value, culture, segment); } #endregion @@ -271,14 +271,14 @@ namespace Umbraco.Core.Models return Properties.Where(x => !x.IsAllValid()).ToArray(); } - public virtual Property[] Validate(int? languageId = null, string segment = null) + public virtual Property[] Validate(string culture = null, string segment = null) { - return Properties.Where(x => !x.IsValid(languageId, segment)).ToArray(); + return Properties.Where(x => !x.IsValid(culture, segment)).ToArray(); } - public virtual Property[] ValidateCulture(int? languageId = null) + public virtual Property[] ValidateCulture(string culture = null) { - return Properties.Where(x => !x.IsCultureValid(languageId)).ToArray(); + return Properties.Where(x => !x.IsCultureValid(culture)).ToArray(); } #endregion diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index 5b7e825d68..0940675346 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -284,7 +284,7 @@ namespace Umbraco.Core.Models /// /// Sets the posted file value of a property. /// - public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, int? languageId = null, string segment = null) + public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value, string culture = null, string segment = null) { // ensure we get the filename without the path in IE in intranet mode // http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie @@ -303,7 +303,7 @@ namespace Umbraco.Core.Models if (string.IsNullOrWhiteSpace(filename)) return; filename = filename.ToLower(); // fixme - er... why? - MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, languageId, segment); + MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream, culture, segment); } /// @@ -312,7 +312,7 @@ namespace Umbraco.Core.Models /// This really is for FileUpload fields only, and should be obsoleted. For anything else, /// you need to store the file by yourself using Store and then figure out /// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself. - public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, int? languageId = null, string segment = null) + public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string culture = null, string segment = null) { if (filename == null || filestream == null) return; @@ -321,7 +321,7 @@ namespace Umbraco.Core.Models if (string.IsNullOrWhiteSpace(filename)) return; filename = filename.ToLower(); // fixme - er... why? - MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, languageId, segment); + MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream, culture, segment); } /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index b686b97108..7d0ffea31e 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -204,10 +204,10 @@ namespace Umbraco.Core.Models /// /// Validates that a variation is valid for the content type. /// - public bool ValidateVariation(int? languageId, string segment, bool throwIfInvalid) + public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) { ContentVariation variation; - if (languageId.HasValue) + if (culture != null) { variation = segment != null ? ContentVariation.CultureSegment diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index 3f7a335620..3df81f7314 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -59,7 +59,7 @@ namespace Umbraco.Core.Models /// Gets the date and time the content was published. /// DateTime? PublishDate { get; } - + /// /// Gets or sets the date and time the content item should be published. /// @@ -87,7 +87,7 @@ namespace Umbraco.Core.Models /// A culture becomes available whenever the content name for this culture is /// non-null, and it becomes unavailable whenever the content name is null. /// - bool IsCultureAvailable(int? languageId); + bool IsCultureAvailable(string culture); /// /// Gets a value indicating whether a given culture is published. @@ -97,17 +97,17 @@ namespace Umbraco.Core.Models /// and the content published name for this culture is non-null. It becomes non-published /// whenever values for this culture are unpublished. /// - bool IsCulturePublished(int? languageId); + bool IsCulturePublished(string culture); /// /// Gets the name of the published version of the content for a given culture. /// /// /// When editing the content, the name can change, but this will not until the content is published. - /// When is null, gets the invariant + /// When is null, gets the invariant /// language, which is the value of the property. /// - string GetPublishName(int? languageId); + string GetPublishName(string culture); /// /// Gets the published names of the content. @@ -116,7 +116,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 PublishNames { get; } + IReadOnlyDictionary PublishNames { get; } // fixme - these two should move to some kind of service @@ -159,7 +159,7 @@ namespace Umbraco.Core.Models /// The document must then be published via the content service. /// Values are not published if they are not valid. /// - bool PublishValues(int? languageId = null, string segment = null); + bool PublishValues(string culture = null, string segment = null); /// /// Publishes the culture/any values. @@ -169,7 +169,7 @@ namespace Umbraco.Core.Models /// The document must then be published via the content service. /// Values are not published if they are not valie. /// - bool PublishCultureValues(int? languageId = null); + bool PublishCultureValues(string culture = null); /// /// Clears all published values. @@ -179,12 +179,12 @@ namespace Umbraco.Core.Models /// /// Clears published values. /// - void ClearPublishedValues(int? languageId = null, string segment = null); + void ClearPublishedValues(string culture = null, string segment = null); /// /// Clears the culture/any published values. /// - void ClearCulturePublishedValues(int? languageId = null); + void ClearCulturePublishedValues(string culture = null); /// /// Copies values from another document. @@ -194,11 +194,11 @@ namespace Umbraco.Core.Models /// /// Copies values from another document. /// - void CopyValues(IContent other, int? languageId = null, string segment = null); + void CopyValues(IContent other, string culture = null, string segment = null); /// /// Copies culture/any values from another document. /// - void CopyCultureValues(IContent other, int? languageId = null); + void CopyCultureValues(IContent other, string culture = null); } } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index aee8acd35e..f8bb0e6985 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -31,19 +31,19 @@ namespace Umbraco.Core.Models /// Sets the name of the content item for a specified language. /// /// - /// When is null, sets the invariant + /// When is null, sets the invariant /// language, which sets the property. /// - void SetName(int? languageId, string value); + void SetName(string culture, string value); /// /// Gets the name of the content item for a specified language. /// /// - /// When is null, gets the invariant + /// When is null, gets the invariant /// language, which is the value of the property. /// - string GetName(int? languageId); + string GetName(string culture); /// /// Gets or sets the names of the content item. @@ -52,7 +52,7 @@ namespace Umbraco.Core.Models /// Because a dictionary key cannot be null this cannot get nor set the invariant /// name, which must be get or set via the property. /// - IReadOnlyDictionary Names { get; set; } + IReadOnlyDictionary Names { get; set; } /// /// List of properties, which make up all the data available for this Content object @@ -82,17 +82,17 @@ namespace Umbraco.Core.Models /// /// Gets the value of a Property /// - object GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false); + object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); /// /// Gets the typed value of a Property /// - TValue GetValue(string propertyTypeAlias, int? languageId = null, string segment = null, bool published = false); + TValue GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false); /// /// Sets the (edited) value of a Property /// - void SetValue(string propertyTypeAlias, object value, int? languageId = null, string segment = null); + void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null); /// /// Gets a value indicating whether the content and all its properties values are valid. @@ -102,11 +102,11 @@ namespace Umbraco.Core.Models /// /// Gets a value indicating whether the content and its properties values are valid. /// - Property[] Validate(int? languageId = null, string segment = null); + Property[] Validate(string culture = null, string segment = null); /// /// Gets a value indicating whether the content and its culture/any properties values are valid. /// - Property[] ValidateCulture(int? languageId = null); + Property[] ValidateCulture(string culture = null); } } diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index 68e5923d66..7c8cc8eafe 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -51,7 +51,7 @@ namespace Umbraco.Core.Models /// /// Validates that a variation is valid for the content type. /// - bool ValidateVariation(int? languageId, string segment, bool throwIfInvalid); + bool ValidateVariation(string culture, string segment, bool throwIfInvalid); /// /// 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 9e3f5fc2e0..709c37daaa 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Models { private List _values = new List(); private PropertyValue _pvalue; - private Dictionary _vvalues; + private Dictionary _vvalues; private static readonly Lazy Ps = new Lazy(); @@ -38,9 +38,14 @@ namespace Umbraco.Core.Models public class PropertyValue { + private string _culture; private string _segment; - public int? LanguageId { get; internal set; } + public string Culture + { + get => _culture; + internal set => _culture = value?.ToLowerInvariant(); + } public string Segment { get => _segment; @@ -50,7 +55,7 @@ namespace Umbraco.Core.Models public object PublishedValue { get; internal set; } public PropertyValue Clone() - => new PropertyValue { LanguageId = LanguageId, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; + => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; } // ReSharper disable once ClassNeverInstantiated.Local @@ -96,10 +101,10 @@ 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.LanguageId, x.Segment, false)).ToList(); - _pvalue = _values.FirstOrDefault(x => !x.LanguageId.HasValue && x.Segment == null); + _values = value.Where(x => PropertyType.ValidateVariation(x.Culture, x.Segment, false)).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 CompositeIntStringKey(x.LanguageId, x.Segment), x => x) + ? _values.Where(x => x != _pvalue).ToDictionary(x => new CompositeStringStringKey(x.Culture, x.Segment), x => x) : null; } } @@ -128,12 +133,12 @@ namespace Umbraco.Core.Models /// /// Gets the value. /// - public object GetValue(int? languageId = null, string segment = null, bool published = false) + public object GetValue(string culture = null, string segment = null, bool published = false) { - if (!PropertyType.ValidateVariation(languageId, segment, false)) return null; - if (!languageId.HasValue && segment == null) return GetPropertyValue(_pvalue, published); + if (!PropertyType.ValidateVariation(culture, segment, false)) return null; + if (culture == null && segment == null) return GetPropertyValue(_pvalue, published); if (_vvalues == null) return null; - return _vvalues.TryGetValue(new CompositeIntStringKey(languageId, segment), out var pvalue) + return _vvalues.TryGetValue(new CompositeStringStringKey(culture, segment), out var pvalue) ? GetPropertyValue(pvalue, published) : null; } @@ -159,7 +164,7 @@ namespace Umbraco.Core.Models if (_vvalues != null) { var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.LanguageId, x.Value.Segment, false)) + .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) .Select(x => x.Value); foreach (var pvalue in pvalues) PublishPropertyValue(pvalue); @@ -168,29 +173,29 @@ namespace Umbraco.Core.Models // internal - must be invoked by the content item // does *not* validate the value - content item must validate first - internal void PublishValue(int? languageId = null, string segment = null) + internal void PublishValue(string culture = null, string segment = null) { - PropertyType.ValidateVariation(languageId, segment, true); + PropertyType.ValidateVariation(culture, segment, true); - (var pvalue, _) = GetPValue(languageId, segment, false); + (var pvalue, _) = GetPValue(culture, segment, false); if (pvalue == null) return; PublishPropertyValue(pvalue); } // internal - must be invoked by the content item // does *not* validate the value - content item must validate first - internal void PublishCultureValues(int? languageId = null) + internal void PublishCultureValues(string culture = null) { // if invariant and invariant-neutral is supported, publish invariant-neutral - if (!languageId.HasValue && PropertyType.ValidateVariation(null, null, false)) + if (culture == null && PropertyType.ValidateVariation(null, null, false)) PublishPropertyValue(_pvalue); // publish everything not invariant-neutral that matches the culture and is supported if (_vvalues != null) { var pvalues = _vvalues - .Where(x => x.Value.LanguageId == languageId) - .Where(x => PropertyType.ValidateVariation(languageId, x.Value.Segment, false)) + .Where(x => x.Value.Culture.InvariantEquals(culture)) + .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) .Select(x => x.Value); foreach (var pvalue in pvalues) PublishPropertyValue(pvalue); @@ -206,7 +211,7 @@ namespace Umbraco.Core.Models if (_vvalues != null) { var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.LanguageId, x.Value.Segment, false)) + .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) .Select(x => x.Value); foreach (var pvalue in pvalues) ClearPublishedPropertyValue(pvalue); @@ -214,25 +219,25 @@ namespace Umbraco.Core.Models } // internal - must be invoked by the content item - internal void ClearPublishedValue(int? languageId = null, string segment = null) + internal void ClearPublishedValue(string culture = null, string segment = null) { - PropertyType.ValidateVariation(languageId, segment, true); - (var pvalue, _) = GetPValue(languageId, segment, false); + PropertyType.ValidateVariation(culture, segment, true); + (var pvalue, _) = GetPValue(culture, segment, false); if (pvalue == null) return; ClearPublishedPropertyValue(pvalue); } // internal - must be invoked by the content item - internal void ClearPublishedCultureValues(int? languageId = null) + internal void ClearPublishedCultureValues(string culture = null) { - if (!languageId.HasValue && PropertyType.ValidateVariation(null, null, false)) + if (culture == null && PropertyType.ValidateVariation(null, null, false)) ClearPublishedPropertyValue(_pvalue); if (_vvalues != null) { var pvalues = _vvalues - .Where(x => x.Value.LanguageId == languageId) - .Where(x => PropertyType.ValidateVariation(languageId, x.Value.Segment, false)) + .Where(x => x.Value.Culture.InvariantEquals(culture)) + .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) .Select(x => x.Value); foreach (var pvalue in pvalues) ClearPublishedPropertyValue(pvalue); @@ -264,10 +269,10 @@ namespace Umbraco.Core.Models /// /// Sets a value. /// - public void SetValue(object value, int? languageId = null, string segment = null) + public void SetValue(object value, string culture = null, string segment = null) { - PropertyType.ValidateVariation(languageId, segment, true); - (var pvalue, var change) = GetPValue(languageId, segment, true); + PropertyType.ValidateVariation(culture, segment, true); + (var pvalue, var change) = GetPValue(culture, segment, true); var origValue = pvalue.EditedValue; var setValue = PropertyType.ConvertAssignedValue(value); @@ -278,9 +283,9 @@ namespace Umbraco.Core.Models } // bypasses all changes detection and is the *only* way to set the published value - internal void FactorySetValue(int? languageId, string segment, bool published, object value) + internal void FactorySetValue(string culture, string segment, bool published, object value) { - (var pvalue, _) = GetPValue(languageId, segment, true); + (var pvalue, _) = GetPValue(culture, segment, true); if (published && PropertyType.IsPublishing) pvalue.PublishedValue = value; @@ -301,24 +306,24 @@ namespace Umbraco.Core.Models return (_pvalue, change); } - private (PropertyValue, bool) GetPValue(int? languageId, string segment, bool create) + private (PropertyValue, bool) GetPValue(string culture, string segment, bool create) { - if (!languageId.HasValue && segment == null) + if (culture == null && segment == null) return GetPValue(create); var change = false; if (_vvalues == null) { if (!create) return (null, false); - _vvalues = new Dictionary(); + _vvalues = new Dictionary(); change = true; } - var k = new CompositeIntStringKey(languageId, segment); + var k = new CompositeStringStringKey(culture, segment); if (!_vvalues.TryGetValue(k, out var pvalue)) { if (!create) return (null, false); pvalue = _vvalues[k] = new PropertyValue(); - pvalue.LanguageId = languageId; + pvalue.Culture = culture; pvalue.Segment = segment; _values.Add(pvalue); change = true; @@ -343,7 +348,7 @@ namespace Umbraco.Core.Models if (_vvalues == null) return true; var pvalues = _vvalues - .Where(x => PropertyType.ValidateVariation(x.Value.LanguageId, x.Value.Segment, false)) + .Where(x => PropertyType.ValidateVariation(x.Value.Culture, x.Value.Segment, false)) .Select(x => x.Value) .ToArray(); @@ -354,11 +359,11 @@ namespace Umbraco.Core.Models /// Gets a value indicating whether the culture/any values are valid. /// /// An invalid value can be saved, but only valid values can be published. - public bool IsCultureValid(int? languageId) + public bool IsCultureValid(string culture) { // culture-neutral is supported, validate culture-neutral // includes mandatory validation - if (PropertyType.ValidateVariation(languageId, null, false) && !IsValidValue(GetValue(languageId))) + if (PropertyType.ValidateVariation(culture, null, false) && !IsValidValue(GetValue(culture))) return false; // either culture-neutral is not supported, or it is valid @@ -368,8 +373,8 @@ namespace Umbraco.Core.Models if (_vvalues == null) return true; var pvalues = _vvalues - .Where(x => x.Value.LanguageId == languageId) - .Where(x => PropertyType.ValidateVariation(languageId, x.Value.Segment, false)) + .Where(x => x.Value.Culture.InvariantEquals(culture)) + .Where(x => PropertyType.ValidateVariation(culture, x.Value.Segment, false)) .Select(x => x.Value) .ToArray(); @@ -380,10 +385,10 @@ namespace Umbraco.Core.Models /// 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(int? languageId = null, string segment = null) + public bool IsValid(string culture = null, string segment = null) { // single value -> validates mandatory - return IsValidValue(GetValue(languageId, segment)); + return IsValidValue(GetValue(culture, segment)); } /// diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 8c5e318719..b34eca7c57 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -226,10 +226,10 @@ namespace Umbraco.Core.Models /// /// Validates that a variation is valid for the property type. /// - public bool ValidateVariation(int? languageId, string segment, bool throwIfInvalid) + public bool ValidateVariation(string culture, string segment, bool throwIfInvalid) { ContentVariation variation; - if (languageId.HasValue) + if (culture != null) { variation = segment != null ? ContentVariation.CultureSegment diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index 24c654604c..9d2cca3e6d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -21,7 +21,7 @@ /// Other caches that get their raw value from the database would consider that a property has "no /// value" if it is missing, null, or an empty string (including whitespace-only). /// - bool HasValue(int? languageId = null, string segment = null); + bool HasValue(string culture = null, string segment = null); /// /// Gets the source value of the property. @@ -35,7 +35,7 @@ /// If you're using that value, you're probably wrong, unless you're doing some internal /// Umbraco stuff. /// - object GetSourceValue(int? languageId = null, string segment = null); + object GetSourceValue(string culture = null, string segment = null); /// /// Gets the object value of the property. @@ -45,7 +45,7 @@ /// It can be null, or any type of CLR object. /// It has been fully prepared and processed by the appropriate converter. /// - object GetValue(int? languageId = null, string segment = null); + object GetValue(string culture = null, string segment = null); /// /// Gets the XPath value of the property. @@ -55,6 +55,6 @@ /// It must be either null, or a string, or an XPathNavigator. /// It has been fully prepared and processed by the appropriate converter. /// - object GetXPathValue(int? languageId = null, string segment = null); + object GetXPathValue(string culture = null, string segment = null); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index 918bdb86e4..7e2a5b5498 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -53,15 +53,15 @@ namespace Umbraco.Core.Models.PublishedContent public string Alias => PropertyType.Alias; /// - public abstract bool HasValue(int? languageId = null, string segment = null); + public abstract bool HasValue(string culture = null, string segment = null); /// - public abstract object GetSourceValue(int? languageId = null, string segment = null); + public abstract object GetSourceValue(string culture = null, string segment = null); /// - public abstract object GetValue(int? languageId = null, string segment = null); + public abstract object GetValue(string culture = null, string segment = null); /// - public abstract object GetXPathValue(int? languageId = null, string segment = null); + public abstract object GetXPathValue(string culture = null, string segment = null); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index f938880060..e20d8cb49c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -20,20 +20,20 @@ namespace Umbraco.Core.Models.PublishedContent private readonly Lazy _objectValue; private readonly Lazy _xpathValue; - public override object GetSourceValue(int? languageId = null, string segment = null) - => languageId == null & segment == null ? _sourceValue : null; + public override object GetSourceValue(string culture = null, string segment = null) + => culture == null & segment == null ? _sourceValue : null; - public override bool HasValue(int? languageId = null, string segment = null) + public override bool HasValue(string culture = null, string segment = null) { - var sourceValue = GetSourceValue(languageId, segment); + var sourceValue = GetSourceValue(culture, segment); return sourceValue is string s ? !string.IsNullOrWhiteSpace(s) : sourceValue != null; } - public override object GetValue(int? languageId = null, string segment = null) - => languageId == null & segment == null ? _objectValue.Value : null; + public override object GetValue(string culture = null, string segment = null) + => culture == null & segment == null ? _objectValue.Value : null; - public override object GetXPathValue(int? languageId = null, string segment = null) - => languageId == null & segment == null ? _xpathValue.Value : null; + public override object GetXPathValue(string culture = null, string segment = null) + => culture == null & segment == null ? _xpathValue.Value : null; public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 4e2789bab7..10f2918204 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Factories { internal static class PropertyFactory { - public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId) + public static IEnumerable BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection dtos, int publishedVersionId, ILanguageRepository languageRepository) { var properties = new List(); var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable) x); @@ -26,7 +28,7 @@ namespace Umbraco.Core.Persistence.Factories if (xdtos.TryGetValue(propertyType.Id, out var propDtos)) { foreach (var propDto in propDtos) - property.FactorySetValue(propDto.LanguageId, propDto.Segment, propDto.VersionId == publishedVersionId, propDto.Value); + property.FactorySetValue(languageRepository.GetIsoCodeById(propDto.LanguageId), propDto.Segment, propDto.VersionId == publishedVersionId, propDto.Value); } property.ResetDirtyProperties(false); @@ -41,12 +43,12 @@ namespace Umbraco.Core.Persistence.Factories return properties; } - private static PropertyDataDto BuildDto(int versionId, Property property, int? nLanguageId, string segment, object value) + private static PropertyDataDto BuildDto(int versionId, Property property, int? languageId, string segment, object value) { var dto = new PropertyDataDto { VersionId = versionId, PropertyTypeId = property.PropertyTypeId }; - if (nLanguageId.HasValue) - dto.LanguageId = nLanguageId; + if (languageId.HasValue) + dto.LanguageId = languageId; if (segment != null) dto.Segment = segment; @@ -88,7 +90,7 @@ namespace Umbraco.Core.Persistence.Factories return dto; } - public static IEnumerable BuildDtos(int currentVersionId, int publishedVersionId, IEnumerable properties, out bool edited) + public static IEnumerable BuildDtos(int currentVersionId, int publishedVersionId, IEnumerable properties, ILanguageRepository languageRepository, out bool edited) { var propertyDataDtos = new List(); edited = false; @@ -102,15 +104,15 @@ namespace Umbraco.Core.Persistence.Factories { // deal with published value if (propertyValue.PublishedValue != null && publishedVersionId > 0) - propertyDataDtos.Add(BuildDto(publishedVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.PublishedValue)); + propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); // deal with edit value if (propertyValue.EditedValue != null) - propertyDataDtos.Add(BuildDto(currentVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.EditedValue)); + propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); // deal with missing edit value (fix inconsistencies) else if (propertyValue.PublishedValue != null) - propertyDataDtos.Add(BuildDto(currentVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.PublishedValue)); + propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue)); // use explicit equals here, else object comparison fails at comparing eg strings var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue); @@ -123,7 +125,7 @@ namespace Umbraco.Core.Persistence.Factories { // not publishing = only deal with edit values if (propertyValue.EditedValue != null) - propertyDataDtos.Add(BuildDto(currentVersionId, property, propertyValue.LanguageId, propertyValue.Segment, propertyValue.EditedValue)); + propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue)); } edited = true; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs index 36dd10c3fb..26e5255d15 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs @@ -7,7 +7,7 @@ namespace Umbraco.Core.Persistence.Repositories ILanguage GetByCultureName(string cultureName); ILanguage GetByIsoCode(string isoCode); - int GetIdByIsoCode(string isoCode); - string GetIsoCodeById(int id); + int? GetIdByIsoCode(string isoCode); + string GetIsoCodeById(int? id); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 84ff426e91..1094d7fb7f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -35,12 +35,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement where TEntity : class, IUmbracoEntity where TRepository : class, IRepository { - protected ContentRepositoryBase(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) + protected ContentRepositoryBase(IScopeAccessor scopeAccessor, CacheHelper cache, ILanguageRepository languageRepository, ILogger logger) : base(scopeAccessor, cache, logger) - { } + { + LanguageRepository = languageRepository; + } protected abstract TRepository This { get; } + protected ILanguageRepository LanguageRepository { get; } + protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // fixme inject #region Versions @@ -464,11 +468,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { propertyDataDtos.AddRange(propertyDataDtos1); if (temp.VersionId == temp.PublishedVersionId) // dirty corner case - propertyDataDtos.AddRange(propertyDataDtos1.Select(x => x.Clone(-1))); + propertyDataDtos.AddRange(propertyDataDtos1.Select(x => x.Clone(-1))); } if (temp.VersionId != temp.PublishedVersionId && indexedPropertyDataDtos.TryGetValue(temp.PublishedVersionId, out var propertyDataDtos2)) propertyDataDtos.AddRange(propertyDataDtos2); - var properties = PropertyFactory.BuildEntities(compositionProperties, propertyDataDtos, temp.PublishedVersionId).ToList(); + var properties = PropertyFactory.BuildEntities(compositionProperties, propertyDataDtos, temp.PublishedVersionId, LanguageRepository).ToList(); // deal with tags foreach (var property in properties) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index cc6247210b..848019cf95 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -25,19 +25,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly ITemplateRepository _templateRepository; private readonly ITagRepository _tagRepository; - private readonly ILanguageRepository _languageRepository; private readonly CacheHelper _cacheHelper; private PermissionRepository _permissionRepository; private readonly ContentByGuidReadRepository _contentByGuidReadRepository; private readonly IScopeAccessor _scopeAccessor; public DocumentRepository(IScopeAccessor scopeAccessor, CacheHelper cacheHelper, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IContentSection settings) - : base(scopeAccessor, cacheHelper, logger) + : base(scopeAccessor, cacheHelper, languageRepository, logger) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); - _languageRepository = languageRepository; _cacheHelper = cacheHelper; _scopeAccessor = scopeAccessor; _contentByGuidReadRepository = new ContentByGuidReadRepository(this, scopeAccessor, cacheHelper, logger); @@ -317,7 +315,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(content.VersionId, content.PublishedVersionId, entity.Properties, out var edited); + var propertyDataDtos = PropertyFactory.BuildDtos(content.VersionId, content.PublishedVersionId, entity.Properties, LanguageRepository, out var edited); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -462,7 +460,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var deletePropertyDataSql = Sql().Delete().WhereIn(x => x.VersionId, versionToDelete); Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(content.VersionId, publishing ? content.PublishedVersionId : 0, entity.Properties, out var edited); + var propertyDataDtos = PropertyFactory.BuildDtos(content.VersionId, publishing ? content.PublishedVersionId : 0, entity.Properties, LanguageRepository, out var edited); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -904,12 +902,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (variations.TryGetValue(content.VersionId, out var variation)) foreach (var v in variation) { - content.SetName(v.LanguageId, v.Name); + content.SetName(v.Culture, v.Name); } if (content.PublishedVersionId > 0 && variations.TryGetValue(content.PublishedVersionId, out variation)) foreach (var v in variation) { - content.SetPublishName(v.LanguageId, v.Name); + content.SetPublishName(v.Culture, v.Name); } } @@ -940,7 +938,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement variation.Add(new CultureVariation { - LanguageId = dto.LanguageId, + Culture = LanguageRepository.GetIsoCodeById(dto.LanguageId), Name = dto.Name, Available = dto.Available }); @@ -955,7 +953,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement yield return new ContentVersionCultureVariationDto { VersionId = content.VersionId, - LanguageId = culture, + LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Name = name }; @@ -965,14 +963,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement yield return new ContentVersionCultureVariationDto { VersionId = content.PublishedVersionId, - LanguageId = culture, + LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."), Name = name }; } private class CultureVariation { - public int LanguageId { get; set; } + public string Culture { get; set; } public string Name { get; set; } public bool Available { get; set; } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index ae9336593d..f97d2707b2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// internal class LanguageRepository : NPocoRepositoryBase, ILanguageRepository { - private readonly Dictionary _codeIdMap = new Dictionary(); + private readonly Dictionary _codeIdMap = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _idCodeMap = new Dictionary(); public LanguageRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger) @@ -62,7 +62,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement foreach (var language in languages) { _codeIdMap[language.IsoCode] = language.Id; - _idCodeMap[language.Id] = language.IsoCode; + _idCodeMap[language.Id] = language.IsoCode.ToLowerInvariant(); } } @@ -211,15 +211,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way var id = GetIdByIsoCode(isoCode, throwOnNotFound: false); - return id > 0 ? Get(id) : null; + return id.HasValue ? Get(id.Value) : null; } // fast way of getting an id for an isoCode - avoiding cloning // _codeIdMap is rebuilt whenever PerformGetAll runs - public int GetIdByIsoCode(string isoCode) => GetIdByIsoCode(isoCode, throwOnNotFound: true); + public int? GetIdByIsoCode(string isoCode) => GetIdByIsoCode(isoCode, throwOnNotFound: true); - private int GetIdByIsoCode(string isoCode, bool throwOnNotFound) + private int? GetIdByIsoCode(string isoCode, bool throwOnNotFound) { + if (isoCode == null) return null; + TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way lock (_codeIdMap) { @@ -232,14 +234,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // fast way of getting an isoCode for an id - avoiding cloning // _idCodeMap is rebuilt whenever PerformGetAll runs - public string GetIsoCodeById(int id) => GetIsoCodeById(id, throwOnNotFound: true); + public string GetIsoCodeById(int? id) => GetIsoCodeById(id, throwOnNotFound: true); - private string GetIsoCodeById(int id, bool throwOnNotFound) + private string GetIsoCodeById(int? id, bool throwOnNotFound) { + if (id == null) return null; + TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way lock (_codeIdMap) // yes, we want to lock _codeIdMap { - if (_idCodeMap.TryGetValue(id, out var isoCode)) return isoCode; + if (_idCodeMap.TryGetValue(id.Value, out var isoCode)) return isoCode; } if (throwOnNotFound) throw new ArgumentException($"Id {id} does not correspond to an existing language.", nameof(id)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index a08ecef98d..dfb30a9cb3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -25,8 +25,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ITagRepository _tagRepository; private readonly MediaByGuidReadRepository _mediaByGuidReadRepository; - public MediaRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) - : base(scopeAccessor, cache, logger) + public MediaRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, languageRepository, logger) { _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); @@ -279,7 +279,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(mediaVersionDto); // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(media.VersionId, 0, entity.Properties, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(media.VersionId, 0, entity.Properties, LanguageRepository, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -336,7 +336,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // replace the property data var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == media.VersionId); Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(media.VersionId, 0, entity.Properties, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(media.VersionId, 0, entity.Properties, LanguageRepository, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 67dbf758db..04e5d64b06 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -23,8 +23,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly ITagRepository _tagRepository; private readonly IMemberGroupRepository _memberGroupRepository; - public MemberRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository) - : base(scopeAccessor, cache, logger) + public MemberRepository(IScopeAccessor scopeAccessor, CacheHelper cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository) + : base(scopeAccessor, cache, languageRepository, logger) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); @@ -306,7 +306,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Database.Insert(dto); // persist the property data - var propertyDataDtos = PropertyFactory.BuildDtos(member.VersionId, 0, entity.Properties, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(member.VersionId, 0, entity.Properties, LanguageRepository, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); @@ -371,7 +371,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // replace the property data var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == member.VersionId); Database.Execute(deletePropertyDataSql); - var propertyDataDtos = PropertyFactory.BuildDtos(member.VersionId, 0, entity.Properties, out _); + var propertyDataDtos = PropertyFactory.BuildDtos(member.VersionId, 0, entity.Properties, LanguageRepository, out _); foreach (var propertyDataDto in propertyDataDtos) Database.Insert(propertyDataDto); diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index ea95f8708f..f235a95aa8 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -272,16 +272,16 @@ namespace Umbraco.Core.PropertyEditors /// /// /// - /// + /// /// /// /// /// The object returned will automatically be serialized into json notation. For most property editors /// the value returned is probably just a string but in some cases a json structure will be returned. /// - public virtual object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public virtual object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var val = property.GetValue(languageId, segment); + var val = property.GetValue(culture, segment); if (val == null) return string.Empty; switch (ValueTypes.ToStorageType(ValueType)) @@ -343,12 +343,8 @@ namespace Umbraco.Core.PropertyEditors continue; var xElement = new XElement(nodeName); - if (pvalue.LanguageId.HasValue) - { - var language = localizationService.GetLanguageById(pvalue.LanguageId.Value); - if (language == null) continue; // uh? - xElement.Add(new XAttribute("lang", language.IsoCode)); - } + if (pvalue.Culture != null) + xElement.Add(new XAttribute("lang", pvalue.Culture)); if (pvalue.Segment != null) xElement.Add(new XAttribute("segment", pvalue.Segment)); diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index 9e31f94121..b5ed7c5917 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -61,7 +61,7 @@ namespace Umbraco.Core.PropertyEditors /// /// Converts a property value to a value for the editor. /// - object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null); + object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null); // fixme - editing - document or remove these // why property vs propertyType? services should be injected! etc... diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index cb56b39e2c..abbab0ef39 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -354,7 +354,7 @@ namespace Umbraco.Core.Services /// /// Saves and publishes a document branch. /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, int? languageId = null, string segment = null, int userId = 0); + IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = null, string segment = null, int userId = 0); /// /// Saves and publishes a document branch. diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs index d61ee5e310..cda28d2818 100644 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ b/src/Umbraco.Core/Services/ILocalizationService.cs @@ -120,7 +120,7 @@ namespace Umbraco.Core.Services /// /// Gets a language identifier by its iso code. /// - int GetLanguageIdByIsoCode(string isoCode); + int? GetLanguageIdByIsoCode(string isoCode); /// /// Gets all available languages diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index bfa6c5916d..84e616d4d7 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -1103,14 +1103,14 @@ namespace Umbraco.Core.Services.Implement } /// - public IEnumerable SaveAndPublishBranch(IContent content, bool force, int? languageId = null, string segment = null, int userId = 0) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = null, string segment = null, int userId = 0) { segment = segment?.ToLowerInvariant(); - bool IsEditing(IContent c, int? l, string s) - => c.Properties.Any(x => x.Values.Where(y => y.LanguageId == l && y.Segment == s).Any(y => y.EditedValue != y.PublishedValue)); + bool IsEditing(IContent c, string l, string s) + => c.Properties.Any(x => x.Values.Where(y => y.Culture == l && y.Segment == s).Any(y => y.EditedValue != y.PublishedValue)); - return SaveAndPublishBranch(content, force, document => IsEditing(document, languageId, segment), document => document.PublishValues(languageId, segment), userId); + return SaveAndPublishBranch(content, force, document => IsEditing(document, culture, segment), document => document.PublishValues(culture, segment), userId); } /// diff --git a/src/Umbraco.Core/Services/Implement/LocalizationService.cs b/src/Umbraco.Core/Services/Implement/LocalizationService.cs index bf01605416..104268f5e8 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizationService.cs @@ -317,7 +317,7 @@ namespace Umbraco.Core.Services.Implement } /// - public int GetLanguageIdByIsoCode(string isoCode) + public int? GetLanguageIdByIsoCode(string isoCode) { using (ScopeProvider.CreateScope(autoComplete: true)) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 77b78f355d..7549d4ba2b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -137,6 +137,7 @@ + diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 8965d60018..6fba071709 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -286,7 +286,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(42, types.Count()); + Assert.AreEqual(43, types.Count()); } [Test] diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index 3b293621d5..70e3d574b3 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -65,6 +65,8 @@ namespace Umbraco.Tests.Models var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop" }; var prop = new Property(propertyType); + const string langFr = "fr-FR"; + // can set value // and get edited and published value // because non-publishing @@ -84,8 +86,8 @@ namespace Umbraco.Tests.Models Assert.IsNull(prop.GetValue(published: true)); // cannot set non-supported variation value - Assert.Throws(() => prop.SetValue("x", 1)); - Assert.IsNull(prop.GetValue(1)); + Assert.Throws(() => prop.SetValue("x", langFr)); + Assert.IsNull(prop.GetValue(langFr)); // can publish value // and get edited and published values @@ -109,41 +111,41 @@ namespace Umbraco.Tests.Models // can set value // and get values - prop.SetValue("c", 1); + prop.SetValue("c", langFr); Assert.AreEqual("b", prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); - Assert.AreEqual("c", prop.GetValue(1)); - Assert.IsNull(prop.GetValue(1, 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(1); + prop.PublishValue(langFr); Assert.AreEqual("b", prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); - Assert.AreEqual("c", prop.GetValue(1)); - Assert.AreEqual("c", prop.GetValue(1, 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()); Assert.IsNull(prop.GetValue(published: true)); - Assert.AreEqual("c", prop.GetValue(1)); - Assert.IsNull(prop.GetValue(1, 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)); - Assert.AreEqual("c", prop.GetValue(1)); - Assert.AreEqual("c", prop.GetValue(1, published: true)); + Assert.AreEqual("c", prop.GetValue(langFr)); + Assert.AreEqual("c", prop.GetValue(langFr, published: true)); // same for culture - prop.ClearPublishedCultureValues(1); - Assert.AreEqual("c", prop.GetValue(1)); - Assert.IsNull(prop.GetValue(1, published: true)); - prop.PublishCultureValues(1); - Assert.AreEqual("c", prop.GetValue(1)); - Assert.AreEqual("c", prop.GetValue(1, published: true)); + prop.ClearPublishedCultureValues(langFr); + Assert.AreEqual("c", prop.GetValue(langFr)); + Assert.IsNull(prop.GetValue(langFr, published: true)); + prop.PublishCultureValues(langFr); + Assert.AreEqual("c", prop.GetValue(langFr)); + Assert.AreEqual("c", prop.GetValue(langFr, published: true)); prop.ClearPublishedCultureValues(); Assert.AreEqual("b", prop.GetValue()); @@ -159,8 +161,8 @@ namespace Umbraco.Tests.Models var contentType = new ContentType(-1) { Alias = "contentType" }; var content = new Content("content", -1, contentType) { Id = 1, VersionId = 1 }; - const int langFr = 1; - const int langUk = 2; + const string langFr = "fr-FR"; + const string langUk = "en-UK"; Assert.Throws(() => content.SetName(langFr, "name-fr")); @@ -188,6 +190,8 @@ namespace Umbraco.Tests.Models [Test] public void ContentTests() { + const string langFr = "fr-FR"; + var propertyType = new PropertyType("editor", ValueStorageType.Nvarchar) { Alias = "prop" }; var contentType = new ContentType(-1) { Alias = "contentType" }; contentType.AddPropertyType(propertyType); @@ -202,8 +206,8 @@ namespace Umbraco.Tests.Models Assert.IsNull(content.GetValue("prop", published: true)); // cannot set non-supported variation value - Assert.Throws(() => content.SetValue("prop", "x", 1)); - Assert.IsNull(content.GetValue("prop", 1)); + Assert.Throws(() => content.SetValue("prop", "x", langFr)); + Assert.IsNull(content.GetValue("prop", langFr)); // can publish value // and get edited and published values @@ -228,41 +232,43 @@ namespace Umbraco.Tests.Models // can set value // and get values - content.SetValue("prop", "c", 1); + content.SetValue("prop", "c", langFr); Assert.AreEqual("b", content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); - Assert.AreEqual("c", content.GetValue("prop", 1)); - Assert.IsNull(content.GetValue("prop", 1, published: true)); + Assert.AreEqual("c", content.GetValue("prop", langFr)); + Assert.IsNull(content.GetValue("prop", langFr, published: true)); // can publish value // and get edited and published values - content.PublishValues(1); + Assert.Throws(() => content.PublishValues(langFr)); // no name + content.SetName(langFr, "name-fr"); + content.PublishValues(langFr); Assert.AreEqual("b", content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); - Assert.AreEqual("c", content.GetValue("prop", 1)); - Assert.AreEqual("c", content.GetValue("prop", 1, 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")); Assert.IsNull(content.GetValue("prop", published: true)); - Assert.AreEqual("c", content.GetValue("prop", 1)); - Assert.IsNull(content.GetValue("prop", 1, published: true)); + Assert.AreEqual("c", content.GetValue("prop", langFr)); + Assert.IsNull(content.GetValue("prop", langFr, published: true)); // can publish all content.PublishAllValues(); Assert.AreEqual("b", content.GetValue("prop")); Assert.AreEqual("b", content.GetValue("prop", published: true)); - Assert.AreEqual("c", content.GetValue("prop", 1)); - Assert.AreEqual("c", content.GetValue("prop", 1, published: true)); + Assert.AreEqual("c", content.GetValue("prop", langFr)); + Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); // same for culture - content.ClearCulturePublishedValues(1); - Assert.AreEqual("c", content.GetValue("prop", 1)); - Assert.IsNull(content.GetValue("prop", 1, published: true)); - content.PublishCultureValues(1); - Assert.AreEqual("c", content.GetValue("prop", 1)); - Assert.AreEqual("c", content.GetValue("prop", 1, published: true)); + content.ClearCulturePublishedValues(langFr); + Assert.AreEqual("c", content.GetValue("prop", langFr)); + Assert.IsNull(content.GetValue("prop", langFr, published: true)); + content.PublishCultureValues(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")); @@ -273,21 +279,21 @@ namespace Umbraco.Tests.Models var other = new Content("other", -1, contentType) { Id = 2, VersionId = 1 }; other.SetValue("prop", "o"); - other.SetValue("prop", "o1", 1); + 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.AreEqual("o1", content.GetValue("prop", 1)); - Assert.AreEqual("c", content.GetValue("prop", 1, 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.AreEqual("c", content.GetValue("prop", 1)); - Assert.AreEqual("c", content.GetValue("prop", 1, published: true)); + Assert.AreEqual("c", content.GetValue("prop", langFr)); + Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); } [Test] diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 2e72a2e14c..53985b7897 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Persistence.Repositories mediaTypeRepository = new MediaTypeRepository(scopeAccessor, cacheHelper, Logger); var tagRepository = new TagRepository(scopeAccessor, cacheHelper, Logger); - var repository = new MediaRepository(scopeAccessor, cacheHelper, Logger, mediaTypeRepository, tagRepository, Mock.Of()); + var repository = new MediaRepository(scopeAccessor, cacheHelper, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 4f901935dc..d07d2dda9a 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -31,7 +31,7 @@ namespace Umbraco.Tests.Persistence.Repositories memberTypeRepository = new MemberTypeRepository(accessor, DisabledCache, Logger); memberGroupRepository = new MemberGroupRepository(accessor, DisabledCache, Logger); var tagRepo = new TagRepository(accessor, DisabledCache, Logger); - var repository = new MemberRepository(accessor, DisabledCache, Logger, memberTypeRepository, memberGroupRepository, tagRepo); + var repository = new MemberRepository(accessor, DisabledCache, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs index 216fb08ecd..90230e15c7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TagRepositoryTest.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -964,7 +965,7 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; var tagRepository = new TagRepository(accessor, DisabledCache, Logger); mediaTypeRepository = new MediaTypeRepository(accessor, DisabledCache, Logger); - var repository = new MediaRepository(accessor, DisabledCache, Logger, mediaTypeRepository, tagRepository, Mock.Of()); + var repository = new MediaRepository(accessor, DisabledCache, Logger, mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); return repository; } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 56d5bfbc0c..2688629c5c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -26,7 +26,7 @@ namespace Umbraco.Tests.Persistence.Repositories var accessor = (IScopeAccessor) provider; mediaTypeRepository = new MediaTypeRepository(accessor, CacheHelper, Mock.Of()); var tagRepository = new TagRepository(accessor, CacheHelper, Mock.Of()); - var repository = new MediaRepository(accessor, CacheHelper, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of()); + var repository = new MediaRepository(accessor, CacheHelper, Mock.Of(), mediaTypeRepository, tagRepository, Mock.Of(), Mock.Of()); return repository; } diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index a916a2d51e..22e110dd20 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -242,10 +242,10 @@ namespace Umbraco.Tests.Published _owner = owner; } - public override bool HasValue(int? languageId = null, string segment = null) => _hasValue; - public override object GetSourceValue(int? languageId = null, string segment = null) => _sourceValue; - public override object GetValue(int? languageId = null, string segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); - public override object GetXPathValue(int? languageId = null, string segment = null) => throw new WontImplementException(); + public override bool HasValue(string culture = null, string segment = null) => _hasValue; + public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; + public override object GetValue(string culture = null, string segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); + public override object GetXPathValue(string culture = null, string segment = null) => throw new WontImplementException(); } class TestPublishedContent : PublishedContentBase diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs index 8f6ee5c2b1..7a6c684eb4 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs @@ -256,10 +256,10 @@ namespace Umbraco.Tests.PublishedContent public bool SolidHasValue { get; set; } public object SolidXPathValue { get; set; } - public object GetSourceValue(int? languageId = null, string segment = null) => SolidSourceValue; - public object GetValue(int? languageId = null, string segment = null) => SolidValue; - public object GetXPathValue(int? languageId = null, string segment = null) => SolidXPathValue; - public bool HasValue(int? languageId = null, string segment = null) => SolidHasValue; + public object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue; + public object GetValue(string culture = null, string segment = null) => SolidValue; + public object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue; + public bool HasValue(string culture = null, string segment = null) => SolidHasValue; } [PublishedModel("ContentType2")] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 0e726064f4..04ed54d81c 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -356,7 +356,7 @@ namespace Umbraco.Tests.PublishedContent var result = doc.Ancestors().OrderBy(x => x.Level) .Single() .Descendants() - .FirstOrDefault(x => x.Value("selectedNodes", "").Split(',').Contains("1173")); + .FirstOrDefault(x => x.Value("selectedNodes", defaultValue: "").Split(',').Contains("1173")); Assert.IsNotNull(result); } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 79929ff29d..b3bdf4f1ba 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -2506,200 +2506,200 @@ namespace Umbraco.Tests.Services var contentService = ServiceContext.ContentService; var content = contentService.Create("Home US", - 1, "umbTextpage"); - // act + // act - content.SetValue("author", "Barack Obama"); - content.SetValue("prop", "value-fr1", langFr.Id); - content.SetValue("prop", "value-uk1", langUk.Id); - content.SetName(langFr.Id, "name-fr"); - content.SetName(langUk.Id, "name-uk"); - contentService.Save(content); + content.SetValue("author", "Barack Obama"); + content.SetValue("prop", "value-fr1", langFr.IsoCode); + content.SetValue("prop", "value-uk1", langUk.IsoCode); + content.SetName(langFr.IsoCode, "name-fr"); + content.SetName(langUk.IsoCode, "name-uk"); + contentService.Save(content); - // content has been saved, - // it has names, but no publishNames, and no published cultures + // content has been saved, + // it has names, but no publishNames, and no published cultures - var content2 = contentService.GetById(content.Id); + var content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US", content2.Name); - Assert.AreEqual("name-fr", content2.GetName(langFr.Id)); - Assert.AreEqual("name-uk", content2.GetName(langUk.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("value-fr1", content2.GetValue("prop", langFr.Id)); - Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.Id)); - Assert.IsNull(content2.GetValue("prop", langFr.Id, published: true)); - Assert.IsNull(content2.GetValue("prop", langUk.Id, published: true)); + Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.IsoCode)); + Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode)); + Assert.IsNull(content2.GetValue("prop", langFr.IsoCode, published: true)); + Assert.IsNull(content2.GetValue("prop", langUk.IsoCode, published: true)); - Assert.IsNull(content2.PublishName); - Assert.IsNull(content2.GetPublishName(langFr.Id)); - Assert.IsNull(content2.GetPublishName(langUk.Id)); + Assert.IsNull(content2.PublishName); + Assert.IsNull(content2.GetPublishName(langFr.IsoCode)); + Assert.IsNull(content2.GetPublishName(langUk.IsoCode)); - Assert.IsTrue(content.IsCultureAvailable(langFr.Id)); - Assert.IsTrue(content.IsCultureAvailable(langUk.Id)); - Assert.IsFalse(content.IsCultureAvailable(langDe.Id)); + Assert.IsTrue(content.IsCultureAvailable(langFr.IsoCode)); + Assert.IsTrue(content.IsCultureAvailable(langUk.IsoCode)); + Assert.IsFalse(content.IsCultureAvailable(langDe.IsoCode)); - Assert.IsFalse(content.IsCulturePublished(langFr.Id)); - Assert.IsFalse(content.IsCulturePublished(langUk.Id)); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsFalse(content.IsCulturePublished(langUk.IsoCode)); - // act + // act - content.PublishValues(langFr.Id); - content.PublishValues(langUk.Id); - contentService.SaveAndPublish(content); + content.PublishValues(langFr.IsoCode); + content.PublishValues(langUk.IsoCode); + contentService.SaveAndPublish(content); - // both FR and UK have been published, - // and content has been published, - // it has names, publishNames, and published cultures + // both FR and UK have been published, + // and content has been published, + // it has names, publishNames, and published cultures - content2 = contentService.GetById(content.Id); + content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US", content2.Name); - Assert.AreEqual("name-fr", content2.GetName(langFr.Id)); - Assert.AreEqual("name-uk", content2.GetName(langUk.Id)); + Assert.AreEqual("Home US", content2.Name); + Assert.AreEqual("name-fr", content2.GetName(langFr.IsoCode)); + Assert.AreEqual("name-uk", content2.GetName(langUk.IsoCode)); - Assert.IsNull(content2.PublishName); // we haven't published InvariantNeutral - Assert.AreEqual("name-fr", content2.GetPublishName(langFr.Id)); - Assert.AreEqual("name-uk", content2.GetPublishName(langUk.Id)); + Assert.IsNull(content2.PublishName); // we haven't published InvariantNeutral + Assert.AreEqual("name-fr", content2.GetPublishName(langFr.IsoCode)); + Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); - Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.Id)); - Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.Id)); - Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.Id, published: true)); - Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.Id, published: true)); + Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.IsoCode)); + Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode)); + Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.IsoCode, published: true)); + Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode, published: true)); - Assert.IsTrue(content.IsCulturePublished(langFr.Id)); - Assert.IsTrue(content.IsCulturePublished(langUk.Id)); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - // act + // act - content.PublishValues(); - contentService.SaveAndPublish(content); + content.PublishValues(); + contentService.SaveAndPublish(content); - // now it has publish name for invariant neutral + // now it has publish name for invariant neutral - content2 = contentService.GetById(content.Id); + content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US", content2.PublishName); + Assert.AreEqual("Home US", content2.PublishName); - // act + // act - content.SetName(null, "Home US2"); - content.SetName(langFr.Id, "name-fr2"); - content.SetName(langUk.Id, "name-uk2"); - content.SetValue("author", "Barack Obama2"); - content.SetValue("prop", "value-fr2", langFr.Id); - content.SetValue("prop", "value-uk2", langUk.Id); - contentService.Save(content); + content.SetName(null, "Home US2"); + content.SetName(langFr.IsoCode, "name-fr2"); + content.SetName(langUk.IsoCode, "name-uk2"); + content.SetValue("author", "Barack Obama2"); + content.SetValue("prop", "value-fr2", langFr.IsoCode); + content.SetValue("prop", "value-uk2", langUk.IsoCode); + contentService.Save(content); - // content has been saved, - // it has updated names, unchanged publishNames, and published cultures + // content has been saved, + // it has updated names, unchanged publishNames, and published cultures - content2 = contentService.GetById(content.Id); + content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.Id)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.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("Home US", content2.PublishName); - Assert.AreEqual("name-fr", content2.GetPublishName(langFr.Id)); - Assert.AreEqual("name-uk", content2.GetPublishName(langUk.Id)); + Assert.AreEqual("Home US", content2.PublishName); + Assert.AreEqual("name-fr", content2.GetPublishName(langFr.IsoCode)); + Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); - Assert.AreEqual("Barack Obama2", content2.GetValue("author")); - Assert.AreEqual("Barack Obama", content2.GetValue("author", published: true)); + Assert.AreEqual("Barack Obama2", content2.GetValue("author")); + Assert.AreEqual("Barack Obama", content2.GetValue("author", published: true)); - Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.Id)); - Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.Id)); - Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.Id, published: true)); - Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.Id, published: true)); + Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.IsoCode)); + Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.IsoCode)); + Assert.AreEqual("value-fr1", content2.GetValue("prop", langFr.IsoCode, published: true)); + Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode, published: true)); - Assert.IsTrue(content.IsCulturePublished(langFr.Id)); - Assert.IsTrue(content.IsCulturePublished(langUk.Id)); + Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - // act - // cannot just 'save' since we are changing what's published! + // act + // cannot just 'save' since we are changing what's published! - content.ClearPublishedValues(langFr.Id); - contentService.SaveAndPublish(content); + content.ClearPublishedValues(langFr.IsoCode); + contentService.SaveAndPublish(content); - // content has been published, - // the french culture is gone + // content has been published, + // the french culture is gone - content2 = contentService.GetById(content.Id); + content2 = contentService.GetById(content.Id); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.Id)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.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("Home US", content2.PublishName); - Assert.IsNull(content2.GetPublishName(langFr.Id)); - Assert.AreEqual("name-uk", content2.GetPublishName(langUk.Id)); + Assert.AreEqual("Home US", content2.PublishName); + Assert.IsNull(content2.GetPublishName(langFr.IsoCode)); + Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); - Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.Id)); - Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.Id)); - Assert.IsNull(content2.GetValue("prop", langFr.Id, published: true)); - Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.Id, published: true)); + Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.IsoCode)); + Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.IsoCode)); + Assert.IsNull(content2.GetValue("prop", langFr.IsoCode, published: true)); + Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode, published: true)); - Assert.IsFalse(content.IsCulturePublished(langFr.Id)); - Assert.IsTrue(content.IsCulturePublished(langUk.Id)); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); - // act + // act - contentService.Unpublish(content); + contentService.Unpublish(content); - // content has been unpublished, - // but properties, names, etc. retain their 'published' values so the content - // can be re-published in its exact original state (before being unpublished) - // - // BEWARE! - // in order for a content to be unpublished as a whole, and then republished in - // its exact previous state, properties and names etc. retain their published - // values even though the content is not published - hence many things being - // non-null or true below - always check against content.Published to be sure + // content has been unpublished, + // but properties, names, etc. retain their 'published' values so the content + // can be re-published in its exact original state (before being unpublished) + // + // BEWARE! + // in order for a content to be unpublished as a whole, and then republished in + // its exact previous state, properties and names etc. retain their published + // values even though the content is not published - hence many things being + // non-null or true below - always check against content.Published to be sure - content2 = contentService.GetById(content.Id); + content2 = contentService.GetById(content.Id); - Assert.IsFalse(content2.Published); + Assert.IsFalse(content2.Published); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.Id)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.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("Home US", content2.PublishName); // not null, see note above - Assert.IsNull(content2.GetPublishName(langFr.Id)); - Assert.AreEqual("name-uk", content2.GetPublishName(langUk.Id)); // not null, see note above + Assert.AreEqual("Home US", 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 - Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.Id)); - Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.Id)); - Assert.IsNull(content2.GetValue("prop", langFr.Id, published: true)); - Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.Id, published: true)); // has value, see note above + Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.IsoCode)); + Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.IsoCode)); + Assert.IsNull(content2.GetValue("prop", langFr.IsoCode, published: true)); + Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode, published: true)); // has value, see note above - Assert.IsFalse(content.IsCulturePublished(langFr.Id)); - Assert.IsTrue(content.IsCulturePublished(langUk.Id)); // still true, see note above + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); // still true, see note above - // act + // act - contentService.SaveAndPublish(content); + contentService.SaveAndPublish(content); - // content has been re-published, - // everything is back to what it was before being unpublished + // content has been re-published, + // everything is back to what it was before being unpublished - content2 = contentService.GetById(content.Id); + content2 = contentService.GetById(content.Id); - Assert.IsTrue(content2.Published); + Assert.IsTrue(content2.Published); - Assert.AreEqual("Home US2", content2.Name); - Assert.AreEqual("name-fr2", content2.GetName(langFr.Id)); - Assert.AreEqual("name-uk2", content2.GetName(langUk.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("Home US", content2.PublishName); - Assert.IsNull(content2.GetPublishName(langFr.Id)); - Assert.AreEqual("name-uk", content2.GetPublishName(langUk.Id)); + Assert.AreEqual("Home US", content2.PublishName); + Assert.IsNull(content2.GetPublishName(langFr.IsoCode)); + Assert.AreEqual("name-uk", content2.GetPublishName(langUk.IsoCode)); - Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.Id)); - Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.Id)); - Assert.IsNull(content2.GetValue("prop", langFr.Id, published: true)); - Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.Id, published: true)); + Assert.AreEqual("value-fr2", content2.GetValue("prop", langFr.IsoCode)); + Assert.AreEqual("value-uk2", content2.GetValue("prop", langUk.IsoCode)); + Assert.IsNull(content2.GetValue("prop", langFr.IsoCode, published: true)); + Assert.AreEqual("value-uk1", content2.GetValue("prop", langUk.IsoCode, published: true)); - Assert.IsFalse(content.IsCulturePublished(langFr.Id)); - Assert.IsTrue(content.IsCulturePublished(langUk.Id)); + Assert.IsFalse(content.IsCulturePublished(langFr.IsoCode)); + Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); } private IEnumerable CreateContentHierarchy() diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 47a847ad43..25ac6314e2 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -140,8 +140,8 @@ namespace Umbraco.Tests.TestHelpers { // compare values var actualProperty = (Property) actual; - var expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.LanguageId).ThenBy(x => x.Segment).ToArray(); - var actualPropertyValues = actualProperty.Values.OrderBy(x => x.LanguageId).ThenBy(x => x.Segment).ToArray(); + var expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); + var actualPropertyValues = actualProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); if (expectedPropertyValues.Length != actualPropertyValues.Length) Assert.Fail($"{property.DeclaringType.Name}.{property.Name}: Expected {expectedPropertyValues.Length} but got {actualPropertyValues.Length}."); for (var i = 0; i < expectedPropertyValues.Length; i++) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index dce9ca531d..6eb8db3c61 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -265,8 +265,8 @@ namespace Umbraco.Web.Editors HandleContentNotFound(id); return null;//irrelevant since the above throws } - - var content = MapToDisplay(foundContent, languageId); + + var content = MapToDisplay(foundContent, GetLanguageCulture(languageId)); return content; } @@ -606,7 +606,7 @@ namespace Umbraco.Web.Editors { //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // add the modelstate to the outgoing object and throw a validation message - var forDisplay = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId); + var forDisplay = MapToDisplay(contentItem.PersistedContent, GetLanguageCulture(contentItem.LanguageId)); forDisplay.Errors = ModelState.ToErrorDictionary(); throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay)); @@ -643,14 +643,14 @@ namespace Umbraco.Web.Editors else { //publish the item and check if it worked, if not we will show a diff msg below - contentItem.PersistedContent.PublishValues(contentItem.LanguageId); //we are not checking for a return value here because we've alraedy pre-validated the property values + contentItem.PersistedContent.PublishValues(GetLanguageCulture(contentItem.LanguageId)); //we are not checking for a return value here because we've alraedy pre-validated the property values //check if we are publishing other variants and validate them var allLangs = Services.LocalizationService.GetAllLanguages().ToList(); var variantsToValidate = contentItem.PublishVariations.Where(x => x.LanguageId != contentItem.LanguageId).ToList(); foreach (var publishVariation in variantsToValidate) { - if (!contentItem.PersistedContent.PublishValues(publishVariation.LanguageId)) + if (!contentItem.PersistedContent.PublishValues(GetLanguageCulture(publishVariation.LanguageId))) { var errMsg = Services.TextService.Localize("speechBubbles/contentLangValidationError", new[]{allLangs.First(x => x.Id == publishVariation.LanguageId).CultureName}); ModelState.AddModelError("publish_variant_" + publishVariation.LanguageId + "_", errMsg); @@ -664,7 +664,7 @@ namespace Umbraco.Web.Editors .Where(x => x.Mandatory); foreach (var lang in mandatoryLangs) { - if (contentItem.PersistedContent.Validate(lang.Id).Length > 0) + if (contentItem.PersistedContent.Validate(GetLanguageCulture(lang.Id)).Length > 0) { var errMsg = Services.TextService.Localize("speechBubbles/contentReqLangValidationError", new[]{allLangs.First(x => x.Id == lang.Id).CultureName}); ModelState.AddModelError("publish_variant_" + lang.Id + "_", errMsg); @@ -676,7 +676,7 @@ namespace Umbraco.Web.Editors } //get the updated model - var display = MapToDisplay(contentItem.PersistedContent, contentItem.LanguageId); + var display = MapToDisplay(contentItem.PersistedContent, GetLanguageCulture(contentItem.LanguageId)); //lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403 HandleInvalidModelState(display); @@ -977,8 +977,8 @@ namespace Umbraco.Web.Editors base.MapPropertyValues( contentItem, - (save, property) => property.GetValue(save.LanguageId), //get prop val - (save, property, v) => property.SetValue(v, save.LanguageId)); //set prop val + (save, property) => property.GetValue(GetLanguageCulture(save.LanguageId)), //get prop val + (save, property, v) => property.SetValue(v, GetLanguageCulture(save.LanguageId))); //set prop val } /// @@ -1169,23 +1169,28 @@ namespace Umbraco.Web.Editors /// Used to map an instance to a and ensuring a language is present if required /// /// - /// + /// /// - private ContentItemDisplay MapToDisplay(IContent content, int? languageId = null) + private ContentItemDisplay MapToDisplay(IContent content, string culture = null) { //a languageId must exist in the mapping context if this content item has any property type that can be varied by language //otherwise the property validation will fail since it's expecting to be get/set with a language ID. If a languageId is not explicitly //sent up, then it means that the user is editing the default variant language. - if (!languageId.HasValue && content.HasPropertyTypeVaryingByCulture()) + if (culture == null && content.HasPropertyTypeVaryingByCulture()) { - languageId = Services.LocalizationService.GetDefaultVariantLanguage().Id; + culture = Services.LocalizationService.GetDefaultVariantLanguage().IsoCode; } var display = ContextMapper.Map(content, UmbracoContext, - new Dictionary { { ContextMapper.LanguageKey, languageId } }); + new Dictionary { { ContextMapper.CultureKey, culture } }); return display; } + private string GetLanguageCulture(int? languageId) + { + if (languageId == null) return null; + return Core.Composing.Current.Services.LocalizationService.GetLanguageById(languageId.Value).IsoCode; // fixme optimize! + } } } diff --git a/src/Umbraco.Web/Models/ContentExtensions.cs b/src/Umbraco.Web/Models/ContentExtensions.cs index 4a016a895b..3fe81757a8 100644 --- a/src/Umbraco.Web/Models/ContentExtensions.cs +++ b/src/Umbraco.Web/Models/ContentExtensions.cs @@ -96,6 +96,5 @@ namespace Umbraco.Web.Models var defaultLanguage = localizationService.GetAllLanguages().FirstOrDefault(); return defaultLanguage == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultLanguage.IsoCode); } - } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index 5f7cc0e052..10fec19b38 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -46,9 +46,9 @@ namespace Umbraco.Web.Models.Mapping editor = _propertyEditors[Constants.PropertyEditors.Aliases.NoEdit]; } - var languageId = context.GetLanguageId(); + var culture = context.GetCulture(); - if (!languageId.HasValue && property.PropertyType.Variations == ContentVariation.CultureNeutral) + if (culture == null && property.PropertyType.Variations == ContentVariation.CultureNeutral) { //a language Id needs to be set for a property type that can be varried by language throw new InvalidOperationException($"No languageId found in mapping operation when one is required for the culture neutral property type {property.PropertyType.Alias}"); @@ -74,7 +74,7 @@ namespace Umbraco.Web.Models.Mapping } // 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, languageId); + result.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture); return result; } } diff --git a/src/Umbraco.Web/Models/Mapping/ContextMapper.cs b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs index b46cd219d8..5aa8af7668 100644 --- a/src/Umbraco.Web/Models/Mapping/ContextMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContextMapper.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Models.Mapping internal static class ContextMapper { public const string UmbracoContextKey = "ContextMapper.UmbracoContext"; - public const string LanguageKey = "ContextMapper.LanguageId"; + public const string CultureKey = "ContextMapper.Culture"; public static TDestination Map(TSource obj, UmbracoContext umbracoContext) => Mapper.Map(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext); @@ -77,12 +77,12 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - public static int? GetLanguageId(this ResolutionContext resolutionContext) + public static string GetCulture(this ResolutionContext resolutionContext) { - if (!resolutionContext.Options.Items.TryGetValue(LanguageKey, out var obj)) return null; + if (!resolutionContext.Options.Items.TryGetValue(CultureKey, out var obj)) return null; - if (obj is int i) - return i; + if (obj is string s) + return s; return null; } diff --git a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs index 9d3de07d77..ece6f87a33 100644 --- a/src/Umbraco.Web/Models/Mapping/VariationResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/VariationResolver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -29,19 +30,19 @@ namespace Umbraco.Web.Models.Mapping { Language = x, Mandatory = x.Mandatory, - Name = source.GetName(x.Id), - Exists = source.IsCultureAvailable(x.Id), // segments ?? + Name = source.GetName(x.IsoCode), + Exists = source.IsCultureAvailable(x.IsoCode), // segments ?? PublishedState = source.PublishedState.ToString(), //Segment = ?? We'll need to populate this one day when we support segments }).ToList(); - var langId = context.GetLanguageId(); + var culture = context.GetCulture(); //set the current variant being edited to the one found in the context or the default if nothing matches var foundCurrent = false; foreach (var variant in variants) { - if (langId.HasValue && langId.Value == variant.Language.Id) + if (culture.InvariantEquals(variant.Language.IsoCode)) { variant.IsCurrent = true; foundCurrent = true; diff --git a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs index 31425fdadd..a03ad4aa00 100644 --- a/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/DateValueEditor.cs @@ -18,9 +18,9 @@ namespace Umbraco.Web.PropertyEditors Validators.Add(new DateTimeValidator()); } - public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture= null, string segment = null) { - var date = property.GetValue(languageId, segment).TryConvertTo(); + var date = property.GetValue(culture, segment).TryConvertTo(); if (date.Success == false || date.Result == null) { return String.Empty; diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index e969d2e45b..0d5f6efaf5 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -93,11 +93,11 @@ namespace Umbraco.Web.PropertyEditors //copy each of the property values (variants, segments) to the destination foreach (var propertyValue in property.Values) { - var propVal = property.GetValue(propertyValue.LanguageId, propertyValue.Segment); + var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); if (propVal == null || !(propVal is string str) || str.IsNullOrWhiteSpace()) continue; var sourcePath = _mediaFileSystem.GetRelativePath(str); var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); - args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.LanguageId, propertyValue.Segment); + args.Copy.SetValue(property.Alias, _mediaFileSystem.GetUrl(copyPath), propertyValue.Culture, propertyValue.Segment); isUpdated = true; } } @@ -153,11 +153,11 @@ namespace Umbraco.Web.PropertyEditors foreach (var pvalue in property.Values) { - var svalue = property.GetValue(pvalue.LanguageId, pvalue.Segment) as string; + var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string; if (string.IsNullOrWhiteSpace(svalue)) - _mediaFileSystem.UploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment); + _mediaFileSystem.UploadAutoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); else - _mediaFileSystem.UploadAutoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(svalue), pvalue.LanguageId, pvalue.Segment); + _mediaFileSystem.UploadAutoFillProperties.Populate(model, autoFillConfig, _mediaFileSystem.GetRelativePath(svalue), pvalue.Culture, pvalue.Segment); } } } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs index 6d3c5b4f61..6183641aaf 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyEditor.cs @@ -148,13 +148,13 @@ namespace Umbraco.Web.PropertyEditors //copy each of the property values (variants, segments) to the destination by using the edited value foreach (var propertyValue in property.Values) { - var propVal = property.GetValue(propertyValue.LanguageId, propertyValue.Segment); + var propVal = property.GetValue(propertyValue.Culture, propertyValue.Segment); var src = GetFileSrcFromPropertyValue(propVal, out var jo); if (src == null) continue; var sourcePath = _mediaFileSystem.GetRelativePath(src); var copyPath = _mediaFileSystem.CopyFile(args.Copy, property.PropertyType, sourcePath); jo["src"] = _mediaFileSystem.GetUrl(copyPath); - args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.LanguageId, propertyValue.Segment); + args.Copy.SetValue(property.Alias, jo.ToString(), propertyValue.Culture, propertyValue.Segment); isUpdated = true; } } @@ -209,10 +209,10 @@ namespace Umbraco.Web.PropertyEditors foreach (var pvalue in property.Values) { - var svalue = property.GetValue(pvalue.LanguageId, pvalue.Segment) as string; + var svalue = property.GetValue(pvalue.Culture, pvalue.Segment) as string; if (string.IsNullOrWhiteSpace(svalue)) { - _autoFillProperties.Reset(model, autoFillConfig, pvalue.LanguageId, pvalue.Segment); + _autoFillProperties.Reset(model, autoFillConfig, pvalue.Culture, pvalue.Segment); continue; } diff --git a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs index bce989fb18..98e8346441 100644 --- a/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ImageCropperPropertyValueEditor.cs @@ -32,9 +32,9 @@ namespace Umbraco.Web.PropertyEditors /// This is called to merge in the prevalue crops with the value that is saved - similar to the property value converter for the front-end /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var val = property.GetValue(languageId, segment); + var val = property.GetValue(culture, segment); if (val == null) return null; ImageCropperValue value; diff --git a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs index a6373bd87d..645296a354 100644 --- a/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/MultipleTextStringPropertyEditor.cs @@ -85,9 +85,9 @@ namespace Umbraco.Web.PropertyEditors /// /// The legacy property editor saved this data as new line delimited! strange but we have to maintain that. /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var val = property.GetValue(languageId, segment); + var val = property.GetValue(culture, segment); return val?.ToString().Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries) .Select(x => JObject.FromObject(new {value = x})) ?? new JObject[] { }; diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 52933e5846..4cb8fde97a 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -139,9 +139,9 @@ namespace Umbraco.Web.PropertyEditors // note: there is NO variant support here - public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var val = property.GetValue(languageId, segment); + var val = property.GetValue(culture, segment); if (val == null || string.IsNullOrWhiteSpace(val.ToString())) return string.Empty; diff --git a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs index 6cd9675f67..25a1c25284 100644 --- a/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/PublishValuesMultipleValueEditor.cs @@ -70,12 +70,12 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - /// + /// /// /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var delimited = base.ToEditor(property, dataTypeService, languageId, segment).ToString(); + var delimited = base.ToEditor(property, dataTypeService, culture, segment).ToString(); return delimited.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); } diff --git a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs index 181471b315..797d0eaf59 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextPropertyEditor.cs @@ -61,12 +61,12 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - /// + /// /// /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var val = property.GetValue(languageId, segment); + var val = property.GetValue(culture, segment); if (val == null) return null; diff --git a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs index f860a5abe7..754cef1f31 100644 --- a/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TextOnlyValueEditor.cs @@ -20,15 +20,15 @@ namespace Umbraco.Web.PropertyEditors /// /// /// - /// + /// /// /// /// /// The object returned will always be a string and if the database type is not a valid string type an exception is thrown /// - public override object ToEditor(Property property, IDataTypeService dataTypeService, int? languageId = null, string segment = null) + public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { - var val = property.GetValue(languageId, segment); + var val = property.GetValue(culture, segment); if (val == null) return string.Empty; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.cs index 6fd731cbee..30bb553ae2 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/BTree.cs @@ -159,7 +159,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource pdatas.Add(pdata); var type = PrimitiveSerializer.Char.ReadFrom(stream); - pdata.LanguageId = (int?) ReadObject(type, stream); + pdata.Culture = (string) ReadObject(type, stream); type = PrimitiveSerializer.Char.ReadFrom(stream); pdata.Segment = (string) ReadObject(type, stream); type = PrimitiveSerializer.Char.ReadFrom(stream); @@ -211,7 +211,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource // write each value foreach (var pdata in kvp.Value) { - WriteObject(pdata.LanguageId, stream); + WriteObject(pdata.Culture, stream); WriteObject(pdata.Segment, stream); WriteObject(pdata.Value, stream); } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs index ab9d3cdcac..ddb9607575 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/PropertyData.cs @@ -4,8 +4,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource { internal class PropertyData { - [JsonProperty("lang")] - public int? LanguageId { get; set; } + [JsonProperty("culture")] + public string Culture { get; set; } [JsonProperty("seg")] public string Segment { get; set; } @@ -13,4 +13,4 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource [JsonProperty("val")] public object Value { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 9a74b66897..7fe898b080 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private object _interValue; // the variant source and inter values - private Dictionary _sourceValues; + private Dictionary _sourceValues; // the variant and non-variant object values private CacheValues _cacheValues; @@ -49,16 +49,16 @@ namespace Umbraco.Web.PublishedCache.NuCache { foreach (var sourceValue in sourceValues) { - if (sourceValue.LanguageId == null && sourceValue.Segment == null) + if (sourceValue.Culture == null && sourceValue.Segment == null) { _sourceValue = sourceValue.Value; } else { if (_sourceValues == null) - _sourceValues = new Dictionary(); - _sourceValues[new CompositeIntStringKey(sourceValue.LanguageId, sourceValue.Segment)] - = new SourceInterValue { LanguageId = sourceValue.LanguageId, Segment = sourceValue.Segment, SourceValue = sourceValue.Value }; + _sourceValues = new Dictionary(); + _sourceValues[new CompositeStringStringKey(sourceValue.Culture, sourceValue.Segment)] + = new SourceInterValue { Culture = sourceValue.Culture, Segment = sourceValue.Segment, SourceValue = sourceValue.Value }; } } } @@ -84,7 +84,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; } - public override bool HasValue(int? languageId = null, string segment = null) => _sourceValue != null + public override bool HasValue(string culture = null, string segment = null) => _sourceValue != null && (!(_sourceValue is string) || string.IsNullOrWhiteSpace((string) _sourceValue) == false); // used to cache the recursive *property* for this property @@ -143,9 +143,9 @@ namespace Umbraco.Web.PublishedCache.NuCache } // this is always invoked from within a lock, so does not require its own lock - private object GetInterValue(int? languageId, string segment) + private object GetInterValue(string culture, string segment) { - if (languageId == null && segment == null) + if (culture == null && segment == null) { if (_interInitialized) return _interValue; _interValue = PropertyType.ConvertSourceToInter(_content, _sourceValue, _isPreviewing); @@ -154,11 +154,11 @@ namespace Umbraco.Web.PublishedCache.NuCache } if (_sourceValues == null) - _sourceValues = new Dictionary(); + _sourceValues = new Dictionary(); - var k = new CompositeIntStringKey(languageId, segment); + var k = new CompositeStringStringKey(culture, segment); if (!_sourceValues.TryGetValue(k, out var vvalue)) - _sourceValues[k] = vvalue = new SourceInterValue { LanguageId = languageId, Segment = segment }; + _sourceValues[k] = vvalue = new SourceInterValue { Culture = culture, Segment = segment }; if (vvalue.InterInitialized) return vvalue.InterValue; vvalue.InterValue = PropertyType.ConvertSourceToInter(_content, vvalue.SourceValue, _isPreviewing); @@ -166,45 +166,45 @@ namespace Umbraco.Web.PublishedCache.NuCache return vvalue.InterValue; } - public override object GetSourceValue(int? languageId = null, string segment = null) + public override object GetSourceValue(string culture = null, string segment = null) { - if (languageId == null && segment == null) + if (culture == null && segment == null) return _sourceValue; lock (_locko) { if (_sourceValues == null) return null; - return _sourceValues.TryGetValue(new CompositeIntStringKey(languageId, segment), out var sourceValue) ? sourceValue.SourceValue : null; + return _sourceValues.TryGetValue(new CompositeStringStringKey(culture, segment), out var sourceValue) ? sourceValue.SourceValue : null; } } - public override object GetValue(int? languageId = null, string segment = null) + public override object GetValue(string culture = null, string segment = null) { lock (_locko) { - var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(languageId, segment); + var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); // initial reference cache level always is .Content const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue; - cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(languageId, segment), _isPreviewing); + cacheValues.ObjectValue = PropertyType.ConvertInterToObject(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); cacheValues.ObjectInitialized = true; return cacheValues.ObjectValue; } } - public override object GetXPathValue(int? languageId = null, string segment = null) + public override object GetXPathValue(string culture = null, string segment = null) { lock (_locko) { - var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(languageId, segment); + var cacheValues = GetCacheValues(PropertyType.CacheLevel).For(culture, segment); // initial reference cache level always is .Content const PropertyCacheLevel initialCacheLevel = PropertyCacheLevel.Element; if (cacheValues.XPathInitialized) return cacheValues.XPathValue; - cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(languageId, segment), _isPreviewing); + cacheValues.XPathValue = PropertyType.ConvertInterToXPath(_content, initialCacheLevel, GetInterValue(culture, segment), _isPreviewing); cacheValues.XPathInitialized = true; return cacheValues.XPathValue; } @@ -222,18 +222,18 @@ namespace Umbraco.Web.PublishedCache.NuCache private class CacheValues : CacheValue { - private Dictionary _values; + private Dictionary _values; // this is always invoked from within a lock, so does not require its own lock - public CacheValue For(int? languageId, string segment) + public CacheValue For(string culture, string segment) { - if (languageId == null && segment == null) + if (culture == null && segment == null) return this; if (_values == null) - _values = new Dictionary(); + _values = new Dictionary(); - var k = new CompositeIntStringKey(languageId, segment); + var k = new CompositeStringStringKey(culture, segment); if (!_values.TryGetValue(k, out var value)) _values[k] = value = new CacheValue(); @@ -243,9 +243,14 @@ namespace Umbraco.Web.PublishedCache.NuCache private class SourceInterValue { + private string _culture; private string _segment; - public int? LanguageId { get; set; } + public string Culture + { + get => _culture; + internal set => _culture = value?.ToLowerInvariant(); + } public string Segment { get => _segment; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 992ac62733..7f26f3c753 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1183,7 +1183,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { var value = published ? pvalue.PublishedValue : pvalue.EditedValue; if (value != null) - pdatas.Add(new PropertyData { LanguageId = pvalue.LanguageId, Segment = pvalue.Segment, Value = value }); + pdatas.Add(new PropertyData { Culture = pvalue.Culture, Segment = pvalue.Segment, Value = value }); //Core.Composing.Current.Logger.Debug($"{content.Id} {prop.Alias} [{pvalue.LanguageId},{pvalue.Segment}] {value} {(published?"pub":"edit")}"); diff --git a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs index 2c5b93f219..d8db937ca8 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedElementPropertyBase.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.PublishedCache IsMember = propertyType.ContentType.ItemType == PublishedItemType.Member; } - public override bool HasValue(int? languageId = null, string segment = null) + public override bool HasValue(string culture = null, string segment = null) => _sourceValue != null && (!(_sourceValue is string s) || !string.IsNullOrWhiteSpace(s)); // used to cache the CacheValues of this property @@ -136,9 +136,9 @@ namespace Umbraco.Web.PublishedCache return _interValue; } - public override object GetSourceValue(int? languageId = null, string segment = null) => _sourceValue; + public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; - public override object GetValue(int? languageId = null, string segment = null) + public override object GetValue(string culture = null, string segment = null) { GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); @@ -152,7 +152,7 @@ namespace Umbraco.Web.PublishedCache } } - public override object GetXPathValue(int? languageId = null, string segment = null) + public override object GetXPathValue(string culture = null, string segment = null) { GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs index 632698c37e..ea8ab925c6 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs @@ -27,13 +27,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// Gets the raw value of the property. /// - public override object GetSourceValue(int? languageId = null, string segment = null) => _sourceValue; + public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; // in the Xml cache, everything is a string, and to have a value // you want to have a non-null, non-empty string. - public override bool HasValue(int? languageId = null, string segment = null) => _sourceValue.Trim().Length > 0; + public override bool HasValue(string culture = null, string segment = null) => _sourceValue.Trim().Length > 0; - public override object GetValue(int? languageId = null, string segment = null) + public override object GetValue(string culture = null, string segment = null) { // NOT caching the source (intermediate) value since we'll never need it // everything in Xml cache is per-request anyways @@ -48,7 +48,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache return _objectValue; } - public override object GetXPathValue(int? languageId = null, string segment = null) { throw new NotImplementedException(); } + public override object GetXPathValue(string culture = null, string segment = null) { throw new NotImplementedException(); } public XmlPublishedProperty(PublishedPropertyType propertyType, IPublishedContent content, bool isPreviewing, XmlNode propertyXmlData) : this(propertyType, content, isPreviewing) diff --git a/src/Umbraco.Web/PublishedContentPropertyExtension.cs b/src/Umbraco.Web/PublishedContentPropertyExtension.cs index 3314bc09fc..6d397ffaa3 100644 --- a/src/Umbraco.Web/PublishedContentPropertyExtension.cs +++ b/src/Umbraco.Web/PublishedContentPropertyExtension.cs @@ -10,24 +10,24 @@ namespace Umbraco.Web { #region Value - public static T Value(this IPublishedProperty property, int? languageId = null, string segment = null) + public static T Value(this IPublishedProperty property, string culture = null, string segment = null) { - return property.Value(false, default(T), languageId, segment); + return property.Value(false, default(T), culture, segment); } - public static T Value(this IPublishedProperty property, T defaultValue, int? languageId = null, string segment = null) + public static T Value(this IPublishedProperty property, T defaultValue, string culture = null, string segment = null) { - return property.Value(true, defaultValue, languageId, segment); + return property.Value(true, defaultValue, culture, segment); } - internal static T Value(this IPublishedProperty property, bool withDefaultValue, T defaultValue, int? languageId = null, string segment = null) + internal static T Value(this IPublishedProperty property, bool withDefaultValue, T defaultValue, string culture = null, string segment = null) { - if (property.HasValue(languageId, segment) == false && withDefaultValue) return defaultValue; + if (property.HasValue(culture, segment) == false && withDefaultValue) return defaultValue; // else we use .Value so we give the converter a chance to handle the default value differently // eg for IEnumerable it may return Enumerable.Empty instead of null - var value = property.GetValue(languageId, segment); + var value = property.GetValue(culture, segment); // if value is null (strange but why not) it still is OK to call TryConvertTo // because it's an extension method (hence no NullRef) which will return a diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs index c2a818a926..bde74c0ab1 100644 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -48,10 +48,10 @@ namespace Umbraco.Web /// Gets a value indicating whether the content has a value for a property identified by its alias. /// /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is true. - public static bool HasValue(this IPublishedElement content, string alias, int? languageId = null, string segment = null) + public static bool HasValue(this IPublishedElement content, string alias, string culture = null, string segment = null) { var prop = content.GetProperty(alias); - return prop != null && prop.HasValue(languageId, segment); + return prop != null && prop.HasValue(culture, segment); } /// @@ -63,8 +63,7 @@ namespace Umbraco.Web /// The value to return if the content has no value for the property. /// Either or depending on whether the content /// has a value for the property identified by the alias. - public static IHtmlString HasValue(this IPublishedElement content, string alias, - string valueIfTrue, string valueIfFalse = null) + public static IHtmlString IfHasValue(this IPublishedElement content, string alias, string valueIfTrue, string valueIfFalse = null) { return content.HasValue(alias) ? new HtmlString(valueIfTrue) @@ -80,7 +79,7 @@ namespace Umbraco.Web /// /// The content. /// The property alias. - /// The variation language. + /// The variation language. /// The variation segment. /// The value of the content's property identified by the alias. /// @@ -89,10 +88,31 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static object Value(this IPublishedElement content, string alias, int? languageId = null, string segment = null) + public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null) { var property = content.GetProperty(alias); - return property?.GetValue(languageId, segment); + return property?.GetValue(culture, segment); + } + + /// + /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. + /// + /// The content. + /// The property alias. + /// The default value. + /// The variation language. + /// The variation segment. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. + /// If no property with the specified alias exists, or if the property has no value, returns . + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. + /// The alias is case-insensitive. + /// + public static object Value(this IPublishedElement content, string alias, string defaultValue, string culture = null, string segment = null) // fixme - kill + { + var property = content.GetProperty(alias); + return property == null || property.HasValue(culture, segment) == false ? defaultValue : property.GetValue(culture, segment); } /// @@ -110,31 +130,10 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static object Value(this IPublishedElement content, string alias, string defaultValue, int? languageId = null, string segment = null) // fixme - kill + public static object Value(this IPublishedElement content, string alias, object defaultValue, string culture = null, string segment = null) { var property = content.GetProperty(alias); - return property == null || property.HasValue(languageId, segment) == false ? defaultValue : property.GetValue(languageId, segment); - } - - /// - /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. - /// - /// The content. - /// The property alias. - /// The default value. - /// The variation language. - /// The variation segment. - /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object Value(this IPublishedElement content, string alias, object defaultValue, int? languageId = null, string segment = null) - { - var property = content.GetProperty(alias); - return property == null || property.HasValue(languageId, segment) == false ? defaultValue : property.GetValue(languageId, segment); + return property == null || property.HasValue(culture, segment) == false ? defaultValue : property.GetValue(culture, segment); } #endregion @@ -147,7 +146,7 @@ namespace Umbraco.Web /// The target property type. /// The content. /// The property alias. - /// The variation language. + /// The variation language. /// The variation segment. /// The value of the content's property identified by the alias, converted to the specified type. /// @@ -156,9 +155,9 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static T Value(this IPublishedElement content, string alias, int? languageId = null, string segment = null) + public static T Value(this IPublishedElement content, string alias, string culture = null, string segment = null) { - return content.Value(alias, false, default(T), languageId, segment); + return content.Value(alias, false, default(T), culture, segment); } /// @@ -168,7 +167,7 @@ namespace Umbraco.Web /// The content. /// The property alias. /// The default value. - /// The variation language. + /// The variation language. /// The variation segment. /// The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value. /// @@ -177,17 +176,17 @@ namespace Umbraco.Web /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. /// The alias is case-insensitive. /// - public static T Value(this IPublishedElement content, string alias, T defaultValue, int? languageId = null, string segment = null) + public static T Value(this IPublishedElement content, string alias, T defaultValue, string culture = null, string segment = null) { - return content.Value(alias, true, defaultValue, languageId, segment); + return content.Value(alias, true, defaultValue, culture, segment); } - internal static T Value(this IPublishedElement content, string alias, bool withDefaultValue, T defaultValue, int? languageId = null, string segment = null) // fixme uh? + internal static T Value(this IPublishedElement content, string alias, bool withDefaultValue, T defaultValue, string culture = null, string segment = null) // fixme uh? { var property = content.GetProperty(alias); if (property == null) return defaultValue; - return property.Value(withDefaultValue, defaultValue, languageId, segment); + return property.Value(withDefaultValue, defaultValue, culture, segment); } #endregion diff --git a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs index 0a399c24c5..525b1fcf2c 100644 --- a/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/WebApi/Binders/ContentItemBinder.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.WebApi.Binders { return ContextMapper.Map>(content, new Dictionary { - [ContextMapper.LanguageKey] = languageId + [ContextMapper.CultureKey] = languageId }); } } diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index ad1ac877be..e940d01470 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -374,17 +374,17 @@ namespace umbraco _content = content; } - public override bool HasValue(int? languageId = null, string segment = null) + public override bool HasValue(string culture = null, string segment = null) { return _sourceValue != null && ((_sourceValue is string) == false || string.IsNullOrWhiteSpace((string)_sourceValue) == false); } - public override object GetSourceValue(int? languageId = null, string segment = null) + public override object GetSourceValue(string culture = null, string segment = null) { return _sourceValue; } - public override object GetValue(int? languageId = null, string segment = null) + public override object GetValue(string culture = null, string segment = null) { // isPreviewing is true here since we want to preview anyway... const bool isPreviewing = true; @@ -392,7 +392,7 @@ namespace umbraco return PropertyType.ConvertInterToObject(_content, PropertyCacheLevel.Unknown, source, isPreviewing); } - public override object GetXPathValue(int? languageId = null, string segment = null) + public override object GetXPathValue(string culture = null, string segment = null) { throw new NotImplementedException(); }