using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; using System.Web; using Examine.LuceneEngine.SearchCriteria; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Web.Models; using Umbraco.Core; using Umbraco.Web.Routing; using ContentType = umbraco.cms.businesslogic.ContentType; namespace Umbraco.Web { /// /// Provides extension methods for IPublishedContent. /// public static class PublishedContentExtensions { #region Key public static Guid GetKey(this IPublishedContent content) { var contentWithKey = content as IPublishedContentWithKey; return contentWithKey == null ? Guid.Empty : contentWithKey.Key; } #endregion #region Urls /// /// Gets the url for the content. /// /// The content. /// The url for the content. [Obsolete("NiceUrl() is obsolete, use the Url() method instead")] public static string NiceUrl(this IPublishedContent content) { return content.Url(); } /// /// Gets the url for the content. /// /// The content. /// The url for the content. /// Better use the Url property but that method is here to complement UrlAbsolute(). public static string Url(this IPublishedContent content) { return content.Url; } /// /// Gets the absolute url for the content. /// /// The content. /// The absolute url for the content. [Obsolete("NiceUrlWithDomain() is obsolete, use the UrlAbsolute() method instead.")] public static string NiceUrlWithDomain(this IPublishedContent content) { return content.UrlAbsolute(); } /// /// Gets the absolute url for the content. /// /// The content. /// The absolute url for the content. //[Obsolete("UrlWithDomain() is obsolete, use the UrlAbsolute() method instead.")] public static string UrlWithDomain(this IPublishedContent content) { return content.UrlAbsolute(); } /// /// Gets the absolute url for the content. /// /// The content. /// The absolute url for the content. public static string UrlAbsolute(this IPublishedContent content) { // adapted from PublishedContentBase.Url switch (content.ItemType) { case PublishedItemType.Content: if (UmbracoContext.Current == null) throw new InvalidOperationException("Cannot resolve a Url for a content item when UmbracoContext.Current is null."); if (UmbracoContext.Current.UrlProvider == null) throw new InvalidOperationException("Cannot resolve a Url for a content item when UmbracoContext.Current.UrlProvider is null."); return UmbracoContext.Current.UrlProvider.GetUrl(content.Id, true); case PublishedItemType.Media: throw new NotSupportedException("AbsoluteUrl is not supported for media types."); default: throw new ArgumentOutOfRangeException(); } } #endregion #region Template /// /// Returns the current template Alias /// /// /// public static string GetTemplateAlias(this IPublishedContent content) { var template = ApplicationContext.Current.Services.FileService.GetTemplate(content.TemplateId); return template == null ? string.Empty : template.Alias; } #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.Contains(alias); } #endregion #region HasProperty /// /// Gets a value indicating whether the content has a property identified by its alias. /// /// The content. /// The property alias. /// A value indicating whether the content has the property identified by the alias. /// The content may have a property, and that property may not have a value. public static bool HasProperty(this IPublishedContent content, string alias) { return content.ContentType.GetPropertyType(alias) != null; } #endregion #region HasValue /// /// Gets a value indicating whether the content has a value for a property identified by its alias. /// /// The content. /// The property alias. /// A value indicating whether the content has a value for the property identified by the alias. /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is true. public static bool HasValue(this IPublishedContent content, string alias) { return content.HasValue(alias, false); } /// /// Gets a value indicating whether the content has a value for a property identified by its alias. /// /// The content. /// The property alias. /// A value indicating whether to navigate the tree upwards until a property with a value is found. /// A value indicating whether the content has a value for the property identified by the alias. /// Returns true if GetProperty(alias, recurse) is not null and GetProperty(alias, recurse).HasValue is true. public static bool HasValue(this IPublishedContent content, string alias, bool recurse) { var prop = content.GetProperty(alias, recurse); return prop != null && prop.HasValue; } /// /// Returns one of two strings depending on whether the content has a value for a property identified by its alias. /// /// The content. /// The property alias. /// The value to return if the content has a value for the property. /// The value to return if the content has no value for the property. /// Either or depending on whether the content /// has a value for the property identified by the alias. public static IHtmlString HasValue(this IPublishedContent content, string alias, string valueIfTrue, string valueIfFalse = null) { return content.HasValue(alias, false) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse ?? string.Empty); } /// /// Returns one of two strings depending on whether the content has a value for a property identified by its alias. /// /// The content. /// The property alias. /// A value indicating whether to navigate the tree upwards until a property with a value is found. /// The value to return if the content has a value for the property. /// The value to return if the content has no value for the property. /// Either or depending on whether the content /// has a value for the property identified by the alias. public static IHtmlString HasValue(this IPublishedContent content, string alias, bool recurse, string valueIfTrue, string valueIfFalse = null) { return content.HasValue(alias, recurse) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse ?? string.Empty); } #endregion #region GetPropertyValue /// /// Gets the value of a content's property identified by its alias. /// /// The content. /// The property alias. /// The value of the content's property identified by the alias. /// /// 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 null. /// 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 GetPropertyValue(this IPublishedContent content, string alias) { var property = content.GetProperty(alias); return property == null ? null : property.Value; } /// /// 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 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 GetPropertyValue(this IPublishedContent content, string alias, string defaultValue) { var property = content.GetProperty(alias); return property == null || property.HasValue == false ? defaultValue : property.Value; } /// /// 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 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 GetPropertyValue(this IPublishedContent content, string alias, object defaultValue) { var property = content.GetProperty(alias); return property == null || property.HasValue == false ? defaultValue : property.Value; } /// /// Recursively gets the value of a content's property identified by its alias. /// /// The content. /// The property alias. /// A value indicating whether to recurse. /// The recursive value of the content's property identified by the alias. /// /// Recursively means: walking up the tree from , get the first value that can be found. /// 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 null. /// 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 GetPropertyValue(this IPublishedContent content, string alias, bool recurse) { var property = content.GetProperty(alias, recurse); return property == null ? null : property.Value; } /// /// Recursively the value of a content's property identified by its alias, if it exists, otherwise a default value. /// /// The content. /// The property alias. /// A value indicating whether to recurse. /// The default value. /// The value of the content's property identified by the alias, if it exists, otherwise a default value. /// /// Recursively means: walking up the tree from , get the first value that can be found. /// 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 GetPropertyValue(this IPublishedContent content, string alias, bool recurse, object defaultValue) { var property = content.GetProperty(alias, recurse); return property == null || property.HasValue == false ? defaultValue : property.Value; } #endregion #region GetPropertyValue /// /// 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 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 GetPropertyValue(this IPublishedContent content, string alias) { return content.GetPropertyValue(alias, false, false, default(T)); } /// /// Gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value. /// /// The target property type. /// The content. /// The property alias. /// The default value. /// The value of the content's property identified by the alias, converted to the specified type, 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, or if it could not be converted, 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 T GetPropertyValue(this IPublishedContent content, string alias, T defaultValue) { return content.GetPropertyValue(alias, false, true, defaultValue); } /// /// Recursively 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. /// A value indicating whether to recurse. /// The value of the content's property identified by the alias, converted to the specified type. /// /// Recursively means: walking up the tree from , get the first value that can be found. /// 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 GetPropertyValue(this IPublishedContent content, string alias, bool recurse) { return content.GetPropertyValue(alias, recurse, false, default(T)); } /// /// Recursively gets the value of a content's property identified by its alias, converted to a specified type, if it exists, otherwise a default value. /// /// The target property type. /// The content. /// The property alias. /// A value indicating whether to recurse. /// The default value. /// The value of the content's property identified by the alias, converted to the specified type, if it exists, otherwise a default value. /// /// Recursively means: walking up the tree from , get the first value that can be found. /// 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 . /// 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 GetPropertyValue(this IPublishedContent content, string alias, bool recurse, T defaultValue) { return content.GetPropertyValue(alias, recurse, true, defaultValue); } internal static T GetPropertyValue(this IPublishedContent content, string alias, bool recurse, bool withDefaultValue, T defaultValue) { var property = content.GetProperty(alias, recurse); if (property == null) return defaultValue; return property.GetValue(withDefaultValue, defaultValue); } #endregion // copied over from Core.PublishedContentExtensions - should be obsoleted [Obsolete("GetRecursiveValue() is obsolete, use GetPropertyValue().")] public static string GetRecursiveValue(this IPublishedContent content, string alias) { var value = content.GetPropertyValue(alias, true); return value == null ? string.Empty : value.ToString(); } #region Search public static IEnumerable Search(this IPublishedContent content, string term, bool useWildCards = true, string searchProvider = null) { var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; if (string.IsNullOrEmpty(searchProvider) == false) searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; var t = term.Escape().Value; if (useWildCards) t = term.MultipleCharacterWildcard().Value; var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); return content.Search(crit, searcher); } public static IEnumerable SearchDescendants(this IPublishedContent content, string term, bool useWildCards = true, string searchProvider = null) { return content.Search(term, useWildCards, searchProvider); } public static IEnumerable SearchChildren(this IPublishedContent content, string term, bool useWildCards = true, string searchProvider = null) { var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; if (string.IsNullOrEmpty(searchProvider) == false) searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; var t = term.Escape().Value; if (useWildCards) t = term.MultipleCharacterWildcard().Value; var luceneQuery = "+parentID:" + content.Id + " +" + t; var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); return content.Search(crit, searcher); } public static IEnumerable Search(this IPublishedContent content, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) { var s = Examine.ExamineManager.Instance.DefaultSearchProvider; if (searchProvider != null) s = searchProvider; var results = s.Search(criteria); return results.ConvertSearchResultToPublishedContent(UmbracoContext.Current.ContentCache); } #endregion #region ToContentSet /// /// Returns the content enumerable as a content set. /// /// The content enumerable. /// A content set wrapping the content enumerable. public static PublishedContentSet ToContentSet(this IEnumerable source) where T : class, IPublishedContent { return new PublishedContentSet(source); } /// /// Returns the ordered content enumerable as an ordered content set. /// /// The ordered content enumerable. /// A ordered content set wrapping the ordered content enumerable. public static PublishedContentOrderedSet ToContentSet(this IOrderedEnumerable source) where T : class, IPublishedContent { return new PublishedContentOrderedSet(source); } #endregion #region Dynamic Linq Extensions // todo - we should keep this file clean and remove dynamic linq stuff from it public static IQueryable OrderBy(this IEnumerable source, string predicate) { var dList = new DynamicPublishedContentList(source); return dList.OrderBy(predicate); } public static IQueryable Where(this IEnumerable list, string predicate) { // wrap in DynamicPublishedContentList so that the ContentSet is correct // though that code is somewhat ugly. var dlist = new DynamicPublishedContentList(new DynamicPublishedContentList(list) .Where(predicate)); return dlist.AsQueryable(); } public static IEnumerable> GroupBy(this IEnumerable list, string predicate) { var dList = new DynamicPublishedContentList(list); return dList.GroupBy(predicate); } public static IQueryable Select(this IEnumerable list, string predicate, params object[] values) { var dList = new DynamicPublishedContentList(list); return dList.Select(predicate); } public static HtmlString Where(this IPublishedContent content, string predicate, string valueIfTrue) { if (content == null) throw new ArgumentNullException("content"); return content.Where(predicate, valueIfTrue, string.Empty); } public static HtmlString Where(this IPublishedContent content, string predicate, string valueIfTrue, string valueIfFalse) { if (content == null) throw new ArgumentNullException("content"); return new HtmlString(content.Where(predicate) ? valueIfTrue : valueIfFalse); } public static bool Where(this IPublishedContent content, string predicate) { if (content == null) throw new ArgumentNullException("content"); var dynamicDocumentList = new DynamicPublishedContentList { content.AsDynamicOrNull() }; var filtered = dynamicDocumentList.Where(predicate); return filtered.Count() == 1; } #endregion #region AsDynamic // it is ok to have dynamic here // content should NOT be null public static dynamic AsDynamic(this IPublishedContent content) { if (content == null) throw new ArgumentNullException("content"); return new DynamicPublishedContent(content); } // content CAN be null internal static DynamicPublishedContent AsDynamicOrNull(this IPublishedContent content) { return content == null ? null : new DynamicPublishedContent(content); } #endregion #region ContentSet public static int Position(this IPublishedContent content) { return content.GetIndex(); } public static int Index(this IPublishedContent content) { return content.GetIndex(); } private static int GetIndex(this IPublishedContent content, IEnumerable set) { var index = set.FindIndex(n => n.Id == content.Id); if (index < 0) throw new IndexOutOfRangeException("Could not find content in the content set."); return index; } #endregion #region IsSomething: misc. /// /// 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 IPublishedContent content) { // note: would be better to ensure we have an IPropertyEditorValueConverter for booleans // and then treat the umbracoNaviHide property as a boolean - vs. the hard-coded "1". // rely on the property converter - will return default bool value, ie false, if property // is not defined, or has no value, else will return its value. return content.GetPropertyValue(Constants.Conventions.Content.NaviHide) == false; } /// /// 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.DocumentTypeAlias.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; if (recursive) return IsDocumentTypeRecursive(content, docTypeAlias); return false; } private static bool IsDocumentTypeRecursive(IPublishedContent content, string docTypeAlias) { var contentTypeService = UmbracoContext.Current.Application.Services.ContentTypeService; var type = contentTypeService.Get(content.DocumentTypeAlias); while (type != null && type.ParentId > 0) { type = contentTypeService.Get(type.ParentId); if (type.Alias.InvariantEquals(docTypeAlias)) return true; } return false; } public static bool IsNull(this IPublishedContent content, string alias, bool recurse) { return content.HasValue(alias, recurse) == false; } public static bool IsNull(this IPublishedContent content, string alias) { return content.HasValue(alias) == false; } #endregion #region IsSomething: position in set public static bool IsFirst(this IPublishedContent content) { return content.GetIndex() == 0; } public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue) { return content.IsFirst(valueIfTrue, string.Empty); } public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsFirst() ? valueIfTrue : valueIfFalse); } public static bool IsNotFirst(this IPublishedContent content) { return content.IsFirst() == false; } public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue) { return content.IsNotFirst(valueIfTrue, string.Empty); } public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsNotFirst() ? valueIfTrue : valueIfFalse); } public static bool IsPosition(this IPublishedContent content, int index) { return content.GetIndex() == index; } public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue) { return content.IsPosition(index, valueIfTrue, string.Empty); } public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsPosition(index) ? valueIfTrue : valueIfFalse); } public static bool IsModZero(this IPublishedContent content, int modulus) { return content.GetIndex() % modulus == 0; } public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue) { return content.IsModZero(modulus, valueIfTrue, string.Empty); } public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsModZero(modulus) ? valueIfTrue : valueIfFalse); } public static bool IsNotModZero(this IPublishedContent content, int modulus) { return content.IsModZero(modulus) == false; } public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue) { return content.IsNotModZero(modulus, valueIfTrue, string.Empty); } public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsNotModZero(modulus) ? valueIfTrue : valueIfFalse); } public static bool IsNotPosition(this IPublishedContent content, int index) { return content.IsPosition(index) == false; } public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue) { return content.IsNotPosition(index, valueIfTrue, string.Empty); } public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsNotPosition(index) ? valueIfTrue : valueIfFalse); } public static bool IsLast(this IPublishedContent content) { return content.GetIndex() == content.ContentSet.Count() - 1; } public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue) { return content.IsLast(valueIfTrue, string.Empty); } public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsLast() ? valueIfTrue : valueIfFalse); } public static bool IsNotLast(this IPublishedContent content) { return content.IsLast() == false; } public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue) { return content.IsNotLast(valueIfTrue, string.Empty); } public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsNotLast() ? valueIfTrue : valueIfFalse); } public static bool IsEven(this IPublishedContent content) { return content.GetIndex() % 2 == 0; } public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue) { return content.IsEven(valueIfTrue, string.Empty); } public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsEven() ? valueIfTrue : valueIfFalse); } public static bool IsOdd(this IPublishedContent content) { return content.GetIndex() % 2 == 1; } public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue) { return content.IsOdd(valueIfTrue, string.Empty); } public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { return new HtmlString(content.IsOdd() ? valueIfTrue : valueIfFalse); } #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 content.Ancestors().Any(x => x.Id == other.Id); } 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.AncestorsOrSelf().Any(x => x.Id == other.Id); } 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) { // avoid using Descendants(), that's expensive return other.Ancestors().Any(x => x.Id == content.Id); } 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) { // avoid using DescendantsOrSelf(), that's expensive return other.AncestorsOrSelf().Any(x => x.Id == content.Id); } 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.DocumentTypeAlias == 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.DocumentTypeAlias == 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.DocumentTypeAlias == 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.DocumentTypeAlias == 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(); } internal 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("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 /// /// /// /// /// /// 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 docTypeAlias) { return parentNodes.SelectMany(x => x.DescendantsOrSelf(docTypeAlias)); } /// /// Returns all DescendantsOrSelf of all content referenced /// /// /// /// /// 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) where T : class, IPublishedContent { return parentNodes.SelectMany(x => x.DescendantsOrSelf()); } // 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) { return content.DescendantsOrSelf(false, null); } public static IEnumerable Descendants(this IPublishedContent content, int level) { return content.DescendantsOrSelf(false, p => p.Level >= level); } public static IEnumerable Descendants(this IPublishedContent content, string contentTypeAlias) { return content.DescendantsOrSelf(false, p => p.DocumentTypeAlias == contentTypeAlias); } public static IEnumerable Descendants(this IPublishedContent content) where T : class, IPublishedContent { return content.Descendants().OfType(); } public static IEnumerable Descendants(this IPublishedContent content, int level) where T : class, IPublishedContent { return content.Descendants(level).OfType(); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content) { return content.DescendantsOrSelf(true, null); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level) { return content.DescendantsOrSelf(true, p => p.Level >= level); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string contentTypeAlias) { return content.DescendantsOrSelf(true, p => p.DocumentTypeAlias == contentTypeAlias); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content) where T : class, IPublishedContent { return content.DescendantsOrSelf().OfType(); } public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level) where T : class, IPublishedContent { return content.DescendantsOrSelf(level).OfType(); } public static IPublishedContent Descendant(this IPublishedContent content) { return content.Children.FirstOrDefault(); } public static IPublishedContent Descendant(this IPublishedContent content, int level) { return content.EnumerateDescendants(false).FirstOrDefault(x => x.Level == level); } public static IPublishedContent Descendant(this IPublishedContent content, string contentTypeAlias) { return content.EnumerateDescendants(false).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); } public static T Descendant(this IPublishedContent content) where T : class, IPublishedContent { return content.EnumerateDescendants(false).FirstOrDefault(x => x is T) as T; } public static T Descendant(this IPublishedContent content, int level) where T : class, IPublishedContent { return content.Descendant(level) as T; } public static IPublishedContent DescendantOrSelf(this IPublishedContent content) { return content; } public static IPublishedContent DescendantOrSelf(this IPublishedContent content, int level) { return content.EnumerateDescendants(true).FirstOrDefault(x => x.Level == level); } public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string contentTypeAlias) { return content.EnumerateDescendants(true).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); } public static T DescendantOrSelf(this IPublishedContent content) where T : class, IPublishedContent { return content.EnumerateDescendants(true).FirstOrDefault(x => x is T) as T; } public static T DescendantOrSelf(this IPublishedContent content, int level) where T : class, IPublishedContent { return content.DescendantOrSelf(level) as T; } internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, bool orSelf, Func func) { return content.EnumerateDescendants(orSelf).Where(x => func == null || func(x)); } internal static IEnumerable EnumerateDescendants(this IPublishedContent content, bool orSelf) { if (content == null) throw new ArgumentNullException("content"); if (orSelf) yield return content; foreach (var child in content.Children) foreach (var child2 in child.EnumerateDescendants()) yield return child2; } internal static IEnumerable EnumerateDescendants(this IPublishedContent content) { yield return content; foreach (var child in content.Children) foreach (var child2 in child.EnumerateDescendants()) yield return child2; } #endregion #region Axes: following-sibling, preceding-sibling, following, preceding + pseudo-axes up, down, next, previous // up pseudo-axe ~ ancestors // bogus, kept for backward compatibility but we should get rid of it // better use ancestors public static IPublishedContent Up(this IPublishedContent content) { return content.Parent; } public static IPublishedContent Up(this IPublishedContent content, int number) { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); return number == 0 ? content : content.EnumerateAncestors(false).Skip(number).FirstOrDefault(); } public static IPublishedContent Up(this IPublishedContent content, string contentTypeAlias) { return string.IsNullOrEmpty(contentTypeAlias) ? content.Parent : content.Ancestor(contentTypeAlias); } // down pseudo-axe ~ children (not descendants) // bogus, kept for backward compatibility but we should get rid of it // better use descendants public static IPublishedContent Down(this IPublishedContent content) { return content.Children.FirstOrDefault(); } public static IPublishedContent Down(this IPublishedContent content, int number) { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); if (number == 0) return content; content = content.Children.FirstOrDefault(); while (content != null && --number > 0) content = content.Children.FirstOrDefault(); return content; } public static IPublishedContent Down(this IPublishedContent content, string contentTypeAlias) { if (string.IsNullOrEmpty(contentTypeAlias)) return content.Children.FirstOrDefault(); // note: this is what legacy did, but with a broken Descendant // so fixing Descendant will change how it works... return content.Descendant(contentTypeAlias); } // next pseudo-axe ~ following within the content set // bogus, kept for backward compatibility but we should get rid of it public static IPublishedContent Next(this IPublishedContent content) { return content.ContentSet.ElementAtOrDefault(content.GetIndex() + 1); } public static IPublishedContent Next(this IPublishedContent current, Func func) { IPublishedContent next = current.Next(); while (next != null) { if (func(next)) return next; next = next.Next(); } return null; } public static IPublishedContent Next(this IPublishedContent content, int number) { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); return number == 0 ? content : content.ContentSet.ElementAtOrDefault(content.GetIndex() + number); } public static IPublishedContent Next(this IPublishedContent content, string contentTypeAlias) { return content.Next(contentTypeAlias, false); } public static IPublishedContent Next(this IPublishedContent content, string contentTypeAlias, bool wrap) { return content.Next(content.ContentSet, x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); } public static T Next(this IPublishedContent content) where T : class, IPublishedContent { return content.Next(false); } public static T Next(this IPublishedContent content, bool wrap) where T : class, IPublishedContent { return content.Next(content.ContentSet, x => x is T, wrap) as T; } static IPublishedContent Next(this IPublishedContent content, IEnumerable axis, Func predicate, bool wrap) { var b4 = true; IPublishedContent wrapped = null; foreach (var c in axis) { if (b4) { if (c.Id == content.Id) b4 = false; else if (wrap && wrapped == null && predicate(c)) wrapped = c; continue; } if (predicate(c)) return c; } return wrapped; } // previous pseudo-axe ~ preceding within the content set // bogus, kept for backward compatibility but we should get rid of it public static IPublishedContent Previous(this IPublishedContent content) { return content.ContentSet.ElementAtOrDefault(content.GetIndex() - 1); } public static IPublishedContent Previous(this IPublishedContent current, Func func) { IPublishedContent prev = current.Previous(); while (prev != null) { if (func(prev)) return prev; prev = prev.Previous(); } return null; } public static IPublishedContent Previous(this IPublishedContent content, int number) { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); return number == 0 ? content : content.ContentSet.ElementAtOrDefault(content.GetIndex() - number); } public static IPublishedContent Previous(this IPublishedContent content, string contentTypeAlias) { return content.Previous(contentTypeAlias, false); } public static IPublishedContent Previous(this IPublishedContent content, string contentTypeAlias, bool wrap) { return content.Next(content.ContentSet.Reverse(), x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); } public static T Previous(this IPublishedContent content) where T : class, IPublishedContent { return content.Previous(false); } public static T Previous(this IPublishedContent content, bool wrap) where T : class, IPublishedContent { return content.Next(content.ContentSet.Reverse(), x => x is T, wrap) as T; } // [Obsolete("Obsolete, use FollowingSibling or PrecedingSibling instead.")] public static IPublishedContent Sibling(this IPublishedContent content, int number) { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); number += 1; // legacy is zero-based return content.FollowingSibling(number); } // contentTypeAlias is case-insensitive [Obsolete("Obsolete, use FollowingSibling or PrecedingSibling instead.")] public static IPublishedContent Sibling(this IPublishedContent content, string contentTypeAlias) { // note: the original implementation seems to loop on all siblings // ie if it reaches the end of the set, it starts again at the beginning. // so here we wrap, although it's not consistent... but anyway those // methods should be obsoleted. return content.FollowingSibling(contentTypeAlias, true); } // following-sibling, preceding-sibling axes public static IPublishedContent FollowingSibling(this IPublishedContent content) { return content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) + 1); } public static IPublishedContent FollowingSibling(this IPublishedContent content, int number) { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); return number == 0 ? content : content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) + number); } // contentTypeAlias is case-insensitive public static IPublishedContent FollowingSibling(this IPublishedContent content, string contentTypeAlias) { return content.FollowingSibling(contentTypeAlias, false); } // contentTypeAlias is case-insensitive // note: not sure that one makes a lot of sense but it is here for backward compatibility public static IPublishedContent FollowingSibling(this IPublishedContent content, string contentTypeAlias, bool wrap) { return content.Next(content.Siblings(), x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); } public static T FollowingSibling(this IPublishedContent content) where T : class, IPublishedContent { return content.FollowingSibling(false); } public static T FollowingSibling(this IPublishedContent content, bool wrap) where T : class, IPublishedContent { return content.Next(content.Siblings(), x => x is T, wrap) as T; } public static IPublishedContent PrecedingSibling(this IPublishedContent content) { return content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) - 1); } public static IPublishedContent PrecedingSibling(this IPublishedContent content, int number) { if (number < 0) throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); return number == 0 ? content : content.Siblings().ElementAtOrDefault(content.GetIndex(content.Siblings()) - number); } // contentTypeAlias is case-insensitive public static IPublishedContent PrecedingSibling(this IPublishedContent content, string contentTypeAlias) { return content.PrecedingSibling(contentTypeAlias, false); } // contentTypeAlias is case-insensitive // note: not sure that one makes a lot of sense but it is here for backward compatibility public static IPublishedContent PrecedingSibling(this IPublishedContent content, string contentTypeAlias, bool wrap) { return content.Next(content.Siblings().Reverse(), x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias), wrap); } public static T PrecedingSibling(this IPublishedContent content) where T : class, IPublishedContent { return content.PrecedingSibling(false); } public static T PrecedingSibling(this IPublishedContent content, bool wrap) where T : class, IPublishedContent { return content.Next(content.Siblings().Reverse(), x => x is T, wrap) as T; } // following, preceding axes - NOT IMPLEMENTED // utilities public static IEnumerable Siblings(this IPublishedContent content) { // content.Parent, content.Children and cache.GetAtRoot() should be fast enough, // or cached by the content cache, so that we don't have to implement cache here. // returns the true tree siblings, even if the content is in a set // get the root docs if parent is null // note: I don't like having to refer to the "current" content cache here, but // what else? would need root content to have a special, non-null but hidden, // parent... var siblings = content.Parent == null ? content.ItemType == PublishedItemType.Media ? UmbracoContext.Current.MediaCache.GetAtRoot() : UmbracoContext.Current.ContentCache.GetAtRoot() : content.Parent.Children; // make sure we run it once return siblings.ToArray(); } public static IEnumerable Siblings(this IPublishedContent content) where T : class, IPublishedContent { return content.Siblings().OfType(); } #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("content"); return content.Parent as T; } #endregion #region Axes: children /// /// Gets the children of the content. /// /// The content. /// The children of the content. /// /// Children are sorted by their sortOrder. /// This method exists for consistency, it is the same as calling content.Children as a property. /// public static IEnumerable Children(this IPublishedContent content) { if (content == null) throw new ArgumentNullException("content"); return content.Children; } /// /// Gets the children of the content, filtered by a predicate. /// /// The content. /// The predicate. /// The children of the content, filtered by the predicate. /// /// Children are sorted by their sortOrder. /// public static IEnumerable Children(this IPublishedContent content, Func predicate) { return content.Children().Where(predicate); } /// /// Gets the children of the content, of a given content type. /// /// The content type. /// The content. /// The children of content, of the given content type. /// /// Children are sorted by their sortOrder. /// public static IEnumerable Children(this IPublishedContent content) where T : class, IPublishedContent { return content.Children().OfType(); } public static IPublishedContent FirstChild(this IPublishedContent content) { return content.Children().FirstOrDefault(); } public static IPublishedContent FirstChild(this IPublishedContent content, Func predicate) { return content.Children(predicate).FirstOrDefault(); } public static IPublishedContent FirstChild(this IPublishedContent content) where T : class, IPublishedContent { return content.Children().FirstOrDefault(); } /// /// Gets the children of the content in a DataTable. /// /// The content. /// An optional content type alias. /// The children of the content. public static DataTable ChildrenAsTable(this IPublishedContent content, string contentTypeAliasFilter = "") { return GenerateDataTable(content, contentTypeAliasFilter); } /// /// Gets the children of the content in a DataTable. /// /// The content. /// An optional content type alias. /// The children of the content. private static DataTable GenerateDataTable(IPublishedContent content, string contentTypeAliasFilter = "") { var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() ? content.Children.Any() ? content.Children.ElementAt(0) : null : content.Children.FirstOrDefault(x => x.DocumentTypeAlias == 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.DocumentTypeAlias, //pass in the callback to extract the Dictionary of all defined aliases to their names alias => GetPropertyAliasesAndNames(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.DocumentTypeAlias != contentTypeAliasFilter) continue; //skip this one, it doesn't match the filter } var standardVals = new Dictionary { { "Id", n.Id }, { "NodeName", n.Name }, { "NodeTypeAlias", n.DocumentTypeAlias }, { "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.DataValue != null select p) { // probably want the "object value" of the property here... userVals[p.PropertyTypeAlias] = p.Value; } //add the row data Core.DataTableExtensions.AddRowData(tableData, standardVals, userVals); } return tableData; } ); return dt; } #endregion #region OfTypes // the .OfType() filter is nice when there's only one type // this is to support filtering with multiple types public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) { return contents.Where(x => types.Contains(x.GetType())); } public static IEnumerable OfTypes(this IEnumerable contents, params string[] types) { types = types.Select(x => x.ToLowerInvariant()).ToArray(); return contents.Where(x => types.Contains(x.DocumentTypeAlias.ToLowerInvariant())); } public static T OfType(this IPublishedContent content) where T : class, IPublishedContent { return content as T; } #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 { return _getPropertyAliasesAndNames ?? (_getPropertyAliasesAndNames = alias => { var userFields = ContentType.GetAliasesAndNames(alias); //ensure the standard fields are there var allFields = new Dictionary() { {"Id", "Id"}, {"NodeName", "NodeName"}, {"NodeTypeAlias", "NodeTypeAlias"}, {"CreateDate", "CreateDate"}, {"UpdateDate", "UpdateDate"}, {"CreatorName", "CreatorName"}, {"WriterName", "WriterName"}, {"Url", "Url"} }; foreach (var f in userFields.Where(f => allFields.ContainsKey(f.Key) == false)) { allFields.Add(f.Key, f.Value); } return allFields; }); } set { _getPropertyAliasesAndNames = value; } } #endregion #region Culture /// /// Gets the culture that would be selected to render a specified content, /// within the context of a specified current request. /// /// The content. /// The request Uri. /// The culture that would be selected to render the content. public static CultureInfo GetCulture(this IPublishedContent content, Uri current = null) { return Models.ContentExtensions.GetCulture(UmbracoContext.Current, ApplicationContext.Current.Services.DomainService, ApplicationContext.Current.Services.LocalizationService, ApplicationContext.Current.Services.ContentService, content.Id, content.Path, current); } #endregion } }