Merge branch 'temp8-I3089' of https://github.com/umbraco/Umbraco-CMS into temp8-I3089

This commit is contained in:
elitsa
2018-10-08 10:23:53 +02:00
201 changed files with 9580 additions and 4378 deletions

View File

@@ -32,7 +32,7 @@
/// <summary>
/// Used when nodes are unpublished
/// </summary>
UnPublish,
Unpublish,
/// <summary>
/// Used when nodes are moved
/// </summary>

View File

@@ -4,34 +4,54 @@ using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a language.
/// </summary>
public interface ILanguage : IEntity, IRememberBeingDirty
{
/// <summary>
/// Gets or sets the Iso Code for the Language
/// Gets or sets the ISO code of the language.
/// </summary>
[DataMember]
string IsoCode { get; set; }
/// <summary>
/// Gets or sets the Culture Name for the Language
/// Gets or sets the culture name of the language.
/// </summary>
[DataMember]
string CultureName { get; set; }
/// <summary>
/// Returns a <see cref="CultureInfo"/> object for the current Language
/// Gets the <see cref="CultureInfo"/> object for the language.
/// </summary>
[IgnoreDataMember]
CultureInfo CultureInfo { get; }
/// <summary>
/// Defines if this language is the default variant language when language variants are in use
/// Gets or sets a value indicating whether the language is the default language.
/// </summary>
bool IsDefaultVariantLanguage { get; set; }
[DataMember]
bool IsDefault { get; set; }
/// <summary>
/// If true, a variant node cannot be published unless this language variant is created
/// Gets or sets a value indicating whether the language is mandatory.
/// </summary>
bool Mandatory { get; set; }
/// <remarks>
/// <para>When a language is mandatory, a multi-lingual document cannot be published
/// without that language being published, and unpublishing that language unpublishes
/// the entire document.</para>
/// </remarks>
[DataMember]
bool IsMandatory { get; set; }
/// <summary>
/// Gets or sets the identifier of a fallback language.
/// </summary>
/// <remarks>
/// <para>The fallback language can be used in multi-lingual scenarios, to help
/// define fallback strategies when a value does not exist for a requested language.</para>
/// </remarks>
[DataMember]
int? FallbackLanguageId { get; set; }
}
}

View File

@@ -19,6 +19,7 @@ namespace Umbraco.Core.Models
private string _cultureName;
private bool _isDefaultVariantLanguage;
private bool _mandatory;
private int? _fallbackLanguageId;
public Language(string isoCode)
{
@@ -30,13 +31,12 @@ namespace Umbraco.Core.Models
{
public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.IsoCode);
public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.CultureName);
public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsDefaultVariantLanguage);
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.Mandatory);
public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsDefault);
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsMandatory);
public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, int?>(x => x.FallbackLanguageId);
}
/// <summary>
/// Gets or sets the Iso Code for the Language
/// </summary>
/// <inheritdoc />
[DataMember]
public string IsoCode
{
@@ -44,9 +44,7 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _isoCode, Ps.Value.IsoCodeSelector);
}
/// <summary>
/// Gets or sets the Culture Name for the Language
/// </summary>
/// <inheritdoc />
[DataMember]
public string CultureName
{
@@ -54,22 +52,29 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _cultureName, Ps.Value.CultureNameSelector);
}
/// <summary>
/// Returns a <see cref="CultureInfo"/> object for the current Language
/// </summary>
/// <inheritdoc />
[IgnoreDataMember]
public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode);
public bool IsDefaultVariantLanguage
/// <inheritdoc />
public bool IsDefault
{
get => _isDefaultVariantLanguage;
set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector);
}
public bool Mandatory
/// <inheritdoc />
public bool IsMandatory
{
get => _mandatory;
set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector);
}
/// <inheritdoc />
public int? FallbackLanguageId
{
get => _fallbackLanguageId;
set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector);
}
}
}

View File

