diff --git a/src/Umbraco.Infrastructure/Models/PublishedContent/IndexedArrayItem.cs b/src/Umbraco.Abstractions/Models/PublishedContent/IndexedArrayItem.cs similarity index 100% rename from src/Umbraco.Infrastructure/Models/PublishedContent/IndexedArrayItem.cs rename to src/Umbraco.Abstractions/Models/PublishedContent/IndexedArrayItem.cs diff --git a/src/Umbraco.Abstractions/PublishedContentExtensions.cs b/src/Umbraco.Abstractions/PublishedContentExtensions.cs index 53618ea632..aa432459dd 100644 --- a/src/Umbraco.Abstractions/PublishedContentExtensions.cs +++ b/src/Umbraco.Abstractions/PublishedContentExtensions.cs @@ -9,6 +9,56 @@ namespace Umbraco.Core { public static class PublishedContentExtensions { + #region Name + + /// + /// Gets the name of the content item. + /// + /// The content item. + /// + /// The specific culture to get the name for. If null is used the current culture is used (Default is null). + public static string Name(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.Name : null; + + // handle context culture for variant + if (culture == null) + culture = variationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Name : null; + } + + #endregion + + #region Url segment + + /// + /// Gets the url segment of the content item. + /// + /// The content item. + /// + /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null). + public static string UrlSegment(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) + return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.UrlSegment : null; + + // handle context culture for variant + if (culture == null) + culture = variationContextAccessor?.VariationContext?.Culture ?? ""; + + // get + return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; + } + + #endregion + + #region Culture + /// /// Determines whether the content has a culture. /// @@ -40,47 +90,6 @@ namespace Umbraco.Core return contents.Where(x => !x.ContentType.VariesByCulture() || HasCulture(x, culture)); } - /// - /// Gets the name of the content item. - /// - /// The content item. - /// - /// The specific culture to get the name for. If null is used the current culture is used (Default is null). - public static string Name(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string culture = null) - { - // invariant has invariant value (whatever the requested culture) - if (!content.ContentType.VariesByCulture()) - return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.Name : null; - - // handle context culture for variant - if (culture == null) - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; - - // get - return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Name : null; - } - - - /// - /// Gets the url segment of the content item. - /// - /// The content item. - /// - /// The specific culture to get the url segment for. If null is used the current culture is used (Default is null). - public static string UrlSegment(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string culture = null) - { - // invariant has invariant value (whatever the requested culture) - if (!content.ContentType.VariesByCulture()) - return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.UrlSegment : null; - - // handle context culture for variant - if (culture == null) - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; - - // get - return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; - } - /// /// Gets the culture date of the content item. /// @@ -101,53 +110,211 @@ namespace Umbraco.Core return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; } + #endregion + + #region IsComposedOf + /// - /// Gets the children of the content item. + /// Gets a value indicating whether the content is of a content type composed of the given alias /// - /// The content item. - /// - /// - /// The specific culture to get the url children for. Default is null which will use the current culture in - /// - /// - /// Gets children that are available for the specified culture. - /// Children are sorted by their sortOrder. - /// - /// For culture, - /// if null is used the current culture is used. - /// If an empty string is used only invariant children are returned. - /// If "*" is used all children are returned. - /// - /// - /// If a variant culture is specified or there is a current culture in the then the Children returned - /// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture. - /// However, if an empty string is specified only invariant children are returned. - /// - /// - public static IEnumerable Children(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string culture = null) + /// The content. + /// The content type alias. + /// A value indicating whether the content is of a content type composed of a content type identified by the alias. + public static bool IsComposedOf(this IPublishedContent content, string alias) { - // handle context culture for variant - if (culture == null) - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; - - var children = content.ChildrenForAllCultures; - return culture == "*" - ? children - : children.Where(x => x.IsInvariantOrHasCulture(culture)); + return content.ContentType.CompositionAliases.InvariantContains(alias); } - #region Writer and creator + #endregion - public static string GetCreatorName(this IPublishedContent content, IUserService userService) + #region Template + + /// + /// Returns the current template Alias + /// + /// Empty string if none is set. + public static string GetTemplateAlias(this IPublishedContent content, IFileService fileService) { - var user = userService.GetProfileById(content.CreatorId); - return user?.Name; + if (content.TemplateId.HasValue == false) + { + return string.Empty; + } + + var template = fileService.GetTemplate(content.TemplateId.Value); + return template == null ? string.Empty : template.Alias; } - public static string GetWriterName(this IPublishedContent content, IUserService userService) + public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId) { - var user = userService.GetProfileById(content.WriterId); - return user?.Name; + if (disableAlternativeTemplates) + return content.TemplateId == templateId; + + if (content.TemplateId == templateId || !validateAlternativeTemplates) + return true; + + var publishedContentContentType = 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); + + } + public static bool IsAllowedTemplate(this IPublishedContent content, IFileService fileService, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, string templateAlias) + { + var template = fileService.GetTemplate(templateAlias); + return template != null && content.IsAllowedTemplate(contentTypeService, disableAlternativeTemplates, validateAlternativeTemplates, template.Id); + } + + #endregion + + #region HasValue, Value, Value + + /// + /// Gets a value indicating whether the content has a value for a property identified by its alias. + /// + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// A value indicating whether the content has a value for the property identified by the alias. + /// Returns true if HasValue is true, or a fallback strategy can provide a value. + public static bool HasValue(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, string alias, string culture = null, string segment = null, Fallback fallback = default) + { + var property = content.GetProperty(alias); + + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + return true; + + // else let fallback try to get a value + return publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _, out _); + } + + /// + /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. + /// + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + public static object Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) + { + var property = content.GetProperty(alias); + + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + 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, out property)) + return value; + + // 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); + } + + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. + /// + /// The target property type. + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, converted to the specified type. + public static T Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) + { + var property = content.GetProperty(alias); + + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + return property.Value(publishedValueFallback, culture, segment); + + // else let fallback try to get a 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 + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property == null ? default : property.Value(publishedValueFallback, culture, segment); + } + + #endregion + + #region IsSomething: misc. + + /// + /// Determines whether the specified content is a specified content type. + /// + /// The content to determine content type of. + /// The alias of the content type to test against. + /// True if the content is of the specified content type; otherwise false. + public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) + { + return content.ContentType.Alias.InvariantEquals(docTypeAlias); + } + + /// + /// Determines whether the specified content is a specified content type or it's derived types. + /// + /// The content to determine content type of. + /// The alias of the content type to test against. + /// When true, recurses up the content type tree to check inheritance; when false just calls IsDocumentType(this IPublishedContent content, string docTypeAlias). + /// True if the content is of the specified content type or a derived content type; otherwise false. + public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias, bool recursive) + { + if (content.IsDocumentType(docTypeAlias)) + return true; + + return recursive && content.IsComposedOf(docTypeAlias); + } + + #endregion + + #region IsSomething: equality + + public static bool IsEqual(this IPublishedContent content, IPublishedContent other) + { + return content.Id == other.Id; + } + + public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) + { + return content.IsEqual(other) == false; + } + + #endregion + + #region IsSomething: ancestors and descendants + + public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) + { + return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(',')); + } + + public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) + { + return content.Path.InvariantEquals(other.Path) || content.IsDescendant(other); + } + + public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) + { + return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(',')); + } + + public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) + { + return other.Path.InvariantEquals(content.Path) || content.IsAncestor(other); } #endregion @@ -639,6 +806,41 @@ namespace Umbraco.Core #region Axes: children + /// + /// Gets the children of the content item. + /// + /// The content item. + /// + /// + /// The specific culture to get the url children for. Default is null which will use the current culture in + /// + /// + /// Gets children that are available for the specified culture. + /// Children are sorted by their sortOrder. + /// + /// For culture, + /// if null is used the current culture is used. + /// If an empty string is used only invariant children are returned. + /// If "*" is used all children are returned. + /// + /// + /// If a variant culture is specified or there is a current culture in the then the Children returned + /// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture. + /// However, if an empty string is specified only invariant children are returned. + /// + /// + public static IEnumerable Children(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string culture = null) + { + // handle context culture for variant + if (culture == null) + culture = variationContextAccessor?.VariationContext?.Culture ?? ""; + + var children = content.ChildrenForAllCultures; + return culture == "*" + ? children + : children.Where(x => x.IsInvariantOrHasCulture(culture)); + } + /// /// Gets the children of the content, filtered by a predicate. /// @@ -741,7 +943,7 @@ namespace Umbraco.Core #endregion - #region Axes: Siblings + #region Axes: siblings /// /// Gets the siblings of the content. @@ -857,5 +1059,21 @@ namespace Umbraco.Core } #endregion + + #region Writer and creator + + public static string GetCreatorName(this IPublishedContent content, IUserService userService) + { + var user = userService.GetProfileById(content.CreatorId); + return user?.Name; + } + + public static string GetWriterName(this IPublishedContent content, IUserService userService) + { + var user = userService.GetProfileById(content.WriterId); + return user?.Name; + } + + #endregion } } diff --git a/src/Umbraco.Abstractions/PublishedElementExtensions.cs b/src/Umbraco.Abstractions/PublishedElementExtensions.cs index 77b6b1516a..4b529147e3 100644 --- a/src/Umbraco.Abstractions/PublishedElementExtensions.cs +++ b/src/Umbraco.Abstractions/PublishedElementExtensions.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core @@ -23,5 +22,158 @@ namespace Umbraco.Core } #endregion + + #region IsComposedOf + + /// + /// Gets a value indicating whether the content is of a content type composed of the given alias + /// + /// The content. + /// The content type alias. + /// A value indicating whether the content is of a content type composed of a content type identified by the alias. + public static bool IsComposedOf(this IPublishedElement content, string alias) + { + return content.ContentType.CompositionAliases.InvariantContains(alias); + } + + #endregion + + #region HasProperty + + /// + /// Gets a value indicating whether the content has a property identified by its alias. + /// + /// The content. + /// The property alias. + /// A value indicating whether the content has the property identified by the alias. + /// The content may have a property, and that property may not have a value. + public static bool HasProperty(this IPublishedElement content, string alias) + { + return content.ContentType.GetPropertyType(alias) != null; + } + + #endregion + + #region HasValue + + /// + /// 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, string culture = null, string segment = null) + { + var prop = content.GetProperty(alias); + return prop != null && prop.HasValue(culture, segment); + } + + #endregion + + #region Value + + /// + /// Gets the value of a content's property identified by its alias. + /// + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// 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, IPublishedValueFallback publishedValueFallback, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) + { + var property = content.GetProperty(alias); + + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + 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)) + return value; + + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property?.GetValue(culture, segment); + } + + #endregion + + #region Value + + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. + /// + /// The target property type. + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, converted to the specified type. + /// + /// 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, or if it could not be converted, returns default(T). + /// 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, IPublishedValueFallback publishedValueFallback, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) + { + var property = content.GetProperty(alias); + + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + return property.Value(publishedValueFallback, culture, segment); + + // else let fallback try to get a value + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) + return value; + + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property == null ? default : property.Value(publishedValueFallback, culture, segment); + } + + #endregion + + #region ToIndexedArray + + public static IndexedArrayItem[] ToIndexedArray(this IEnumerable source) + where TContent : class, IPublishedElement + { + var set = source.Select((content, index) => new IndexedArrayItem(content, index)).ToArray(); + foreach (var setItem in set) setItem.TotalCount = set.Length; + return set; + } + + #endregion + + #region IsSomething + + /// + /// Gets a value indicating whether the content is visible. + /// + /// The content. + /// The published value fallback implementation. + /// A value indicating whether the content is visible. + /// A content is not visible if it has an umbracoNaviHide property with a value of "1". Otherwise, + /// the content is visible. + public static bool IsVisible(this IPublishedElement content, IPublishedValueFallback publishedValueFallback) + { + // rely on the property converter - will return default bool value, ie false, if property + // is not defined, or has no value, else will return its value. + return content.Value(publishedValueFallback, Constants.Conventions.Content.NaviHide) == false; + } + + #endregion } } diff --git a/src/Umbraco.Abstractions/PublishedPropertyExtension.cs b/src/Umbraco.Abstractions/PublishedPropertyExtension.cs new file mode 100644 index 0000000000..259a2714d3 --- /dev/null +++ b/src/Umbraco.Abstractions/PublishedPropertyExtension.cs @@ -0,0 +1,59 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core +{ + /// + /// Provides extension methods for IPublishedProperty. + /// + public static class PublishedPropertyExtension + { + #region Value + + public static object Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) + { + if (property.HasValue(culture, segment)) + return property.GetValue(culture, segment); + + return publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var value) + ? value + : property.GetValue(culture, segment); // give converter a chance to return it's own vision of "no value" + } + + #endregion + + #region Value + + public static T Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) + { + if (property.HasValue(culture, segment)) + { + // we have a value + // try to cast or convert it + var value = property.GetValue(culture, segment); + if (value is T valueAsT) return valueAsT; + var valueConverted = value.TryConvertTo(); + if (valueConverted) return valueConverted.Result; + + // cannot cast nor convert the value, nothing we can return but 'default' + // note: we don't want to fallback in that case - would make little sense + return default; + } + + // we don't have a value, try fallback + if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue)) + return fallbackValue; + + // we don't have a value - neither direct nor fallback + // give a chance to the converter to return something (eg empty enumerable) + var noValue = property.GetValue(culture, segment); + if (noValue is T noValueAsT) return noValueAsT; + var noValueConverted = noValue.TryConvertTo(); + if (noValueConverted) return noValueConverted.Result; + + // cannot cast noValue nor convert it, nothing we can return but 'default' + return default; + } + + #endregion + } +} diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 19adf28528..7a456f427a 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -15,13 +15,14 @@ using Umbraco.Core.Strings; using Umbraco.Tests.Components; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.PublishedCache; namespace Umbraco.Tests.Published { [TestFixture] - public class ConvertersTests + public class ConvertersTests : UmbracoTestBase { #region SimpleConverter1 diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 1b79b65b19..3bb2c0a5e4 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -17,16 +17,15 @@ using Umbraco.Web; using Umbraco.Web.PropertyEditors; using Umbraco.Web.PropertyEditors.ValueConverters; using Umbraco.Web.PublishedCache; +using Umbraco.Tests.Testing; namespace Umbraco.Tests.Published { [TestFixture] - public class NestedContentTests + public class NestedContentTests : UmbracoTestBase { private (IPublishedContentType, IPublishedContentType) CreateContentTypes() { - Current.Reset(); - var logger = Mock.Of(); var profiler = Mock.Of(); var proflog = new ProfilingLogger(logger, profiler); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 8f8e4ae7a9..d3e6dae26b 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -12,13 +12,14 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.PublishedCache; namespace Umbraco.Tests.Published { [TestFixture] - public class PropertyCacheLevelTests + public class PropertyCacheLevelTests : UmbracoTestBase { [TestCase(PropertyCacheLevel.None, 2)] [TestCase(PropertyCacheLevel.Element, 1)] diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs index d1c39e1e31..fa1e5c6288 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentExtensionTests.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using NUnit.Framework; using Umbraco.Web.Composing; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.Testing; diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 93a3e52446..9d29516c2a 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -49,13 +49,11 @@ using Umbraco.Web.Templates; using Umbraco.Web.PropertyEditors; using Umbraco.Core.Dictionary; using Umbraco.Core.Models.Identity; -using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Net; using Umbraco.Web.Security; using Current = Umbraco.Web.Composing.Current; - namespace Umbraco.Tests.Testing { /// @@ -296,7 +294,7 @@ namespace Umbraco.Tests.Testing Composition.RegisterUnique(); Composition.RegisterUnique(); Composition.RegisterUnique(); - + Composition.RegisterUnique(); } protected virtual void ComposeMisc() diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 11e8bd9f64..fb8012d44e 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -31,21 +31,6 @@ namespace Umbraco.Web private static IExamineManager ExamineManager => Current.Factory.GetInstance(); private static IUserService UserService => Current.Services.UserService; - #region IsComposedOf - - /// - /// Gets a value indicating whether the content is of a content type composed of the given alias - /// - /// The content. - /// The content type alias. - /// A value indicating whether the content is of a content type composed of a content type identified by the alias. - public static bool IsComposedOf(this IPublishedContent content, string alias) - { - return content.ContentType.CompositionAliases.InvariantContains(alias); - } - - #endregion - #region Template /// @@ -55,34 +40,26 @@ namespace Umbraco.Web /// Empty string if none is set. public static string GetTemplateAlias(this IPublishedContent content) { - if(content.TemplateId.HasValue == false) - { - return string.Empty; - } - - var template = Current.Services.FileService.GetTemplate(content.TemplateId.Value); - return template == null ? string.Empty : template.Alias; + return content.GetTemplateAlias(Current.Services.FileService); } public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) { - if (Current.Configs.Settings().WebRouting.DisableAlternativeTemplates) - return content.TemplateId == templateId; - - if (content.TemplateId == templateId || !Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates) - return true; - - 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 content.IsAllowedTemplate( + Current.Services.ContentTypeService, + Current.Configs.Settings().WebRouting.DisableAlternativeTemplates, + Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates, + templateId); } public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) { - var template = Current.Services.FileService.GetTemplate(templateAlias); - return template != null && content.IsAllowedTemplate(template.Id); + return content.IsAllowedTemplate( + Current.Services.FileService, + Current.Services.ContentTypeService, + Current.Configs.Settings().WebRouting.DisableAlternativeTemplates, + Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates, + templateAlias); } #endregion @@ -101,14 +78,7 @@ namespace Umbraco.Web /// Returns true if HasValue is true, or a fallback strategy can provide a value. public static bool HasValue(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default) { - var property = content.GetProperty(alias); - - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - return true; - - // else let fallback try to get a value - return PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _, out _); + return content.HasValue(PublishedValueFallback, alias, culture, segment, fallback); } /// @@ -123,19 +93,7 @@ namespace Umbraco.Web /// The value of the content's property identified by the alias, if it exists, otherwise a default value. public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) { - var property = content.GetProperty(alias); - - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - 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, out property)) - return value; - - // 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 content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); } /// @@ -151,19 +109,7 @@ namespace Umbraco.Web /// The value of the content's property identified by the alias, converted to the specified type. public static T Value(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) { - var property = content.GetProperty(alias); - - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - 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, out property)) - return value; - - // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - otherwise, default - return property == null ? default : property.Value(culture, segment); + return content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); } #endregion @@ -239,42 +185,7 @@ namespace Umbraco.Web #endregion - #region IsSomething: misc. - - /// - /// Determines whether the specified content is a specified content type. - /// - /// The content to determine content type of. - /// The alias of the content type to test against. - /// True if the content is of the specified content type; otherwise false. - public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) - { - return content.ContentType.Alias.InvariantEquals(docTypeAlias); - } - - /// - /// Determines whether the specified content is a specified content type or it's derived types. - /// - /// The content to determine content type of. - /// The alias of the content type to test against. - /// When true, recurses up the content type tree to check inheritance; when false just calls IsDocumentType(this IPublishedContent content, string docTypeAlias). - /// True if the content is of the specified content type or a derived content type; otherwise false. - public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias, bool recursive) - { - if (content.IsDocumentType(docTypeAlias)) - return true; - - return recursive && content.IsComposedOf(docTypeAlias); - } - - #endregion - - #region IsSomething: equality - - public static bool IsEqual(this IPublishedContent content, IPublishedContent other) - { - return content.Id == other.Id; - } + #region IsSomething: equality public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { @@ -286,11 +197,6 @@ namespace Umbraco.Web return new HtmlString(content.IsEqual(other) ? valueIfTrue : valueIfFalse); } - public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) - { - return content.IsEqual(other) == false; - } - public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsNotEqual(other, valueIfTrue, string.Empty); @@ -305,11 +211,6 @@ namespace Umbraco.Web #region IsSomething: ancestors and descendants - public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) - { - return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(',')); - } - public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsDescendant(other, valueIfTrue, string.Empty); @@ -320,11 +221,6 @@ namespace Umbraco.Web return new HtmlString(content.IsDescendant(other) ? valueIfTrue : valueIfFalse); } - public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) - { - return content.Path.InvariantEquals(other.Path) || content.IsDescendant(other); - } - public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsDescendantOrSelf(other, valueIfTrue, string.Empty); @@ -335,11 +231,6 @@ namespace Umbraco.Web return new HtmlString(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse); } - public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) - { - return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(',')); - } - public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsAncestor(other, valueIfTrue, string.Empty); @@ -350,11 +241,6 @@ namespace Umbraco.Web return new HtmlString(content.IsAncestor(other) ? valueIfTrue : valueIfFalse); } - public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) - { - return other.Path.InvariantEquals(content.Path) || content.IsAncestor(other); - } - public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsAncestorOrSelf(other, valueIfTrue, string.Empty); @@ -664,7 +550,7 @@ namespace Umbraco.Web #endregion - #region Axes: Siblings + #region Axes: siblings /// /// Gets the siblings of the content. diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs index 661c740d90..49e686835f 100644 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ b/src/Umbraco.Web/PublishedElementExtensions.cs @@ -23,53 +23,13 @@ namespace Umbraco.Web // // besides, for tests, Current support setting a fallback without even a container // + // Update to this comment 8/2/2020: issue as been ameliorated by creating extensions methods in Umbraco.Abstractions + // that accept the dependencies as arguments for many of these extension methods, and can be used within the Umbraco code-base. + // For site developers, the "friendly" extension methods using service location have been maintained, delegating to the ones that + // take the dependencies as parameters. + private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; - #region IsComposedOf - - /// - /// Gets a value indicating whether the content is of a content type composed of the given alias - /// - /// The content. - /// The content type alias. - /// A value indicating whether the content is of a content type composed of a content type identified by the alias. - public static bool IsComposedOf(this IPublishedElement content, string alias) - { - return content.ContentType.CompositionAliases.InvariantContains(alias); - } - - #endregion - - #region HasProperty - - /// - /// Gets a value indicating whether the content has a property identified by its alias. - /// - /// The content. - /// The property alias. - /// A value indicating whether the content has the property identified by the alias. - /// The content may have a property, and that property may not have a value. - public static bool HasProperty(this IPublishedElement content, string alias) - { - return content.ContentType.GetPropertyType(alias) != null; - } - - #endregion - - #region HasValue - - /// - /// 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, string culture = null, string segment = null) - { - var prop = content.GetProperty(alias); - return prop != null && prop.HasValue(culture, segment); - } - - #endregion - #region Value /// @@ -90,19 +50,7 @@ namespace Umbraco.Web /// public static object Value(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) { - var property = content.GetProperty(alias); - - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - 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)) - return value; - - // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - otherwise, default - return property?.GetValue(culture, segment); + return content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); } #endregion @@ -128,31 +76,7 @@ namespace Umbraco.Web /// public static T Value(this IPublishedElement content, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) { - var property = content.GetProperty(alias); - - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - 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)) - return value; - - // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - otherwise, default - return property == null ? default : property.Value(culture, segment); - } - - #endregion - - #region ToIndexedArray - - public static IndexedArrayItem[] ToIndexedArray(this IEnumerable source) - where TContent : class, IPublishedElement - { - var set = source.Select((content, index) => new IndexedArrayItem(content, index)).ToArray(); - foreach (var setItem in set) setItem.TotalCount = set.Length; - return set; + return content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); } #endregion @@ -168,9 +92,7 @@ namespace Umbraco.Web /// the content is visible. public static bool IsVisible(this IPublishedElement content) { - // rely on the property converter - will return default bool value, ie false, if property - // is not defined, or has no value, else will return its value. - return content.Value(Constants.Conventions.Content.NaviHide) == false; + return content.IsVisible(PublishedValueFallback); } #endregion diff --git a/src/Umbraco.Web/PublishedPropertyExtension.cs b/src/Umbraco.Web/PublishedPropertyExtension.cs index 829b9ced43..917a5b4572 100644 --- a/src/Umbraco.Web/PublishedPropertyExtension.cs +++ b/src/Umbraco.Web/PublishedPropertyExtension.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using Umbraco.Core; +using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Core.Models.PublishedContent; @@ -18,12 +17,7 @@ namespace Umbraco.Web public static object Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) { - if (property.HasValue(culture, segment)) - return property.GetValue(culture, segment); - - return PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var value) - ? value - : property.GetValue(culture, segment); // give converter a chance to return it's own vision of "no value" + return property.Value(PublishedValueFallback, culture, segment, fallback, defaultValue); } #endregion @@ -32,33 +26,7 @@ namespace Umbraco.Web public static T Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) { - if (property.HasValue(culture, segment)) - { - // we have a value - // try to cast or convert it - var value = property.GetValue(culture, segment); - if (value is T valueAsT) return valueAsT; - var valueConverted = value.TryConvertTo(); - if (valueConverted) return valueConverted.Result; - - // cannot cast nor convert the value, nothing we can return but 'default' - // note: we don't want to fallback in that case - would make little sense - return default; - } - - // we don't have a value, try fallback - if (PublishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue)) - return fallbackValue; - - // we don't have a value - neither direct nor fallback - // give a chance to the converter to return something (eg empty enumerable) - var noValue = property.GetValue(culture, segment); - if (noValue is T noValueAsT) return noValueAsT; - var noValueConverted = noValue.TryConvertTo(); - if (noValueConverted) return noValueConverted.Result; - - // cannot cast noValue nor convert it, nothing we can return but 'default' - return default; + return property.Value(PublishedValueFallback, culture, segment, fallback, defaultValue); } #endregion