2017-07-20 11:21:28 +02:00
using System ;
2016-06-29 15:28:40 +02:00
using System.Collections.Generic ;
using System.Linq ;
using System.Web ;
2018-07-21 09:41:07 +02:00
using Umbraco.Core ;
2018-05-02 13:38:45 +02:00
using Umbraco.Core.Composing ;
2016-06-29 15:28:40 +02:00
using Umbraco.Core.Models.PublishedContent ;
namespace Umbraco.Web
{
/// <summary>
2017-09-25 08:59:32 +02:00
/// Provides extension methods for <c>IPublishedElement</c>.
2016-06-29 15:28:40 +02:00
/// </summary>
2017-09-25 08:59:32 +02:00
public static class PublishedElementExtensions
2016-06-29 15:28:40 +02:00
{
2018-05-02 13:38:45 +02:00
// lots of debates about accessing dependencies (IPublishedValueFallback) from extension methods, ranging
// from "don't do it" i.e. if an extension method is relying on dependencies, a proper service should be
// created instead, to discussing method injection vs service locator vs other subtleties, see for example
// this post http://marisks.net/2016/12/19/dependency-injection-with-extension-methods/
//
// point is, we do NOT want a service, we DO want to write model.Value<int>("alias", "fr-FR") and hit
// fallback somehow - which pretty much rules out method injection, and basically anything but service
// locator - bah, let's face it, it works
//
// besides, for tests, Current support setting a fallback without even a container
//
private static IPublishedValueFallback PublishedValueFallback = > Current . PublishedValueFallback ;
2016-06-29 15:28:40 +02:00
#region IsComposedOf
/// <summary>
/// Gets a value indicating whether the content is of a content type composed of the given alias
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The content type alias.</param>
/// <returns>A value indicating whether the content is of a content type composed of a content type identified by the alias.</returns>
2017-09-25 08:59:32 +02:00
public static bool IsComposedOf ( this IPublishedElement content , string alias )
2016-06-29 15:28:40 +02:00
{
2019-10-15 13:25:50 +02:00
return content . ContentType . CompositionAliases . InvariantContains ( alias ) ;
2016-06-29 15:28:40 +02:00
}
#endregion
#region HasProperty
/// <summary>
/// Gets a value indicating whether the content has a property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
/// <returns>A value indicating whether the content has the property identified by the alias.</returns>
/// <remarks>The content may have a property, and that property may not have a value.</remarks>
2017-09-25 08:59:32 +02:00
public static bool HasProperty ( this IPublishedElement content , string alias )
2016-06-29 15:28:40 +02:00
{
return content . ContentType . GetPropertyType ( alias ) ! = null ;
}
#endregion
#region HasValue
/// <summary>
/// Gets a value indicating whether the content has a value for a property identified by its alias.
/// </summary>
/// <remarks>Returns true if <c>GetProperty(alias)</c> is not <c>null</c> and <c>GetProperty(alias).HasValue</c> is <c>true</c>.</remarks>
2018-04-30 21:03:43 +02:00
public static bool HasValue ( this IPublishedElement content , string alias , string culture = null , string segment = null )
2016-06-29 15:28:40 +02:00
{
var prop = content . GetProperty ( alias ) ;
2018-04-21 09:57:28 +02:00
return prop ! = null & & prop . HasValue ( culture , segment ) ;
2016-06-29 15:28:40 +02:00
}
#endregion
2016-06-30 19:35:25 +02:00
#region Value
2016-06-29 15:28:40 +02:00
/// <summary>
/// Gets the value of a content's property identified by its alias.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
2018-04-21 09:57:28 +02:00
/// <param name="culture">The variation language.</param>
2017-12-06 11:51:35 +01:00
/// <param name="segment">The variation segment.</param>
2018-07-28 08:39:02 +02:00
/// <param name="fallback">Optional fallback strategy.</param>
2018-10-03 10:31:35 +02:00
/// <param name="defaultValue">The default value.</param>
2016-06-29 15:28:40 +02:00
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, returns <paramref name="defaultValue"/>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
2018-10-03 10:31:35 +02:00
public static object Value ( this IPublishedElement content , string alias , string culture = null , string segment = null , Fallback fallback = default , object defaultValue = default )
2016-06-29 15:28:40 +02:00
{
var property = content . GetProperty ( alias ) ;
2018-10-03 10:31:35 +02:00
// if we have a property, and it has a value, return that value
2018-05-02 13:38:45 +02:00
if ( property ! = null & & property . HasValue ( culture , segment ) )
return property . GetValue ( culture , segment ) ;
2018-10-03 10:31:35 +02:00
// else let fallback try to get a value
if ( PublishedValueFallback . TryGetValue ( content , alias , culture , segment , fallback , defaultValue , out var value ) )
return value ;
2018-07-24 13:32:29 +02:00
2018-10-03 10:31:35 +02:00
// 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 ? . GetValue ( culture , segment ) ;
2016-06-29 15:28:40 +02:00
}
#endregion
2016-06-30 19:35:25 +02:00
#region Value < T >
2016-06-29 15:28:40 +02:00
/// <summary>
/// Gets the value of a content's property identified by its alias, converted to a specified type.
/// </summary>
/// <typeparam name="T">The target property type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="alias">The property alias.</param>
2018-04-21 09:57:28 +02:00
/// <param name="culture">The variation language.</param>
2017-12-06 11:51:35 +01:00
/// <param name="segment">The variation segment.</param>
2018-07-28 08:39:02 +02:00
/// <param name="fallback">Optional fallback strategy.</param>
2018-10-03 10:31:35 +02:00
/// <param name="defaultValue">The default value.</param>
2016-06-29 15:28:40 +02:00
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
/// <remarks>
/// <para>The value comes from <c>IPublishedProperty</c> field <c>Value</c> ie it is suitable for use when rendering content.</para>
/// <para>If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns <c>default(T)</c>.</para>
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
2018-10-03 10:31:35 +02:00
public static T Value < T > ( this IPublishedElement content , string alias , string culture = null , string segment = null , Fallback fallback = default , T defaultValue = default )
2016-06-29 15:28:40 +02:00
{
var property = content . GetProperty ( alias ) ;
2018-10-03 10:31:35 +02:00
// if we have a property, and it has a value, return that value
2018-05-02 13:38:45 +02:00
if ( property ! = null & & property . HasValue ( culture , segment ) )
return property . Value < T > ( culture , segment ) ;
2018-10-03 10:31:35 +02:00
// else let fallback try to get a value
if ( PublishedValueFallback . TryGetValue ( content , alias , culture , segment , fallback , defaultValue , out var value ) )
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 < T > ( culture , segment ) ;
2016-06-29 15:28:40 +02:00
}
#endregion
2019-04-24 11:53:35 +02:00
2016-06-29 15:28:40 +02:00
#region ToIndexedArray
public static IndexedArrayItem < TContent > [ ] ToIndexedArray < TContent > ( this IEnumerable < TContent > source )
2017-09-25 08:59:32 +02:00
where TContent : class , IPublishedElement
2016-06-29 15:28:40 +02:00
{
var set = source . Select ( ( content , index ) = > new IndexedArrayItem < TContent > ( content , index ) ) . ToArray ( ) ;
foreach ( var setItem in set ) setItem . TotalCount = set . Length ;
return set ;
}
#endregion
#region OfTypes
// the .OfType<T>() filter is nice when there's only one type
// this is to support filtering with multiple types
public static IEnumerable < T > OfTypes < T > ( this IEnumerable < T > contents , params string [ ] types )
2017-09-25 08:59:32 +02:00
where T : IPublishedElement
2016-06-29 15:28:40 +02:00
{
2019-10-15 13:25:50 +02:00
if ( types = = null | | types . Length = = 0 ) return Enumerable . Empty < T > ( ) ;
return contents . Where ( x = > types . InvariantContains ( x . ContentType . Alias ) ) ;
2016-06-29 15:28:40 +02:00
}
#endregion
2019-03-21 15:01:46 +01:00
#region IsSomething
/// <summary>
/// Gets a value indicating whether the content is visible.
/// </summary>
/// <param name="content">The content.</param>
/// <returns>A value indicating whether the content is visible.</returns>
/// <remarks>A content is not visible if it has an umbracoNaviHide property with a value of "1". Otherwise,
/// the content is visible.</remarks>
public static bool IsVisible ( this IPublishedElement content )
{
// 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 . Value < bool > ( Constants . Conventions . Content . NaviHide ) = = false ;
}
#endregion
2019-04-24 09:32:49 +02:00
#region MediaUrl
/// <summary>
2020-10-05 20:48:38 +02:00
/// Gets the URL for a media.
2019-04-24 09:32:49 +02:00
/// </summary>
2019-04-24 11:53:35 +02:00
/// <param name="content">The content item.</param>
/// <param name="culture">The culture (use current culture by default).</param>
2020-10-05 20:48:38 +02:00
/// <param name="mode">The URL mode (use site configuration by default).</param>
2019-04-24 11:53:35 +02:00
/// <param name="propertyAlias">The alias of the property (use 'umbracoFile' by default).</param>
2020-10-05 20:48:38 +02:00
/// <returns>The URL for the media.</returns>
2019-04-24 11:53:35 +02:00
/// <remarks>
/// <para>The value of this property is contextual. It depends on the 'current' request uri,
2020-10-05 20:48:38 +02:00
/// 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.</para>
2019-04-24 11:53:35 +02:00
/// </remarks>
public static string MediaUrl ( this IPublishedContent content , string culture = null , UrlMode mode = UrlMode . Default , string propertyAlias = Constants . Conventions . Media . File )
2019-04-24 09:32:49 +02:00
{
var umbracoContext = Composing . Current . UmbracoContext ;
if ( umbracoContext = = null )
2019-04-24 14:25:41 +02:00
throw new InvalidOperationException ( "Cannot resolve a Url when Current.UmbracoContext is null." ) ;
2019-04-24 09:32:49 +02:00
if ( umbracoContext . UrlProvider = = null )
2019-04-24 14:25:41 +02:00
throw new InvalidOperationException ( "Cannot resolve a Url when Current.UmbracoContext.UrlProvider is null." ) ;
2019-04-24 09:32:49 +02:00
2019-04-24 11:53:35 +02:00
return umbracoContext . UrlProvider . GetMediaUrl ( content , mode , culture , propertyAlias ) ;
2019-04-24 09:32:49 +02:00
}
#endregion
2016-06-29 15:28:40 +02:00
}
2017-07-20 11:21:28 +02:00
}