using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; using Examine; using Umbraco.Core; 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 UmbracoContext UmbracoContext => Current.UmbracoContext; private static ISiteDomainHelper SiteDomainHelper => Current.Factory.GetInstance(); #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; } #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) { return content.ContentType.CompositionAliases.InvariantContains(alias); } #endregion #region Template /// /// Returns the current template Alias /// /// /// Empty string if none is set. public static string GetTemplateAlias(this IPublishedContent content) { if(content.TemplateId.HasValue == false) { return string.Empty; } var template = Current.Services.FileService.GetTemplate(content.TemplateId.Value); return template == null ? string.Empty : template.Alias; } public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) { if (Current.Configs.Settings().WebRouting.DisableAlternativeTemplates) return content.TemplateId == templateId; if (content.TemplateId == templateId || !Current.Configs.Settings().WebRouting.ValidateAlternativeTemplates) return true; var publishedContentContentType = Current.Services.ContentTypeService.Get(content.ContentType.Id); if (publishedContentContentType == null) throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); return publishedContentContentType.IsAllowedTemplate(templateId); } public static bool IsAllowedTemplate(this IPublishedContent content, string templateAlias) { var template = Current.Services.FileService.GetTemplate(templateAlias); return template != null && content.IsAllowedTemplate(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 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) { var property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) return true; // else let fallback try to get a value return PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _, out _); } /// /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. /// /// The content. /// The 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) { var property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) return property.GetValue(culture, segment); // else let fallback try to get a value if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, out property)) return value; // else... if we have a property, at least let the converter return its own // vision of 'no value' (could be an empty enumerable) return property?.GetValue(culture, segment); } /// /// Gets the value of a content's property identified by its alias, converted to a specified type. /// /// The target property type. /// The content. /// The 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) { var property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) return property.Value(culture, segment); // else let fallback try to get a value if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, out property)) return value; // else... if we have a property, at least let the converter return its own // vision of 'no value' (could be an empty enumerable) - otherwise, default return property == null ? default : property.Value(culture, segment); } #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.Instance.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(UmbracoExamineIndex.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.Instance.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: misc. /// /// Determines whether the specified content is a specified content type. /// /// The content to determine content type of. /// The alias of the content type to test against. /// True if the content is of the specified content type; otherwise false. public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) { return content.ContentType.Alias.InvariantEquals(docTypeAlias); } /// /// Determines whether the specified content is a specified content type or it's derived types. /// /// The content to determine content type of. /// The alias of the content type to test against. /// When true, recurses up the content type tree to check inheritance; when false just calls IsDocumentType(this IPublishedContent content, string docTypeAlias). /// True if the content is of the specified content type or a derived content type; otherwise false. public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias, bool recursive) { if (content.IsDocumentType(docTypeAlias)) return true; return recursive && content.IsComposedOf(docTypeAlias); } #endregion #region IsSomething: equality public static bool IsEqual(this IPublishedContent content, IPublishedContent other) { return content.Id == other.Id; } public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsEqual(other, valueIfTrue, string.Empty); } public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsEqual(other) ? valueIfTrue : valueIfFalse); } public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) { return content.IsEqual(other) == false; } public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsNotEqual(other, valueIfTrue, string.Empty); } public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse); } #endregion #region IsSomething: ancestors and descendants public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) { return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(',')); } public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsDescendant(other, valueIfTrue, string.Empty); } public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsDescendant(other) ? valueIfTrue : valueIfFalse); } public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) { return content.Path.InvariantEquals(other.Path) || content.IsDescendant(other); } public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsDescendantOrSelf(other, valueIfTrue, string.Empty); } public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse); } public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) { return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(',')); } public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsAncestor(other, valueIfTrue, string.Empty); } public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsAncestor(other) ? valueIfTrue : valueIfFalse); } public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) { return other.Path.InvariantEquals(content.Path) || content.IsAncestor(other); } public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { return content.IsAncestorOrSelf(other, valueIfTrue, string.Empty); } public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsAncestorOrSelf(other) ? valueIfTrue : valueIfFalse); } #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) { return 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) { return 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) { return 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 { return 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 { return 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) { return 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) { return 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) { return 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 { return 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 { return 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) { return 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) { return 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) { return 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 { return 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 { return 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) { return 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) { return content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= maxLevel); } /// /// 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) { return content.EnumerateAncestors(true).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); } /// /// 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 { return 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 { return content.AncestorsOrSelf(maxLevel).FirstOrDefault(); } public static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func func) { var 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: 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 TypedContentAtRoot /// public static IEnumerable DescendantsOrSelfOfType(this IEnumerable parentNodes, string docTypeAlias, string culture = null) { return parentNodes.SelectMany(x => x.DescendantsOrSelfOfType(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 { return parentNodes.SelectMany(x => x.DescendantsOrSelf(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, string culture = null) { return content.DescendantsOrSelf(false, null, culture); } public static IEnumerable Descendants(this IPublishedContent content, int level, string culture = null) { return content.DescendantsOrSelf(false, p => p.Level >= level, culture); } public static IEnumerable DescendantsOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { return content.DescendantsOrSelf(false, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); } public static IEnumerable Descendants(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { return content.Descendants(culture).OfType(); } public static IEnumerable Descendants(this IPublishedContent content, int level, string culture = null) where T : class, IPublishedContent { return content.Descendants(level, culture).OfType(); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string culture = null) { return content.DescendantsOrSelf(true, null, culture); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string culture = null) { return content.DescendantsOrSelf(true, p => p.Level >= level, culture); } public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { return content.DescendantsOrSelf(true, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { return content.DescendantsOrSelf(culture).OfType(); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level, string culture = null) where T : class, IPublishedContent { return content.DescendantsOrSelf(level, culture).OfType(); } public static IPublishedContent Descendant(this IPublishedContent content, string culture = null) { return content.Children(culture).FirstOrDefault(); } public static IPublishedContent Descendant(this IPublishedContent content, int level, string culture = null) { return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x.Level == level); } public static IPublishedContent DescendantOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); } public static T Descendant(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x is T) as T; } public static T Descendant(this IPublishedContent content, int level, string culture = null) where T : class, IPublishedContent { return content.Descendant(level, culture) as T; } public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string culture = null) { return content; } public static IPublishedContent DescendantOrSelf(this IPublishedContent content, int level, string culture = null) { return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x.Level == level); } public static IPublishedContent DescendantOrSelfOfType(this IPublishedContent content, string contentTypeAlias, string culture = null) { return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); } public static T DescendantOrSelf(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x is T) as T; } public static T DescendantOrSelf(this IPublishedContent content, int level, string culture = null) where T : class, IPublishedContent { return content.DescendantOrSelf(level, culture) as T; } internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, bool orSelf, Func func, string culture = null) { return content.EnumerateDescendants(orSelf, culture).Where(x => func == null || func(x)); } internal static IEnumerable EnumerateDescendants(this IPublishedContent content, bool orSelf, string culture = null) { if (content == null) throw new ArgumentNullException(nameof(content)); if (orSelf) yield return content; foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants(culture))) yield return desc; } internal static IEnumerable EnumerateDescendants(this IPublishedContent content, string culture = null) { yield return content; foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants(culture))) yield return desc; } #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 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(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(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(culture).OfType(); } public static IPublishedContent FirstChild(this IPublishedContent content, string culture = null) { return content.Children(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(contentTypeAlias, culture).FirstOrDefault(); } public static IPublishedContent FirstChild(this IPublishedContent content, Func predicate, string culture = null) { return content.Children(predicate, culture).FirstOrDefault(); } public static IPublishedContent FirstChild(this IPublishedContent content, Guid uniqueId, string culture = null) { return content.Children(x=>x.Key == uniqueId, culture).FirstOrDefault(); } public static T FirstChild(this IPublishedContent content, string culture = null) where T : class, IPublishedContent { return content.Children(culture).FirstOrDefault(); } public static T FirstChild(this IPublishedContent content, Func predicate, string culture = null) where T : class, IPublishedContent { return content.Children(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(culture).Any() ? content.Children(culture).ElementAt(0) : null : content.Children(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().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() }, { "NodeTypeAlias", n.ContentType.Alias }, { "CreateDate", n.CreateDate }, { "UpdateDate", n.UpdateDate }, { "CreatorName", n.CreatorName }, { "WriterName", n.WriterName }, { "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 SiblingsAndSelf(content, culture).Where(x => x.Id != content.Id); } /// /// 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 SiblingsAndSelfOfType(content, contentTypeAlias, culture).Where(x => x.Id != content.Id); } /// /// 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 SiblingsAndSelf(content, culture).Where(x => x.Id != content.Id); } /// /// 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.Parent != null ? content.Parent.Children(culture) : PublishedSnapshot.Content.GetAtRoot().WhereIsInvariantOrHasCulture(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.Parent != null ? content.Parent.ChildrenOfType(contentTypeAlias, culture) : PublishedSnapshot.Content.GetAtRoot().OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(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.Parent != null ? content.Parent.Children(culture) : PublishedSnapshot.Content.GetAtRoot().OfType().WhereIsInvariantOrHasCulture(culture); } #endregion #region Axes: custom /// /// Gets the root content for this content. /// /// The content. /// The 'site' content ie AncestorOrSelf(1). public static IPublishedContent Root(this IPublishedContent content) { return content.AncestorOrSelf(1); } #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) { var umbracoContext = Composing.Current.UmbracoContext; if (umbracoContext == null) throw new InvalidOperationException("Cannot resolve a Url when Current.UmbracoContext is null."); if (umbracoContext.UrlProvider == null) throw new InvalidOperationException("Cannot resolve a Url when Current.UmbracoContext.UrlProvider is null."); switch (content.ContentType.ItemType) { case PublishedItemType.Content: return umbracoContext.UrlProvider.GetUrl(content, mode, culture); case PublishedItemType.Media: return umbracoContext.UrlProvider.GetMediaUrl(content, mode, culture, Constants.Conventions.Media.File); default: throw new NotSupportedException(); } } #endregion } }