using System.Data; using Examine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; namespace Umbraco.Extensions; public static class FriendlyPublishedContentExtensions { private static IVariationContextAccessor VariationContextAccessor { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IPublishedModelFactory PublishedModelFactory { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IPublishedUrlProvider PublishedUrlProvider { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IUserService UserService { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IUmbracoContextAccessor UmbracoContextAccessor { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static ISiteDomainMapper SiteDomainHelper { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IExamineManager ExamineManager { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IFileService FileService { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IOptions WebRoutingSettings { get; } = StaticServiceProvider.Instance.GetRequiredService>(); private static IContentTypeService ContentTypeService { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IPublishedValueFallback PublishedValueFallback { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IPublishedSnapshot? PublishedSnapshot { get { if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { return null; } return umbracoContext.PublishedSnapshot; } } private static IMediaTypeService MediaTypeService { get; } = StaticServiceProvider.Instance.GetRequiredService(); private static IMemberTypeService MemberTypeService { get; } = StaticServiceProvider.Instance.GetRequiredService(); /// /// Creates a strongly typed published content model for an internal published content. /// /// The internal published content. /// The strongly typed published content model. public static IPublishedContent? CreateModel( this IPublishedContent content) => content.CreateModel(PublishedModelFactory); /// /// 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, string? culture = null) => content.Name(VariationContextAccessor, culture); /// /// 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, string? culture = null) => content.UrlSegment(VariationContextAccessor, 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, string? culture = null) => content.CultureDate(VariationContextAccessor, culture); /// /// Returns the current template Alias /// /// Empty string if none is set. public static string GetTemplateAlias(this IPublishedContent content) => content.GetTemplateAlias(FileService); public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) => content.IsAllowedTemplate(ContentTypeService, WebRoutingSettings.Value, templateId); public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) => content.IsAllowedTemplate( WebRoutingSettings.Value.DisableAlternativeTemplates, WebRoutingSettings.Value.ValidateAlternativeTemplates, templateAlias); public static bool IsAllowedTemplate( this IPublishedContent content, bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId) => content.IsAllowedTemplate( ContentTypeService, disableAlternativeTemplates, validateAlternativeTemplates, templateId); public static bool IsAllowedTemplate( this IPublishedContent content, bool disableAlternativeTemplates, bool validateAlternativeTemplates, string templateAlias) => content.IsAllowedTemplate( FileService, ContentTypeService, disableAlternativeTemplates, validateAlternativeTemplates, templateAlias); /// /// Gets a value indicating whether the content has a value for a property identified by its alias. /// /// The content. /// 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, string alias, string? culture = null, string? segment = null, Fallback fallback = default) => content.HasValue(PublishedValueFallback, alias, culture, segment, fallback); /// /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. /// /// The content. /// 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, string alias, string? culture = null, string? segment = null, Fallback fallback = default, object? defaultValue = default) => content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); /// /// Gets the value of a content's property identified by its alias, converted to a specified type. /// /// The target property type. /// The content. /// 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, string alias, string? culture = null, string? segment = null, Fallback fallback = default, T? defaultValue = default) => content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); /// /// Returns all DescendantsOrSelf of all content referenced /// /// /// /// /// 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, string docTypeAlias, string? culture = null) => parentNodes.DescendantsOrSelfOfType(VariationContextAccessor, docTypeAlias, culture); /// /// Returns all DescendantsOrSelf of all content referenced /// /// /// /// 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, string? culture = null) where T : class, IPublishedContent => parentNodes.DescendantsOrSelf(VariationContextAccessor, culture); public static IEnumerable Descendants(this IPublishedContent content, string? culture = null) => content.Descendants(VariationContextAccessor, culture); public static IEnumerable Descendants(this IPublishedContent content, int level, string? culture = null) => content.Descendants(VariationContextAccessor, level, culture); public static IEnumerable DescendantsOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null) => content.DescendantsOfType(VariationContextAccessor, contentTypeAlias, culture); public static IEnumerable Descendants(this IPublishedContent content, string? culture = null) where T : class, IPublishedContent => content.Descendants(VariationContextAccessor, culture); public static IEnumerable Descendants(this IPublishedContent content, int level, string? culture = null) where T : class, IPublishedContent => content.Descendants(VariationContextAccessor, level, culture); public static IEnumerable DescendantsOrSelf( this IPublishedContent content, string? culture = null) => content.DescendantsOrSelf(VariationContextAccessor, culture); public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string? culture = null) => content.DescendantsOrSelf(VariationContextAccessor, level, culture); public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null) => content.DescendantsOrSelfOfType(VariationContextAccessor, contentTypeAlias, culture); public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string? culture = null) where T : class, IPublishedContent => content.DescendantsOrSelf(VariationContextAccessor, culture); public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string? culture = null) where T : class, IPublishedContent => content.DescendantsOrSelf(VariationContextAccessor, level, culture); public static IPublishedContent? Descendant(this IPublishedContent content, string? culture = null) => content.Descendant(VariationContextAccessor, culture); public static IPublishedContent? Descendant(this IPublishedContent content, int level, string? culture = null) => content.Descendant(VariationContextAccessor, level, culture); public static IPublishedContent? DescendantOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null) => content.DescendantOfType(VariationContextAccessor, contentTypeAlias, culture); public static T? Descendant(this IPublishedContent content, string? culture = null) where T : class, IPublishedContent => content.Descendant(VariationContextAccessor, culture); public static T? Descendant(this IPublishedContent content, int level, string? culture = null) where T : class, IPublishedContent => content.Descendant(VariationContextAccessor, level, culture); public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string? culture = null) => content.DescendantOrSelf(VariationContextAccessor, culture); public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, int level, string? culture = null) => content.DescendantOrSelf(VariationContextAccessor, level, culture); public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null) => content.DescendantOrSelfOfType(VariationContextAccessor, contentTypeAlias, culture); public static T? DescendantOrSelf(this IPublishedContent content, string? culture = null) where T : class, IPublishedContent => content.DescendantOrSelf(VariationContextAccessor, culture); public static T? DescendantOrSelf(this IPublishedContent content, int level, string? culture = null) where T : class, IPublishedContent => content.DescendantOrSelf(VariationContextAccessor, level, culture); /// /// 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, string? culture = null) => content.Children(VariationContextAccessor, culture); /// /// Gets the children of the content, filtered by a predicate. /// /// The content. /// 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, Func predicate, string? culture = null) => content.Children(VariationContextAccessor, predicate, culture); /// /// Gets the children of the content, of any of the specified types. /// /// The content. /// /// 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, string contentTypeAlias, string? culture = null) => content.ChildrenOfType(VariationContextAccessor, contentTypeAlias, culture); /// /// Gets the children of the content, of a given content type. /// /// The content type. /// The content. /// /// 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, string? culture = null) where T : class, IPublishedContent => content.Children(VariationContextAccessor, culture); public static IPublishedContent? FirstChild(this IPublishedContent content, string? culture = null) => content.FirstChild(VariationContextAccessor, culture); /// /// Gets the first child of the content, of a given content type. /// public static IPublishedContent? FirstChildOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null) => content.FirstChildOfType(VariationContextAccessor, contentTypeAlias, culture); public static IPublishedContent? FirstChild(this IPublishedContent content, Func predicate, string? culture = null) => content.FirstChild(VariationContextAccessor, predicate, culture); public static IPublishedContent? FirstChild(this IPublishedContent content, Guid uniqueId, string? culture = null) => content.FirstChild(VariationContextAccessor, uniqueId, culture); public static T? FirstChild(this IPublishedContent content, string? culture = null) where T : class, IPublishedContent => content.FirstChild(VariationContextAccessor, culture); public static T? FirstChild(this IPublishedContent content, Func predicate, string? culture = null) where T : class, IPublishedContent => content.FirstChild(VariationContextAccessor, predicate, culture); /// /// Gets the siblings of the content. /// /// The content. /// /// 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, string? culture = null) => content.Siblings(PublishedSnapshot, VariationContextAccessor, culture); /// /// Gets the siblings of the content, of a given content type. /// /// The content. /// The content type alias. /// /// 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? SiblingsOfType(this IPublishedContent content, string contentTypeAlias, string? culture = null) => content.SiblingsOfType(PublishedSnapshot, VariationContextAccessor, contentTypeAlias, culture); /// /// Gets the siblings of the content, of a given content type. /// /// The content type. /// The content. /// /// 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, string? culture = null) where T : class, IPublishedContent => content.Siblings(PublishedSnapshot, VariationContextAccessor, culture); /// /// Gets the siblings of the content including the node itself to indicate the position. /// /// The content. /// /// 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, string? culture = null) => content.SiblingsAndSelf(PublishedSnapshot, VariationContextAccessor, culture); /// /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. /// /// The content. /// /// 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, string contentTypeAlias, string? culture = null) => content.SiblingsAndSelfOfType(PublishedSnapshot, VariationContextAccessor, contentTypeAlias, culture); /// /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. /// /// The content type. /// The content. /// /// 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, string? culture = null) where T : class, IPublishedContent => content.SiblingsAndSelf(PublishedSnapshot, VariationContextAccessor, culture); /// /// 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, string? culture = null, UrlMode mode = UrlMode.Default) => content.Url(PublishedUrlProvider, culture, mode); /// /// Gets the children of the content in a DataTable. /// /// The content. /// 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, string contentTypeAliasFilter = "", string? culture = null) => content.ChildrenAsTable( VariationContextAccessor, ContentTypeService, MediaTypeService, MemberTypeService, PublishedUrlProvider, contentTypeAliasFilter, culture); /// /// Gets the url for a media. /// /// The content item. /// The culture (use current culture by default). /// The url mode (use site configuration by default). /// The alias of the property (use 'umbracoFile' by default). /// The url for the media. /// /// /// 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 MediaUrl( this IPublishedContent content, string? culture = null, UrlMode mode = UrlMode.Default, string propertyAlias = Constants.Conventions.Media.File) => content.MediaUrl(PublishedUrlProvider, culture, mode, propertyAlias); /// /// Gets the name of the content item creator. /// /// The content item. public static string? CreatorName(this IPublishedContent content) => content.CreatorName(UserService); /// /// Gets the name of the content item writer. /// /// The content item. public static string? WriterName(this IPublishedContent content) => content.WriterName(UserService); /// /// Gets the culture assigned to a document by domains, in the context of a current Uri. /// /// The document. /// An optional current Uri. /// The culture assigned to the document by domains. /// /// /// In 1:1 multilingual setup, a document contains several cultures (there is not /// one document per culture), and domains, withing the context of a current Uri, assign /// a culture to that document. /// /// public static string? GetCultureFromDomains( this IPublishedContent content, Uri? current = null) => content.GetCultureFromDomains(UmbracoContextAccessor, SiteDomainHelper, current); public static IEnumerable SearchDescendants( this IPublishedContent content, string term, string? indexName = null) => content.SearchDescendants(ExamineManager, UmbracoContextAccessor, term, indexName); public static IEnumerable SearchChildren( this IPublishedContent content, string term, string? indexName = null) => content.SearchChildren(ExamineManager, UmbracoContextAccessor, term, indexName); }