2012-11-12 10:50:23 +05:00
using System ;
2013-02-02 07:06:27 +06:00
using System.Collections.Concurrent ;
using System.Collections.Generic ;
2012-11-12 10:50:23 +05:00
using System.Linq ;
using System.Xml.Linq ;
using Umbraco.Core.Configuration ;
using Umbraco.Core.Dynamics ;
2013-08-29 11:59:07 +10:00
using Umbraco.Core.Models ;
2013-09-06 16:00:35 +10:00
using Umbraco.Core.Persistence ;
2012-11-12 10:50:23 +05:00
using Umbraco.Core.PropertyEditors ;
2013-02-02 07:06:27 +06:00
using Umbraco.Core.Services ;
2013-09-09 15:17:33 +10:00
using log4net.Util.TypeConverters ;
2012-11-12 10:50:23 +05:00
namespace Umbraco.Core
{
2013-02-02 07:06:27 +06:00
/// <summary>
/// Utility class for dealing with data types and value conversions
/// </summary>
/// <remarks>
/// TODO: The logic for the GetDataType + cache should probably be moved to a service, no ?
///
/// We inherit from ApplicationEventHandler so we can bind to the ContentTypeService events to ensure that our local cache
/// object gets cleared when content types change.
/// </remarks>
internal class PublishedContentHelper : ApplicationEventHandler
2012-11-12 10:50:23 +05:00
{
2013-04-19 23:47:51 +06:00
/// <summary>
/// Used to invalidate the cache from the ICacherefresher
/// </summary>
internal static void ClearPropertyTypeCache ( )
2013-02-02 07:06:27 +06:00
{
PropertyTypeCache . Clear ( ) ;
}
/// <summary>
/// This callback is used only for unit tests which enables us to return any data we want and not rely on having the data in a database
/// </summary>
2013-09-06 16:00:35 +10:00
internal static Func < string , string , string > GetDataTypeCallback = null ;
2013-02-02 07:06:27 +06:00
2013-09-06 16:00:35 +10:00
private static readonly ConcurrentDictionary < Tuple < string , string , PublishedItemType > , string > PropertyTypeCache = new ConcurrentDictionary < Tuple < string , string , PublishedItemType > , string > ( ) ;
2013-02-02 07:06:27 +06:00
/// <summary>
/// Return the GUID Id for the data type assigned to the document type with the property alias
/// </summary>
/// <param name="applicationContext"></param>
/// <param name="docTypeAlias"></param>
/// <param name="propertyAlias"></param>
2013-08-29 11:59:07 +10:00
/// <param name="itemType"></param>
2013-02-02 07:06:27 +06:00
/// <returns></returns>
2013-09-06 16:00:35 +10:00
internal static string GetPropertyEditor ( ApplicationContext applicationContext , string docTypeAlias , string propertyAlias , PublishedItemType itemType )
2013-02-02 07:06:27 +06:00
{
if ( GetDataTypeCallback ! = null )
return GetDataTypeCallback ( docTypeAlias , propertyAlias ) ;
2013-08-29 12:11:48 +10:00
var key = new Tuple < string , string , PublishedItemType > ( docTypeAlias , propertyAlias , itemType ) ;
2013-02-02 07:06:27 +06:00
return PropertyTypeCache . GetOrAdd ( key , tuple = >
{
2013-08-29 11:59:07 +10:00
IContentTypeComposition result = null ;
switch ( itemType )
{
case PublishedItemType . Content :
result = applicationContext . Services . ContentTypeService . GetContentType ( docTypeAlias ) ;
break ;
case PublishedItemType . Media :
2013-08-29 12:11:48 +10:00
result = applicationContext . Services . ContentTypeService . GetMediaType ( docTypeAlias ) ;
2013-08-29 11:59:07 +10:00
break ;
default :
throw new ArgumentOutOfRangeException ( "itemType" ) ;
}
2013-09-06 16:00:35 +10:00
if ( result = = null ) return string . Empty ;
2013-04-19 23:47:51 +06:00
//SD: we need to check for 'any' here because the collection is backed by KeyValuePair which is a struct
// and can never be null so FirstOrDefault doesn't actually work. Have told Seb and Morten about thsi
// issue.
if ( ! result . CompositionPropertyTypes . Any ( x = > x . Alias . InvariantEquals ( propertyAlias ) ) )
2013-02-23 01:50:04 +06:00
{
2013-09-06 16:00:35 +10:00
return string . Empty ;
2013-02-23 01:50:04 +06:00
}
2013-04-19 23:47:51 +06:00
var property = result . CompositionPropertyTypes . FirstOrDefault ( x = > x . Alias . InvariantEquals ( propertyAlias ) ) ;
//as per above, this will never be null but we'll keep the check here anyways.
2013-09-06 16:00:35 +10:00
if ( property = = null ) return string . Empty ;
return property . PropertyEditorAlias ;
2013-02-02 07:06:27 +06:00
} ) ;
2012-11-12 10:50:23 +05:00
}
2013-09-09 15:17:33 +10:00
/// <summary>
/// Converts the currentValue to a correctly typed value based on known registered converters, then based on known standards.
/// </summary>
/// <param name="currentValue"></param>
/// <param name="propertyDefinition"></param>
/// <returns></returns>
internal static Attempt < object > ConvertPropertyValue ( object currentValue , PublishedPropertyDefinition propertyDefinition )
2012-11-12 10:50:23 +05:00
{
if ( currentValue = = null ) return Attempt < object > . False ;
2013-09-09 15:17:33 +10:00
//First, we need to check the v7+ PropertyValueConverters
var converters = PropertyValueConvertersResolver . Current . Converters
. Where ( x = > x . AssociatedPropertyEditorAlias = = propertyDefinition . PropertyEditorAlias )
. ToArray ( ) ;
if ( converters . Any ( ) )
{
if ( converters . Count ( ) > 1 )
{
throw new NotSupportedException ( "Only one " + typeof ( PropertyValueConverter ) + " can be registered for the property editor: " + propertyDefinition . PropertyEditorAlias ) ;
}
var result = converters . Single ( ) . ConvertSourceToObject (
currentValue ,
propertyDefinition ,
false ) ;
//if it is good return it, otherwise we'll continue processing the legacy stuff below.
if ( result . Success )
{
return new Attempt < object > ( true , result . Result ) ;
}
}
2013-09-06 16:00:35 +10:00
//In order to maintain backwards compatibility here with IPropertyEditorValueConverter we need to attempt to lookup the
// legacy GUID for the current property editor. If one doesn't exist then we will abort the conversion.
2013-09-09 15:17:33 +10:00
var legacyId = LegacyPropertyEditorIdToAliasConverter . GetLegacyIdFromAlias ( propertyDefinition . PropertyEditorAlias ) ;
2013-09-06 16:00:35 +10:00
if ( legacyId . HasValue = = false )
{
return Attempt < object > . False ;
}
2012-11-12 10:50:23 +05:00
//First lets check all registered converters for this data type.
2013-09-09 15:17:33 +10:00
var legacyConverters = PropertyEditorValueConvertersResolver . Current . Converters
. Where ( x = > x . IsConverterFor ( legacyId . Value , propertyDefinition . DocumentTypeAlias , propertyDefinition . PropertyTypeAlias ) )
2012-11-12 10:50:23 +05:00
. ToArray ( ) ;
//try to convert the value with any of the converters:
2013-09-09 15:17:33 +10:00
foreach ( var converted in legacyConverters
2012-11-12 10:50:23 +05:00
. Select ( p = > p . ConvertPropertyValue ( currentValue ) )
. Where ( converted = > converted . Success ) )
{
return new Attempt < object > ( true , converted . Result ) ;
}
//if none of the converters worked, then we'll process this from what we know
var sResult = Convert . ToString ( currentValue ) . Trim ( ) ;
//this will eat csv strings, so only do it if the decimal also includes a decimal seperator (according to the current culture)
if ( sResult . Contains ( System . Globalization . CultureInfo . CurrentCulture . NumberFormat . NumberDecimalSeparator ) )
{
decimal dResult ;
if ( decimal . TryParse ( sResult , System . Globalization . NumberStyles . Number , System . Globalization . CultureInfo . CurrentCulture , out dResult ) )
{
return new Attempt < object > ( true , dResult ) ;
}
}
//process string booleans as booleans
if ( sResult . InvariantEquals ( "true" ) )
{
return new Attempt < object > ( true , true ) ;
}
if ( sResult . InvariantEquals ( "false" ) )
{
return new Attempt < object > ( true , false ) ;
}
//a really rough check to see if this may be valid xml
//TODO: This is legacy code, I'm sure there's a better and nicer way
if ( sResult . StartsWith ( "<" ) & & sResult . EndsWith ( ">" ) & & sResult . Contains ( "/" ) )
{
try
{
2013-02-05 04:29:01 +06:00
var e = XElement . Parse ( sResult , LoadOptions . None ) ;
2012-11-12 10:50:23 +05:00
//check that the document element is not one of the disallowed elements
//allows RTE to still return as html if it's valid xhtml
var documentElement = e . Name . LocalName ;
//TODO: See note against this setting, pretty sure we don't need this
2013-09-16 16:16:42 +10:00
if ( UmbracoConfiguration . Current . UmbracoSettings . Scripting . NotDynamicXmlDocumentElements . Any (
2013-09-13 18:11:20 +10:00
tag = > string . Equals ( tag . Element , documentElement , StringComparison . CurrentCultureIgnoreCase ) ) = = false )
2012-11-12 10:50:23 +05:00
{
return new Attempt < object > ( true , new DynamicXml ( e ) ) ;
}
return Attempt < object > . False ;
}
catch ( Exception )
{
return Attempt < object > . False ;
}
}
return Attempt < object > . False ;
}
}
}