diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs index f30a53c8b6..4b6e7cb10a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs @@ -94,12 +94,17 @@ /// A fallback strategy. /// An optional default value. /// The fallback value. + /// The property that does not have a value. /// A value indicating whether a fallback value could be provided. /// /// 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. + /// In an , because walking up the tree is possible, the content itself may not even + /// have a property with the specified alias, but such a property may exist up in the tree. The + /// parameter is used to return a property with no value. That can then be used to invoke a converter and get the + /// converter's interpretation of "no value". /// - bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value); + bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value, out IPublishedProperty noValueProperty); /// /// Tries to get a fallback value for a published content property. @@ -117,6 +122,6 @@ /// 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. /// - bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value); + bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs index cd7b063d44..245bbd1d39 100644 --- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs @@ -37,16 +37,18 @@ } /// - public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value) + public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value, out IPublishedProperty noValueProperty) { value = default; + noValueProperty = default; return false; } /// - public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) + public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty) { value = default; + noValueProperty = default; return false; } } diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 691744d6df..2890591ef1 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -45,7 +45,7 @@ namespace Umbraco.Web.Models.PublishedContent value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(property, culture, segment, defaultValue, out value)) + if (TryGetValueWithLanguageFallback(property, culture, segment, out value)) return true; break; default: @@ -53,7 +53,7 @@ namespace Umbraco.Web.Models.PublishedContent } } - value = defaultValue; + value = default; return false; } @@ -72,6 +72,7 @@ namespace Umbraco.Web.Models.PublishedContent value = default; return false; } + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); foreach (var f in fallback) @@ -84,7 +85,7 @@ namespace Umbraco.Web.Models.PublishedContent value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value)) + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) return true; break; default: @@ -92,33 +93,32 @@ namespace Umbraco.Web.Models.PublishedContent } } - value = defaultValue; + value = default; return false; } /// - public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value) + public bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, object defaultValue, out object value, out IPublishedProperty noValueProperty) { - // is that ok? - return TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value); + return TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value, out noValueProperty); } /// - public virtual bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value) + public virtual bool TryGetValue(IPublishedContent content, string alias, string culture, string segment, Fallback fallback, T defaultValue, out T value, out IPublishedProperty noValueProperty) { + noValueProperty = default; + var propertyType = content.ContentType.GetPropertyType(alias); - if (propertyType == null) + if (propertyType != null) { - value = default; - return false; + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + noValueProperty = content.GetProperty(alias); } - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); // 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) { switch (f) @@ -129,34 +129,21 @@ namespace Umbraco.Web.Models.PublishedContent value = defaultValue; return true; case Fallback.Language: - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, defaultValue, out value)) + if (propertyType == null) + continue; + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) return true; break; case Fallback.Ancestors: - IPublishedProperty noValueProperty; - if (TryGetValueWithRecursiveFallback(content, alias, culture, segment, defaultValue, out value, - out noValueProperty)) - { + if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty)) return true; - } - - // if we found a property, even though with no value, return that property value - // because the converter may want to handle the missing value. ie if defaultValue is default, - // either specified or by default, the converter may want to substitute something else. - if (noValueProperty != null) - { - value = noValueProperty.Value(culture, segment, fallback.Contains(Fallback.DefaultValue) ? Fallback.ToDefaultValue : Fallback.To(), defaultValue: defaultValue); - return true; - } break; default: throw NotSupportedFallbackMethod(f, "content"); } } - - - value = defaultValue; + value = default; return false; } @@ -166,10 +153,11 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, recursing the tree - private bool TryGetValueWithRecursiveFallback(IPublishedContent content, string alias, string culture, string segment, T defaultValue, out T value, out IPublishedProperty noValueProperty) + // 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(IPublishedContent content, string alias, string culture, string segment, out T value, ref IPublishedProperty noValueProperty) { - IPublishedProperty property = null; // if we are here, content's property has no value - noValueProperty = null; + IPublishedProperty property; // if we are here, content's property has no value do { content = content.Parent; @@ -184,7 +172,7 @@ namespace Umbraco.Web.Models.PublishedContent } property = content?.GetProperty(alias); - if (property != null) + if (property != null && noValueProperty != null) { noValueProperty = property; } @@ -198,15 +186,14 @@ namespace Umbraco.Web.Models.PublishedContent return true; } - - value = defaultValue; + value = default; return false; } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, T defaultValue, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string culture, string segment, out T value) { - value = defaultValue; + value = default; if (culture.IsNullOrWhiteSpace()) return false; @@ -238,9 +225,9 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, T defaultValue, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string culture, string segment, out T value) { - value = defaultValue; + value = default; if (culture.IsNullOrWhiteSpace()) return false; @@ -272,15 +259,15 @@ namespace Umbraco.Web.Models.PublishedContent } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, T defaultValue, out T value) + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string culture, string segment, out T value) { - value = defaultValue; + value = default; if (culture.IsNullOrWhiteSpace()) return false; var visited = new HashSet(); - // fixme + // TODO // _localizationService.GetXxx() is expensive, it deep clones objects // we want _localizationService.GetReadOnlyXxx() returning IReadOnlyLanguage which cannot be saved back = no need to clone diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index c7fbd51389..f38df4288f 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -4,9 +4,7 @@ using System.Data; using System.Linq; using System.Web; using Examine; -using Examine.Search; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -15,8 +13,6 @@ using Umbraco.Web.Composing; namespace Umbraco.Web { - using Examine = global::Examine; - /// /// Provides extension methods for IPublishedContent. /// @@ -88,24 +84,21 @@ namespace Umbraco.Web public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) { - if (Current.Configs.Settings().WebRouting.DisableAlternativeTemplates == true) + if (Current.Configs.Settings().WebRouting.DisableAlternativeTemplates) return content.TemplateId == templateId; - if (content.TemplateId != templateId && Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates == true) - { - // fixme - perfs? nothing cached here - var publishedContentContentType = Current.Services.ContentTypeService.Get(content.ContentType.Id); - if (publishedContentContentType == null) - throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); + if (content.TemplateId == templateId || !Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates) + return true; - return publishedContentContentType.IsAllowedTemplate(templateId); - } + var publishedContentContentType = Current.Services.ContentTypeService.Get(content.ContentType.Id); + if (publishedContentContentType == null) + throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); + + return publishedContentContentType.IsAllowedTemplate(templateId); - return true; } public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) { - // fixme - perfs? nothing cached here var template = Current.Services.FileService.GetTemplate(templateAlias); return template != null && content.IsAllowedTemplate(template.Id); } @@ -168,12 +161,7 @@ namespace Umbraco.Web return true; // else let fallback try to get a value - // fixme - really? - if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _)) - return true; - - // else... no - return false; + return PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _, out _); } /// @@ -195,15 +183,12 @@ namespace Umbraco.Web return property.GetValue(culture, segment); // else let fallback try to get a value - if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) + if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, out property)) return value; - if (property == null) - return null; - // else... if we have a property, at least let the converter return its own // vision of 'no value' (could be an empty enumerable) - return property.GetValue(culture, segment); + return property?.GetValue(culture, segment); } /// @@ -226,7 +211,7 @@ namespace Umbraco.Web return property.Value(culture, segment); // else let fallback try to get a value - if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) + if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, out property)) return value; // else... if we have a property, at least let the converter return its own @@ -234,29 +219,6 @@ namespace Umbraco.Web return property == null ? default : property.Value(culture, segment); } - // fixme - .Value() refactoring - in progress - public static IHtmlString Value(this IPublishedContent content, string aliases, Func format, string alt = "", int fallback = 0) - { - var aliasesA = aliases.Split(','); - if (aliasesA.Length == 0) - return new HtmlString(string.Empty); - - throw new NotImplementedException("WorkInProgress"); - - var property = content.GetProperty(aliasesA[0]); - - //var property = aliases.Split(',') - // .Where(x => string.IsNullOrWhiteSpace(x) == false) - // .Select(x => content.GetProperty(x.Trim(), recurse)) - // .FirstOrDefault(x => x != null); - - //if (format == null) format = x => x.ToString(); - - //return property != null - // ? new HtmlString(format(property.Value())) - // : new HtmlString(alt); - } - #endregion #region Variations @@ -992,9 +954,8 @@ namespace Umbraco.Web /// public static IEnumerable Children(this IPublishedContent content, string culture = null) { - if (content == null) throw new ArgumentNullException(nameof(content)); + if (content == null) throw new ArgumentNullException(nameof(content)); // fixme wtf is this? -// return content.Children.Where(x => { if (!x.ContentType.VariesByCulture()) return true; // invariant = always ok @@ -1056,10 +1017,7 @@ namespace Umbraco.Web /// /// Gets the first child of the content, of a given content type. /// - /// The content. - /// The content type alias. - /// The first child of content, of the given content type. - public static IPublishedContent FirstChild(this IPublishedContent content, string alias, string culture = null) + public static IPublishedContent FirstChild(this IPublishedContent content, string alias, string culture = null) // fixme oops { return content.Children(culture,alias).FirstOrDefault(); }