// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Data;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Extensions;
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)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
// invariant has invariant value (whatever the requested culture)
if (!content.ContentType.VariesByCulture())
{
return content.Cultures.TryGetValue(string.Empty, out PublishedCultureInfo? invariantInfos)
? invariantInfos.Name
: string.Empty;
}
// handle context culture for variant
if (culture == null)
{
culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty;
}
// get
return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo? infos)
? infos.Name
: string.Empty;
}
#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)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
// invariant has invariant value (whatever the requested culture)
if (!content.ContentType.VariesByCulture())
{
return content.Cultures.TryGetValue(string.Empty, out PublishedCultureInfo? invariantInfos)
? invariantInfos.UrlSegment
: null;
}
// handle context culture for variant
if (culture == null)
{
culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty;
}
// get
return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo? infos)
? infos.UrlSegment
: null;
}
#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 IPublishedContent content, string alias) =>
content.ContentType.CompositionAliases.InvariantContains(alias);
#endregion
#region Axes: parent
// Parent is native
///
/// Gets the parent of the content, of a given content type.
///
/// The content type.
/// The content.
/// The parent of content, of the given content type, else null.
public static T? Parent(this IPublishedContent content)
where T : class, IPublishedContent
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
return content.Parent as T;
}
#endregion
#region Url
///
/// Gets the url of the content item.
///
///
///
/// If the content item is a document, then this method returns the url of the
/// document. If it is a media, then this methods return the media url for the
/// 'umbracoFile' property. Use the MediaUrl() method to get the media url for other
/// properties.
///
///
/// The value of this property is contextual. It depends on the 'current' request uri,
/// if any. In addition, when the content type is multi-lingual, this is the url for the
/// specified culture. Otherwise, it is the invariant url.
///
///
public static string Url(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, string? culture = null, UrlMode mode = UrlMode.Default)
{
if (publishedUrlProvider == null)
{
throw new InvalidOperationException(
"Cannot resolve a Url when Current.UmbracoContext.UrlProvider is null.");
}
switch (content.ContentType.ItemType)
{
case PublishedItemType.Content:
return publishedUrlProvider.GetUrl(content, mode, culture);
case PublishedItemType.Media:
return publishedUrlProvider.GetMediaUrl(content, mode, culture);
default:
throw new NotSupportedException();
}
}
#endregion
#region Culture
///
/// Determines whether the content has a culture.
///
/// Culture is case-insensitive.
public static bool HasCulture(this IPublishedContent content, string? culture)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
return content.Cultures.ContainsKey(culture ?? string.Empty);
}
///
/// Determines whether the content is invariant, or has a culture.
///
/// Culture is case-insensitive.
public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture)
=> !content.ContentType.VariesByCulture() || content.Cultures.ContainsKey(culture ?? string.Empty);
///
/// Filters a sequence of to return invariant items, and items that are published for
/// the specified culture.
///
/// The content items.
///
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null).
///
internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent
{
if (contents == null)
{
throw new ArgumentNullException(nameof(contents));
}
culture = culture ?? variationContextAccessor.VariationContext?.Culture ?? string.Empty;
// either does not vary by culture, or has the specified culture
return contents.Where(x => !x.ContentType.VariesByCulture() || HasCulture(x, culture));
}
///
/// Gets the culture date 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 DateTime CultureDate(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
{
// invariant has invariant value (whatever the requested culture)
if (!content.ContentType.VariesByCulture())
{
return content.UpdateDate;
}
// handle context culture for variant
if (culture == null)
{
culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty;
}
// get
return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo? infos)
? infos.Date
: DateTime.MinValue;
}
#endregion
#region Template
///
/// Returns the current template Alias
///
/// Empty string if none is set.
public static string GetTemplateAlias(this IPublishedContent content, IFileService fileService)
{
if (content.TemplateId.HasValue == false)
{
return string.Empty;
}
ITemplate? template = fileService.GetTemplate(content.TemplateId.Value);
return template?.Alias ?? string.Empty;
}
public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, WebRoutingSettings webRoutingSettings, int templateId) =>
content.IsAllowedTemplate(contentTypeService, webRoutingSettings.DisableAlternativeTemplates, webRoutingSettings.ValidateAlternativeTemplates, templateId);
public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId)
{
if (disableAlternativeTemplates)
{
return content.TemplateId == templateId;
}
if (content.TemplateId == templateId || !validateAlternativeTemplates)
{
return true;
}
IContentType? 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)
{
ITemplate? 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)
{
IPublishedProperty? 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)
{
IPublishedProperty? 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)
{
IPublishedProperty? 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 T? 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) =>
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) => content.Id == other.Id;
public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) =>
content.IsEqual(other) == false;
#endregion
#region IsSomething: ancestors and descendants
public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) =>
other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(','));
public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) =>
content.Path.InvariantEquals(other.Path) || content.IsDescendant(other);
public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) =>
content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(','));
public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) =>
other.Path.InvariantEquals(content.Path) || content.IsAncestor(other);
#endregion
#region Axes: ancestors, ancestors-or-self
// as per XPath 1.0 specs �2.2,
// - the ancestor axis contains the ancestors of the context node; the ancestors of the context node consist
// of the parent of context node and the parent's parent and so on; thus, the ancestor axis will always
// include the root node, unless the context node is the root node.
// - the ancestor-or-self axis contains the context node and the ancestors of the context node; thus,
// the ancestor axis will always include the root node.
//
// as per XPath 2.0 specs �3.2.1.1,
// - the ancestor axis is defined as the transitive closure of the parent axis; it contains the ancestors
// of the context node (the parent, the parent of the parent, and so on) - The ancestor axis includes the
// root node of the tree in which the context node is found, unless the context node is the root node.
// - the ancestor-or-self axis contains the context node and the ancestors of the context node; thus,
// the ancestor-or-self axis will always include the root node.
//
// the ancestor and ancestor-or-self axis are reverse axes ie they contain the context node or nodes that
// are before the context node in document order.
//
// document order is defined by �2.4.1 as:
// - the root node is the first node.
// - every node occurs before all of its children and descendants.
// - the relative order of siblings is the order in which they occur in the children property of their parent node.
// - children and descendants occur before following siblings.
///
/// Gets the ancestors of the content.
///
/// The content.
/// The ancestors of the content, in down-top order.
/// Does not consider the content itself.
public static IEnumerable Ancestors(this IPublishedContent content) =>
content.AncestorsOrSelf(false, null);
///
/// Gets the ancestors of the content, at a level lesser or equal to a specified level.
///
/// The content.
/// The level.
/// The ancestors of the content, at a level lesser or equal to the specified level, in down-top order.
/// Does not consider the content itself. Only content that are "high enough" in the tree are returned.
public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) =>
content.AncestorsOrSelf(false, n => n.Level <= maxLevel);
///
/// Gets the ancestors of the content, of a specified content type.
///
/// The content.
/// The content type.
/// The ancestors of the content, of the specified content type, in down-top order.
/// Does not consider the content itself. Returns all ancestors, of the specified content type.
public static IEnumerable Ancestors(this IPublishedContent content, string contentTypeAlias) =>
content.AncestorsOrSelf(false, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias));
///
/// Gets the ancestors of the content, of a specified content type.
///
/// The content type.
/// The content.
/// The ancestors of the content, of the specified content type, in down-top order.
/// Does not consider the content itself. Returns all ancestors, of the specified content type.
public static IEnumerable Ancestors(this IPublishedContent content)
where T : class, IPublishedContent =>
content.Ancestors().OfType();
///
/// Gets the ancestors of the content, at a level lesser or equal to a specified level, and of a specified content
/// type.
///
/// The content type.
/// The content.
/// The level.
///
/// The ancestors of the content, at a level lesser or equal to the specified level, and of the specified
/// content type, in down-top order.
///
///
/// Does not consider the content itself. Only content that are "high enough" in the trees, and of the
/// specified content type, are returned.
///
public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel)
where T : class, IPublishedContent =>
content.Ancestors(maxLevel).OfType();
///
/// Gets the content and its ancestors.
///
/// The content.
/// The content and its ancestors, in down-top order.
public static IEnumerable AncestorsOrSelf(this IPublishedContent content) =>
content.AncestorsOrSelf(true, null);
///
/// Gets the content and its ancestors, at a level lesser or equal to a specified level.
///
/// The content.
/// The level.
///
/// The content and its ancestors, at a level lesser or equal to the specified level,
/// in down-top order.
///
///
/// Only content that are "high enough" in the tree are returned. So it may or may not begin
/// with the content itself, depending on its level.
///
public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) =>
content.AncestorsOrSelf(true, n => n.Level <= maxLevel);
///
/// Gets the content and its ancestors, of a specified content type.
///
/// The content.
/// The content type.
/// The content and its ancestors, of the specified content type, in down-top order.
/// May or may not begin with the content itself, depending on its content type.
public static IEnumerable
AncestorsOrSelf(this IPublishedContent content, string contentTypeAlias) =>
content.AncestorsOrSelf(true, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias));
///
/// Gets the content and its ancestors, of a specified content type.
///
/// The content type.
/// The content.
/// The content and its ancestors, of the specified content type, in down-top order.
/// May or may not begin with the content itself, depending on its content type.
public static IEnumerable AncestorsOrSelf(this IPublishedContent content)
where T : class, IPublishedContent =>
content.AncestorsOrSelf().OfType();
///
/// Gets the content and its ancestor, at a lever lesser or equal to a specified level, and of a specified content
/// type.
///
/// The content type.
/// The content.
/// The level.
///
/// The content and its ancestors, at a level lesser or equal to the specified level, and of the specified
/// content type, in down-top order.
///
/// May or may not begin with the content itself, depending on its level and content type.
public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel)
where T : class, IPublishedContent =>
content.AncestorsOrSelf(maxLevel).OfType();
///
/// Gets the ancestor of the content, ie its parent.
///
/// The content.
/// The ancestor of the content.
/// This method is here for consistency purposes but does not make much sense.
public static IPublishedContent? Ancestor(this IPublishedContent content) => content.Parent;
///
/// Gets the nearest ancestor of the content, at a lever lesser or equal to a specified level.
///
/// The content.
/// The level.
/// The nearest (in down-top order) ancestor of the content, at a level lesser or equal to the specified level.
/// Does not consider the content itself. May return null.
public static IPublishedContent? Ancestor(this IPublishedContent content, int maxLevel) =>
content.EnumerateAncestors(false).FirstOrDefault(x => x.Level <= maxLevel);
///
/// Gets the nearest ancestor of the content, of a specified content type.
///
/// The content.
/// The content type alias.
/// The nearest (in down-top order) ancestor of the content, of the specified content type.
/// Does not consider the content itself. May return null.
public static IPublishedContent? Ancestor(this IPublishedContent content, string contentTypeAlias) => content
.EnumerateAncestors(false).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias));
///
/// Gets the nearest ancestor of the content, of a specified content type.
///
/// The content type.
/// The content.
/// The nearest (in down-top order) ancestor of the content, of the specified content type.
/// Does not consider the content itself. May return null.
public static T? Ancestor(this IPublishedContent content)
where T : class, IPublishedContent =>
content.Ancestors().FirstOrDefault();
///
/// Gets the nearest ancestor of the content, at the specified level and of the specified content type.
///
/// The content type.
/// The content.
/// The level.
/// The ancestor of the content, at the specified level and of the specified content type.
///
/// Does not consider the content itself. If the ancestor at the specified level is
/// not of the specified type, returns null.
///
public static T? Ancestor(this IPublishedContent content, int maxLevel)
where T : class, IPublishedContent =>
content.Ancestors(maxLevel).FirstOrDefault();
///
/// Gets the content or its nearest ancestor.
///
/// The content.
/// The content.
/// This method is here for consistency purposes but does not make much sense.
public static IPublishedContent AncestorOrSelf(this IPublishedContent content) => content;
///
/// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level.
///
/// The content.
/// The level.
/// The content or its nearest (in down-top order) ancestor, at a level lesser or equal to the specified level.
/// May or may not return the content itself depending on its level. May return null.
public static IPublishedContent AncestorOrSelf(this IPublishedContent content, int maxLevel) =>
content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= maxLevel) ?? content;
///
/// Gets the content or its nearest ancestor, of a specified content type.
///
/// The content.
/// The content type.
/// The content or its nearest (in down-top order) ancestor, of the specified content type.
/// May or may not return the content itself depending on its content type. May return null.
public static IPublishedContent AncestorOrSelf(this IPublishedContent content, string contentTypeAlias) => content
.EnumerateAncestors(true).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)) ?? content;
///
/// Gets the content or its nearest ancestor, of a specified content type.
///
/// The content type.
/// The content.
/// The content or its nearest (in down-top order) ancestor, of the specified content type.
/// May or may not return the content itself depending on its content type. May return null.
public static T? AncestorOrSelf(this IPublishedContent content)
where T : class, IPublishedContent =>
content.AncestorsOrSelf().FirstOrDefault();
///
/// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level, and of a specified
/// content type.
///
/// The content type.
/// The content.
/// The level.
///
public static T? AncestorOrSelf(this IPublishedContent content, int maxLevel)
where T : class, IPublishedContent =>
content.AncestorsOrSelf(maxLevel).FirstOrDefault();
public static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func? func)
{
IEnumerable ancestorsOrSelf = content.EnumerateAncestors(orSelf);
return func == null ? ancestorsOrSelf : ancestorsOrSelf.Where(func);
}
///
/// Enumerates ancestors of the content, bottom-up.
///
/// The content.
/// Indicates whether the content should be included.
/// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc).
internal static IEnumerable EnumerateAncestors(this IPublishedContent? content, bool orSelf)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
if (orSelf)
{
yield return content;
}
while ((content = content.Parent) != null)
{
yield return content;
}
}
#endregion
#region Axes: breadcrumbs
///
/// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified .
///
/// The content.
/// Indicates whether the specified content should be included.
///
/// The breadcrumbs (ancestors and self, top to bottom) for the specified .
///
public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true) =>
content.AncestorsOrSelf(andSelf, null).Reverse();
///
/// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level
/// higher or equal to .
///
/// The content.
/// The minimum level.
/// Indicates whether the specified content should be included.
///
/// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher
/// or equal to .
///
public static IEnumerable Breadcrumbs(
this IPublishedContent content,
int minLevel,
bool andSelf = true) =>
content.AncestorsOrSelf(andSelf, n => n.Level >= minLevel).Reverse();
///
/// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level
/// higher or equal to the specified root content type .
///
/// The root content type.
/// The content.
/// Indicates whether the specified content should be included.
///
/// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher
/// or equal to the specified root content type .
///
public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true)
where T : class, IPublishedContent
{
static IEnumerable TakeUntil(IEnumerable source, Func predicate)
{
foreach (IPublishedContent item in source)
{
yield return item;
if (predicate(item))
{
yield break;
}
}
}
return TakeUntil(content.AncestorsOrSelf(andSelf, null), n => n is T).Reverse();
}
#endregion
#region Axes: descendants, descendants-or-self
///
/// Returns all DescendantsOrSelf of all content referenced
///
///
/// Variation context accessor.
///
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
///
///
/// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot
///
public static IEnumerable DescendantsOrSelfOfType(
this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string docTypeAlias, string? culture = null) => parentNodes.SelectMany(x =>
x.DescendantsOrSelfOfType(variationContextAccessor, docTypeAlias, culture));
///
/// Returns all DescendantsOrSelf of all content referenced
///
///
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
///
///
/// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot
///
public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
parentNodes.SelectMany(x => x.DescendantsOrSelf(variationContextAccessor, culture));
// as per XPath 1.0 specs �2.2,
// - the descendant axis contains the descendants of the context node; a descendant is a child or a child of a child and so on; thus
// the descendant axis never contains attribute or namespace nodes.
// - the descendant-or-self axis contains the context node and the descendants of the context node.
//
// as per XPath 2.0 specs �3.2.1.1,
// - the descendant axis is defined as the transitive closure of the child axis; it contains the descendants of the context node (the
// children, the children of the children, and so on).
// - the descendant-or-self axis contains the context node and the descendants of the context node.
//
// the descendant and descendant-or-self axis are forward axes ie they contain the context node or nodes that are after the context
// node in document order.
//
// document order is defined by �2.4.1 as:
// - the root node is the first node.
// - every node occurs before all of its children and descendants.
// - the relative order of siblings is the order in which they occur in the children property of their parent node.
// - children and descendants occur before following siblings.
public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
content.DescendantsOrSelf(variationContextAccessor, false, null, culture);
public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) =>
content.DescendantsOrSelf(variationContextAccessor, false, p => p.Level >= level, culture);
public static IEnumerable DescendantsOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) =>
content.DescendantsOrSelf(variationContextAccessor, false, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
content.Descendants(variationContextAccessor, culture).OfType();
public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
where T : class, IPublishedContent =>
content.Descendants(variationContextAccessor, level, culture).OfType();
public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
content.DescendantsOrSelf(variationContextAccessor, true, null, culture);
public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) =>
content.DescendantsOrSelf(variationContextAccessor, true, p => p.Level >= level, culture);
public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) =>
content.DescendantsOrSelf(variationContextAccessor, true, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
content.DescendantsOrSelf(variationContextAccessor, culture).OfType();
public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
where T : class, IPublishedContent =>
content.DescendantsOrSelf(variationContextAccessor, level, culture).OfType();
public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
content.Children(variationContextAccessor, culture)?.FirstOrDefault();
public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content
.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x.Level == level);
public static IPublishedContent? DescendantOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content
.EnumerateDescendants(variationContextAccessor, false, culture)
.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias));
public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
content.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x is T) as T;
public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
where T : class, IPublishedContent =>
content.Descendant(variationContextAccessor, level, culture) as T;
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) => content;
public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content
.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x.Level == level);
public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content
.EnumerateDescendants(variationContextAccessor, true, culture)
.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias));
public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
content.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x is T) as T;
public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null)
where T : class, IPublishedContent =>
content.DescendantOrSelf(variationContextAccessor, level, culture) as T;
internal static IEnumerable DescendantsOrSelf(
this IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
bool orSelf,
Func? func,
string? culture = null) =>
content.EnumerateDescendants(variationContextAccessor, orSelf, culture)
.Where(x => func == null || func(x));
internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, bool orSelf, string? culture = null)
{
if (content == null)
{
throw new ArgumentNullException(nameof(content));
}
if (orSelf)
{
yield return content;
}
IEnumerable? children = content.Children(variationContextAccessor, culture);
if (children is not null)
{
foreach (IPublishedContent desc in children.SelectMany(x =>
x.EnumerateDescendants(variationContextAccessor, culture)))
{
yield return desc;
}
}
}
internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
{
yield return content;
IEnumerable? children = content.Children(variationContextAccessor, culture);
if (children is not null)
{
foreach (IPublishedContent desc in children.SelectMany(x =>
x.EnumerateDescendants(variationContextAccessor, culture)))
{
yield return desc;
}
}
}
#endregion
#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 ?? string.Empty;
}
IEnumerable? children = content.ChildrenForAllCultures;
return (culture == "*"
? children : children?.Where(x => x.IsInvariantOrHasCulture(culture)))
?? Enumerable.Empty();
}
///
/// Gets the children of the content, filtered by a predicate.
///
/// The content.
/// The accessor for VariationContext
/// The predicate.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The children of the content, filtered by the predicate.
///
/// Children are sorted by their sortOrder.
///
public static IEnumerable Children(
this IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
Func predicate,
string? culture = null) =>
content.Children(variationContextAccessor, culture).Where(predicate);
///
/// Gets the children of the content, of any of the specified types.
///
/// The content.
/// The accessor for the VariationContext
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The content type alias.
/// The children of the content, of any of the specified types.
public static IEnumerable ChildrenOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? contentTypeAlias, string? culture = null) =>
content.Children(variationContextAccessor, x => x.ContentType.Alias.InvariantEquals(contentTypeAlias), culture);
///
/// Gets the children of the content, of a given content type.
///
/// The content type.
/// The content.
/// The accessor for the VariationContext
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The children of content, of the given content type.
///
/// Children are sorted by their sortOrder.
///
public static IEnumerable Children(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
content.Children(variationContextAccessor, culture).OfType();
public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) =>
content.Children(variationContextAccessor, culture)?.FirstOrDefault();
///
/// Gets the first child of the content, of a given content type.
///
public static IPublishedContent? FirstChildOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) =>
content.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)?.FirstOrDefault();
public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null) => content.Children(variationContextAccessor, predicate, culture)?.FirstOrDefault();
public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Guid uniqueId, string? culture = null) => content
.Children(variationContextAccessor, x => x.Key == uniqueId, culture)?.FirstOrDefault();
public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
content.Children(variationContextAccessor, culture)?.FirstOrDefault();
public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null)
where T : class, IPublishedContent =>
content.Children(variationContextAccessor, culture)?.FirstOrDefault(predicate);
#endregion
#region Axes: siblings
///
/// Gets the siblings of the content.
///
/// The content.
/// Published snapshot instance
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The siblings of the content.
///
/// Note that in V7 this method also return the content node self.
///
public static IEnumerable Siblings(
this IPublishedContent content,
IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
string? culture = null) =>
SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture)
?.Where(x => x.Id != content.Id) ?? Enumerable.Empty();
///
/// Gets the siblings of the content, of a given content type.
///
/// The content.
/// Published snapshot instance
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The content type alias.
/// The siblings of the content, of the given content type.
///
/// Note that in V7 this method also return the content node self.
///
public static IEnumerable SiblingsOfType(
this IPublishedContent content,
IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
string contentTypeAlias,
string? culture = null) =>
SiblingsAndSelfOfType(content, publishedSnapshot, variationContextAccessor, contentTypeAlias, culture)
?.Where(x => x.Id != content.Id) ?? Enumerable.Empty();
///
/// Gets the siblings of the content, of a given content type.
///
/// The content type.
/// The content.
/// Published snapshot instance
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The siblings of the content, of the given content type.
///
/// Note that in V7 this method also return the content node self.
///
public static IEnumerable Siblings(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string? culture = null)
where T : class, IPublishedContent =>
SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture)
?.Where(x => x.Id != content.Id) ?? Enumerable.Empty();
///
/// Gets the siblings of the content including the node itself to indicate the position.
///
/// The content.
/// Published snapshot instance
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The siblings of the content including the node itself.
public static IEnumerable? SiblingsAndSelf(
this IPublishedContent content,
IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
string? culture = null) =>
content.Parent != null
? content.Parent.Children(variationContextAccessor, culture)
: publishedSnapshot?.Content?.GetAtRoot(culture)
.WhereIsInvariantOrHasCulture(variationContextAccessor, culture);
///
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
///
/// The content.
/// Published snapshot instance
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The content type alias.
/// The siblings of the content including the node itself, of the given content type.
public static IEnumerable SiblingsAndSelfOfType(
this IPublishedContent content,
IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
string contentTypeAlias,
string? culture = null) =>
(content.Parent != null
? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)
: publishedSnapshot?.Content?.GetAtRoot(culture).OfTypes(contentTypeAlias)
.WhereIsInvariantOrHasCulture(variationContextAccessor, culture))
?? Enumerable.Empty();
///
/// Gets the siblings of the content including the node itself to indicate the position, of a given content type.
///
/// The content type.
/// The content.
/// Published snapshot instance
/// Variation context accessor.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The siblings of the content including the node itself, of the given content type.
public static IEnumerable SiblingsAndSelf(
this IPublishedContent content,
IPublishedSnapshot? publishedSnapshot,
IVariationContextAccessor variationContextAccessor,
string? culture = null)
where T : class, IPublishedContent =>
(content.Parent != null
? content.Parent.Children(variationContextAccessor, culture)
: publishedSnapshot?.Content?.GetAtRoot(culture).OfType()
.WhereIsInvariantOrHasCulture(variationContextAccessor, culture))
?? Enumerable.Empty();
#endregion
#region Axes: custom
///
/// Gets the root content (ancestor or self at level 1) for the specified .
///
/// The content.
///
/// The root content (ancestor or self at level 1) for the specified .
///
///
/// This is the same as calling
/// with maxLevel
/// set to 1.
///
public static IPublishedContent Root(this IPublishedContent content) => content.AncestorOrSelf(1);
///
/// Gets the root content (ancestor or self at level 1) for the specified if it's of the
/// specified content type .
///
/// The content type.
/// The content.
///
/// The root content (ancestor or self at level 1) for the specified of content type
/// .
///
///
/// This is the same as calling
/// with
/// maxLevel set to 1.
///
public static T? Root(this IPublishedContent content)
where T : class, IPublishedContent =>
content.AncestorOrSelf(1);
#endregion
#region Writer and creator
public static string GetCreatorName(this IPublishedContent content, IUserService userService)
{
IProfile? user = userService.GetProfileById(content.CreatorId);
return user?.Name ?? string.Empty;
}
public static string GetWriterName(this IPublishedContent content, IUserService userService)
{
IProfile? user = userService.GetProfileById(content.WriterId);
return user?.Name ?? string.Empty;
}
#endregion
#region Axes: children
///
/// Gets the children of the content in a DataTable.
///
/// The content.
/// Variation context accessor.
/// The content type service.
/// The media type service.
/// The member type service.
/// The published url provider.
/// An optional content type alias.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The children of the content.
public static DataTable ChildrenAsTable(
this IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
IContentTypeService contentTypeService,
IMediaTypeService mediaTypeService,
IMemberTypeService memberTypeService,
IPublishedUrlProvider publishedUrlProvider,
string contentTypeAliasFilter = "",
string? culture = null)
=> GenerateDataTable(content, variationContextAccessor, contentTypeService, mediaTypeService, memberTypeService, publishedUrlProvider, contentTypeAliasFilter, culture);
///
/// Gets the children of the content in a DataTable.
///
/// The content.
/// Variation context accessor.
/// The content type service.
/// The media type service.
/// The member type service.
/// The published url provider.
/// An optional content type alias.
///
/// The specific culture to filter for. If null is used the current culture is used. (Default is
/// null)
///
/// The children of the content.
private static DataTable GenerateDataTable(
IPublishedContent content,
IVariationContextAccessor variationContextAccessor,
IContentTypeService contentTypeService,
IMediaTypeService mediaTypeService,
IMemberTypeService memberTypeService,
IPublishedUrlProvider publishedUrlProvider,
string contentTypeAliasFilter = "",
string? culture = null)
{
IPublishedContent? firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace()
? content.Children(variationContextAccessor, culture)?.Any() ?? false
? content.Children(variationContextAccessor, culture)?.ElementAt(0)
: null
: content.Children(variationContextAccessor, culture)
?.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAliasFilter));
if (firstNode == null)
{
// No children found
return new DataTable();
}
// use new utility class to create table so that we don't have to maintain code in many places, just one
DataTable dt = DataTableExtensions.GenerateDataTable(
// pass in the alias of the first child node since this is the node type we're rendering headers for
firstNode.ContentType.Alias,
// pass in the callback to extract the Dictionary of all defined aliases to their names
alias => GetPropertyAliasesAndNames(contentTypeService, mediaTypeService, memberTypeService, alias),
() =>
{
// here we pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place.
// create all row data
List>, IEnumerable>>>
tableData = DataTableExtensions.CreateTableData();
IOrderedEnumerable? children =
content.Children(variationContextAccessor)?.OrderBy(x => x.SortOrder);
if (children is not null)
{
// loop through each child and create row data for it
foreach (IPublishedContent n in children)
{
if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false)
{
if (n.ContentType.Alias.InvariantEquals(contentTypeAliasFilter) == false)
{
continue; // skip this one, it doesn't match the filter
}
}
var standardVals = new Dictionary
{
{ "Id", n.Id },
{ "NodeName", n.Name(variationContextAccessor) },
{ "NodeTypeAlias", n.ContentType.Alias },
{ "CreateDate", n.CreateDate },
{ "UpdateDate", n.UpdateDate },
{ "CreatorId", n.CreatorId },
{ "WriterId", n.WriterId },
{ "Url", n.Url(publishedUrlProvider) },
};
var userVals = new Dictionary();
IEnumerable properties =
n.Properties?.Where(p => p.GetSourceValue() is not null) ??
Array.Empty();
foreach (IPublishedProperty p in properties)
{
// probably want the "object value" of the property here...
userVals[p.Alias] = p.GetValue();
}
// Add the row data
DataTableExtensions.AddRowData(tableData, standardVals, userVals);
}
}
return tableData;
});
return dt;
}
#endregion
#region PropertyAliasesAndNames
private static Func>? _getPropertyAliasesAndNames;
///
/// This is used only for unit tests to set the delegate to look up aliases/names dictionary of a content type
///
internal static Func>
GetPropertyAliasesAndNames
{
get => _getPropertyAliasesAndNames ?? GetAliasesAndNames;
set => _getPropertyAliasesAndNames = value;
}
private static Dictionary GetAliasesAndNames(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, string alias)
{
IContentTypeBase? type = contentTypeService.Get(alias)
?? mediaTypeService.Get(alias)
?? (IContentTypeBase?)memberTypeService.Get(alias);
Dictionary fields = GetAliasesAndNames(type);
// ensure the standard fields are there
var stdFields = new Dictionary
{
{ "Id", "Id" },
{ "NodeName", "NodeName" },
{ "NodeTypeAlias", "NodeTypeAlias" },
{ "CreateDate", "CreateDate" },
{ "UpdateDate", "UpdateDate" },
{ "CreatorId", "CreatorId" },
{ "WriterId", "WriterId" },
{ "Url", "Url" },
};
foreach (KeyValuePair field in stdFields.Where(x => fields.ContainsKey(x.Key) == false))
{
fields[field.Key] = field.Value;
}
return fields;
}
private static Dictionary GetAliasesAndNames(IContentTypeBase? contentType) =>
contentType?.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name) ?? new Dictionary();
#endregion
}