diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index 2510f3ff71..0a917a6d86 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Collections.Generic; +using System.Data; using System.Linq; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Web.PublishedCache; @@ -1119,5 +1121,144 @@ namespace Umbraco.Core } #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) + { + var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() + ? content.Children(variationContextAccessor, culture).Any() + ? content.Children(variationContextAccessor, culture).ElementAt(0) + : null + : content.Children(variationContextAccessor, culture).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAliasFilter)); + if (firstNode == null) + return new DataTable(); //no children found + + //use new utility class to create table so that we don't have to maintain code in many places, just one + var dt = Core.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), + //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 + var tableData = Core.DataTableExtensions.CreateTableData(); + //loop through each child and create row data for it + foreach (var n in content.Children(variationContextAccessor).OrderBy(x => x.SortOrder)) + { + 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(); + foreach (var p in from IPublishedProperty p in n.Properties where p.GetSourceValue() != null select p) + { + // probably want the "object value" of the property here... + userVals[p.Alias] = p.GetValue(); + } + //add the row data + Core.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) + { + var type = contentTypeService.Get(alias) + ?? mediaTypeService.Get(alias) + ?? (IContentTypeBase)memberTypeService.Get(alias); + var fields = GetAliasesAndNames(type); + + // ensure the standard fields are there + var stdFields = new Dictionary + { + {"Id", "Id"}, + {"NodeName", "NodeName"}, + {"NodeTypeAlias", "NodeTypeAlias"}, + {"CreateDate", "CreateDate"}, + {"UpdateDate", "UpdateDate"}, + {"CreatorName", "CreatorName"}, + {"WriterName", "WriterName"}, + {"Url", "Url"} + }; + + foreach (var 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); + + #endregion } } diff --git a/src/Umbraco.Web.Website/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Web.Website/Extensions/PublishedContentExtensions.cs new file mode 100644 index 0000000000..f6105665c4 --- /dev/null +++ b/src/Umbraco.Web.Website/Extensions/PublishedContentExtensions.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Web; +using Examine; +using Microsoft.AspNetCore.Html; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Examine; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Website.Extensions +{ + public static class PublishedContentExtensions + { + #region Creator/Writer Names + + /// + /// Gets the name of the content item creator. + /// + /// The content item. + /// + public static string CreatorName(this IPublishedContent content, IUserService userService) => userService.GetProfileById(content.CreatorId)?.Name; + + /// + /// Gets the name of the content item writer. + /// + /// The content item. + /// + public static string WriterName(this IPublishedContent content, IUserService userService) => userService.GetProfileById(content.WriterId)?.Name; + + #endregion + + #region Variations + + /// + /// 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, IUmbracoContextAccessor umbracoContextAccessor, ISiteDomainHelper siteDomainHelper, Uri current = null) + => DomainUtilities.GetCultureFromDomains(content.Id, content.Path, current, umbracoContextAccessor.UmbracoContext, siteDomainHelper); + + #endregion + + #region Search + + public static IEnumerable SearchDescendants(this IPublishedContent content, IExamineManager examineManager, IUmbracoContextAccessor umbracoContextAccessor, string term, string indexName = null) + { + indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; + if (!examineManager.TryGetIndex(indexName, out var index)) + { + throw new InvalidOperationException("No index found with name " + indexName); + } + + var searcher = index.GetSearcher(); + + //var t = term.Escape().Value; + //var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; + + var query = searcher.CreateQuery() + .Field(UmbracoExamineFieldNames.IndexPathFieldName, (content.Path + ",").MultipleCharacterWildcard()) + .And() + .ManagedQuery(term); + + return query.Execute().ToPublishedSearchResults(umbracoContextAccessor.UmbracoContext.Content); + } + + public static IEnumerable SearchChildren(this IPublishedContent content, IExamineManager examineManager, IUmbracoContextAccessor umbracoContextAccessor, string term, string indexName = null) + { + indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; + if (!examineManager.TryGetIndex(indexName, out var index)) + { + throw new InvalidOperationException("No index found with name " + indexName); + } + + var searcher = index.GetSearcher(); + + //var t = term.Escape().Value; + //var luceneQuery = "+parentID:" + content.Id + " +" + t; + + var query = searcher.CreateQuery() + .Field("parentID", content.Id) + .And() + .ManagedQuery(term); + + return query.Execute().ToPublishedSearchResults(umbracoContextAccessor.UmbracoContext.Content); + } + + #endregion + + #region IsSomething: equality + + public static bool IsEqual(this IPublishedContent content, IPublishedContent other) => content.Id == other.Id; + + /// + /// If the specified is equal to , the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) => content.IsEqual(other, valueIfTrue, string.Empty); + + /// + /// If the specified is equal to , the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) => new HtmlString(HttpUtility.HtmlEncode(content.IsEqual(other) ? valueIfTrue : valueIfFalse)); + + /// + /// If the specified is not equal to , true will be returned; otherwise, the result will be false />. + /// + /// The content. + /// The other content. + /// + /// The result from checking whether the two published content items are not equal. + /// + public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) => content.IsEqual(other) == false; + + /// + /// If the specified is not equal to , the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) => content.IsNotEqual(other, valueIfTrue, string.Empty); + + /// + /// If the specified is not equal to , the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) => new HtmlString(HttpUtility.HtmlEncode(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse)); + + #endregion + + #region IsSomething: ancestors and descendants + + /// + /// If the specified is a decendant of , the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue) => content.IsDescendant(other, valueIfTrue, string.Empty); + + /// + /// If the specified is a decendant of , the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) => new HtmlString(HttpUtility.HtmlEncode(content.IsDescendant(other) ? valueIfTrue : valueIfFalse)); + + public static IHtmlContent IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) => content.IsDescendantOrSelf(other, valueIfTrue, string.Empty); + + /// + /// If the specified is a decendant of or are the same, the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) => new HtmlString(HttpUtility.HtmlEncode(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse)); + + + public static IHtmlContent IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue) => content.IsAncestor(other, valueIfTrue, string.Empty); + + /// + /// If the specified is an ancestor of , the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) => new HtmlString(HttpUtility.HtmlEncode(content.IsAncestor(other) ? valueIfTrue : valueIfFalse)); + + public static IHtmlContent IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) => content.IsAncestorOrSelf(other, valueIfTrue, string.Empty); + + /// + /// If the specified is an ancestor of or are the same, the HTML encoded will be returned; otherwise, . + /// + /// The content. + /// The other content. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + public static IHtmlContent IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) => new HtmlString(HttpUtility.HtmlEncode(content.IsAncestorOrSelf(other) ? valueIfTrue : valueIfFalse)); + + #endregion + } +} diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs deleted file mode 100644 index b8546dda3e..0000000000 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ /dev/null @@ -1,859 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Web; -using Examine; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Examine; -using Umbraco.Web.Composing; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; - -namespace Umbraco.Web -{ - /// - /// Provides extension methods for IPublishedContent. - /// - public static class PublishedContentExtensions - { - // see notes in PublishedElementExtensions - // (yes, this is not pretty, but works for now) - // - private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; - private static IPublishedSnapshot PublishedSnapshot => Current.PublishedSnapshot; - private static IUmbracoContext UmbracoContext => Current.UmbracoContext; - private static ISiteDomainHelper SiteDomainHelper => Current.Factory.GetRequiredService(); - private static IVariationContextAccessor VariationContextAccessor => Current.VariationContextAccessor; - private static IExamineManager ExamineManager => Current.Factory.GetRequiredService(); - private static IUserService UserService => Current.Services.UserService; - - - #region Creator/Writer Names - - public static string CreatorName(this IPublishedContent content, IUserService userService) - { - return userService.GetProfileById(content.CreatorId)?.Name; - } - - public static string WriterName(this IPublishedContent content, IUserService userService) - { - return userService.GetProfileById(content.WriterId)?.Name; - } - - public static string CreatorName(this IPublishedContent content) - { - return content.GetCreatorName(UserService); - } - - public static string WriterName(this IPublishedContent content) - { - return content.GetWriterName(UserService); - } - - #endregion - - #region Template - - /// - /// Returns the current template Alias - /// - /// - /// Empty string if none is set. - public static string GetTemplateAlias(this IPublishedContent content) - { - return content.GetTemplateAlias(Current.Services.FileService); - } - - public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) - { - return content.IsAllowedTemplate( - Current.Services.ContentTypeService, - /*Current.Configs.WebRouting().DisableAlternativeTemplates, - Current.Configs.WebRouting().ValidateAlternativeTemplates, - TODO get values from config*/ - false, false, - templateId); - } - - public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) - { - return content.IsAllowedTemplate( - Current.Services.FileService, - Current.Services.ContentTypeService, - /*Current.Configs.WebRouting().DisableAlternativeTemplates, - Current.Configs.WebRouting().ValidateAlternativeTemplates, - TODO get values from config*/ - false, false, - templateAlias); - } - - #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 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) - { - return 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) - { - return 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) - { - return content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); - } - - #endregion - - #region Variations - - /// - /// 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) - { - var umbracoContext = UmbracoContext; - - if (umbracoContext == null) - throw new InvalidOperationException("A current UmbracoContext is required."); - - return DomainUtilities.GetCultureFromDomains(content.Id, content.Path, current, umbracoContext, SiteDomainHelper); - } - - #endregion - - #region Search - - public static IEnumerable SearchDescendants(this IPublishedContent content, string term, string indexName = null) - { - // TODO: inject examine manager - - indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; - if (!ExamineManager.TryGetIndex(indexName, out var index)) - throw new InvalidOperationException("No index found with name " + indexName); - - var searcher = index.GetSearcher(); - - //var t = term.Escape().Value; - //var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; - - var query = searcher.CreateQuery() - .Field(UmbracoExamineFieldNames.IndexPathFieldName, (content.Path + ",").MultipleCharacterWildcard()) - .And() - .ManagedQuery(term); - - return query.Execute().ToPublishedSearchResults(Current.UmbracoContext.Content); - } - - public static IEnumerable SearchChildren(this IPublishedContent content, string term, string indexName = null) - { - // TODO: inject examine manager - - indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; - if (!ExamineManager.TryGetIndex(indexName, out var index)) - throw new InvalidOperationException("No index found with name " + indexName); - - var searcher = index.GetSearcher(); - - //var t = term.Escape().Value; - //var luceneQuery = "+parentID:" + content.Id + " +" + t; - - var query = searcher.CreateQuery() - .Field("parentID", content.Id) - .And() - .ManagedQuery(term); - - return query.Execute().ToPublishedSearchResults(Current.UmbracoContext.Content); - } - - #endregion - - #region IsSomething: equality - - public static bool IsEqual(this IPublishedContent content, IPublishedContent other) - { - return content.Id == other.Id; - } - - /// - /// If the specified is equal to , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsEqual(other, valueIfTrue, string.Empty); - } - - /// - /// If the specified is equal to , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(HttpUtility.HtmlEncode(content.IsEqual(other) ? valueIfTrue : valueIfFalse)); - } - - /// - /// If the specified is not equal to , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) - { - return content.IsEqual(other) == false; - } - - /// - /// If the specified is not equal to , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsNotEqual(other, valueIfTrue, string.Empty); - } - - /// - /// If the specified is not equal to , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(HttpUtility.HtmlEncode(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse)); - } - - #endregion - - #region IsSomething: ancestors and descendants - - - /// - /// If the specified is a decendant of , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsDescendant(other, valueIfTrue, string.Empty); - } - - /// - /// If the specified is a decendant of , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(HttpUtility.HtmlEncode(content.IsDescendant(other) ? valueIfTrue : valueIfFalse)); - } - - public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsDescendantOrSelf(other, valueIfTrue, string.Empty); - } - - /// - /// If the specified is a decendant of or are the same, the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(HttpUtility.HtmlEncode(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse)); - } - - - public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsAncestor(other, valueIfTrue, string.Empty); - } - - /// - /// If the specified is an ancestor of , the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(HttpUtility.HtmlEncode(content.IsAncestor(other) ? valueIfTrue : valueIfFalse)); - } - - public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsAncestorOrSelf(other, valueIfTrue, string.Empty); - } - - /// - /// If the specified is an ancestor of or are the same, the HTML encoded will be returned; otherwise, . - /// - /// The content. - /// The other content. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return new HtmlString(HttpUtility.HtmlEncode(content.IsAncestorOrSelf(other) ? valueIfTrue : valueIfFalse)); - } - - #endregion - - #region Axes: descendants, descendants-or-self - - /// - /// 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 or similar. - /// - public static IEnumerable DescendantsOrSelfOfType(this IEnumerable parentNodes, string docTypeAlias, string culture = null) - { - return 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 or similar. - /// - public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, string culture = null) - where T : class, IPublishedContent - { - return parentNodes.DescendantsOrSelf(VariationContextAccessor, culture); - } - - public static IEnumerable Descendants(this IPublishedContent content, string culture = null) - { - return content.Descendants(VariationContextAccessor, culture); - } - - public static IEnumerable Descendants(this IPublishedContent content, int level, string culture = null) - { - return content.Descendants(VariationContextAccessor, level, culture); - } - - public static IEnumerable DescendantsOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) - { - return content.DescendantsOfType(VariationContextAccessor, contentTypeAlias, culture); - } - - public static IEnumerable Descendants(this IPublishedContent content, string culture = null) - where T : class, IPublishedContent - { - return content.Descendants(VariationContextAccessor, culture); - } - - public static IEnumerable Descendants(this IPublishedContent content, int level, string culture = null) - where T : class, IPublishedContent - { - return content.Descendants(VariationContextAccessor, level, culture); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string culture = null) - { - return content.DescendantsOrSelf(VariationContextAccessor, culture); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string culture = null) - { - return content.DescendantsOrSelf(VariationContextAccessor, level, culture); - } - - public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) - { - return content.DescendantsOrSelfOfType(VariationContextAccessor, contentTypeAlias, culture); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string culture = null) - where T : class, IPublishedContent - { - return content.DescendantsOrSelf(VariationContextAccessor, culture); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string culture = null) - where T : class, IPublishedContent - { - return content.DescendantsOrSelf(VariationContextAccessor, level, culture); - } - - public static IPublishedContent Descendant(this IPublishedContent content, string culture = null) - { - return content.Descendant(VariationContextAccessor, culture); - } - - public static IPublishedContent Descendant(this IPublishedContent content, int level, string culture = null) - { - return content.Descendant(VariationContextAccessor, level, culture); - } - - public static IPublishedContent DescendantOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) - { - return content.DescendantOfType(VariationContextAccessor, contentTypeAlias, culture); - } - - public static T Descendant(this IPublishedContent content, string culture = null) - where T : class, IPublishedContent - { - return content.Descendant(VariationContextAccessor, culture); - } - - public static T Descendant(this IPublishedContent content, int level, string culture = null) - where T : class, IPublishedContent - { - return content.Descendant(VariationContextAccessor, level, culture); - } - - public static IPublishedContent DescendantOrSelf(this IPublishedContent content, int level, string culture = null) - { - return content.DescendantOrSelf(VariationContextAccessor, level, culture); - } - - public static IPublishedContent DescendantOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) - { - return content.DescendantOrSelfOfType(VariationContextAccessor, contentTypeAlias, culture); - } - - public static T DescendantOrSelf(this IPublishedContent content, string culture = null) - where T : class, IPublishedContent - { - return content.DescendantOrSelf(VariationContextAccessor, culture); - } - - public static T DescendantOrSelf(this IPublishedContent content, int level, string culture = null) - where T : class, IPublishedContent - { - return content.DescendantOrSelf(VariationContextAccessor, level, culture); - } - - #endregion - - #region Axes: children - - /// - /// 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) - { - return content.Children(VariationContextAccessor, culture).Where(predicate); - } - - /// - /// 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) - { - return 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 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 - { - return content.Children(VariationContextAccessor, culture).OfType(); - } - - public static IPublishedContent FirstChild(this IPublishedContent content, string culture = null) - { - return content.Children(VariationContextAccessor, culture).FirstOrDefault(); - } - - /// - /// Gets the first child of the content, of a given content type. - /// - public static IPublishedContent FirstChildOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) - { - return content.ChildrenOfType(VariationContextAccessor, contentTypeAlias, culture).FirstOrDefault(); - } - - public static IPublishedContent FirstChild(this IPublishedContent content, Func predicate, string culture = null) - { - return content.Children(VariationContextAccessor, predicate, culture).FirstOrDefault(); - } - - public static IPublishedContent FirstChild(this IPublishedContent content, Guid uniqueId, string culture = null) - { - return content.Children(VariationContextAccessor, x => x.Key == uniqueId, culture).FirstOrDefault(); - } - - public static T FirstChild(this IPublishedContent content, string culture = null) - where T : class, IPublishedContent - { - return content.Children(VariationContextAccessor, culture).FirstOrDefault(); - } - - public static T FirstChild(this IPublishedContent content, Func predicate, string culture = null) - where T : class, IPublishedContent - { - return content.Children(VariationContextAccessor, culture).FirstOrDefault(predicate); - } - - /// - /// Gets the children of the content in a DataTable. - /// - /// The content. - /// A service context. - /// 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, ServiceContext services, string contentTypeAliasFilter = "", string culture = null) - { - return GenerateDataTable(content, services, contentTypeAliasFilter, culture); - } - - /// - /// Gets the children of the content in a DataTable. - /// - /// The content. - /// A service context. - /// 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, ServiceContext services, string contentTypeAliasFilter = "", string culture = null) - { - var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() - ? content.Children(VariationContextAccessor, culture).Any() - ? content.Children(VariationContextAccessor, culture).ElementAt(0) - : null - : content.Children(VariationContextAccessor, culture).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAliasFilter)); - if (firstNode == null) - return new DataTable(); //no children found - - //use new utility class to create table so that we don't have to maintain code in many places, just one - var dt = Core.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(services, alias), - //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 - var tableData = Core.DataTableExtensions.CreateTableData(); - //loop through each child and create row data for it - foreach (var n in content.Children(VariationContextAccessor).OrderBy(x => x.SortOrder)) - { - 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() } - }; - - var userVals = new Dictionary(); - foreach (var p in from IPublishedProperty p in n.Properties where p.GetSourceValue() != null select p) - { - // probably want the "object value" of the property here... - userVals[p.Alias] = p.GetValue(); - } - //add the row data - Core.DataTableExtensions.AddRowData(tableData, standardVals, userVals); - } - return tableData; - } - ); - return dt; - } - - #endregion - - #region Axes: siblings - - /// - /// 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) - { - return content.Siblings(PublishedSnapshot, VariationContextAccessor, culture); - } - - /// - /// Gets the siblings of the content, 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, 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) - { - return 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 - { - return 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) - { - return 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) - { - return 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 - { - return content.SiblingsAndSelf(PublishedSnapshot, VariationContextAccessor, culture); - } - - #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(ServiceContext services, string alias) - { - var type = services.ContentTypeService.Get(alias) - ?? services.MediaTypeService.Get(alias) - ?? (IContentTypeBase)services.MemberTypeService.Get(alias); - var fields = GetAliasesAndNames(type); - - // ensure the standard fields are there - var stdFields = new Dictionary - { - {"Id", "Id"}, - {"NodeName", "NodeName"}, - {"NodeTypeAlias", "NodeTypeAlias"}, - {"CreateDate", "CreateDate"}, - {"UpdateDate", "UpdateDate"}, - {"CreatorName", "CreatorName"}, - {"WriterName", "WriterName"}, - {"Url", "Url"} - }; - - foreach (var field in stdFields.Where(x => fields.ContainsKey(x.Key) == false)) - { - fields[field.Key] = field.Value; - } - - return fields; - } - - private static Dictionary GetAliasesAndNames(IContentTypeBase contentType) - { - return contentType.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); - } - - #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, string culture = null, UrlMode mode = UrlMode.Default) - { - return content.Url(Current.PublishedUrlProvider, culture, mode); - } - - - #endregion - } -} diff --git a/src/Umbraco.Web/PublishedElementExtensions.cs b/src/Umbraco.Web/PublishedElementExtensions.cs deleted file mode 100644 index 637d22902c..0000000000 --- a/src/Umbraco.Web/PublishedElementExtensions.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Core; -using Umbraco.Web.Composing; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web -{ - /// - /// Provides extension methods for IPublishedElement. - /// - public static class PublishedElementExtensions - { - // lots of debates about accessing dependencies (IPublishedValueFallback) from extension methods, ranging - // from "don't do it" i.e. if an extension method is relying on dependencies, a proper service should be - // created instead, to discussing method injection vs service locator vs other subtleties, see for example - // this post http://marisks.net/2016/12/19/dependency-injection-with-extension-methods/ - // - // point is, we do NOT want a service, we DO want to write model.Value("alias", "fr-FR") and hit - // fallback somehow - which pretty much rules out method injection, and basically anything but service - // locator - bah, let's face it, it works - // - // 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.Core - // 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 Value - - /// - /// Gets the value of a content's property identified by its alias. - /// - /// 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. - /// - /// 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, string alias, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) - { - return content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); - } - - #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 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, string alias, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) - { - return content.Value(PublishedValueFallback, alias, culture, segment, fallback, defaultValue); - } - - #endregion - - #region IsSomething - - /// - /// Gets a value indicating whether the content is visible. - /// - /// The content. - /// 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) - { - return content.IsVisible(PublishedValueFallback); - } - - #endregion - - - } -} diff --git a/src/Umbraco.Web/PublishedPropertyExtension.cs b/src/Umbraco.Web/PublishedPropertyExtension.cs deleted file mode 100644 index 917a5b4572..0000000000 --- a/src/Umbraco.Web/PublishedPropertyExtension.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Umbraco.Core; -using Umbraco.Web.Composing; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Web -{ - /// - /// Provides extension methods for IPublishedProperty. - /// - public static class PublishedPropertyExtension - { - // see notes in PublishedElementExtensions - // - private static IPublishedValueFallback PublishedValueFallback => Current.PublishedValueFallback; - - #region Value - - public static object Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, object defaultValue = default) - { - return property.Value(PublishedValueFallback, culture, segment, fallback, defaultValue); - } - - #endregion - - #region Value - - public static T Value(this IPublishedProperty property, string culture = null, string segment = null, Fallback fallback = default, T defaultValue = default) - { - return property.Value(PublishedValueFallback, culture, segment, fallback, defaultValue); - } - - #endregion - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4838ab2e69..2446653e9d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -177,7 +177,6 @@ - @@ -192,7 +191,6 @@ - @@ -217,7 +215,6 @@ - @@ -268,7 +265,7 @@ - + \ No newline at end of file