2018-07-21 15:58:49 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2019-01-11 11:47:42 +01:00
|
|
|
|
using System.Linq;
|
2018-07-24 13:32:29 +02:00
|
|
|
|
using Umbraco.Core;
|
2018-07-21 09:41:07 +02:00
|
|
|
|
using Umbraco.Core.Models.PublishedContent;
|
2018-07-24 13:32:29 +02:00
|
|
|
|
using Umbraco.Core.Services;
|
2018-05-02 13:38:45 +02:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.Models.PublishedContent
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Provides a default implementation for <see cref="IPublishedValueFallback"/>.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class PublishedValueFallback : IPublishedValueFallback
|
|
|
|
|
|
{
|
2018-07-24 13:32:29 +02:00
|
|
|
|
private readonly ILocalizationService _localizationService;
|
2018-10-03 16:19:41 +02:00
|
|
|
|
private readonly IVariationContextAccessor _variationContextAccessor;
|
2018-07-24 13:32:29 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="PublishedValueFallback"/> class.
|
|
|
|
|
|
/// </summary>
|
2018-10-03 16:19:41 +02:00
|
|
|
|
public PublishedValueFallback(ServiceContext serviceContext, IVariationContextAccessor variationContextAccessor)
|
2018-07-24 13:32:29 +02:00
|
|
|
|
{
|
2018-07-28 07:58:22 +02:00
|
|
|
|
_localizationService = serviceContext.LocalizationService;
|
2018-10-03 16:19:41 +02:00
|
|
|
|
_variationContextAccessor = variationContextAccessor;
|
2018-07-24 13:32:29 +02:00
|
|
|
|
}
|
2018-05-02 13:38:45 +02:00
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-10-03 10:31:35 +02:00
|
|
|
|
public bool TryGetValue(IPublishedProperty property, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
2018-05-02 13:38:45 +02:00
|
|
|
|
{
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return TryGetValue<object>(property, culture, segment, fallback, defaultValue, out value);
|
2018-05-02 13:38:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-10-03 10:31:35 +02:00
|
|
|
|
public bool TryGetValue<T>(IPublishedProperty property, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
2018-05-02 13:38:45 +02:00
|
|
|
|
{
|
2018-10-03 16:19:41 +02:00
|
|
|
|
_variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment);
|
|
|
|
|
|
|
2018-10-03 10:31:35 +02:00
|
|
|
|
foreach (var f in fallback)
|
2018-07-28 08:39:02 +02:00
|
|
|
|
{
|
2018-10-03 10:31:35 +02:00
|
|
|
|
switch (f)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Fallback.None:
|
|
|
|
|
|
continue;
|
|
|
|
|
|
case Fallback.DefaultValue:
|
|
|
|
|
|
value = defaultValue;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
case Fallback.Language:
|
2019-01-21 14:27:11 +01:00
|
|
|
|
if (TryGetValueWithLanguageFallback(property, culture, segment, out value))
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw NotSupportedFallbackMethod(f, "property");
|
|
|
|
|
|
}
|
2018-07-28 08:39:02 +02:00
|
|
|
|
}
|
2018-10-03 10:31:35 +02:00
|
|
|
|
|
2019-01-21 14:27:11 +01:00
|
|
|
|
value = default;
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return false;
|
2018-05-02 13:38:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-10-03 10:31:35 +02:00
|
|
|
|
public bool TryGetValue(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value)
|
2018-05-02 13:38:45 +02:00
|
|
|
|
{
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return TryGetValue<object>(content, alias, culture, segment, fallback, defaultValue, out value);
|
2018-05-02 13:38:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2018-10-03 10:31:35 +02:00
|
|
|
|
public bool TryGetValue<T>(IPublishedElement content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value)
|
2018-05-02 13:38:45 +02:00
|
|
|
|
{
|
2018-10-03 16:19:41 +02:00
|
|
|
|
var propertyType = content.ContentType.GetPropertyType(alias);
|
|
|
|
|
|
if (propertyType == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
value = default;
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2019-01-21 14:27:11 +01:00
|
|
|
|
|
2018-10-03 16:19:41 +02:00
|
|
|
|
_variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment);
|
|
|
|
|
|
|
2018-10-03 10:31:35 +02:00
|
|
|
|
foreach (var f in fallback)
|
2018-07-28 08:39:02 +02:00
|
|
|
|
{
|
2018-10-03 10:31:35 +02:00
|
|
|
|
switch (f)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Fallback.None:
|
|
|
|
|
|
continue;
|
|
|
|
|
|
case Fallback.DefaultValue:
|
|
|
|
|
|
value = defaultValue;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
case Fallback.Language:
|
2019-01-21 14:27:11 +01:00
|
|
|
|
if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value))
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw NotSupportedFallbackMethod(f, "element");
|
|
|
|
|
|
}
|
2018-07-28 08:39:02 +02:00
|
|
|
|
}
|
2018-10-03 10:31:35 +02:00
|
|
|
|
|
2019-01-21 14:27:11 +01:00
|
|
|
|
value = default;
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return false;
|
2018-05-02 13:38:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2019-01-21 14:27:11 +01:00
|
|
|
|
public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value, out IPublishedProperty noValueProperty)
|
2018-05-02 13:38:45 +02:00
|
|
|
|
{
|
2019-01-21 14:27:11 +01:00
|
|
|
|
return TryGetValue<object>(content, alias, culture, segment, fallback, defaultValue, out value, out noValueProperty);
|
2018-05-02 13:38:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
2019-01-21 14:27:11 +01:00
|
|
|
|
public virtual bool TryGetValue<T>(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty)
|
2018-07-21 15:58:49 +02:00
|
|
|
|
{
|
2019-01-21 14:27:11 +01:00
|
|
|
|
noValueProperty = default;
|
|
|
|
|
|
|
2018-10-03 16:19:41 +02:00
|
|
|
|
var propertyType = content.ContentType.GetPropertyType(alias);
|
2019-01-21 14:27:11 +01:00
|
|
|
|
if (propertyType != null)
|
2018-10-03 16:19:41 +02:00
|
|
|
|
{
|
2019-01-21 14:27:11 +01:00
|
|
|
|
_variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment);
|
|
|
|
|
|
noValueProperty = content.GetProperty(alias);
|
2018-10-03 16:19:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-10-03 10:31:35 +02:00
|
|
|
|
// note: we don't support "recurse & language" which would walk up the tree,
|
|
|
|
|
|
// looking at languages at each level - should someone need it... they'll have
|
|
|
|
|
|
// to implement it.
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var f in fallback)
|
2018-07-21 15:58:49 +02:00
|
|
|
|
{
|
2018-10-03 10:31:35 +02:00
|
|
|
|
switch (f)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Fallback.None:
|
|
|
|
|
|
continue;
|
|
|
|
|
|
case Fallback.DefaultValue:
|
|
|
|
|
|
value = defaultValue;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
case Fallback.Language:
|
2019-01-21 14:27:11 +01:00
|
|
|
|
if (propertyType == null)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value))
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case Fallback.Ancestors:
|
2019-01-21 14:27:11 +01:00
|
|
|
|
if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty))
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw NotSupportedFallbackMethod(f, "content");
|
|
|
|
|
|
}
|
2018-07-21 15:58:49 +02:00
|
|
|
|
}
|
2018-10-03 10:31:35 +02:00
|
|
|
|
|
2019-01-21 14:27:11 +01:00
|
|
|
|
value = default;
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return false;
|
2018-07-21 15:58:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-10-03 10:31:35 +02:00
|
|
|
|
private NotSupportedException NotSupportedFallbackMethod(int fallback, string level)
|
2018-07-28 08:39:02 +02:00
|
|
|
|
{
|
2018-10-03 10:31:35 +02:00
|
|
|
|
return new NotSupportedException($"Fallback {GetType().Name} does not support fallback code '{fallback}' at {level} level.");
|
2018-07-28 08:39:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 13:32:29 +02:00
|
|
|
|
// tries to get a value, recursing the tree
|
2019-01-21 14:27:11 +01:00
|
|
|
|
// because we recurse, content may not even have the a property with the specified alias (but only some ancestor)
|
|
|
|
|
|
// in case no value was found, noValueProperty contains the first property that was found (which does not have a value)
|
|
|
|
|
|
private bool TryGetValueWithAncestorsFallback<T>(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty)
|
2018-07-21 15:58:49 +02:00
|
|
|
|
{
|
2019-01-21 14:27:11 +01:00
|
|
|
|
IPublishedProperty property; // if we are here, content's property has no value
|
2018-05-02 13:38:45 +02:00
|
|
|
|
do
|
|
|
|
|
|
{
|
|
|
|
|
|
content = content.Parent;
|
2019-01-11 11:47:42 +01:00
|
|
|
|
|
|
|
|
|
|
var propertyType = content?.ContentType.GetPropertyType(alias);
|
|
|
|
|
|
|
|
|
|
|
|
if (propertyType != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
culture = null;
|
|
|
|
|
|
segment = null;
|
|
|
|
|
|
_variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-02 13:38:45 +02:00
|
|
|
|
property = content?.GetProperty(alias);
|
2019-01-21 17:36:25 +01:00
|
|
|
|
if (property != null && noValueProperty == null)
|
2018-07-21 15:58:49 +02:00
|
|
|
|
{
|
|
|
|
|
|
noValueProperty = property;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
while (content != null && (property == null || property.HasValue(culture, segment) == false));
|
2018-05-02 13:38:45 +02:00
|
|
|
|
|
|
|
|
|
|
// if we found a content with the property having a value, return that property value
|
|
|
|
|
|
if (property != null && property.HasValue(culture, segment))
|
2018-07-21 15:58:49 +02:00
|
|
|
|
{
|
|
|
|
|
|
value = property.Value<T>(culture, segment);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2018-05-02 13:38:45 +02:00
|
|
|
|
|
2019-01-21 14:27:11 +01:00
|
|
|
|
value = default;
|
2018-07-21 15:58:49 +02:00
|
|
|
|
return false;
|
2018-05-02 13:38:45 +02:00
|
|
|
|
}
|
2018-07-24 13:32:29 +02:00
|
|
|
|
|
2018-07-28 08:39:02 +02:00
|
|
|
|
// tries to get a value, falling back onto other languages
|
2019-01-21 14:27:11 +01:00
|
|
|
|
private bool TryGetValueWithLanguageFallback<T>(IPublishedProperty property, string culture, string segment, out T value)
|
2018-07-28 08:39:02 +02:00
|
|
|
|
{
|
2019-01-21 14:27:11 +01:00
|
|
|
|
value = default;
|
2018-07-28 08:39:02 +02:00
|
|
|
|
|
|
|
|
|
|
if (culture.IsNullOrWhiteSpace()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var visited = new HashSet<int>();
|
|
|
|
|
|
|
|
|
|
|
|
var language = _localizationService.GetLanguageByIsoCode(culture);
|
|
|
|
|
|
if (language == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (language.FallbackLanguageId == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var language2Id = language.FallbackLanguageId.Value;
|
|
|
|
|
|
if (visited.Contains(language2Id)) return false;
|
|
|
|
|
|
visited.Add(language2Id);
|
|
|
|
|
|
|
|
|
|
|
|
var language2 = _localizationService.GetLanguageById(language2Id);
|
|
|
|
|
|
if (language2 == null) return false;
|
|
|
|
|
|
var culture2 = language2.IsoCode;
|
|
|
|
|
|
|
|
|
|
|
|
if (property.HasValue(culture2, segment))
|
|
|
|
|
|
{
|
|
|
|
|
|
value = property.Value<T>(culture2, segment);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
language = language2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// tries to get a value, falling back onto other languages
|
2019-01-21 14:27:11 +01:00
|
|
|
|
private bool TryGetValueWithLanguageFallback<T>(IPublishedElement content, string alias, string culture, string segment, out T value)
|
2018-07-28 08:39:02 +02:00
|
|
|
|
{
|
2019-01-21 14:27:11 +01:00
|
|
|
|
value = default;
|
2018-07-28 08:39:02 +02:00
|
|
|
|
|
|
|
|
|
|
if (culture.IsNullOrWhiteSpace()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var visited = new HashSet<int>();
|
|
|
|
|
|
|
|
|
|
|
|
var language = _localizationService.GetLanguageByIsoCode(culture);
|
|
|
|
|
|
if (language == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (language.FallbackLanguageId == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var language2Id = language.FallbackLanguageId.Value;
|
|
|
|
|
|
if (visited.Contains(language2Id)) return false;
|
|
|
|
|
|
visited.Add(language2Id);
|
|
|
|
|
|
|
|
|
|
|
|
var language2 = _localizationService.GetLanguageById(language2Id);
|
|
|
|
|
|
if (language2 == null) return false;
|
|
|
|
|
|
var culture2 = language2.IsoCode;
|
|
|
|
|
|
|
|
|
|
|
|
if (content.HasValue(alias, culture2, segment))
|
|
|
|
|
|
{
|
|
|
|
|
|
value = content.Value<T>(alias, culture2, segment);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
language = language2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-24 13:32:29 +02:00
|
|
|
|
// tries to get a value, falling back onto other languages
|
2019-01-21 14:27:11 +01:00
|
|
|
|
private bool TryGetValueWithLanguageFallback<T>(IPublishedContent content, string alias, string culture, string segment, out T value)
|
2018-07-24 13:32:29 +02:00
|
|
|
|
{
|
2019-01-21 14:27:11 +01:00
|
|
|
|
value = default;
|
2018-07-24 13:32:29 +02:00
|
|
|
|
|
|
|
|
|
|
if (culture.IsNullOrWhiteSpace()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var visited = new HashSet<int>();
|
|
|
|
|
|
|
2019-01-21 15:57:48 +01:00
|
|
|
|
// todo
|
2018-07-24 13:32:29 +02:00
|
|
|
|
// _localizationService.GetXxx() is expensive, it deep clones objects
|
|
|
|
|
|
// we want _localizationService.GetReadOnlyXxx() returning IReadOnlyLanguage which cannot be saved back = no need to clone
|
|
|
|
|
|
|
|
|
|
|
|
var language = _localizationService.GetLanguageByIsoCode(culture);
|
|
|
|
|
|
if (language == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (language.FallbackLanguageId == null) return false;
|
|
|
|
|
|
|
|
|
|
|
|
var language2Id = language.FallbackLanguageId.Value;
|
|
|
|
|
|
if (visited.Contains(language2Id)) return false;
|
|
|
|
|
|
visited.Add(language2Id);
|
|
|
|
|
|
|
|
|
|
|
|
var language2 = _localizationService.GetLanguageById(language2Id);
|
|
|
|
|
|
if (language2 == null) return false;
|
|
|
|
|
|
var culture2 = language2.IsoCode;
|
|
|
|
|
|
|
|
|
|
|
|
if (content.HasValue(alias, culture2, segment))
|
|
|
|
|
|
{
|
|
|
|
|
|
value = content.Value<T>(alias, culture2, segment);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
language = language2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-05-02 13:38:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|