@@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
/// <summary>
/// A set of tag changes.
/// </summary>
internal class PropertyTagChange
{
public ChangeType Type { get; set; }
public IEnumerable<(string Type, string Tags)> Tags { get; set; }
public enum ChangeType
{
Replace,
Remove,
Merge
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Manages the built-in fallback policies.
/// </summary>
public struct Fallback : IEnumerable<int>
{
private readonly int[] _values;
/// <summary>
/// Initializes a new instance of the <see cref="Fallback"/> struct with values.
/// </summary>
private Fallback(int[] values)
{
_values = values;
}
/// <summary>
/// Gets an ordered set of fallback policies.
/// </summary>
/// <param name="values"></param>
public static Fallback To(params int[] values) => new Fallback(values);
/// <summary>
/// Do not fallback.
/// </summary>
public const int None = 0;
/// <summary>
/// Fallback to default value.
/// </summary>
public const int DefaultValue = 1;
/// <summary>
/// Gets the fallback to default value policy.
/// </summary>
public static Fallback ToDefaultValue => new Fallback(new[] { DefaultValue });
/// <summary>
/// Fallback to other languages.
/// </summary>
public const int Language = 2;
/// <summary>
/// Gets the fallback to language policy.
/// </summary>
public static Fallback ToLanguage => new Fallback(new[] { Language });
/// <summary>
/// Fallback to tree ancestors.
/// </summary>
public const int Ancestors = 3;
/// <summary>
/// Gets the fallback to tree ancestors policy.
/// </summary>
public static Fallback ToAncestors => new Fallback(new[] { Ancestors });
/// <inheritdoc />
public IEnumerator<int> GetEnumerator()
{
return ((IEnumerable<int>)_values ?? Array.Empty<int>()).GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -1,37 +1,122 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.Models.PublishedContent
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Provides a fallback strategy for getting <see cref="IPublishedElement"/> values.
/// </summary>
// fixme - IPublishedValueFallback is still WorkInProgress
// todo - properly document methods, etc
// todo - understand caching vs fallback (recurse etc)
public interface IPublishedValueFallback
{
// note that at property level, property.GetValue() does NOT implement fallback, and one has
// to get property.Value() or property.Value<T>() to trigger fallback
/// <summary>
/// Tries to get a fallback value for a property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever property.Value(culture, segment, defaultValue) is called, and
/// property.HasValue(culture, segment) is false.</para>
/// <para>It can only fallback at property level (no recurse).</para>
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
/// <para>Note that <paramref name="culture"/> and <paramref name="segment"/> may not be contextualized,
/// so the variant context should be used to contextualize them (see our default implementation in
/// the web project.</para>
/// </remarks>
bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value);
// this method is called whenever property.Value(culture, segment, defaultValue) is called, and
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
/// <summary>
/// Tries to get a fallback value for a property.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="property">The property.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and
/// property.HasValue(culture, segment) is false.</para>
/// <para>It can only fallback at property level (no recurse).</para>
/// <para>At property level, property.GetValue() does *not* implement fallback, and one has to
/// get property.Value() or property.Value{T}() to trigger fallback.</para>
/// </remarks>
bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value);
object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue);
/// <summary>
/// Tries to get a fallback value for a published element property.
/// </summary>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// <para>It can only fallback at element level (no recurse).</para>
/// </remarks>
bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
// this method is called whenever property.Value<T>(culture, segment, defaultValue) is called, and
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
/// <summary>
/// Tries to get a fallback value for a published element property.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// <para>It can only fallback at element level (no recurse).</para>
/// </remarks>
bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue);
/// <summary>
/// Tries to get a fallback value for a published content property.
/// </summary>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// </remarks>
bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value);
// these methods to be called whenever getting the property value for the specified alias, culture and segment,
// either returned no property at all, or a property that does not HasValue for the specified culture and segment.
object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue);
T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue);
object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse);
T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse);
/// <summary>
/// Tries to get a fallback value for a published content property.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="content">The published element.</param>
/// <param name="alias">The property alias.</param>
/// <param name="culture">The requested culture.</param>
/// <param name="segment">The requested segment.</param>
/// <param name="fallback">A fallback strategy.</param>
/// <param name="defaultValue">An optional default value.</param>
/// <param name="value">The fallback value.</param>
/// <returns>A value indicating whether a fallback value could be provided.</returns>
/// <remarks>
/// <para>This method is called whenever getting the property value for the specified alias, culture and
/// segment, either returned no property at all, or a property with HasValue(culture, segment) being false.</para>
/// </remarks>
bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value);
}
}

View File

@@ -49,17 +49,24 @@ namespace Umbraco.Core.Models.PublishedContent
/// <param name="modelTypes">The model types map.</param>
/// <returns>The actual Clr type.</returns>
public static Type Map(Type type, Dictionary<string, Type> modelTypes)
=> Map(type, modelTypes, false);
public static Type Map(Type type, Dictionary<string, Type> modelTypes, bool dictionaryIsInvariant)
{
// it may be that senders forgot to send an invariant dictionary (garbage-in)
if (!dictionaryIsInvariant)
modelTypes = new Dictionary<string, Type>(modelTypes, StringComparer.InvariantCultureIgnoreCase);
if (type is ModelType modelType)
{
if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out Type actualType))
if (modelTypes.TryGetValue(modelType.ContentTypeAlias, out var actualType))
return actualType;
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\".");
}
if (type is ModelTypeArrayType arrayType)
{
if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out Type actualType))
if (modelTypes.TryGetValue(arrayType.ContentTypeAlias, out var actualType))
return actualType.MakeArrayType();
throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\".");
}
@@ -70,7 +77,7 @@ namespace Umbraco.Core.Models.PublishedContent
if (def == null)
throw new InvalidOperationException("panic");
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes)).ToArray();
var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray();
return def.MakeGenericType(args);
}
@@ -81,7 +88,14 @@ namespace Umbraco.Core.Models.PublishedContent
/// <param name="map">The model types map.</param>
/// <returns>The actual Clr type name.</returns>
public static string MapToName(Type type, Dictionary<string, string> map)
=> MapToName(type, map, false);
private static string MapToName(Type type, Dictionary<string, string> map, bool dictionaryIsInvariant)
{
// it may be that senders forgot to send an invariant dictionary (garbage-in)
if (!dictionaryIsInvariant)
map = new Dictionary<string, string>(map, StringComparer.InvariantCultureIgnoreCase);
if (type is ModelType modelType)
{
if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName))
@@ -102,7 +116,7 @@ namespace Umbraco.Core.Models.PublishedContent
if (def == null)
throw new InvalidOperationException("panic");
var args = type.GetGenericArguments().Select(x => MapToName(x, map)).ToArray();
var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray();
var defFullName = def.FullName.Substring(0, def.FullName.IndexOf('`'));
return defFullName + "<" + string.Join(", ", args) + ">";
}

View File

@@ -9,21 +9,45 @@
public class NoopPublishedValueFallback : IPublishedValueFallback
{
/// <inheritdoc />
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue) => defaultValue;
public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
value = default;
return false;
}
/// <inheritdoc />
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue) => defaultValue;
public bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
value = default;
return false;
}
/// <inheritdoc />
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue) => defaultValue;
public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
value = default;
return false;
}
/// <inheritdoc />
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue) => defaultValue;
public bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
value = default;
return false;
}
/// <inheritdoc />
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) => defaultValue;
public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
{
value = default;
return false;
}
/// <inheritdoc />
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) => defaultValue;
public bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
{
value = default;
return false;
}
}
}
}

View File

@@ -0,0 +1,15 @@
namespace Umbraco.Core.Models.PublishedContent
{
public static class VariationContextAccessorExtensions
{
public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment)
{
if (culture != null && segment != null) return;
// use context values
var publishedVariationContext = variationContextAccessor?.VariationContext;
if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : "";
if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : "";
}
}
}