From 0415a31d0e8a68507c83e9a324f47f7340d26ea8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 5 Sep 2013 17:47:13 +0200 Subject: [PATCH] PublishedContent - the big refactoring --- src/Umbraco.Core/Cache/CacheKeys.cs | 1 - .../Configuration/UmbracoSettings.cs | 8 +- src/Umbraco.Core/CoreBootManager.cs | 25 +- src/Umbraco.Core/Dynamics/DynamicNull.cs | 35 +- src/Umbraco.Core/Dynamics/DynamicXml.cs | 2 +- .../Dynamics/ExtensionMethodFinder.cs | 81 +- src/Umbraco.Core/Dynamics/PropertyResult.cs | 71 +- src/Umbraco.Core/Models/IPublishedContent.cs | 151 +- .../Models/IPublishedContentProperty.cs | 11 - src/Umbraco.Core/Models/IPublishedProperty.cs | 60 + .../IPublishedContentExtended.cs | 46 + .../IPublishedContentModelFactory.cs | 16 + .../PublishedContentExtended.cs | 151 ++ .../PublishedContentModelFactory.cs | 20 + .../PublishedContentModelFactoryResolver.cs | 45 + .../PublishedContentOrderedSet.cs | 63 + .../PublishedContent/PublishedContentSet.cs | 239 ++ .../PublishedContent/PublishedContentType.cs | 168 ++ .../PublishedContentWrapped.cs | 208 ++ .../PublishedContent/PublishedPropertyBase.cs | 31 + .../PublishedContent/PublishedPropertyType.cs | 238 ++ src/Umbraco.Core/Models/PublishedItemType.cs | 9 +- .../DatePickerPropertyEditorValueConverter.cs | 27 - .../DatePickerValueConverter.cs | 67 + .../IPropertyEditorValueConverter.cs | 39 +- .../IPropertyValueConverter.cs | 82 + .../PropertyEditors/PropertyCacheLevel.cs | 33 + .../PropertyEditors/PropertyCacheValue.cs | 29 + .../PropertyEditorValueConvertersResolver.cs | 36 +- .../PropertyValueCacheAttribute.cs | 34 + .../PropertyValueConverterBase.cs | 43 + .../PropertyValueConvertersResolver.cs | 40 + .../PropertyValueTypeAttribute.cs | 26 + .../TinyMcePropertyEditorValueConverter.cs | 26 - .../PropertyEditors/TinyMceValueConverter.cs | 50 + .../YesNoPropertyEditorValueConverter.cs | 22 - .../PropertyEditors/YesNoValueConverter.cs | 49 + .../PublishedContentExtensions.cs | 144 -- src/Umbraco.Core/PublishedContentHelper.cs | 162 -- src/Umbraco.Core/TypeExtensions.cs | 35 +- src/Umbraco.Core/Umbraco.Core.csproj | 29 +- .../CodeFirst/ContentTypeBase.cs | 4 +- .../CodeFirst/StronglyTypedMapperTest.cs | 30 +- .../CodeFirst/TestModels/Home.cs | 2 + .../CoreXml/NavigableNavigatorTests.cs | 33 +- .../ExtensionMethodFinderTests.cs | 198 ++ .../DynamicsAndReflection/ReflectionTests.cs | 73 + src/Umbraco.Tests/LibraryTests.cs | 31 +- src/Umbraco.Tests/Masterpages/dummy.txt | 1 - .../PropertyEditorValueConverterTests.cs | 64 +- .../DynamicDocumentTestsBase.cs | 699 ------ .../PublishedContent/DynamicNodeTests.cs | 98 - ...cPublishedContentCustomExtensionMethods.cs | 35 - .../DynamicPublishedContentTests.cs | 107 - .../DynamicXmlConverterTests.cs | 110 - .../PublishedContent/DynamicXmlTests.cs | 178 -- .../LegacyExamineBackedMediaTests.cs | 86 - .../PublishedContentDataTableTests.cs | 215 -- .../PublishedContentTestBase.cs | 63 - .../PublishedContent/PublishedContentTests.cs | 491 ----- .../PublishedContent/PublishedMediaTests.cs | 387 ---- .../StronglyTypedQueryTests.cs | 432 ---- .../PublishedContentCacheTests.cs | 60 +- .../PublishedMediaCacheTests.cs | 24 +- .../DynamicDocumentTestsBase.cs | 33 +- .../PublishedContentDataTableTests.cs | 128 +- .../PublishedContentMoreTests.cs | 227 ++ .../PublishedContentTestBase.cs | 28 +- .../PublishedContentTestElements.cs | 271 +++ .../PublishedContent/PublishedContentTests.cs | 105 +- .../PublishedContent/PublishedMediaTests.cs | 8 +- .../StronglyTypedModels/TypedModelBase.cs | 56 +- .../StronglyTypedQueryTests.cs | 434 ---- .../TestHelpers/BaseDatabaseFactoryTest.cs | 8 + src/Umbraco.Tests/TestHelpers/BaseWebTest.cs | 9 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 5 +- src/Umbraco.Tests/Views/dummy.txt | 1 - .../Cache/ContentTypeCacheRefresher.cs | 2 - .../ContextualPublishedCacheExtensions.cs | 6 +- src/Umbraco.Web/Dynamics/ExtensionMethods.cs | 31 +- src/Umbraco.Web/ExamineExtensions.cs | 37 +- .../Macros/PartialViewMacroController.cs | 2 +- .../Models/DynamicPublishedContent.cs | 1217 +++++----- .../Models/DynamicPublishedContentList.cs | 206 +- .../Models/IOwnerCollectionAware.cs | 12 - .../Models/PublishedContentBase.cs | 191 +- .../Models/PublishedContentTypeCaching.cs | 76 + src/Umbraco.Web/Models/XmlPublishedContent.cs | 368 ---- .../Models/XmlPublishedContentProperty.cs | 78 - ...roRenderingPropertyEditorValueConverter.cs | 41 - .../RteMacroRenderingValueConverter.cs | 55 + .../TextValueConverterHelper.cs | 18 + .../ContextualPublishedCache.cs | 9 + .../ContextualPublishedCacheOfT.cs | 2 + .../PublishedCache/PublishedCachesResolver.cs | 2 +- .../PublishedContentCache.cs | 23 +- .../XmlPublishedCache/PublishedMediaCache.cs | 89 +- .../XmlPublishedCache/UmbracoContextCache.cs | 28 + .../XmlPublishedCache/XmlPublishedContent.cs | 448 ++++ .../XmlPublishedCache/XmlPublishedProperty.cs | 67 + src/Umbraco.Web/PublishedContentExtensions.cs | 1962 ++++++++++------- .../PublishedContentPropertyExtension.cs | 61 + src/Umbraco.Web/Umbraco.Web.csproj | 18 +- src/Umbraco.Web/UmbracoContext.cs | 3 +- src/Umbraco.Web/UmbracoContextExtensions.cs | 29 + src/Umbraco.Web/UmbracoHelper.cs | 48 +- src/Umbraco.Web/WebBootManager.cs | 8 +- src/Umbraco.Web/umbraco.presentation/item.cs | 6 +- .../umbraco.presentation/library.cs | 5 +- src/Umbraco.Web/umbraco.presentation/page.cs | 2 +- .../RazorDynamicNode/DynamicBackingItem.cs | 4 +- .../RazorDynamicNode/DynamicNull.cs | 2 +- .../RazorDynamicNode/ExamineBackedMedia.cs | 6 +- .../RazorDynamicNode/PropertyResult.cs | 35 +- .../PublishedContentExtensions.cs | 16 +- 115 files changed, 6366 insertions(+), 6233 deletions(-) delete mode 100644 src/Umbraco.Core/Models/IPublishedContentProperty.cs create mode 100644 src/Umbraco.Core/Models/IPublishedProperty.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentOrderedSet.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs create mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs delete mode 100644 src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs create mode 100644 src/Umbraco.Core/PropertyEditors/DatePickerValueConverter.cs create mode 100644 src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs create mode 100644 src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs create mode 100644 src/Umbraco.Core/PropertyEditors/PropertyCacheValue.cs create mode 100644 src/Umbraco.Core/PropertyEditors/PropertyValueCacheAttribute.cs create mode 100644 src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs create mode 100644 src/Umbraco.Core/PropertyEditors/PropertyValueConvertersResolver.cs create mode 100644 src/Umbraco.Core/PropertyEditors/PropertyValueTypeAttribute.cs delete mode 100644 src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs create mode 100644 src/Umbraco.Core/PropertyEditors/TinyMceValueConverter.cs delete mode 100644 src/Umbraco.Core/PropertyEditors/YesNoPropertyEditorValueConverter.cs create mode 100644 src/Umbraco.Core/PropertyEditors/YesNoValueConverter.cs delete mode 100644 src/Umbraco.Core/PublishedContentExtensions.cs delete mode 100644 src/Umbraco.Core/PublishedContentHelper.cs create mode 100644 src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs create mode 100644 src/Umbraco.Tests/DynamicsAndReflection/ReflectionTests.cs delete mode 100644 src/Umbraco.Tests/Masterpages/dummy.txt delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicDocumentTestsBase.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicNodeTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentCustomExtensionMethods.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlConverterTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/LegacyExamineBackedMediaTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentDataTableTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTestBase.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedMediaTests.cs delete mode 100644 src/Umbraco.Tests/PublishedCache/PublishedContent/StronglyTypedQueryTests.cs create mode 100644 src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs create mode 100644 src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs delete mode 100644 src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs delete mode 100644 src/Umbraco.Tests/Views/dummy.txt delete mode 100644 src/Umbraco.Web/Models/IOwnerCollectionAware.cs create mode 100644 src/Umbraco.Web/Models/PublishedContentTypeCaching.cs delete mode 100644 src/Umbraco.Web/Models/XmlPublishedContent.cs delete mode 100644 src/Umbraco.Web/Models/XmlPublishedContentProperty.cs delete mode 100644 src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs create mode 100644 src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs create mode 100644 src/Umbraco.Web/PropertyEditors/TextValueConverterHelper.cs create mode 100644 src/Umbraco.Web/PublishedCache/XmlPublishedCache/UmbracoContextCache.cs create mode 100644 src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs create mode 100644 src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs create mode 100644 src/Umbraco.Web/PublishedContentPropertyExtension.cs create mode 100644 src/Umbraco.Web/UmbracoContextExtensions.cs diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 54f9ab4832..fc17bd7245 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -47,6 +47,5 @@ namespace Umbraco.Core.Cache public const string StylesheetPropertyCacheKey = "UmbracoStylesheetProperty"; public const string DataTypeCacheKey = "UmbracoDataTypeDefinition"; - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings.cs b/src/Umbraco.Core/Configuration/UmbracoSettings.cs index 22b5e4e3b6..6b53540238 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings.cs @@ -502,8 +502,12 @@ namespace Umbraco.Core.Configuration } } - //TODO: I"m not sure why we need this, need to ask Gareth what the deal is, pretty sure we can remove it or change it, seems like - // massive overkill. + // we have that one because we auto-discover when a property is "xml" and should be returned by Razor as + // dynamic xml. But, stuff such as "

hello

" can be parsed into xml and would be returned as xml, + // unless

has been defined in the exclusion list... this is dirty and not-efficient, we should at least + // cache the list somewhere! + // + // TODO get rid of that whole dynamic xml mess ///

/// razor DynamicNode typecasting detects XML and returns DynamicXml - Root elements that won't convert to DynamicXml diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 57d31d7e00..76045b011e 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -125,8 +125,9 @@ namespace Umbraco.Core { CanResolveBeforeFrozen = true }; - //add custom types here that are internal - ApplicationEventsResolver.Current.AddType(); + + // add custom types here that are internal, if needed + //ApplicationEventsResolver.Current.AddType<>(); } /// @@ -254,15 +255,21 @@ namespace Umbraco.Core //the database migration objects MigrationResolver.Current = new MigrationResolver( () => PluginManager.Current.ResolveMigrationTypes()); - - - PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( + // fixme - remove that one eventually + PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( PluginManager.Current.ResolvePropertyEditorValueConverters()); - //add the internal ones, these are not public currently so need to add them manually - PropertyEditorValueConvertersResolver.Current.AddType(); - PropertyEditorValueConvertersResolver.Current.AddType(); - PropertyEditorValueConvertersResolver.Current.AddType(); + + // initialize the new property value converters + // fixme - discuss: explicit registration vs. discovery? + PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( + PluginManager.Current.ResolveTypes()); + + // add the internal ones + // fixme - should be public not internal? + PropertyValueConvertersResolver.Current.AddType(); + PropertyValueConvertersResolver.Current.AddType(); + PropertyValueConvertersResolver.Current.AddType(); // this is how we'd switch over to DefaultShortStringHelper _and_ still use // UmbracoSettings UrlReplaceCharacters... diff --git a/src/Umbraco.Core/Dynamics/DynamicNull.cs b/src/Umbraco.Core/Dynamics/DynamicNull.cs index 0788e68923..473df3d203 100644 --- a/src/Umbraco.Core/Dynamics/DynamicNull.cs +++ b/src/Umbraco.Core/Dynamics/DynamicNull.cs @@ -11,78 +11,96 @@ namespace Umbraco.Core.Dynamics //Because it's IEnumerable, if the user is actually trying @Model.TextPages or similar //it will still return an enumerable object (assuming the call actually failed because there were no children of that type) //but in .Where, if they use a property that doesn't exist, the lambda will bypass this and return false + + // returned when TryGetMember fails on a DynamicPublishedContent + // + // so if user does @CurrentPage.TextPages it will get something that is enumerable (but empty) + // fixme - not sure I understand the stuff about .Where, though + public class DynamicNull : DynamicObject, IEnumerable, IHtmlString { + public static readonly DynamicNull Null = new DynamicNull(); + + private DynamicNull() {} + public IEnumerator GetEnumerator() { return (new List()).GetEnumerator(); } + public DynamicNull Where(string predicate, params object[] values) { return this; } + public DynamicNull OrderBy(string orderBy) { return this; } + public int Count() { return 0; } + public override string ToString() { return string.Empty; } + public override bool TryGetMember(GetMemberBinder binder, out object result) { result = this; return true; } + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { result = this; return true; } + public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) { result = this; return true; } + public bool IsNull() { return true; } + public bool HasValue() { return false; } + public string Name { - get - { - return string.Empty; - } + get { return string.Empty; } } + public int Id { - get - { - return 0; - } + get { return 0; } } public static implicit operator bool(DynamicNull n) { return false; } + public static implicit operator DateTime(DynamicNull n) { return DateTime.MinValue; } + public static implicit operator int(DynamicNull n) { return 0; } + public static implicit operator string(DynamicNull n) { return string.Empty; @@ -92,6 +110,5 @@ namespace Umbraco.Core.Dynamics { return string.Empty; } - } } diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs index a84389d2e5..7a4d714fbe 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -203,7 +203,7 @@ namespace Umbraco.Core.Dynamics if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod && attempt.Exception != null && attempt.Exception is TargetInvocationException) { - result = new DynamicNull(); + result = DynamicNull.Null; return true; } diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs index 2fe86ddeec..215d94e566 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethodFinder.cs @@ -13,7 +13,86 @@ namespace Umbraco.Core.Dynamics /// Utility class for finding extension methods on a type to execute /// internal static class ExtensionMethodFinder - { + { + private static readonly MethodInfo[] AllExtensionMethods; + + static ExtensionMethodFinder() + { + AllExtensionMethods = TypeFinder.GetAssembliesWithKnownExclusions() + // assemblies that contain extension methods + .Where(a => a.IsDefined(typeof(ExtensionAttribute), false)) + // types that contain extension methods + .SelectMany(a => a.GetTypes() + .Where(t => t.IsDefined(typeof(ExtensionAttribute), false) && t.IsSealed && t.IsGenericType == false && t.IsNested == false)) + // actual extension methods + .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public) + .Where(m => m.IsDefined(typeof(ExtensionAttribute), false))) + // and also IEnumerable extension methods - because the assembly is excluded + .Concat(typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)) + .ToArray(); + } + + // get all extension methods for type thisType, with name name, + // accepting argsCount arguments (not counting the instance of thisType). + private static IEnumerable GetExtensionMethods(Type thisType, string name, int argsCount) + { + var key = string.Format("{0}.{1}::{2}", thisType.FullName, name, argsCount); + + var types = thisType.GetBaseTypes(true); // either do this OR have MatchFirstParameter handle the stuff... FIXME? + + var methods = AllExtensionMethods + .Where(m => m.Name == name) + .Where(m => m.GetParameters().Length == argsCount) + .Where(m => MatchFirstParameter(thisType, m.GetParameters()[0].ParameterType)); + + // fixme - is this what we should cache? + return methods; + } + + // find out whether the first parameter is a match for thisType + private static bool MatchFirstParameter(Type thisType, Type firstParameterType) + { + return MethodArgZeroHasCorrectTargetType(null, firstParameterType, thisType); + } + + // get the single extension method for type thisType, with name name, + // that accepts the arguments in args (which does not contain the instance of thisType). + public static MethodInfo GetExtensionMethod(Type thisType, string name, object[] args) + { + MethodInfo result = null; + foreach (var method in GetExtensionMethods(thisType, name, args.Length).Where(m => MatchParameters(m, args))) + { + if (result == null) + result = method; + else + throw new AmbiguousMatchException("More than one matching extension method was found."); + } + return result; + } + + // find out whether the method can accept the arguments + private static bool MatchParameters(MethodInfo method, IList args) + { + var parameters = method.GetParameters(); + + var i = 0; + for (; i < parameters.Length; ++i) + { + if (MatchParameter(parameters[i].ParameterType, args[i].GetType()) == false) + break; + } + return (i == parameters.Length); + } + + internal static bool MatchParameter(Type parameterType, Type argumentType) + { + // public static int DoSomething(Foo foo, T t1, T t2) + // DoSomething(foo, t1, t2) => how can we match?! + return parameterType == argumentType; // fixme of course! + } + + // BELOW IS THE ORIGINAL CODE... + /// /// Returns all extension methods found matching the definition /// diff --git a/src/Umbraco.Core/Dynamics/PropertyResult.cs b/src/Umbraco.Core/Dynamics/PropertyResult.cs index bb2165d184..26bc475b6c 100644 --- a/src/Umbraco.Core/Dynamics/PropertyResult.cs +++ b/src/Umbraco.Core/Dynamics/PropertyResult.cs @@ -1,65 +1,50 @@ using System; using Umbraco.Core.Models; -using umbraco.interfaces; using System.Web; namespace Umbraco.Core.Dynamics { - internal class PropertyResult : IPublishedContentProperty, IHtmlString - { - internal PropertyResult(IPublishedContentProperty source, PropertyResultType type) + internal class PropertyResult : IPublishedProperty, IHtmlString + { + private readonly IPublishedProperty _source; + private readonly string _alias; + private readonly object _value; + private readonly PropertyResultType _type; + + internal PropertyResult(IPublishedProperty source, PropertyResultType type) { if (source == null) throw new ArgumentNullException("source"); - - Alias = source.Alias; - Value = source.Value; - Version = source.Version; - PropertyType = type; + + _type = type; + _source = source; } - internal PropertyResult(string alias, object value, Guid version, PropertyResultType type) + + internal PropertyResult(string alias, object value, Guid version, PropertyResultType type) { if (alias == null) throw new ArgumentNullException("alias"); if (value == null) throw new ArgumentNullException("value"); - Alias = alias; - Value = value; - Version = version; - PropertyType = type; + _type = type; + _alias = alias; + _value = value; } - internal PropertyResultType PropertyType { get; private set; } - - public string Alias { get; private set; } + internal PropertyResultType PropertyType { get { return _type; } } - public object Value { get; private set; } - - /// - /// Returns the value as a string output, this is used in the final rendering process of a property - /// - internal string ValueAsString - { - get - { - return Value == null ? "" : Convert.ToString(Value); - } - } - - public Guid Version { get; private set; } - - - /// - /// The Id of the document for which this property belongs to - /// - public int DocumentId { get; set; } - - /// - /// The alias of the document type alias for which this property belongs to - /// - public string DocumentTypeAlias { get; set; } + public string Alias { get { return _source == null ? _alias : _source.Alias; } } + public object RawValue { get { return _source == null ? _value : _source.RawValue; } } + public bool HasValue { get { return _source == null || _source.HasValue; } } + public object Value { get { return _source == null ? _value : _source.Value; } } + public object XPathValue { get { return Value == null ? null : Value.ToString(); } } + // implements IHtmlString.ToHtmlString public string ToHtmlString() { - return ValueAsString; + // note - use RawValue here, because that's what the original + // Razor macro engine seems to do... + + var value = RawValue; + return value == null ? string.Empty : value.ToString(); } } } diff --git a/src/Umbraco.Core/Models/IPublishedContent.cs b/src/Umbraco.Core/Models/IPublishedContent.cs index 6f55bdb413..5c93b32c16 100644 --- a/src/Umbraco.Core/Models/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/IPublishedContent.cs @@ -1,20 +1,48 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.Models { /// - /// Defines a published item in Umbraco + /// Represents a cached content. /// /// - /// A replacement for INode which needs to occur since INode doesn't contain the document type alias - /// and INode is poorly formatted with mutable properties (i.e. Lists instead of IEnumerable) + /// SD: A replacement for INode which needs to occur since INode doesn't contain the document type alias + /// and INode is poorly formatted with mutable properties (i.e. Lists instead of IEnumerable). + /// Stephan: initially, that was for cached published content only. Now, we're using it also for + /// cached preview (so, maybe unpublished) content. A better name would therefore be ICachedContent, as + /// has been suggested. However, can't change now. Maybe in v7? /// - public interface IPublishedContent - { - int Id { get; } + public interface IPublishedContent + { + #region ContentSet + + // Because of http://issues.umbraco.org/issue/U4-1797 and in order to implement + // Index() and methods that derive from it such as IsFirst(), IsLast(), etc... all + // content items must know about their containing content set. + + /// + /// Gets the content set to which the content belongs. + /// + /// The default set consists in the siblings of the content (including the content + /// itself) ordered by sortOrder. + IEnumerable ContentSet { get; } + + #endregion + + #region ContentType + + /// + /// Gets the content type. + /// + PublishedContentType ContentType { get; } + + #endregion + + #region Content + + int Id { get; } int TemplateId { get; } int SortOrder { get; } string Name { get; } @@ -31,32 +59,97 @@ namespace Umbraco.Core.Models Guid Version { get; } int Level { get; } string Url { get; } + + /// + /// Gets a value indicating whether the content is a content (aka a document) or a media. + /// PublishedItemType ItemType { get; } + + /// + /// Gets a value indicating whether the content is draft. + /// + /// A content is draft when it is the unpublished version of a content, which may + /// have a published version, or not. + bool IsDraft { get; } + + /// + /// Gets the index of the published content within its current owning content set. + /// + /// The index of the published content within its current owning content set. + int GetIndex(); + + #endregion + + #region Tree + + /// + /// Gets the parent of the content. + /// + /// The parent of root content is null. IPublishedContent Parent { get; } + + /// + /// Gets the children of the content. + /// + /// Children are sorted by their sortOrder. IEnumerable Children { get; } - ICollection Properties { get; } + #endregion - /// - /// Returns the property value for the property alias specified - /// - /// - /// - object this[string propertyAlias] { get; } + #region Properties - /// - /// Returns a property on the object based on an alias + /// + /// Gets the properties of the content. + /// + /// + /// Contains one IPublishedProperty for each property defined for the content type, including + /// inherited properties. Some properties may have no value. + /// The properties collection of an IPublishedContent instance should be read-only ie it is illegal + /// to add properties to the collection. + /// + ICollection Properties { get; } + + /// + /// Gets a property identified by its alias. + /// + /// The property alias. + /// The property identified by the alias. + /// + /// If the content type has no property with that alias, including inherited properties, returns null, + /// otherwise return a property -- that may have no value (ie HasValue is false). + /// The alias is case-insensitive. + /// + IPublishedProperty GetProperty(string alias); + + /// + /// Gets a property identified by its alias. + /// + /// The property alias. + /// A value indicating whether to navigate the tree upwards until a property with a value is found. + /// The property identified by the alias. + /// + /// Navigate the tree upwards and look for a property with that alias and with a value (ie HasValue is true). + /// If found, return the property. If no property with that alias is found, having a value or not, return null. Otherwise + /// return the first property that was found with the alias but had no value (ie HasValue is false). + /// The alias is case-insensitive. + /// + IPublishedProperty GetProperty(string alias, bool recurse); + + /// + /// Gets the value of a property identified by its alias. /// - /// - /// - /// - /// Although we do have a a property to return Properties of the object, in some cases a custom implementation may not know - /// about all properties until specifically asked for one by alias. - /// - /// This method is mostly used in places such as DynamicPublishedContent when trying to resolve a property based on an alias. - /// In some cases Pulish Stores, a property value may exist in multiple places and we need to fallback to different cached locations - /// therefore sometimes the 'Properties' collection may not be sufficient. - /// - IPublishedContentProperty GetProperty(string alias); - } + /// The property alias. + /// The value of the property identified by the alias. + /// + /// If GetProperty(alias) is null then returns null else return GetProperty(alias).Value. + /// So if the property has no value, returns the default value for that property type. + /// This one is defined here really because we cannot define index extension methods, but all it should do is: + /// var p = GetProperty(alias); return p == null ? null : p.Value; and nothing else. + /// The recursive syntax (eg "_title") is _not_ supported here. + /// The alias is case-insensitive. + /// + object this[string alias] { get; } // fixme - kill in v7 + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IPublishedContentProperty.cs b/src/Umbraco.Core/Models/IPublishedContentProperty.cs deleted file mode 100644 index 6361c29683..0000000000 --- a/src/Umbraco.Core/Models/IPublishedContentProperty.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Umbraco.Core.Models -{ - public interface IPublishedContentProperty - { - string Alias { get; } - object Value { get; } - Guid Version { get; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/IPublishedProperty.cs b/src/Umbraco.Core/Models/IPublishedProperty.cs new file mode 100644 index 0000000000..1b5813874f --- /dev/null +++ b/src/Umbraco.Core/Models/IPublishedProperty.cs @@ -0,0 +1,60 @@ +using System; + +namespace Umbraco.Core.Models +{ + /// + /// Represents a property of an IPublishedContent. + /// + public interface IPublishedProperty + { + /// + /// Gets the alias of the property. + /// + string Alias { get; } + + /// + /// Gets a value indicating whether the property has a value. + /// + /// + /// This is somewhat implementation-dependent -- depending on whatever IPublishedCache considers + /// a missing value. + /// The XmlPublishedCache raw values are strings, and it will consider missing, null or empty (and + /// that includes whitespace-only) strings as "no value". + /// Other caches that get their raw value from the database would consider that a property has "no + /// value" if it is missing, null, or an empty string (including whitespace-only). + /// + bool HasValue { get; } + + /// + /// Gets the raw value of the property. + /// + /// + /// The raw value is whatever was passed to the property when it was instanciated, and it is + /// somewhat implementation-dependent -- depending on how the IPublishedCache is implemented. + /// The XmlPublishedCache raw values are strings exclusively since they come from the Xml cache. + /// For other cachesthat get their raw value from the database, it would be either a string, + /// an integer (Int32), or a date and time (DateTime). + /// + object RawValue { get; } + + /// + /// Gets the value of the property. + /// + /// + /// The value is what you want to use when rendering content in an MVC view ie in C#. + /// It can be null, or any type of CLR object. + /// It has been fully prepared and processed by the appropriate converters. + /// + object Value { get; } + + /// + /// Gets the XPath value of the property. + /// + /// + /// The XPath value is what you want to use when navigating content via XPath eg in the XSLT engine. + /// It must be either null, or a non-empty string, or an XPathNavigator. + /// It has been fully prepared and processed by the appropriate converters. + /// + object XPathValue { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs new file mode 100644 index 0000000000..1a05c2e07a --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentExtended.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides methods to handle extended content. + /// + internal interface IPublishedContentExtended : IPublishedContent + { + /// + /// Adds a property to the extended content. + /// + /// The property to add. + void AddProperty(IPublishedProperty property); + + /// + /// Gets a value indicating whether properties were added to the extended content. + /// + bool HasAddedProperties { get; } + + /// + /// Sets the content set of the extended content. + /// + /// + void SetContentSet(IEnumerable contentSet); + + /// + /// Resets the content set of the extended content. + /// + void ClearContentSet(); + + /// + /// Sets the index of the extended content. + /// + /// The index value. + void SetIndex(int value); + + /// + /// Resets the index of the extended content. + /// + void ClearIndex(); + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs new file mode 100644 index 0000000000..cd58a43b54 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentModelFactory.cs @@ -0,0 +1,16 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides the model creation service. + /// + internal interface IPublishedContentModelFactory + { + /// + /// Creates a strongly-typed model representing a published content. + /// + /// The original published content. + /// The strongly-typed model representing the published content, or the published content + /// itself it the factory has no model for that content type. + IPublishedContent CreateModel(IPublishedContent content); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs new file mode 100644 index 0000000000..eee3a194dd --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtended.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Umbraco.Core.Models.PublishedContent +{ + + public class PublishedContentExtended : PublishedContentWrapped, IPublishedContentExtended + { + #region Constructor + + // protected for models, private for our static Extend method + protected PublishedContentExtended(IPublishedContent content) + : base(content) + { } + + #endregion + + #region Index + + private int? _index; + + public override int GetIndex() + { + // fast + if (_index.HasValue) return _index.Value; + + // slow -- and don't cache, not in a set + if (_contentSet == null) return Content.GetIndex(); + + // slow -- but cache for next time + var index = _contentSet.FindIndex(x => x.Id == Id); + if (index < 0) + throw new IndexOutOfRangeException("Could not find content in the content set."); + _index = index; + return index; + } + + #endregion + + #region Extend + + internal static IPublishedContentExtended Extend(IPublishedContent content, IEnumerable contentSet) + { + var wrapped = content as PublishedContentExtended; + while (wrapped != null && ((IPublishedContentExtended)wrapped).HasAddedProperties == false) + wrapped = (content = wrapped.Unwrap()) as PublishedContentExtended; + + // if the factory returns something else than content it means it has created + // a model, and then that model has to inherit from PublishedContentExtended, + // => implements the internal IPublishedContentExtended. + + var model = PublishedContentModelFactory.CreateModel(content); + var extended = model == content // == means the factory did not create a model + ? new PublishedContentExtended(content) // so we have to extend + : model; // else we can use what the factory returned + + var extended2 = extended as IPublishedContentExtended; + if (extended2 != null) // always true, but keeps Resharper happy + extended2.SetContentSet(contentSet); + return extended2; + } + + #endregion + + #region IPublishedContentExtended + + void IPublishedContentExtended.AddProperty(IPublishedProperty property) + { + if (_properties == null) + _properties = new Collection(); + _properties.Add(property); + } + + bool IPublishedContentExtended.HasAddedProperties + { + get { return _properties != null; } + } + + void IPublishedContentExtended.SetContentSet(IEnumerable contentSet) + { + _contentSet = contentSet; + } + + void IPublishedContentExtended.ClearContentSet() + { + _contentSet = null; + } + + void IPublishedContentExtended.SetIndex(int value) + { + _index = value; + } + + void IPublishedContentExtended.ClearIndex() + { + _index = null; + } + + #endregion + + #region Content set + + private IEnumerable _contentSet; + + public override IEnumerable ContentSet + { + get { return _contentSet ?? Content.ContentSet; } + } + + #endregion + + #region Properties + + private ICollection _properties; + + public override ICollection Properties + { + get + { + return _properties == null + ? Content.Properties + : Content.Properties.Union(_properties).ToList(); + } + } + + public override object this[string alias] + { + get + { + if (_properties != null) + { + var property = _properties.FirstOrDefault(prop => prop.Alias.InvariantEquals(alias)); + if (property != null) return property.HasValue ? property.Value : null; + } + return Content[alias]; + } + } + + public override IPublishedProperty GetProperty(string alias) + { + return _properties == null + ? Content.GetProperty(alias) + : _properties.FirstOrDefault(prop => prop.Alias.InvariantEquals(alias)) ?? Content.GetProperty(alias); + } + + #endregion + } + +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs new file mode 100644 index 0000000000..ce63a640e6 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactory.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides strongly typed published content models services. + /// + internal static class PublishedContentModelFactory + { + /// + /// Creates a strongly typed published content model for an internal published content. + /// + /// The internal published content. + /// The strongly typed published content model. + public static IPublishedContent CreateModel(IPublishedContent content) + { + return PublishedContentModelFactoryResolver.Current.HasValue + ? PublishedContentModelFactoryResolver.Current.Factory.CreateModel(content) + : content; + } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs new file mode 100644 index 0000000000..6995cafc7f --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelFactoryResolver.cs @@ -0,0 +1,45 @@ +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Resolves the IPublishedContentModelFactory object. + /// + internal class PublishedContentModelFactoryResolver : SingleObjectResolverBase + { + /// + /// Initializes a new instance of the . + /// + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal PublishedContentModelFactoryResolver() + : base() + { } + + /// + /// Initializes a new instance of the with a factory. + /// + /// The factory. + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal PublishedContentModelFactoryResolver(IPublishedContentModelFactory factory) + : base(factory) + { } + + /// + /// Sets the factory. + /// + /// The factory. + /// For developers, at application startup. + public void SetFactory(IPublishedContentModelFactory factory) + { + Value = factory; + } + + /// + /// Gets the factory. + /// + public IPublishedContentModelFactory Factory + { + get { return Value; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentOrderedSet.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentOrderedSet.cs new file mode 100644 index 0000000000..4770051649 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentOrderedSet.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents an ordered set of . + /// + /// The type of content. + public class PublishedContentOrderedSet : PublishedContentSet, IOrderedEnumerable + where T : class, IPublishedContent + { +// ReSharper disable ParameterTypeCanBeEnumerable.Local + internal PublishedContentOrderedSet(IOrderedEnumerable content) +// ReSharper restore ParameterTypeCanBeEnumerable.Local + : base(content) + { } + + #region IOrderedEnumerable + + public IOrderedEnumerable CreateOrderedEnumerable(Func keySelector, IComparer comparer, bool descending) + { + return new PublishedContentOrderedSet(((IOrderedEnumerable)Source).CreateOrderedEnumerable(keySelector, comparer, descending)); + } + + #endregion + + // fixme wtf?! +#if IMPLEMENT_LINQ_EXTENSIONS + + // BEWARE! + // here, Source.Whatever() will invoke the System.Linq.Enumerable extension method + // and not the extension methods that we may have defined on IEnumerable or + // IOrderedEnumerable, provided that they are NOT within the scope at compile time. + + #region Wrap methods returning IOrderedEnumerable + + public PublishedContentOrderedSet ThenBy(Func keySelector) + { + return new PublishedContentOrderedSet(((IOrderedEnumerable)Source).ThenBy(keySelector)); + } + + public PublishedContentOrderedSet ThenBy(Func keySelector, IComparer comparer) + { + return new PublishedContentOrderedSet(((IOrderedEnumerable)Source).ThenBy(keySelector, comparer)); + } + + public PublishedContentOrderedSet ThenByDescending(Func keySelector) + { + return new PublishedContentOrderedSet(((IOrderedEnumerable)Source).ThenByDescending(keySelector)); + } + + public PublishedContentOrderedSet ThenByDescending(Func keySelector, IComparer comparer) + { + return new PublishedContentOrderedSet(((IOrderedEnumerable)Source).ThenByDescending(keySelector, comparer)); + } + + #endregion + +#endif + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs new file mode 100644 index 0000000000..94f54d03aa --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents a set of . + /// + /// The type of content. + /// + /// A ContentSet{T} is created from an IEnumerable{T} using the ToContentSet + /// extension method. + /// The content set source is enumerated only once. Same as what you get + /// when you call ToList on an IEnumerable. Only, ToList enumerates its source when + /// created, whereas a content set enumerates its source only when the content set itself + /// is enumerated. + /// + public class PublishedContentSet : IEnumerable + where T : class, IPublishedContent + { + // used by ToContentSet extension method to initialize a new set from an IEnumerable. + internal PublishedContentSet(IEnumerable source) + { + if (source == null) + throw new ArgumentNullException("source"); + Source = source; + } + + #region Source + + protected readonly IEnumerable Source; + + #endregion + + #region Enumerated + + // cache the enumeration so we don't enumerate more than once. Same as what you get + // when you call ToList on an IEnumerable. Only, ToList enumerates its source when + // created, whereas a content set enumerates its source only when the content set itself + // is enumerated. + + // cache the wrapped items so if we reset the enumeration, we do not re-wrap everything (only new items). + + private T[] _enumerated; + private readonly Dictionary _xContent = new Dictionary(); + + // wrap an item, ie create the actual clone for this set + private T MapContentAsT(T t) + { + // fixme - cleanup + return MapContent(t) /*.Content*/ as T; + } + + // fixme - cleanup + internal IPublishedContentExtended /*Handle*/ MapContent(T t) + { + IPublishedContentExtended extend; + if (_xContent.TryGetValue(t, out extend) == false) + { + // fixme - cleanup + extend = PublishedContentExtended.Extend(t, this); + //extend = t.Extend(this); + var asT = extend as T; + //var asT = extend.Content as T; + if (asT == null) + throw new InvalidOperationException(string.Format("Failed extend a published content of type {0}." + + "Got {1} when expecting {2}.", t.GetType().FullName, extend /*.Content*/ .GetType().FullName, typeof(T).FullName)); + _xContent[t] = extend; + } + return extend; + } + + private T[] Enumerated + { + get + { + // enumerate the source and cache the result + // tell clones about their index within the set (for perfs purposes) + var index = 0; + return _enumerated ?? (_enumerated = Source.Select(t => + { + var extend = MapContent(t); + extend.SetIndex(index++); + return extend /*.Content*/ as T; // fixme - cleanup + }).ToArray()); + } + } + + // indicates that the source has changed + // so the set can clear its inner caches + public void SourceChanged() + { + // reset the cached enumeration so it's enumerated again + if (_enumerated == null) return; + _enumerated = null; + + foreach (var item in _xContent.Values) + item.ClearIndex(); + + var removed = _xContent.Keys.Except(Source); + foreach (var content in removed) + { + _xContent[content].ClearContentSet(); + _xContent.Remove(content); + } + } + + /// + /// Gets the number of items in the set. + /// + /// The number of items in the set. + /// Will cause the set to be enumerated if it hasn't been already. + public virtual int Count + { + get { return Enumerated.Length; } + } + #endregion + + #region IEnumerable + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Enumerated).GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Wrap methods returning T + + public T ElementAt(int index) + { + return MapContentAsT(Source.ElementAt(index)); + } + + public T ElementAtOrDefault(int index) + { + var element = Source.ElementAtOrDefault(index); + return element == null ? null : MapContentAsT(element); + } + + public T First() + { + return MapContentAsT(Source.First()); + } + + public T First(Func predicate) + { + return MapContentAsT(Source.First(predicate)); + } + + public T FirstOrDefault() + { + var first = Source.FirstOrDefault(); + return first == null ? null : MapContentAsT(first); + } + + public T FirstOrDefault(Func predicate) + { + var first = Source.FirstOrDefault(predicate); + return first == null ? null : MapContentAsT(first); + } + + public T Last() + { + return MapContentAsT(Source.Last()); + } + + public T Last(Func predicate) + { + return MapContentAsT(Source.Last(predicate)); + } + + public T LastOrDefault() + { + var last = Source.LastOrDefault(); + return last == null ? null : MapContentAsT(last); + } + + public T LastOrDefault(Func predicate) + { + var last = Source.LastOrDefault(predicate); + return last == null ? null : MapContentAsT(last); + } + + public T Single() + { + return MapContentAsT(Source.Single()); + } + + public T Single(Func predicate) + { + return MapContentAsT(Source.Single(predicate)); + } + + public T SingleOrDefault() + { + var single = Source.SingleOrDefault(); + return single == null ? null : MapContentAsT(single); + } + + public T SingleOrDefault(Func predicate) + { + var single = Source.SingleOrDefault(predicate); + return single == null ? null : MapContentAsT(single); + } + + #endregion + + #region Wrap methods returning IOrderedEnumerable + + public PublishedContentOrderedSet OrderBy(Func keySelector) + { + return new PublishedContentOrderedSet(Source.OrderBy(keySelector)); + } + + public PublishedContentOrderedSet OrderBy(Func keySelector, IComparer comparer) + { + return new PublishedContentOrderedSet(Source.OrderBy(keySelector, comparer)); + } + + public PublishedContentOrderedSet OrderByDescending(Func keySelector) + { + return new PublishedContentOrderedSet(Source.OrderByDescending(keySelector)); + } + + public PublishedContentOrderedSet OrderByDescending(Func keySelector, IComparer comparer) + { + return new PublishedContentOrderedSet(Source.OrderByDescending(keySelector, comparer)); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs new file mode 100644 index 0000000000..779fc267e9 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Web.Caching; +using Umbraco.Core.Cache; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents an type. + /// + /// Instances of the class are immutable, ie + /// if the content type changes, then a new class needs to be created. + public class PublishedContentType + { + private readonly PublishedPropertyType[] _propertyTypes; + + // fast alias-to-index xref containing both the raw alias and its lowercase version + private readonly Dictionary _indexes = new Dictionary(); + + // internal so it can be used by PublishedNoCache which does _not_ want to cache anything and so will never + // use the static cache getter PublishedContentType.GetPublishedContentType(alias) below - anything else + // should use it. + internal PublishedContentType(IContentTypeComposition contentType) + { + Id = contentType.Id; + Alias = contentType.Alias; + _propertyTypes = contentType.CompositionPropertyTypes + .Select(x => new PublishedPropertyType(this, x)) + .ToArray(); + InitializeIndexes(); + } + + // internal so it can be used for unit tests + internal PublishedContentType(int id, string alias, IEnumerable propertyTypes) + { + Id = id; + Alias = alias; + _propertyTypes = propertyTypes.ToArray(); + foreach (var propertyType in _propertyTypes) + propertyType.ContentType = this; + InitializeIndexes(); + } + + private void InitializeIndexes() + { + for (var i = 0; i < _propertyTypes.Length; i++) + { + var propertyType = _propertyTypes[i]; + _indexes[propertyType.Alias] = i; + _indexes[propertyType.Alias.ToLowerInvariant()] = i; + } + } + + #region Content type + + public int Id { get; private set; } + public string Alias { get; private set; } + + #endregion + + #region Properties + + public IEnumerable PropertyTypes + { + get { return _propertyTypes; } + } + + // alias is case-insensitive + // this is the ONLY place where we compare ALIASES! + public int GetPropertyIndex(string alias) + { + int index; + if (_indexes.TryGetValue(alias, out index)) return index; // fastest + if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) return index; // slower + return -1; + } + + // virtual for unit tests + public virtual PublishedPropertyType GetPropertyType(string alias) + { + var index = GetPropertyIndex(alias); + return GetPropertyType(index); + } + + // virtual for unit tests + public virtual PublishedPropertyType GetPropertyType(int index) + { + return index >= 0 && index < _propertyTypes.Length ? _propertyTypes[index] : null; + } + + #endregion + + #region Cache + + // note + // default cache refresher events will contain the ID of the refreshed / removed IContentType + // and not the alias. Also, we cannot hook into the cache refresher event here, because it belongs + // to Umbraco.Web, so we do it in Umbraco.Web.Models.PublishedContentTypeCaching. + + // fixme + // how do we know a content type has changed? if just the property has changed, do we trigger an event? + // must run in debug mode to figure out... what happens when a DATATYPE changes? how do I get the type + // of a content right with the content and be sure it's OK? + // ******** HERE IS THE REAL ISSUE ******* + + static readonly ConcurrentDictionary ContentTypes = new ConcurrentDictionary(); + + // fixme - should not be public + internal static void ClearAll() + { + Logging.LogHelper.Debug("Clear all."); + ContentTypes.Clear(); + } + + // fixme - should not be public + internal static void ClearContentType(int id) + { + Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); + + // see http://blogs.msdn.com/b/pfxteam/archive/2011/04/02/10149222.aspx + // that should be race-cond safe + ContentTypes.RemoveAll(kvp => kvp.Value.Id == id); + } + + // fixme + internal static void ClearDataType(int id) + { + Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); + + // see note in ClearContentType() + ContentTypes.RemoveAll(kvp => kvp.Value.PropertyTypes.Any(x => x.DataTypeId == id)); + } + + public static PublishedContentType Get(PublishedItemType itemType, string alias) + { + var key = (itemType == PublishedItemType.Content ? "content" : "media") + "::" + alias.ToLowerInvariant(); + return ContentTypes.GetOrAdd(key, k => CreatePublishedContentType(itemType, alias)); + } + + private static PublishedContentType CreatePublishedContentType(PublishedItemType itemType, string alias) + { + if (GetPublishedContentTypeCallback != null) + return GetPublishedContentTypeCallback(alias); + + var contentType = itemType == PublishedItemType.Content + ? (IContentTypeComposition) ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias) + : (IContentTypeComposition) ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); + + return new PublishedContentType(contentType); + } + + // for unit tests - changing the callback must reset the cache obviously + private static Func _getPublishedContentTypeCallBack; + internal static Func GetPublishedContentTypeCallback + { + get { return _getPublishedContentTypeCallBack; } + set + { + ClearAll(); + _getPublishedContentTypeCallBack = value; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs new file mode 100644 index 0000000000..38e2537b40 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.Models.PublishedContent +{ + // + // This class has two purposes. + // + // - First, we cannot implement strongly-typed content by inheriting from some sort + // of "master content" because that master content depends on the actual content cache + // that is being used. It can be an XmlPublishedContent with the XmlPublishedCache, + // or just anything else. + // + // So we implement strongly-typed content by encapsulating whatever content is + // returned by the content cache, and providing extra properties (mostly) or + // methods or whatever. This class provides the base for such encapsulation. + // + // - Second, any time a content is used in a content set obtained from + // IEnumerable.ToContentSet(), it needs to be cloned and extended + // in order to know about its position in the set. This class provides the base + // for implementing such extension. + // + + /// + /// Provides an abstract base class for IPublishedContent implementations that + /// wrap and extend another IPublishedContent. + /// + public abstract class PublishedContentWrapped : IPublishedContent + { + protected readonly IPublishedContent Content; + + /// + /// Initialize a new instance of the class + /// with an IPublishedContent instance to wrap and extend. + /// + /// The content to wrap and extend. + protected PublishedContentWrapped(IPublishedContent content) + { + Content = content; + } + + /// + /// Gets the wrapped content. + /// + /// The wrapped content, that was passed as an argument to the constructor. + public IPublishedContent Unwrap() + { + return Content; + } + + #region ContentSet + + public virtual IEnumerable ContentSet + { + get { return Content.ContentSet; } + } + + #endregion + + #region ContentType + + public virtual PublishedContentType ContentType { get { return Content.ContentType; } } + + #endregion + + #region Content + + public virtual int Id + { + get { return Content.Id; } + } + + public virtual int TemplateId + { + get { return Content.TemplateId; } + } + + public virtual int SortOrder + { + get { return Content.SortOrder; } + } + + public virtual string Name + { + get { return Content.Name; } + } + + public virtual string UrlName + { + get { return Content.UrlName; } + } + + public virtual string DocumentTypeAlias + { + get { return Content.DocumentTypeAlias; } + } + + public virtual int DocumentTypeId + { + get { return Content.DocumentTypeId; } + } + + public virtual string WriterName + { + get { return Content.WriterName; } + } + + public virtual string CreatorName + { + get { return Content.CreatorName; } + } + + public virtual int WriterId + { + get { return Content.WriterId; } + } + + public virtual int CreatorId + { + get { return Content.CreatorId; } + } + + public virtual string Path + { + get { return Content.Path; } + } + + public virtual DateTime CreateDate + { + get { return Content.CreateDate; } + } + + public virtual DateTime UpdateDate + { + get { return Content.UpdateDate; } + } + + public virtual Guid Version + { + get { return Content.Version; } + } + + public virtual int Level + { + get { return Content.Level; } + } + + public virtual string Url + { + get { return Content.Url; } + } + + public virtual PublishedItemType ItemType + { + get { return Content.ItemType; } + } + + public virtual bool IsDraft + { + get { return Content.IsDraft; } + } + + public virtual int GetIndex() + { + return Content.GetIndex(); + } + + #endregion + + #region Tree + + public virtual IPublishedContent Parent + { + get { return Content.Parent; } + } + + public virtual IEnumerable Children + { + get { return Content.Children; } + } + + #endregion + + #region Properties + + public virtual ICollection Properties + { + get { return Content.Properties; } + } + + public virtual object this[string alias] + { + get { return Content[alias]; } + } + + public virtual IPublishedProperty GetProperty(string alias) + { + return Content.GetProperty(alias); + } + + public virtual IPublishedProperty GetProperty(string alias, bool recurse) + { + return Content.GetProperty(alias, recurse); + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs new file mode 100644 index 0000000000..872efb0d46 --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -0,0 +1,31 @@ +using System; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Provides a base class for IPublishedProperty implementations which converts and caches + /// the value source to the actual value to use when rendering content. + /// + internal abstract class PublishedPropertyBase : IPublishedProperty + { + public readonly PublishedPropertyType PropertyType; + + protected PublishedPropertyBase(PublishedPropertyType propertyType) + { + if (propertyType == null) + throw new ArgumentNullException("propertyType"); + PropertyType = propertyType; + } + + public string Alias + { + get { return PropertyType.Alias; } + } + + // these have to be provided by the actual implementation + public abstract bool HasValue { get; } + public abstract object RawValue { get; } + public abstract object Value { get; } + public abstract object XPathValue { get; } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs new file mode 100644 index 0000000000..35ff64cd3e --- /dev/null +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using Umbraco.Core.Dynamics; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// Represents an type. + /// + /// Instances of the class are immutable, ie + /// if the property type changes, then a new class needs to be created. + public class PublishedPropertyType + { + public PublishedPropertyType(PublishedContentType contentType, PropertyType propertyType) + { + // one control identified by its DataTypeGuid + // can be used to create several datatypes, identified by their DataTypeDefinitionId and supporting prevalues + // which can be used to create several property types, identified by their Id + + ContentType = contentType; + Id = propertyType.Id; + Alias = propertyType.Alias; + + DataTypeId = propertyType.DataTypeDefinitionId; + EditorGuid = propertyType.DataTypeId; + + InitializeConverters(); + } + + // for unit tests + internal PublishedPropertyType(string alias, Guid dataTypeGuid, int propertyTypeId, int dataTypeDefinitionId) + { + // ContentType to be set by PublishedContentType when creating it + Id = propertyTypeId; + Alias = alias; + + DataTypeId = dataTypeDefinitionId; + EditorGuid = dataTypeGuid; + + InitializeConverters(); + } + + #region Property type + + // gets the content type + // internally set by PublishedContentType constructor + public PublishedContentType ContentType { get; internal set; } + + // gets the property type id + public int Id { get; private set; } + + // gets the property alias + public string Alias { get; private set; } + + public int DataTypeId { get; private set; } + + public Guid EditorGuid { get; private set; } + + #endregion + + #region Converters + + private IPropertyValueConverter _sourceConverter; + private IPropertyValueConverter _objectConverter; + private IPropertyValueConverter _xpathConverter; + + private PropertyCacheLevel _sourceCacheLevel; + private PropertyCacheLevel _objectCacheLevel; + private PropertyCacheLevel _xpathCacheLevel; + + private void InitializeConverters() + { + var converters = PropertyValueConvertersResolver.Current.Converters.ToArray(); + + // fixme - get rid of the IPropertyValueEditorConverter support eventually + _sourceConverter = GetSingleConverterOrDefault(converters.Union(GetCompatConverters()), x => x.IsDataToSourceConverter(this), "data-to-source"); + _sourceCacheLevel = GetCacheLevel(_sourceConverter, PropertyCacheValue.Source); + + _objectConverter = GetSingleConverterOrDefault(converters, x => x.IsSourceToObjectConverter(this), "source-to-object"); + _objectCacheLevel = GetCacheLevel(_objectConverter, PropertyCacheValue.Object); + if (_objectCacheLevel < _sourceCacheLevel) + _objectCacheLevel = _sourceCacheLevel; // quietely fix the inconsistency, no need to throw + + _xpathConverter = GetSingleConverterOrDefault(converters, x => x.IsSourceToXPathConverter(this), "source-to-xpath"); + _objectCacheLevel = GetCacheLevel(_objectConverter, PropertyCacheValue.XPath); + if (_xpathCacheLevel < _sourceCacheLevel) + _xpathCacheLevel = _sourceCacheLevel; // quietely fix the inconsistency, no need to throw + } + + static IPropertyValueConverter GetSingleConverterOrDefault(IEnumerable converters, + Func predicate, string name) + { + IPropertyValueConverter result = null; + foreach (var converter in converters.Where(predicate)) + { + if (result == null) result = converter; + else throw new InvalidOperationException("More than one " + name + " converter."); + } + return result; + } + + static PropertyCacheLevel GetCacheLevel(IPropertyValueConverter converter, PropertyCacheValue value) + { + if (converter == null) + return PropertyCacheLevel.Request; + + var attr = converter.GetType().GetCustomAttributes(false) + .FirstOrDefault(x => x.Value == value || x.Value == PropertyCacheValue.All); + + return attr == null ? PropertyCacheLevel.Request : attr.Level; + } + + // converts the raw value into the source value + // uses converters, else falls back to dark (& performance-wise expensive) magic + // source: the property raw value + // preview: whether we are previewing or not + public object ConvertDataToSource(object source, bool preview) + { + // use the converter else use dark (& performance-wise expensive) magic + return _sourceConverter != null + ? _sourceConverter.ConvertDataToSource(this, source, preview) + : ConvertSourceUsingDarkMagic(source); + } + + // gets the source cache level + public PropertyCacheLevel SourceCacheLevel { get { return _sourceCacheLevel; } } + + // converts the source value into the clr value + // uses converters, else returns the source value + // source: the property source value + // preview: whether we are previewing or not + public object ConvertSourceToObject(object source, bool preview) + { + // use the converter if any + // else just return the source value + return _objectConverter != null + ? _objectConverter.ConvertSourceToObject(this, source, preview) + : source; + } + + // gets the value cache level + public PropertyCacheLevel ObjectCacheLevel { get { return _objectCacheLevel; } } + + // converts the source value into the xpath value + // uses the converter else returns the source value as a string + // if successful, returns either a string or an XPathNavigator + // source: the property source value + // preview: whether we are previewing or not + public object ConvertSourceToXPath(object source, bool preview) + { + // use the converter if any + if (_xpathConverter != null) + return _xpathConverter.ConvertSourceToXPath(this, source, preview); + + // else just return the source value as a string or an XPathNavigator + if (source == null) return null; + var xElement = source as XElement; + if (xElement != null) + return xElement.CreateNavigator(); + return source.ToString().Trim(); + } + + // gets the xpath cache level + public PropertyCacheLevel XPathCacheLevel { get { return _xpathCacheLevel; } } + + private static object ConvertSourceUsingDarkMagic(object source) + { + // convert to string + var stringSource = source as string; + if (stringSource == null) return source; // not a string => return the object + stringSource = stringSource.Trim(); + if (stringSource.Length == 0) return null; // empty string => return null + + // try numbers and booleans + // make sure we use the invariant culture ie a dot decimal point, comma is for csv + // NOTE far from perfect: "01a" is returned as a string but "012" is returned as an integer... + int i; + if (int.TryParse(stringSource, NumberStyles.Integer, CultureInfo.InvariantCulture, out i)) + return i; + float f; + if (float.TryParse(stringSource, NumberStyles.Float, CultureInfo.InvariantCulture, out f)) + return f; + bool b; + if (bool.TryParse(stringSource, out b)) + return b; + + // try xml - that is expensive, performance-wise + XElement elt; + if (XmlHelper.TryCreateXElementFromPropertyValue(stringSource, out elt)) + return Attempt.Succeed(new DynamicXml(elt)); // xml => return DynamicXml for compatiblity's sake + + return source; + } + + #endregion + + #region Compat + + // fixme - remove in v7 + // backward-compatibility: support IPropertyEditorValueConverter while we have to + IEnumerable GetCompatConverters() + { + return PropertyEditorValueConvertersResolver.HasCurrent + ? PropertyEditorValueConvertersResolver.Current.Converters + .Where(x => x.IsConverterFor(EditorGuid, ContentType.Alias, Alias)) + .Select(x => new CompatConverter(x)) + : Enumerable.Empty(); + } + + class CompatConverter : PropertyValueConverterBase + { + private readonly IPropertyEditorValueConverter _converter; + + public CompatConverter(IPropertyEditorValueConverter converter) + { + _converter = converter; + } + + public override bool IsDataToSourceConverter(PublishedPropertyType propertyType) + { + return true; + } + + public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + { + // NOTE: ignore preview, because IPropertyEditorValueConverter does not support it + return _converter.ConvertPropertyValue(source).Result; + } + } + + #endregion + } +} diff --git a/src/Umbraco.Core/Models/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedItemType.cs index b9fb603735..a98a3c2a75 100644 --- a/src/Umbraco.Core/Models/PublishedItemType.cs +++ b/src/Umbraco.Core/Models/PublishedItemType.cs @@ -1,11 +1,18 @@ namespace Umbraco.Core.Models { /// - /// The type of published item + /// The type of published content, ie whether it is a content or a media. /// public enum PublishedItemType { + /// + /// A content, ie what was formerly known as a document. + /// Content, + + /// + /// A media. + /// Media } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs deleted file mode 100644 index 658de7e399..0000000000 --- a/src/Umbraco.Core/PropertyEditors/DatePickerPropertyEditorValueConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Linq; - -namespace Umbraco.Core.PropertyEditors -{ - internal class DatePickerPropertyEditorValueConverter : IPropertyEditorValueConverter - { - public bool IsConverterFor(Guid propertyEditorId, string docTypeAlias, string propertyTypeAlias) - { - return (new[] - { - Guid.Parse(Constants.PropertyEditors.DateTime), - Guid.Parse(Constants.PropertyEditors.Date) - }).Contains(propertyEditorId); - } - - /// - /// return a DateTime object even if the value is a string - /// - /// - /// - public Attempt ConvertPropertyValue(object value) - { - return value.TryConvertTo(typeof(DateTime)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/DatePickerValueConverter.cs new file mode 100644 index 0000000000..1ce4e74dd0 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/DatePickerValueConverter.cs @@ -0,0 +1,67 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Xml; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors +{ + [PropertyValueType(typeof(DateTime))] + [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] + internal class DatePickerValueConverter : IPropertyValueConverter + { + private static readonly Guid[] DataTypeGuids = new[] + { + Guid.Parse(Constants.PropertyEditors.DateTime), + Guid.Parse(Constants.PropertyEditors.Date) + }; + + public bool IsDataToSourceConverter(PublishedPropertyType propertyType) + { + return DataTypeGuids.Contains(propertyType.EditorGuid); + } + + public object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return DateTime.MinValue; + + // in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss" + var sourceString = source as string; + if (sourceString != null) + { + DateTime value; + return DateTime.TryParseExact(sourceString, "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out value) + ? value + : DateTime.MinValue; + } + + // in the database a DateTime is: DateTime + // default value is: DateTime.MinValue + return (source is DateTime) + ? source + : DateTime.MinValue; + } + + public bool IsSourceToObjectConverter(PublishedPropertyType propertyType) + { + return IsDataToSourceConverter(propertyType); + } + + public object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) + { + // source should come from ConvertSource and be a DateTime already + return source; + } + + public bool IsSourceToXPathConverter(PublishedPropertyType propertyType) + { + return IsDataToSourceConverter(propertyType); + } + + public object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) + { + // source should come from ConvertSource and be a DateTime already + return XmlConvert.ToString((DateTime) source, "yyyy-MM-ddTHH:mm:ss"); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs index 386a350e29..1fd767da53 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs @@ -1,31 +1,30 @@ using System; -using Umbraco.Core.Dynamics; namespace Umbraco.Core.PropertyEditors { + /// + /// Maps a property source value to a data object. + /// + // fixme - should obsolete, use IPropertyValueConverter instead public interface IPropertyEditorValueConverter { + /// + /// Returns a value indicating whether this provider applies to the specified property. + /// + /// A Guid identifying the property datatype. + /// The content type alias. + /// The property alias. + /// True if this provider applies to the specified property. + bool IsConverterFor(Guid datatypeGuid, string contentTypeAlias, string propertyTypeAlias); /// - /// Returns true if this converter can perform the value conversion for the specified property editor id + /// Attempts to convert a source value specified into a property model. /// - /// - /// - /// - /// - bool IsConverterFor(Guid propertyEditorId, string docTypeAlias, string propertyTypeAlias); - - /// - /// Attempts to convert the value specified into a useable value on the front-end - /// - /// - /// - /// - /// This is used to convert the value stored in the repository into a usable value on the front-end. - /// For example, if a 0 or 1 is stored for a boolean, we'd want to convert this to a real boolean. - /// - /// Also note that the value might not come in as a 0 or 1 but as a "0" or "1" - /// - Attempt ConvertPropertyValue(object value); + /// The source value. + /// An Attempt representing the result of the conversion. + /// The source value is dependent on the content cache. With the Xml content cache it + /// is always a string, but with other caches it may be an object (numeric, time...) matching + /// what is in the database. Be prepared. + Attempt ConvertPropertyValue(object sourceValue); } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs new file mode 100644 index 0000000000..c69ad86efa --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -0,0 +1,82 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Provides published content properties conversion service. + /// + public interface IPropertyValueConverter + { + #region Data to Source + + /// + /// Gets a value indicating whether the converter can convert from Data value to Source value. + /// + /// The property type. + /// A value indicating whether the converter can convert from Data value to Source value. + bool IsDataToSourceConverter(PublishedPropertyType propertyType); + + /// + /// Converts a property Data value to a Source value. + /// + /// The property type. + /// The data value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// fixme + /// The converter should know how to convert a null raw value into the default value for the property type. + /// Raw values may come from the database or from the XML cache (thus being strings). + /// + object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview); + + #endregion + + #region Source to Object + + /// + /// Gets a value indicating whether the converter can convert from Source value to Object value. + /// + /// The property type. + /// A value indicating whether the converter can convert from Source value to Object value. + bool IsSourceToObjectConverter(PublishedPropertyType propertyType); + + /// + /// Converts a property Source value to an Object value. + /// + /// The property type. + /// The source value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// fixme + /// The converter should know how to convert a null source value into the default value for the property type. + object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview); + + #endregion + + #region Source to XPath + + /// + /// Gets a value indicating whether the converter can convert from Source value to XPath value. + /// + /// The property type. + /// A value indicating whether the converter can convert from Source value to XPath value. + bool IsSourceToXPathConverter(PublishedPropertyType propertyType); + + /// + /// Converts a property Source value to an XPath value. + /// + /// The property type. + /// The source value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// fixme + /// The converter should know how to convert a null source value into the default value for the property type. + /// If successful, the result should be either null, a non-empty string, or an XPathNavigator instance. + /// + object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview); + + #endregion + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs new file mode 100644 index 0000000000..1e365d1ac4 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs @@ -0,0 +1,33 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Specifies the acceptable level of cache for a property value. + /// + /// By default, Request is assumed. + public enum PropertyCacheLevel + { + /// + /// Indicates that the property value can be cached at the content level, ie it can be + /// cached until the content itself is modified. + /// + Content = 1, + + /// + /// Indicates that the property value can be cached at the content cache level, ie it can + /// be cached until any content in the cache is modified. + /// + ContentCache = 2, + + /// + /// Indicates that the property value can be cached at the request level, ie it can be + /// cached for the duration of the current request. + /// + Request = 3, + + /// + /// Indicates that the property value cannot be cached and has to be converted any time + /// it is requested. + /// + None = 4 + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheValue.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheValue.cs new file mode 100644 index 0000000000..c4f438fb5e --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheValue.cs @@ -0,0 +1,29 @@ +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Specifies the different types of property cacheable values. + /// + public enum PropertyCacheValue + { + /// + /// All of them. + /// + All, + + /// + /// The source value ie the internal value that can be used to create both the + /// object value and the xpath value. + /// + Source, + + /// + /// The object value ie the strongly typed value of the property as seen when accessing content via C#. + /// + Object, + + /// + /// The XPath value ie the value of the property as seen when accessing content via XPath. + /// + XPath + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorValueConvertersResolver.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorValueConvertersResolver.cs index f804f424a6..17bb95fd7e 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorValueConvertersResolver.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorValueConvertersResolver.cs @@ -4,17 +4,35 @@ using Umbraco.Core.ObjectResolution; namespace Umbraco.Core.PropertyEditors { - /// - /// Manages the list of IPropertyEditorValueConverter's - /// - internal sealed class PropertyEditorValueConvertersResolver : ManyObjectsResolverBase + /// + /// Resolves the IPropertyEditorValueConverter objects. + /// + internal sealed class PropertyEditorValueConvertersResolver : ManyObjectsResolverBase { - public PropertyEditorValueConvertersResolver(IEnumerable converters) + /// + /// Initializes a new instance of the class with + /// an initial list of converter types. + /// + /// The list of converter types + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal PropertyEditorValueConvertersResolver(IEnumerable converters) : base(converters) - { - } - - public IEnumerable Converters + { } + + /// + /// Initializes a new instance of the class with + /// an initial list of converter types. + /// + /// The list of converter types + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal PropertyEditorValueConvertersResolver(params Type[] converters) + : base(converters) + { } + + /// + /// Gets the converteres. + /// + public IEnumerable Converters { get { return Values; } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueCacheAttribute.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueCacheAttribute.cs new file mode 100644 index 0000000000..76d16b79c6 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueCacheAttribute.cs @@ -0,0 +1,34 @@ +using System; +using log4net.Core; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Indicates the cache level for a property cacheable value. + /// + /// Use this attribute to mark property values converters. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class PropertyValueCacheAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a cacheable value and a cache level. + /// + /// The cacheable value. + /// The cache level. + public PropertyValueCacheAttribute(PropertyCacheValue value, PropertyCacheLevel level) + { + Value = value; + Level = level; + } + + /// + /// Gets or sets the cacheable value. + /// + public PropertyCacheValue Value { get; private set; } + + /// + /// Gets or sets the cache level; + /// + public PropertyCacheLevel Level { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs new file mode 100644 index 0000000000..e807c9de43 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Provides a default overridable implementation for that does nothing. + /// + class PropertyValueConverterBase : IPropertyValueConverter + { + public virtual bool IsSourceToObjectConverter(Models.PublishedContent.PublishedPropertyType propertyType) + { + return false; + } + + public virtual object ConvertSourceToObject(Models.PublishedContent.PublishedPropertyType propertyType, object source, bool preview) + { + return null; + } + + public virtual bool IsDataToSourceConverter(Models.PublishedContent.PublishedPropertyType propertyType) + { + return false; + } + + public virtual object ConvertDataToSource(Models.PublishedContent.PublishedPropertyType propertyType, object source, bool preview) + { + return null; + } + + public virtual bool IsSourceToXPathConverter(Models.PublishedContent.PublishedPropertyType propertyType) + { + return false; + } + + public virtual object ConvertSourceToXPath(Models.PublishedContent.PublishedPropertyType propertyType, object source, bool preview) + { + return null; + } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConvertersResolver.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConvertersResolver.cs new file mode 100644 index 0000000000..7af9927978 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConvertersResolver.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.ObjectResolution; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Resolves the IPropertyValueConverter objects. + /// + public sealed class PropertyValueConvertersResolver : ManyObjectsResolverBase + { + /// + /// Initializes a new instance of the class with + /// an initial list of converter types. + /// + /// The list of converter types + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal PropertyValueConvertersResolver(IEnumerable converters) + : base(converters) + { } + + /// + /// Initializes a new instance of the class with + /// an initial list of converter types. + /// + /// The list of converter types + /// The resolver is created by the WebBootManager and thus the constructor remains internal. + internal PropertyValueConvertersResolver(params Type[] converters) + : base(converters) + { } + + /// + /// Gets the converters. + /// + public IEnumerable Converters + { + get { return Values; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueTypeAttribute.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueTypeAttribute.cs new file mode 100644 index 0000000000..5d41b7f184 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueTypeAttribute.cs @@ -0,0 +1,26 @@ +using System; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Indicates the CLR type of property object values returned by a converter. + /// + /// Use this attribute to mark property values converters. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public class PropertyValueTypeAttribute : Attribute + { + /// + /// Initializes a new instance of the class with a type. + /// + /// The type. + public PropertyValueTypeAttribute(Type type) + { + Type = type; + } + + /// + /// Gets or sets the type. + /// + public Type Type { get; private set; } + } +} diff --git a/src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs deleted file mode 100644 index 8480d91260..0000000000 --- a/src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Web; - -namespace Umbraco.Core.PropertyEditors -{ - /// - /// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. - /// - internal class TinyMcePropertyEditorValueConverter : IPropertyEditorValueConverter - { - public bool IsConverterFor(Guid propertyEditorId, string docTypeAlias, string propertyTypeAlias) - { - return Guid.Parse(Constants.PropertyEditors.TinyMCEv3).Equals(propertyEditorId); - } - - /// - /// Return IHtmlString so devs doesn't need to decode html - /// - /// - /// - public virtual Attempt ConvertPropertyValue(object value) - { - return Attempt.Succeed(new HtmlString(value.ToString())); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/TinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TinyMceValueConverter.cs new file mode 100644 index 0000000000..1e054c8733 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/TinyMceValueConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.Web; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. + /// + // PropertyCacheLevel.Content is ok here because that version of RTE converter does not parse {locallink} nor executes macros + [PropertyValueType(typeof(IHtmlString))] + [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] + internal class TinyMceValueConverter : IPropertyValueConverter + { + public bool IsDataToSourceConverter(PublishedPropertyType propertyType) + { + return Guid.Parse(Constants.PropertyEditors.TinyMCEv3).Equals(propertyType.EditorGuid); + } + + public virtual object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + { + // in xml a string is: string + // in the database a string is: string + // default value is: null + return source; + } + + public bool IsSourceToObjectConverter(PublishedPropertyType propertyType) + { + return IsDataToSourceConverter(propertyType); + } + + public virtual object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) + { + // source should come from ConvertSource and be a string already + return new HtmlString((string)source); + } + + public bool IsSourceToXPathConverter(PublishedPropertyType propertyType) + { + return IsDataToSourceConverter(propertyType); + } + + public virtual object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) + { + // source should come from ConvertSource and be a string already + return source; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/YesNoPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/YesNoPropertyEditorValueConverter.cs deleted file mode 100644 index 6e52ca295b..0000000000 --- a/src/Umbraco.Core/PropertyEditors/YesNoPropertyEditorValueConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Umbraco.Core.PropertyEditors -{ - internal class YesNoPropertyEditorValueConverter : IPropertyEditorValueConverter - { - public bool IsConverterFor(Guid propertyEditorId, string docTypeAlias, string propertyTypeAlias) - { - return Guid.Parse(Constants.PropertyEditors.TrueFalse).Equals(propertyEditorId); - } - - /// - /// Convert from string boolean or 0 or 1 to real boolean - /// - /// - /// - public Attempt ConvertPropertyValue(object value) - { - return value.TryConvertTo(typeof(bool)); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/YesNoValueConverter.cs new file mode 100644 index 0000000000..9d7c35c856 --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/YesNoValueConverter.cs @@ -0,0 +1,49 @@ +using System; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors +{ + [PropertyValueType(typeof(bool))] + [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] + class YesNoValueConverter : IPropertyValueConverter + { + public bool IsDataToSourceConverter(PublishedPropertyType propertyType) + { + return Guid.Parse(Constants.PropertyEditors.TrueFalse).Equals(propertyType.EditorGuid); + } + + public object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + { + // in xml a boolean is: string + // in the database a boolean is: string "1" or "0" or empty + // the converter does not need to handle anything else ("true"...) + + // default value is: false + var sourceString = source as string; + if (sourceString == null) return false; + return sourceString == "1"; + } + + public bool IsSourceToObjectConverter(PublishedPropertyType propertyType) + { + return IsDataToSourceConverter(propertyType); + } + + public object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) + { + // source should come from ConvertSource and be a boolean already + return source; + } + + public bool IsSourceToXPathConverter(PublishedPropertyType propertyType) + { + return IsDataToSourceConverter(propertyType); + } + + public object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) + { + // source should come from ConvertSource and be a boolean already + return (bool) source ? "1" : "0"; + } + } +} diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs deleted file mode 100644 index 83e5c53402..0000000000 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Net.Mime; -using System.Web; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; -using umbraco.interfaces; - -namespace Umbraco.Core -{ - /// - /// Extension methods for IPublishedContent - /// - public static class PublishedContentExtensions - { - - #region GetProperty - public static IPublishedContentProperty GetProperty(this IPublishedContent content, string alias, bool recursive) - { - return content.GetPropertyRecursive(alias, recursive); - } - - private static IPublishedContentProperty GetPropertyRecursive(this IPublishedContent content, string alias, bool recursive = false) - { - if (!recursive) - { - return content.GetProperty(alias); - } - var context = content; - var prop = content.GetPropertyRecursive(alias); - while (prop == null || prop.Value == null || prop.Value.ToString().IsNullOrWhiteSpace()) - { - if (context.Parent == null) break; - context = context.Parent; - prop = context.GetPropertyRecursive(alias); - } - return prop; - } - #endregion - - #region HasValue - - public static bool HasValue(this IPublishedContentProperty prop) - { - if (prop == null) return false; - if (prop.Value == null) return false; - return !prop.Value.ToString().IsNullOrWhiteSpace(); - } - - public static bool HasValue(this IPublishedContent doc, string alias) - { - return doc.HasValue(alias, false); - } - public static bool HasValue(this IPublishedContent doc, string alias, bool recursive) - { - var prop = doc.GetProperty(alias, recursive); - if (prop == null) return false; - return prop.HasValue(); - } - public static IHtmlString HasValue(this IPublishedContent doc, string alias, string valueIfTrue, string valueIfFalse) - { - return doc.HasValue(alias, false) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); - } - public static IHtmlString HasValue(this IPublishedContent doc, string alias, bool recursive, string valueIfTrue, string valueIfFalse) - { - return doc.HasValue(alias, recursive) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); - } - public static IHtmlString HasValue(this IPublishedContent doc, string alias, string valueIfTrue) - { - return doc.HasValue(alias, false) ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); - } - public static IHtmlString HasValue(this IPublishedContent doc, string alias, bool recursive, string valueIfTrue) - { - return doc.HasValue(alias, recursive) ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty); - } - #endregion - - /// - /// Returns the recursive value of a field by iterating up the parent chain but starting at the publishedContent passed in - /// - /// - /// - /// - public static string GetRecursiveValue(this IPublishedContent publishedContent, string fieldname) - { - //check for the cached value in the objects properties first - var cachedVal = publishedContent["__recursive__" + fieldname]; - if (cachedVal != null) - { - return cachedVal.ToString(); - } - - var contentValue = ""; - var currentContent = publishedContent; - - while (contentValue.IsNullOrWhiteSpace()) - { - var val = currentContent[fieldname]; - if (val == null || val.ToString().IsNullOrWhiteSpace()) - { - if (currentContent.Parent == null) - { - break; //we've reached the top - } - currentContent = currentContent.Parent; - } - else - { - contentValue = val.ToString(); //we've found a recursive val - } - } - - //cache this lookup in a new custom (hidden) property - publishedContent.Properties.Add(new PropertyResult("__recursive__" + fieldname, contentValue, Guid.Empty, PropertyResultType.CustomProperty)); - - return contentValue; - } - - public static bool IsVisible(this IPublishedContent doc) - { - var umbracoNaviHide = doc.GetProperty(Constants.Conventions.Content.NaviHide); - if (umbracoNaviHide != null) - { - return umbracoNaviHide.Value.ToString().Trim() != "1"; - } - return true; - } - - public static bool HasProperty(this IPublishedContent doc, string name) - { - if (doc != null) - { - var prop = doc.GetProperty(name); - - return (prop != null); - } - return false; - } - - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/PublishedContentHelper.cs b/src/Umbraco.Core/PublishedContentHelper.cs deleted file mode 100644 index 6dffef290d..0000000000 --- a/src/Umbraco.Core/PublishedContentHelper.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; - -namespace Umbraco.Core -{ - - /// - /// Utility class for dealing with data types and value conversions - /// - /// - /// 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. - /// - internal class PublishedContentHelper : ApplicationEventHandler - { - /// - /// Used to invalidate the cache from the ICacherefresher - /// - internal static void ClearPropertyTypeCache() - { - PropertyTypeCache.Clear(); - } - - /// - /// 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 - /// - internal static Func GetDataTypeCallback = null; - - private static readonly ConcurrentDictionary, Guid> PropertyTypeCache = new ConcurrentDictionary, Guid>(); - - /// - /// Return the GUID Id for the data type assigned to the document type with the property alias - /// - /// - /// - /// - /// - /// - internal static Guid GetDataType(ApplicationContext applicationContext, string docTypeAlias, string propertyAlias, PublishedItemType itemType) - { - if (GetDataTypeCallback != null) - return GetDataTypeCallback(docTypeAlias, propertyAlias); - - var key = new Tuple(docTypeAlias, propertyAlias, itemType); - return PropertyTypeCache.GetOrAdd(key, tuple => - { - IContentTypeComposition result = null; - switch (itemType) - { - case PublishedItemType.Content: - result = applicationContext.Services.ContentTypeService.GetContentType(docTypeAlias); - break; - case PublishedItemType.Media: - result = applicationContext.Services.ContentTypeService.GetMediaType(docTypeAlias); - break; - default: - throw new ArgumentOutOfRangeException("itemType"); - } - - if (result == null) return Guid.Empty; - - //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))) - { - return Guid.Empty; - } - 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. - if (property == null) return Guid.Empty; - return property.DataTypeId; - }); - } - - /// - /// Converts the currentValue to a correctly typed value based on known registered converters, then based on known standards. - /// - /// - /// - /// - /// - /// - internal static Attempt ConvertPropertyValue(object currentValue, Guid dataType, string docTypeAlias, string propertyTypeAlias) - { - if (currentValue == null) return Attempt.Fail(); - - //First lets check all registered converters for this data type. - var converters = PropertyEditorValueConvertersResolver.Current.Converters - .Where(x => x.IsConverterFor(dataType, docTypeAlias, propertyTypeAlias)) - .ToArray(); - - //try to convert the value with any of the converters: - foreach (var converted in converters - .Select(p => p.ConvertPropertyValue(currentValue)) - .Where(converted => converted.Success)) - { - return Attempt.Succeed(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 Attempt.Succeed(dResult); - } - } - //process string booleans as booleans - if (sResult.InvariantEquals("true")) - { - return Attempt.Succeed(true); - } - if (sResult.InvariantEquals("false")) - { - return Attempt.Succeed(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 - { - var e = XElement.Parse(sResult, LoadOptions.None); - - //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 - if (!UmbracoSettings.NotDynamicXmlDocumentElements.Any( - tag => string.Equals(tag, documentElement, StringComparison.CurrentCultureIgnoreCase))) - { - return Attempt.Succeed(new DynamicXml(e)); - } - return Attempt.Fail(); - } - catch (Exception) - { - return Attempt.Fail(); - } - } - return Attempt.Fail(); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/TypeExtensions.cs b/src/Umbraco.Core/TypeExtensions.cs index b7c079f384..c8da74f067 100644 --- a/src/Umbraco.Core/TypeExtensions.cs +++ b/src/Umbraco.Core/TypeExtensions.cs @@ -83,26 +83,35 @@ namespace Umbraco.Core return true; } - public static IEnumerable AllInterfaces(this Type target) + // that method is broken (will return duplicates) and useless (GetInterfaces already does the job) + //public static IEnumerable AllInterfaces(this Type target) + //{ + // foreach (var IF in target.GetInterfaces()) + // { + // yield return IF; + // foreach (var childIF in IF.AllInterfaces()) + // { + // yield return childIF; + // } + // } + //} + + public static IEnumerable GetBaseTypes(this Type type, bool andSelf) { - foreach (var IF in target.GetInterfaces()) - { - yield return IF; - foreach (var childIF in IF.AllInterfaces()) - { - yield return childIF; - } - } + if (andSelf) + yield return type; + + while ((type = type.BaseType) != null) + yield return type; } public static IEnumerable AllMethods(this Type target) { - var allTypes = target.AllInterfaces().ToList(); + //var allTypes = target.AllInterfaces().ToList(); + var allTypes = target.GetInterfaces().ToList(); // GetInterfaces is ok here allTypes.Add(target); - return from type in allTypes - from method in type.GetMethods() - select method; + return allTypes.SelectMany(t => t.GetMethods()); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index b6e95a4c1b..a270070d9e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -182,6 +182,20 @@ + + + + + + + + + + + + + + @@ -201,6 +215,11 @@ + + + + + @@ -562,7 +581,6 @@ - @@ -647,7 +665,7 @@ - + @@ -655,7 +673,7 @@ - + @@ -677,14 +695,12 @@ - - - + @@ -831,7 +847,6 @@ - diff --git a/src/Umbraco.Tests/CodeFirst/ContentTypeBase.cs b/src/Umbraco.Tests/CodeFirst/ContentTypeBase.cs index 2de26d87f1..2f175ad668 100644 --- a/src/Umbraco.Tests/CodeFirst/ContentTypeBase.cs +++ b/src/Umbraco.Tests/CodeFirst/ContentTypeBase.cs @@ -93,7 +93,7 @@ namespace Umbraco.Tests.CodeFirst //Using this attribute to hide Properties from Intellisense (when compiled?) [EditorBrowsable(EditorBrowsableState.Never)] - public ICollection Properties + public ICollection Properties { get { return _content.Properties; } } @@ -107,7 +107,7 @@ namespace Umbraco.Tests.CodeFirst //Using this attribute to hide Properties from Intellisense (when compiled?) [EditorBrowsable(EditorBrowsableState.Never)] - public IPublishedContentProperty GetProperty(string alias) + public IPublishedProperty GetProperty(string alias) { return _content.GetProperty(alias); } diff --git a/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs b/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs index 26054a6fdf..37a3d7c500 100644 --- a/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs +++ b/src/Umbraco.Tests/CodeFirst/StronglyTypedMapperTest.cs @@ -1,8 +1,12 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; using Umbraco.Tests.CodeFirst.TestModels; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; @@ -58,9 +62,33 @@ namespace Umbraco.Tests.CodeFirst #region Test setup public override void Initialize() { + // required so we can access property.Value + //PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver(); + TestHelper.EnsureUmbracoSettingsConfig(); base.Initialize(); + + // need to specify a custom callback for unit tests + // AutoPublishedContentTypes generates properties automatically + // when they are requested, but we must declare those that we + // explicitely want to be here... + + var propertyTypes = new[] + { + // AutoPublishedContentType will auto-generate other properties + new PublishedPropertyType("siteDescription", Guid.Empty, 0, 0), + new PublishedPropertyType("siteName", Guid.Empty, 0, 0), + new PublishedPropertyType("articleContent", Guid.Empty, 0, 0), + new PublishedPropertyType("articleAuthor", Guid.Empty, 0, 0), + new PublishedPropertyType("articleDate", Guid.Empty, 0, 0), + new PublishedPropertyType("pageTitle", Guid.Empty, 0, 0), + }; + var type = new AutoPublishedContentType(0, "anything", propertyTypes); + PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; + Console.WriteLine("INIT STRONG {0}", + PublishedContentType.Get(PublishedItemType.Content, "anything") + .PropertyTypes.Count()); } public override void TearDown() diff --git a/src/Umbraco.Tests/CodeFirst/TestModels/Home.cs b/src/Umbraco.Tests/CodeFirst/TestModels/Home.cs index ffed8cf6c3..637400ef1e 100644 --- a/src/Umbraco.Tests/CodeFirst/TestModels/Home.cs +++ b/src/Umbraco.Tests/CodeFirst/TestModels/Home.cs @@ -10,6 +10,8 @@ namespace Umbraco.Tests.CodeFirst.TestModels [PropertyType(typeof(TextFieldDataType))] public string SiteName { get; set; } + // fixme - yet the property alias is "siteDescription"? + [Alias("umbSiteDescription", Name = "Site Description")] [PropertyType(typeof(textfieldMultipleDataType))] public string SiteDescription { get; set; } diff --git a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs index a23a6f6435..2c12d420af 100644 --- a/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs +++ b/src/Umbraco.Tests/CoreXml/NavigableNavigatorTests.cs @@ -882,24 +882,37 @@ namespace Umbraco.Tests.CoreXml public object Value(int id) { - var fieldType = _type.FieldTypes[id]; + var fieldType = _type.FieldTypes[id] as TestPropertyType; + if (fieldType == null) throw new Exception("Oops"); + var value = FieldValues[id]; var isAttr = id <= _type.Source.LastAttributeIndex; + // null => return null if (value == null) return null; + + // attribute => return string value if (isAttr) return value.ToString(); + + // has a converter => use the converter + if (fieldType.XmlStringConverter != null) + return fieldType.XmlStringConverter(value); - if (fieldType.XmlStringConverter != null) return fieldType.XmlStringConverter(value); + // not a string => return value as a string + var s = value as string; + if (s == null) return value.ToString(); - // though in reality we should use the converters, which should - // know whether the property is XML or not, instead of guessing. - XPathDocument doc; - if (XmlHelper.TryCreateXPathDocumentFromPropertyValue(value, out doc)) - return doc.CreateNavigator(); + // xml content... try xml + if (fieldType.IsXmlContent) + { + XPathDocument doc; + if (XmlHelper.TryCreateXPathDocumentFromPropertyValue(s, out doc)) + return doc.CreateNavigator(); + } - //var s = value.ToString(); - //return XmlHelper.IsXmlWhitespace(s) ? null : s; - return value.ToString(); + // return the string + // even if it's xml that can't be parsed... + return s; } // locals diff --git a/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs new file mode 100644 index 0000000000..6736ab0289 --- /dev/null +++ b/src/Umbraco.Tests/DynamicsAndReflection/ExtensionMethodFinderTests.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Dynamics; + +namespace Umbraco.Tests.DynamicsAndReflection +{ + [TestFixture] + public class ExtensionMethodFinderTests + { + // To expand on Jon's answer, the reason this doesn't work is because in regular, + // non-dynamic code extension methods work by doing a full search of all the + // classes known to the compiler for a static class that has an extension method + // that match. The search goes in order based on the namespace nesting and available + // "using" directives in each namespace. + // + // That means that in order to get a dynamic extension method invocation resolved + // correctly, somehow the DLR has to know at runtime what all the namespace nestings + // and "using" directives were in your source code. We do not have a mechanism handy + // for encoding all that information into the call site. We considered inventing + // such a mechanism, but decided that it was too high cost and produced too much + // schedule risk to be worth it. + // + // Eric Lippert, http://stackoverflow.com/questions/5311465/extension-method-and-dynamic-object-in-c-sharp + + [Test] + [Ignore("fails")] + public void TypesTests() + { + Assert.IsTrue(typeof(int[]).Inherits()); + Assert.IsFalse(typeof(int[]).Inherits()); + + var m1 = typeof (ExtensionMethodFinderTests).GetMethod("TestMethod1"); + + var a1A = new object[] {1}; + var m1A = GetMethodForArguments(m1, a1A); + Assert.IsNotNull(m1A); + m1A.Invoke(this, a1A); + + var a1B = new object[] {"foo"}; + var m1B = GetMethodForArguments(m1, a1B); + Assert.IsNull(m1B); + + var m2 = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod2"); + + var m2A = GetMethodForArguments(m2, a1A); + Assert.IsNotNull(m2A); + m2A.Invoke(this, a1A); + + var m2B = GetMethodForArguments(m2, a1B); + Assert.IsNotNull(m2B); + m2B.Invoke(this, a1B); + + var m3 = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod3"); + + var a3A = new object[] {1, 2}; + var m3A = GetMethodForArguments(m3, a3A); + Assert.IsNotNull(m3A); + m3A.Invoke(this, a3A); + + var a3B = new object[] {1, "foo"}; + var m3B = GetMethodForArguments(m3, a3B); + Assert.IsNull(m3B); + + var m4 = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod4"); + + var m4A = GetMethodForArguments(m4, a3A); + Assert.IsNotNull(m4A); + m4A.Invoke(this, a3A); + + var m4B = GetMethodForArguments(m4, a3B); + Assert.IsNotNull(m4B); + m4B.Invoke(this, a3B); + + var m5 = typeof(ExtensionMethodFinderTests).GetMethod("TestMethod5"); + + // fixme - currently that fails because we can't match List with List + var a5 = new object[] {new List()}; + var m5A = GetMethodForArguments(m5, a5); + Assert.IsNotNull(m5A); + + // fixme - should we also handle "ref" and "out" parameters? + // fixme - should we pay attention to array types? + } + + public void TestMethod1(int value) {} + public void TestMethod2(T value) {} + public void TestMethod3(T value1, T value2) { } + public void TestMethod4(T1 value1, T2 value2) { } + public void TestMethod5(List value) { } + + // gets the method that can apply to the arguments + // either the method itself, or a generic one + // or null if it couldn't match + // + // this is a nightmare - if we want to do it right, then we have + // to re-do the whole compiler type inference stuff by ourselves?! + // + static MethodInfo GetMethodForArguments(MethodInfo method, IList arguments) + { + var parameters = method.GetParameters(); + var genericArguments = method.GetGenericArguments(); + + if (parameters.Length != arguments.Count) return null; + + var genericArgumentTypes = new Type[genericArguments.Length]; + var i = 0; + for (; i < parameters.Length; i++) + { + var parameterType = parameters[i].ParameterType; + var argumentType = arguments[i].GetType(); + + Console.WriteLine("{0} / {1}", parameterType, argumentType); + + if (parameterType == argumentType) continue; // match + if (parameterType.IsGenericParameter) // eg T + { + var pos = parameterType.GenericParameterPosition; + if (genericArgumentTypes[pos] != null) + { + // fixme - is this OK? what about variance and such? + // it is NOT ok, if the first pass is SomethingElse then next is Something + // it will fail... the specs prob. indicate how it works, trying to find a common + // type... + if (genericArgumentTypes[pos].IsAssignableFrom(argumentType) == false) + break; + } + else + { + genericArgumentTypes[pos] = argumentType; + } + } + else if (parameterType.IsGenericType) // eg List + { + if (argumentType.IsGenericType == false) break; + + var pg = parameterType.GetGenericArguments(); + var ag = argumentType.GetGenericArguments(); + + // then what ?! + // should _variance_ be of some importance? + Console.WriteLine("generic {0}", argumentType.IsGenericType); + } + else + { + if (parameterType.IsAssignableFrom(argumentType) == false) + break; + } + } + if (i != parameters.Length) return null; + return genericArguments.Length == 0 + ? method + : method.MakeGenericMethod(genericArgumentTypes); + } + + public class Class1 + {} + + [Test] + [Ignore("fails")] + public void FinderTests() + { + MethodInfo method; + var class1 = new Class1(); + + method = ExtensionMethodFinder.FindExtensionMethod(typeof (Class1), new object[] {1}, "TestMethod1", false); + Assert.IsNotNull(method); + method.Invoke(null, new object[] { class1, 1 }); + + method = ExtensionMethodFinder.FindExtensionMethod(typeof(Class1), new object[] { "x" }, "TestMethod1", false); + Assert.IsNull(method); // fixme - fails, return TestMethod1! + + method = ExtensionMethodFinder.FindExtensionMethod(typeof(Class1), new object[] { 1 }, "TestMethod2", false); + Assert.IsNotNull(method); + method.Invoke(null, new object[] { class1, "1" }); + + method = ExtensionMethodFinder.FindExtensionMethod(typeof(Class1), new object[] { "x" }, "TestMethod2", false); + Assert.IsNotNull(method); + method.Invoke(null, new object[] { class1, "x" }); + } + } + + static class ExtensionMethodFinderTestsExtensions + { + public static void TestMethod1(this ExtensionMethodFinderTests.Class1 source, int value) + { } + + public static void TestMethod2(this ExtensionMethodFinderTests.Class1 source, int value) + { } + + public static void TestMethod2(this ExtensionMethodFinderTests.Class1 source, string value) + { } + } +} diff --git a/src/Umbraco.Tests/DynamicsAndReflection/ReflectionTests.cs b/src/Umbraco.Tests/DynamicsAndReflection/ReflectionTests.cs new file mode 100644 index 0000000000..857517724f --- /dev/null +++ b/src/Umbraco.Tests/DynamicsAndReflection/ReflectionTests.cs @@ -0,0 +1,73 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests.DynamicsAndReflection +{ + [TestFixture] + public class ReflectionTests + { + [Test] + public void GetBaseTypesIsOk() + { + // tests that the GetBaseTypes extension method works. + + var type = typeof(Class2); + var types = type.GetBaseTypes(true).ToArray(); + Assert.AreEqual(3, types.Length); + Assert.Contains(typeof(Class2), types); + Assert.Contains(typeof(Class1), types); + Assert.Contains(typeof(object), types); + + types = type.GetBaseTypes(false).ToArray(); + Assert.AreEqual(2, types.Length); + Assert.Contains(typeof(Class1), types); + Assert.Contains(typeof(object), types); + } + + [Test] + public void GetInterfacesIsOk() + { + // tests that GetInterfaces gets _all_ interfaces + // so the AllInterfaces extension method is useless + + var type = typeof(Class2); + var interfaces = type.GetInterfaces(); + Assert.AreEqual(2, interfaces.Length); + Assert.Contains(typeof(IInterface1), interfaces); + Assert.Contains(typeof(IInterface2), interfaces); + } + + // TypeExtensions.AllInterfaces was broken an not used, has been commented out + // + //[Test] + //public void AllInterfacesIsBroken() + //{ + // // tests that the AllInterfaces extension method is broken + // + // var type = typeof(Class2); + // var interfaces = type.AllInterfaces().ToArray(); + // Assert.AreEqual(3, interfaces.Length); // should be 2! + // Assert.Contains(typeof(IInterface1), interfaces); + // Assert.Contains(typeof(IInterface2), interfaces); + // Assert.AreEqual(2, interfaces.Count(i => i == typeof(IInterface1))); // duplicate! + // Assert.AreEqual(1, interfaces.Count(i => i == typeof(IInterface2))); + //} + + interface IInterface1 + { } + + interface IInterface2 : IInterface1 + { + void Method(); + } + + class Class1 : IInterface2 + { + public void Method() { } + } + + class Class2 : Class1 + { } + } +} diff --git a/src/Umbraco.Tests/LibraryTests.cs b/src/Umbraco.Tests/LibraryTests.cs index 9cc6302761..a61d39049e 100644 --- a/src/Umbraco.Tests/LibraryTests.cs +++ b/src/Umbraco.Tests/LibraryTests.cs @@ -4,6 +4,10 @@ using System.IO; using System.Linq; using System.Text; using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.PublishedCache; @@ -19,11 +23,30 @@ namespace Umbraco.Tests [TestFixture] public class LibraryTests : BaseRoutingTest { - public override void Initialize() - { - base.Initialize(); + public override void Initialize() + { + // required so we can access property.Value + PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver(); + + base.Initialize(); - var routingContext = GetRoutingContext("/test", 1234); + // need to specify a custom callback for unit tests + // AutoPublishedContentTypes generates properties automatically + // when they are requested, but we must declare those that we + // explicitely want to be here... + + var propertyTypes = new[] + { + // AutoPublishedContentType will auto-generate other properties + new PublishedPropertyType("content", Guid.Empty, 0, 0), + }; + var type = new AutoPublishedContentType(0, "anything", propertyTypes); + PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; + Console.WriteLine("INIT LIB {0}", + PublishedContentType.Get(PublishedItemType.Content, "anything") + .PropertyTypes.Count()); + + var routingContext = GetRoutingContext("/test", 1234); UmbracoContext.Current = routingContext.UmbracoContext; } diff --git a/src/Umbraco.Tests/Masterpages/dummy.txt b/src/Umbraco.Tests/Masterpages/dummy.txt deleted file mode 100644 index 9c01dce8f4..0000000000 --- a/src/Umbraco.Tests/Masterpages/dummy.txt +++ /dev/null @@ -1 +0,0 @@ -This file is just here to make sure the directory gets created. \ No newline at end of file diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs index 4e5cfe56dd..54c3f47129 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueConverterTests.cs @@ -10,42 +10,54 @@ namespace Umbraco.Tests.PropertyEditors [TestFixture] public class PropertyEditorValueConverterTests { - [TestCase("2012-11-10", true)] - [TestCase("2012/11/10", true)] - [TestCase("10/11/2012", true)] - [TestCase("11/10/2012", false)] - [TestCase("Sat 10, Nov 2012", true)] - [TestCase("Saturday 10, Nov 2012", true)] - [TestCase("Sat 10, November 2012", true)] - [TestCase("Saturday 10, November 2012", true)] - [TestCase("2012-11-10 13:14:15", true)] - [TestCase("", false)] + // see notes in the converter + // only ONE date format is expected here + + //[TestCase("2012-11-10", true)] + //[TestCase("2012/11/10", true)] + //[TestCase("10/11/2012", true)] + //[TestCase("11/10/2012", false)] + //[TestCase("Sat 10, Nov 2012", true)] + //[TestCase("Saturday 10, Nov 2012", true)] + //[TestCase("Sat 10, November 2012", true)] + //[TestCase("Saturday 10, November 2012", true)] + //[TestCase("2012-11-10 13:14:15", true)] + [TestCase("2012-11-10 13:14:15", false)] + [TestCase("2012-11-10T13:14:15", true)] + [TestCase("", false)] public void CanConvertDatePickerPropertyEditor(string date, bool expected) { - var converter = new DatePickerPropertyEditorValueConverter(); + var converter = new DatePickerValueConverter(); var dateTime = new DateTime(2012, 11, 10, 13, 14, 15); - var result = converter.ConvertPropertyValue(date); + var result = converter.ConvertDataToSource(null, date, false); // does not use type for conversion - Assert.IsTrue(result.Success); - Assert.AreEqual(DateTime.Equals(dateTime.Date, ((DateTime) result.Result).Date), expected); - } + if (expected) + Assert.AreEqual(dateTime.Date, ((DateTime) result).Date); + else + Assert.AreNotEqual(dateTime.Date, ((DateTime)result).Date); + } - [TestCase("TRUE", true)] - [TestCase("True", true)] - [TestCase("true", true)] + // see the notes in the converter + // values such as "true" are NOT expected here + + //[TestCase("TRUE", true)] + //[TestCase("True", true)] + //[TestCase("true", true)] [TestCase("1", true)] - [TestCase("FALSE", false)] - [TestCase("False", false)] - [TestCase("false", false)] + //[TestCase("FALSE", false)] + //[TestCase("False", false)] + //[TestCase("false", false)] [TestCase("0", false)] [TestCase("", false)] - public void CanConvertYesNoPropertyEditor(string value, bool expected) + [TestCase("true", false)] + [TestCase("false", false)] + [TestCase("blah", false)] + public void CanConvertYesNoPropertyEditor(string value, bool expected) { - var converter = new YesNoPropertyEditorValueConverter(); - var result = converter.ConvertPropertyValue(value); + var converter = new YesNoValueConverter(); + var result = converter.ConvertDataToSource(null, value, false); // does not use type for conversion - Assert.IsTrue(result.Success); - Assert.AreEqual(expected, result.Result); + Assert.AreEqual(expected, result); } } } diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicDocumentTestsBase.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicDocumentTestsBase.cs deleted file mode 100644 index e0321e08d5..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicDocumentTestsBase.cs +++ /dev/null @@ -1,699 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Dynamics; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public abstract class DynamicDocumentTestsBase : PublishedContentTestBase - { - protected override DatabaseBehavior DatabaseTestBehavior - { - get { return DatabaseBehavior.NoDatabasePerFixture; } - } - - protected override string GetXmlContent(int templateId) - { - return @" - - - - -]> - - - - - 1 - - - This is some content]]> - - - - - - - - - - - - 1 - - - - - - - - - - - -"; - } - - /// - /// Returns the dynamic node/document to run tests against - /// - /// - /// - protected abstract dynamic GetDynamicNode(int id); - - [Test] - public void Recursive_Property() - { - var doc = GetDynamicNode(1174); - var prop = doc.GetProperty("siteTitle", true); - Assert.IsNotNull(prop); - Assert.AreEqual("This is my site", prop.Value); - prop = doc.GetProperty("_siteTitle"); //test with underscore prefix - Assert.IsNotNull(prop); - Assert.AreEqual("This is my site", prop.Value); - Assert.AreEqual("This is my site", doc._siteTitle); - } - - /// - /// Tests the internal instance level caching of returning properties - /// - /// - /// http://issues.umbraco.org/issue/U4-1824 - /// http://issues.umbraco.org/issue/U4-1825 - /// - [Test] - public void Can_Return_Property_And_Value() - { - var doc = GetDynamicNode(1173); - - Assert.IsTrue(doc.HasProperty(Constants.Conventions.Content.UrlAlias)); - var prop = doc.GetProperty(Constants.Conventions.Content.UrlAlias); - Assert.IsNotNull(prop); - Assert.AreEqual("page2/alias, 2ndpagealias", prop.Value); - Assert.AreEqual("page2/alias, 2ndpagealias", doc.umbracoUrlAlias); - } - - /// - /// Tests the IsLast method with the result set from a Where statement - /// - [Test] - public void Is_Last_From_Where_Filter() - { - var doc = GetDynamicNode(1173); - - foreach (var d in doc.Children.Where("Visible")) - { - if (d.Id != 1178) - { - Assert.IsFalse(d.IsLast()); - } - else - { - Assert.IsTrue(d.IsLast()); - } - } - - } - - [Test] - public void Single() - { - var doc = GetDynamicNode(4444); - - var result = doc.Children().Single(); - - Assert.IsNotNull(result); - Assert.AreEqual(5555, result.Id); - } - - [Test] - public void Single_With_Query() - { - var doc = GetDynamicNode(1046); - - var result = doc.Children().Single("id==1175"); - - Assert.IsNotNull(result); - Assert.AreEqual(1175, result.Id); - } - - [Test] - public void First() - { - var doc = GetDynamicNode(1173); - - var result = doc.Children().First(); - - Assert.IsNotNull(result); - Assert.AreEqual(1174, result.Id); - } - - [Test] - public void First_With_Query() - { - var doc = GetDynamicNode(1173); - - var result = doc.Children().First("blah==\"some content\""); - - Assert.IsNotNull(result); - Assert.AreEqual(1176, result.Id); - } - - [Test] - public void Where_User_Property_Value() - { - var doc = GetDynamicNode(1173); - - var result = (IEnumerable)doc.Children().Where("blah==\"some content\""); - - Assert.IsNotNull(result); - Assert.AreEqual(1, result.Count()); - Assert.AreEqual(1176, result.Single().Id); - } - - [Test] - public void String_ContainsValue_Extension_Method() - { - var doc = GetDynamicNode(1046); - - var paramVals = new Dictionary { { "searchId", 1173 } }; //this is an integer value - var result = doc.Children() - .Where("selectedNodes.ContainsValue(searchId)", paramVals) //call an extension method - .FirstOrDefault(); - - Assert.IsNotNull(result); - Assert.AreEqual(4444, result.Id); - - //don't find! - paramVals = new Dictionary { { "searchId", 1111777 } }; - result = doc.Children() - .Where("selectedNodes.ContainsValue(searchId)", paramVals) - .FirstOrDefault(); - - Assert.IsNotNull(result); - Assert.IsTrue(result.GetType() == typeof(DynamicNull) || result.GetType() == typeof(umbraco.MacroEngines.DynamicNull)); - //Assert.AreEqual(typeof(DynamicNull), result.GetType()); - } - - [Test] - public void String_Contains_Method() - { - var doc = GetDynamicNode(1046); - - var paramVals = new Dictionary { { "searchId", "1173" } }; - var result = doc.Children() - .Where("selectedNodes.Contains(searchId)", paramVals) - .FirstOrDefault(); - - Assert.IsNotNull(result); - Assert.AreEqual(4444, result.Id); - - //don't find! - paramVals = new Dictionary { { "searchId", "1aaa173" } }; - result = doc.Children() - .Where("selectedNodes.Contains(searchId)", paramVals) - .FirstOrDefault(); - - Assert.IsNotNull(result); - Assert.IsTrue(result.GetType() == typeof (DynamicNull) || result.GetType() == typeof (umbraco.MacroEngines.DynamicNull)); - //Assert.AreEqual(typeof (DynamicNull), result.GetType()); - } - - [Test] - public void String_Split_Method() - { - var doc = GetDynamicNode(1046); - - var paramVals = new Dictionary - { - { "splitTerm", new char[] { ',' } }, - { "splitOptions", StringSplitOptions.RemoveEmptyEntries } - }; - var result = doc.Children() - .Where("selectedNodes.Split(splitTerm, splitOptions).Length == 3", paramVals) - .FirstOrDefault(); - - Assert.IsNotNull(result); - Assert.AreEqual(4444, result.Id); - } - - [Ignore("We are ignoring this test because currently our ExpressionParser class cannot deal with this... it needs some serious TLC but it is very complex.")] - [Test] - public void Complex_Linq() - { - var doc = GetDynamicNode(1173); - - var paramVals = new Dictionary {{"splitTerm", new char[] {','}}, {"searchId", "1173"}}; - var result = doc.Ancestors().OrderBy("level") - .Single() - .Descendants() - .Where("selectedNodes != null && selectedNodes != String.Empty && selectedNodes.Split(splitTerm).Contains(searchId)", paramVals) - .FirstOrDefault(); - - Assert.IsNotNull(result); - Assert.AreEqual(4444, result.Id); - } - - [Test] - public void Index() - { - var doc = GetDynamicNode(1173); - Assert.AreEqual(0, doc.Index()); - doc = GetDynamicNode(1176); - Assert.AreEqual(3, doc.Index()); - doc = GetDynamicNode(1177); - Assert.AreEqual(1, doc.Index()); - doc = GetDynamicNode(1178); - Assert.AreEqual(2, doc.Index()); - } - - [Test] - public virtual void Is_First_Root_Nodes() - { - var doc = GetDynamicNode(1046); //test root nodes - Assert.IsTrue(doc.IsFirst()); - doc = GetDynamicNode(1172); - Assert.IsFalse(doc.IsFirst()); - } - - [Test] - public void Is_First() - { - var doc = GetDynamicNode(1173); //test normal nodes - Assert.IsTrue(doc.IsFirst()); - doc = GetDynamicNode(1175); - Assert.IsFalse(doc.IsFirst()); - } - - [Test] - public virtual void Is_Not_First_Root_Nodes() - { - var doc = GetDynamicNode(1046); //test root nodes - Assert.IsFalse(doc.IsNotFirst()); - doc = GetDynamicNode(1172); - Assert.IsTrue(doc.IsNotFirst()); - } - - [Test] - public void Is_Not_First() - { - var doc = GetDynamicNode(1173); //test normal nodes - Assert.IsFalse(doc.IsNotFirst()); - doc = GetDynamicNode(1175); - Assert.IsTrue(doc.IsNotFirst()); - } - - [Test] - public virtual void Is_Position_Root_Nodes() - { - var doc = GetDynamicNode(1046); //test root nodes - Assert.IsTrue(doc.IsPosition(0)); - doc = GetDynamicNode(1172); - Assert.IsTrue(doc.IsPosition(1)); - } - - [Test] - public void Is_Position() - { - var doc = GetDynamicNode(1173); //test normal nodes - Assert.IsTrue(doc.IsPosition(0)); - doc = GetDynamicNode(1175); - Assert.IsTrue(doc.IsPosition(1)); - } - - [Test] - public void Children_GroupBy_DocumentTypeAlias() - { - var doc = GetDynamicNode(1046); - - var found1 = doc.Children.GroupBy("DocumentTypeAlias"); - - var casted = (IEnumerable>)(found1); - Assert.AreEqual(2, casted.Count()); - Assert.AreEqual(2, casted.Single(x => x.Key.ToString() == "Home").Count()); - Assert.AreEqual(1, casted.Single(x => x.Key.ToString() == "CustomDocument").Count()); - } - - [Test] - public void Children_Where_DocumentTypeAlias() - { - var doc = GetDynamicNode(1046); - - var found1 = doc.Children.Where("DocumentTypeAlias == \"CustomDocument\""); - var found2 = doc.Children.Where("DocumentTypeAlias == \"Home\""); - - Assert.AreEqual(1, found1.Count()); - Assert.AreEqual(2, found2.Count()); - } - - [Test] - public void Children_Where_NodeTypeAlias() - { - var doc = GetDynamicNode(1046); - - var found1 = doc.Children.Where("NodeTypeAlias == \"CustomDocument\""); - var found2 = doc.Children.Where("NodeTypeAlias == \"Home\""); - - Assert.AreEqual(1, found1.Count()); - Assert.AreEqual(2, found2.Count()); - } - - [Test] - public void Children_Order_By_Update_Date() - { - var asDynamic = GetDynamicNode(1173); - - var ordered = asDynamic.Children.OrderBy("UpdateDate"); - var casted = (IEnumerable)ordered; - - var correctOrder = new[] { 1178, 1177, 1174, 1176 }; - for (var i = 0; i < correctOrder.Length ;i++) - { - Assert.AreEqual(correctOrder[i], ((dynamic)casted.ElementAt(i)).Id); - } - - } - - [Test] - public void Children_Order_By_Update_Date_Descending() - { - var asDynamic = GetDynamicNode(1173); - - var ordered = asDynamic.Children.OrderBy("UpdateDate desc"); - var casted = (IEnumerable)ordered; - - var correctOrder = new[] { 1176, 1174, 1177, 1178 }; - for (var i = 0; i < correctOrder.Length; i++) - { - Assert.AreEqual(correctOrder[i], ((dynamic)casted.ElementAt(i)).Id); - } - - } - - [Test] - public void HasProperty() - { - var asDynamic = GetDynamicNode(1173); - - var hasProp = asDynamic.HasProperty(Constants.Conventions.Content.UrlAlias); - - Assert.AreEqual(true, (bool)hasProp); - - } - - [Test] - public void Skip() - { - var asDynamic = GetDynamicNode(1173); - - var skip = asDynamic.Children.Skip(2); - var casted = (IEnumerable)skip; - - Assert.AreEqual(2, casted.Count()); - Assert.IsTrue(casted.Select(x => ((dynamic) x).Id).ContainsAll(new dynamic[] {1178, 1176})); - - } - - [Test] - public void HasValue() - { - var asDynamic = GetDynamicNode(1173); - - var hasValue = asDynamic.HasValue(Constants.Conventions.Content.UrlAlias); - var noValue = asDynamic.HasValue("blahblahblah"); - - Assert.IsTrue(hasValue); - Assert.IsFalse(noValue); - } - - [Test] - public void Take() - { - var asDynamic = GetDynamicNode(1173); - - var take = asDynamic.Children.Take(2); - var casted = (IEnumerable)take; - - Assert.AreEqual(2, casted.Count()); - Assert.IsTrue(casted.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1177 })); - } - - [Test] - public void Ancestors_Where_Visible() - { - var asDynamic = GetDynamicNode(1174); - - var whereVisible = asDynamic.Ancestors().Where("Visible"); - var casted = (IEnumerable)whereVisible; - - Assert.AreEqual(1, casted.Count()); - - } - - [Test] - public void Visible() - { - var asDynamicHidden = GetDynamicNode(1046); - var asDynamicVisible = GetDynamicNode(1173); - - Assert.IsFalse(asDynamicHidden.Visible); - Assert.IsTrue(asDynamicVisible.Visible); - } - - [Test] - public void Ensure_TinyMCE_Converted_Type_User_Property() - { - var asDynamic = GetDynamicNode(1173); - - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(asDynamic.Content.GetType())); - Assert.AreEqual("
This is some content
", asDynamic.Content.ToString()); - } - - [Test] - public void Get_Children_With_Pluralized_Alias() - { - var asDynamic = GetDynamicNode(1173); - - Action doAssert = d => - { - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(d)); - var casted = (IEnumerable)d; - Assert.AreEqual(2, casted.Count()); - }; - - doAssert(asDynamic.Homes); //pluralized alias - doAssert(asDynamic.homes); //pluralized alias - doAssert(asDynamic.CustomDocuments); //pluralized alias - doAssert(asDynamic.customDocuments); //pluralized alias - } - - [Test] - public void GetPropertyValue_Non_Reflected() - { - var asDynamic = GetDynamicNode(1174); - - Assert.AreEqual("Custom data with same property name as the member name", asDynamic.GetPropertyValue("creatorName")); - Assert.AreEqual("Custom data with same property name as the member name", asDynamic.GetPropertyValue("CreatorName")); - } - - [Test] - public void GetPropertyValue_Reflected() - { - var asDynamic = GetDynamicNode(1174); - - Assert.AreEqual("admin", asDynamic.GetPropertyValue("@creatorName")); - Assert.AreEqual("admin", asDynamic.GetPropertyValue("@CreatorName")); - } - - [Test] - public void Get_User_Property_With_Same_Name_As_Member_Property() - { - var asDynamic = GetDynamicNode(1174); - - Assert.AreEqual("Custom data with same property name as the member name", asDynamic.creatorName); - - //because CreatorName is defined on DynamicNode, it will not return the user defined property - Assert.AreEqual("admin", asDynamic.CreatorName); - } - - [Test] - public void Get_Member_Property() - { - var asDynamic = GetDynamicNode(1173); - - Assert.AreEqual((int) 2, (int) asDynamic.Level); - Assert.AreEqual((int) 2, (int) asDynamic.level); - - Assert.AreEqual((int) 1046, (int) asDynamic.ParentId); - Assert.AreEqual((int) 1046, (int) asDynamic.parentId); - } - - [Test] - public void Get_Children() - { - var asDynamic = GetDynamicNode(1173); - - var children = asDynamic.Children; - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(children)); - - var childrenAsList = asDynamic.ChildrenAsList; //test ChildrenAsList too - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(childrenAsList)); - - var castChildren = (IEnumerable)children; - Assert.AreEqual(4, castChildren.Count()); - - var castChildrenAsList = (IEnumerable)childrenAsList; - Assert.AreEqual(4, castChildrenAsList.Count()); - } - - [Test] - public void Ancestor_Or_Self() - { - var asDynamic = GetDynamicNode(1173); - - var result = asDynamic.AncestorOrSelf(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int) 1046, (int) result.Id); - } - - [Test] - public void Ancestors_Or_Self() - { - var asDynamic = GetDynamicNode(1174); - - var result = asDynamic.AncestorsOrSelf(); - - Assert.IsNotNull(result); - - var list = (IEnumerable)result; - Assert.AreEqual(3, list.Count()); - Assert.IsTrue(list.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1173, 1046 })); - } - - [Test] - public void Ancestors() - { - var asDynamic = GetDynamicNode(1174); - - var result = asDynamic.Ancestors(); - - Assert.IsNotNull(result); - - var list = (IEnumerable)result; - Assert.AreEqual(2, list.Count()); - Assert.IsTrue(list.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1046 })); - } - - [Test] - public void Descendants_Or_Self() - { - var asDynamic = GetDynamicNode(1046); - - var result = asDynamic.DescendantsOrSelf(); - - Assert.IsNotNull(result); - - var list = (IEnumerable)result; - Assert.AreEqual(9, list.Count()); - Assert.IsTrue(list.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175, 4444 })); - } - - [Test] - public void Descendants() - { - var asDynamic = GetDynamicNode(1046); - - var result = asDynamic.Descendants(); - - Assert.IsNotNull(result); - - var list = (IEnumerable)result; - Assert.AreEqual(8, list.Count()); - Assert.IsTrue(list.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 })); - } - - [Test] - public void Up() - { - var asDynamic = GetDynamicNode(1173); - - var result = asDynamic.Up(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int) 1046, (int) result.Id); - } - - [Test] - public void Down() - { - var asDynamic = GetDynamicNode(1173); - - var result = asDynamic.Down(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int) 1174, (int) result.Id); - } - - [Test] - public void Next() - { - var asDynamic = GetDynamicNode(1173); - - var result = asDynamic.Next(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int) 1175, (int) result.Id); - } - - [Test] - public void Next_Without_Sibling() - { - var asDynamic = GetDynamicNode(1176); - - Assert.IsNull(asDynamic.Next()); - } - - [Test] - public void Previous_Without_Sibling() - { - var asDynamic = GetDynamicNode(1173); - - Assert.IsNull(asDynamic.Previous()); - } - - [Test] - public void Previous() - { - var asDynamic = GetDynamicNode(1176); - - var result = asDynamic.Previous(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int)1178, (int)result.Id); - } - } - - /// - /// Extension methods used in tests - /// - public static class TestExtensionMethods - { - public static bool ContainsValue(this string s, int val) - { - return s.Contains(val.ToString()); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicNodeTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicNodeTests.cs deleted file mode 100644 index b000466a85..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicNodeTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.IO; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; -using umbraco.MacroEngines; -using umbraco.NodeFactory; -using System.Linq; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class DynamicNodeTests : DynamicDocumentTestsBase - { - /// - /// We only need a new schema per fixture... speeds up testing - /// - protected override DatabaseBehavior DatabaseTestBehavior - { - get { return DatabaseBehavior.NewSchemaPerFixture; } - } - - public override void Initialize() - { - base.Initialize(); - //copy the umbraco settings file over - var currDir = new DirectoryInfo(TestHelper.CurrentAssemblyDirectory); - File.Copy( - currDir.Parent.Parent.Parent.GetDirectories("Umbraco.Web.UI") - .First() - .GetDirectories("config").First() - .GetFiles("umbracoSettings.Release.config").First().FullName, - Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"), - true); - - UmbracoSettings.SettingsFilePath = IOHelper.MapPath(SystemDirectories.Config + Path.DirectorySeparatorChar, false); - - //need to specify a custom callback for unit tests - DynamicNode.GetDataTypeCallback = (docTypeAlias, propertyAlias) => - { - if (propertyAlias == "content") - { - //return the rte type id - return Guid.Parse(Constants.PropertyEditors.TinyMCEv3); - } - return Guid.Empty; - }; - - } - - [Test] - [Ignore("This test will never work unless DynamicNode is refactored a lot in order to get a list of root nodes since root nodes don't have a parent to look up")] - public override void Is_First_Root_Nodes() - { - base.Is_First_Root_Nodes(); - } - - [Test] - [Ignore("This test will never work unless DynamicNode is refactored a lot in order to get a list of root nodes since root nodes don't have a parent to look up")] - public override void Is_Not_First_Root_Nodes() - { - base.Is_Not_First_Root_Nodes(); - } - - [Test] - [Ignore("This test will never work unless DynamicNode is refactored a lot in order to get a list of root nodes since root nodes don't have a parent to look up")] - public override void Is_Position_Root_Nodes() - { - base.Is_Position_Root_Nodes(); - } - - public override void TearDown() - { - base.TearDown(); - } - - protected override dynamic GetDynamicNode(int id) - { - //var template = Template.MakeNew("test", new User(0)); - //var ctx = GetUmbracoContext("/test", template.Id); - var ctx = GetUmbracoContext("/test", 1234); - - var cache = ctx.ContentCache.InnerCache as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the legacy one is supported."); - - var node = new DynamicNode( - new DynamicBackingItem( - new Node(cache.GetXml(ctx).SelectSingleNode("//*[@id='" + id + "' and @isDoc]")))); - Assert.IsNotNull(node); - return (dynamic)node; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentCustomExtensionMethods.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentCustomExtensionMethods.cs deleted file mode 100644 index b2cebf3ed0..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentCustomExtensionMethods.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Web.Models; - -namespace Umbraco.Tests.PublishedContent -{ - public static class DynamicPublishedContentCustomExtensionMethods - { - - public static string DynamicDocumentNoParameters(this DynamicPublishedContent doc) - { - return "Hello world"; - } - - public static string DynamicDocumentCustomString(this DynamicPublishedContent doc, string custom) - { - return custom; - } - - public static string DynamicDocumentMultiParam(this DynamicPublishedContent doc, string custom, int i, bool b) - { - return custom + i + b; - } - - public static string DynamicDocumentListMultiParam(this DynamicPublishedContentList doc, string custom, int i, bool b) - { - return custom + i + b; - } - - public static string DynamicDocumentEnumerableMultiParam(this IEnumerable doc, string custom, int i, bool b) - { - return custom + i + b; - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentTests.cs deleted file mode 100644 index fdf097472d..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicPublishedContentTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Web; -using Umbraco.Web.Models; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class DynamicPublishedContentTests : DynamicDocumentTestsBase - { - public override void Initialize() - { - base.Initialize(); - - } - - public override void TearDown() - { - base.TearDown(); - } - - internal DynamicPublishedContent GetNode(int id) - { - //var template = Template.MakeNew("test", new User(0)); - //var ctx = GetUmbracoContext("/test", template.Id); - var ctx = GetUmbracoContext("/test", 1234); - var doc = ctx.ContentCache.GetById(id); - Assert.IsNotNull(doc); - var dynamicNode = new DynamicPublishedContent(doc); - Assert.IsNotNull(dynamicNode); - return dynamicNode; - } - - protected override dynamic GetDynamicNode(int id) - { - return GetNode(id).AsDynamic(); - } - - [Test] - public void Custom_Extension_Methods() - { - var asDynamic = GetDynamicNode(1173); - - Assert.AreEqual("Hello world", asDynamic.DynamicDocumentNoParameters()); - Assert.AreEqual("Hello world!", asDynamic.DynamicDocumentCustomString("Hello world!")); - Assert.AreEqual("Hello world!" + 123 + false, asDynamic.DynamicDocumentMultiParam("Hello world!", 123, false)); - Assert.AreEqual("Hello world!" + 123 + false, asDynamic.Children.DynamicDocumentListMultiParam("Hello world!", 123, false)); - Assert.AreEqual("Hello world!" + 123 + false, asDynamic.Children.DynamicDocumentEnumerableMultiParam("Hello world!", 123, false)); - - } - - [Test] - public void Returns_IDocument_Object() - { - var helper = new TestHelper(GetNode(1173)); - var doc = helper.GetDoc(); - //HasProperty is only a prop on DynamicPublishedContent, NOT IPublishedContent - Assert.IsFalse(doc.GetType().GetProperties().Any(x => x.Name == "HasProperty")); - } - - [Test] - public void Returns_DynamicDocument_Object() - { - var helper = new TestHelper(GetNode(1173)); - var doc = helper.GetDocAsDynamic(); - //HasProperty is only a prop on DynamicPublishedContent, NOT IPublishedContent - Assert.IsTrue(doc.HasProperty(Constants.Conventions.Content.UrlAlias)); - } - - [Test] - public void Returns_DynamicDocument_Object_After_Casting() - { - var helper = new TestHelper(GetNode(1173)); - var doc = helper.GetDoc(); - var ddoc = (dynamic) doc; - //HasProperty is only a prop on DynamicPublishedContent, NOT IPublishedContent - Assert.IsTrue(ddoc.HasProperty(Constants.Conventions.Content.UrlAlias)); - } - - /// - /// Test class to mimic UmbracoHelper when returning docs - /// - public class TestHelper - { - private readonly DynamicPublishedContent _doc; - - public TestHelper(DynamicPublishedContent doc) - { - _doc = doc; - } - - public IPublishedContent GetDoc() - { - return _doc; - } - - public dynamic GetDocAsDynamic() - { - return _doc.AsDynamic(); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlConverterTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlConverterTests.cs deleted file mode 100644 index f0ad32a1bc..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlConverterTests.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.Xml; -using System.Xml.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Dynamics; -using Umbraco.Tests.PartialTrust; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class DynamicXmlConverterTests : AbstractPartialTrustFixture - { - [Test] - public void Convert_To_Raw_String() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml( - XmlHelper.StripDashesInElementOrAttributeNames(xml), - xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result.Value); - } - - [Test] - public void Convert_To_Raw_XElement() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml( - XmlHelper.StripDashesInElementOrAttributeNames(xml), - xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result.Value.ToString(SaveOptions.DisableFormatting)); - } - - [Test] - public void Convert_To_Raw_XmlElement() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml( - XmlHelper.StripDashesInElementOrAttributeNames(xml), - xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result.Value.OuterXml); - } - - [Test] - public void Convert_To_Raw_XmlDocument() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml( - XmlHelper.StripDashesInElementOrAttributeNames(xml), - xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result.Value.InnerXml); - } - - [Test] - public void Convert_To_String() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml(xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result); - } - - [Test] - public void Convert_To_XElement() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml(xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result.ToString(SaveOptions.DisableFormatting)); - } - - [Test] - public void Convert_To_XmlElement() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml(xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result.OuterXml); - } - - [Test] - public void Convert_To_XmlDocument() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var dXml = new DynamicXml(xml); - var result = dXml.TryConvertTo(); - Assert.IsTrue(result.Success); - Assert.AreEqual(xml, result.Result.InnerXml); - } - - public override void TestSetup() - { - - } - - public override void TestTearDown() - { - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlTests.cs deleted file mode 100644 index 642952e4aa..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/DynamicXmlTests.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Diagnostics; -using Microsoft.CSharp.RuntimeBinder; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Dynamics; -using System.Linq; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class DynamicXmlTests - { - /// - /// Ensures that when we return the xml structure we get the real structure, not the replaced hyphen structure - /// see: http://issues.umbraco.org/issue/U4-1405#comment=67-5113 - /// http://issues.umbraco.org/issue/U4-1636 - /// - [Test] - public void Deals_With_Hyphenated_Values() - { - var xml = @" - - True - 1161 - /content/ - 12 december Zorgbeurs Care - -"; - - var typedXml = new DynamicXml( - XmlHelper.StripDashesInElementOrAttributeNames(xml), - xml); - dynamic dynamicXml = typedXml; - - var typedElement = typedXml.RawXmlElement.Element("url-picker"); - var dynamicElementByCleanedName = dynamicXml.urlpicker; - - Assert.IsNotNull(typedElement); - Assert.IsNotNull(dynamicElementByCleanedName); - - Assert.AreEqual( - typedElement.Attribute("some-attribute").Value, - dynamicElementByCleanedName.someattribute); - } - - [Test] - public void Custom_Extension_Method_Legacy() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var typedXml = new global::umbraco.MacroEngines.DynamicXml(xml); - dynamic dynamicXml = typedXml; - - //we haven't explicitly defined ElementAt so this will dynamically invoke this method - var element = dynamicXml.ElementAt(0); - - Assert.AreEqual("1057", Enumerable.First(element.BaseElement.Elements()).Attribute("id").Value); - } - - [Test] - public void Custom_Extension_Method() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var typedXml = new DynamicXml(xml); - - dynamic dynamicXml = typedXml; - - //we haven't explicitly defined ElementAt so this will dynamically invoke this method - var element = dynamicXml.ElementAt(0); - - Assert.AreEqual("1057", Enumerable.First(element.BaseElement.Elements()).Attribute("id").Value); - } - - [Test] - public void Take_Legacy() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var typedXml = new global::umbraco.MacroEngines.DynamicXml(xml); - dynamic dynamicXml = typedXml; - var typedTaken = typedXml.Take(1); - var dynamicTaken = dynamicXml.Take(1); - - Assert.AreEqual(1, typedTaken.Count()); - Assert.AreEqual(1, Enumerable.Count(dynamicTaken)); - - Assert.AreEqual("1057", typedTaken.ElementAt(0).BaseElement.Elements().First().Attribute("id").Value); - Assert.AreEqual("1057", Enumerable.First(Enumerable.ElementAt(dynamicTaken, 0).BaseElement.Elements()).Attribute("id").Value); - } - - [Test] - public void Take() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var typedXml = new DynamicXml(xml); - dynamic dynamicXml = typedXml; - var typedTaken = typedXml.Take(1); - var dynamicTaken = dynamicXml.Take(1); - - Assert.AreEqual(1, typedTaken.Count()); - Assert.AreEqual(1, Enumerable.Count(dynamicTaken)); - - Assert.AreEqual("1057", typedTaken.ElementAt(0).BaseElement.Elements().First().Attribute("id").Value); - Assert.AreEqual("1057", Enumerable.First(Enumerable.ElementAt(dynamicTaken, 0).BaseElement.Elements()).Attribute("id").Value); - } - - [Test] - public void Ensure_Legacy_Objects_Are_Returned() - { - var xml = "/media/54/tulips.jpg1024768620888jpg/media/41/hydrangeas.jpg1024768595284jpg"; - var mediaItems = new global::umbraco.MacroEngines.DynamicXml(xml); - //Debug.WriteLine("full xml = {0}", mediaItems.ToXml()); - - if (mediaItems.Count() != 0) - { - foreach (dynamic item in mediaItems) - { - Type itemType = item.GetType(); - Debug.WriteLine("item type = {0}", itemType); - dynamic image = item.Image; - - Type imageType = image.GetType(); - Debug.WriteLine("image type = {0}", imageType); - - //ensure they are the same - Assert.AreEqual(itemType, imageType); - - //ensure they are legacy - Assert.AreEqual(typeof(global::umbraco.MacroEngines.DynamicXml), itemType); - Assert.AreEqual(typeof(global::umbraco.MacroEngines.DynamicXml), imageType); - } - } - } - - /// - /// Test the current Core class - /// - [Test] - public void Find_Test_Core_Class() - { - RunFindTest(x => new DynamicXml(x)); - } - - /// - /// Tests the macroEngines legacy class - /// - [Test] - public void Find_Test_Legacy_Class() - { - RunFindTest(x => new global::umbraco.MacroEngines.DynamicXml(x)); - } - - private void RunFindTest(Func getDynamicXml) - { - var xmlstring = @" - - - -"; - - dynamic dXml = getDynamicXml(xmlstring); - - var result1 = dXml.Find("@name", "test 1"); - var result2 = dXml.Find("@name", "test 2"); - var result3 = dXml.Find("@name", "test 3"); - var result4 = dXml.Find("@name", "dont find"); - - Assert.AreEqual("found 1", result1.value); - Assert.AreEqual("found 2", result2.value); - Assert.AreEqual("found 3", result3.value); - Assert.Throws(() => - { - //this will throw because result4 is not found - var temp = result4.value; - }); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/LegacyExamineBackedMediaTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/LegacyExamineBackedMediaTests.cs deleted file mode 100644 index 19f3046211..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/LegacyExamineBackedMediaTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using Lucene.Net.Documents; -using Lucene.Net.Store; -using NUnit.Framework; -using Umbraco.Core.Configuration; -using Umbraco.Tests.UmbracoExamine; -using umbraco.MacroEngines; - -namespace Umbraco.Tests.PublishedContent -{ - public class LegacyExamineBackedMediaTests : ExamineBaseTest - { - public override void TestSetup() - { - base.TestSetup(); - UmbracoSettings.ForceSafeAliases = true; - UmbracoSettings.UmbracoLibraryCacheDuration = 1800; - UmbracoSettings.ForceSafeAliases = true; - } - - public override void TestTearDown() - { - base.TestTearDown(); - } - - [Test] - public void Ensure_Children_Are_Sorted() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile()); - Assert.IsNotNull(result); - Assert.AreEqual(1, result.TotalItemCount); - - var searchItem = result.First(); - var backedMedia = new ExamineBackedMedia(searchItem, indexer, searcher); - var children = backedMedia.ChildrenAsList.Value; - - var currSort = 0; - for (var i = 0; i < children.Count(); i++) - { - Assert.GreaterOrEqual(children[i].SortOrder, currSort); - currSort = children[i].SortOrder; - } - } - - } - - [Test] - public void Ensure_Result_Has_All_Values() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile()); - Assert.IsNotNull(result); - Assert.AreEqual(1, result.TotalItemCount); - - var searchItem = result.First(); - var backedMedia = new ExamineBackedMedia(searchItem, indexer, searcher); - - Assert.AreEqual(searchItem.Id, backedMedia.Id); - Assert.AreEqual(searchItem.Fields["sortOrder"], backedMedia.SortOrder.ToString()); - Assert.AreEqual(searchItem.Fields["urlName"], backedMedia.UrlName); - Assert.AreEqual(DateTools.StringToDate(searchItem.Fields["createDate"]), backedMedia.CreateDate); - Assert.AreEqual(DateTools.StringToDate(searchItem.Fields["updateDate"]), backedMedia.UpdateDate); - Assert.AreEqual(Guid.Parse(searchItem.Fields["version"]), backedMedia.Version); - Assert.AreEqual(searchItem.Fields["level"], backedMedia.Level.ToString()); - Assert.AreEqual(searchItem.Fields["writerID"], backedMedia.WriterID.ToString()); - Assert.AreEqual(searchItem.Fields["writerID"], backedMedia.CreatorID.ToString()); //there's only writerId in the xml - Assert.AreEqual(searchItem.Fields["writerName"], backedMedia.CreatorName); - Assert.AreEqual(searchItem.Fields["writerName"], backedMedia.WriterName); //tehre's only writer name in the xml - } - - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentDataTableTests.cs deleted file mode 100644 index c6ce668a40..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentDataTableTests.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; - -namespace Umbraco.Tests.PublishedContent -{ - /// - /// Unit tests for IPublishedContent and extensions - /// - [TestFixture] - public class PublishedContentDataTableTests : BaseRoutingTest - { - public override void Initialize() - { - base.Initialize(); - //need to specify a different callback for testing - Umbraco.Web.PublishedContentExtensions.GetPropertyAliasesAndNames = s => - { - var userFields = new Dictionary() - { - {"property1", "Property 1"}, - {"property2", "Property 2"} - }; - if (s == "Child") - { - userFields.Add("property4", "Property 4"); - } - else - { - userFields.Add("property3", "Property 3"); - } - - //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))) - { - allFields.Add(f.Key, f.Value); - } - return allFields; - }; - var routingContext = GetRoutingContext("/test"); - - //set the UmbracoContext.Current since the extension methods rely on it - UmbracoContext.Current = routingContext.UmbracoContext; - } - - public override void TearDown() - { - base.TearDown(); - Umbraco.Web.PublishedContentExtensions.GetPropertyAliasesAndNames = null; - UmbracoContext.Current = null; - } - - [Test] - public void To_DataTable() - { - var doc = GetContent(true, 1); - var dt = doc.ChildrenAsTable(); - - Assert.AreEqual(11, dt.Columns.Count); - Assert.AreEqual(3, dt.Rows.Count); - Assert.AreEqual("value4", dt.Rows[0]["Property 1"]); - Assert.AreEqual("value5", dt.Rows[0]["Property 2"]); - Assert.AreEqual("value6", dt.Rows[0]["Property 4"]); - Assert.AreEqual("value7", dt.Rows[1]["Property 1"]); - Assert.AreEqual("value8", dt.Rows[1]["Property 2"]); - Assert.AreEqual("value9", dt.Rows[1]["Property 4"]); - Assert.AreEqual("value10", dt.Rows[2]["Property 1"]); - Assert.AreEqual("value11", dt.Rows[2]["Property 2"]); - Assert.AreEqual("value12", dt.Rows[2]["Property 4"]); - } - - [Test] - public void To_DataTable_With_Filter() - { - var doc = GetContent(true, 1); - //change a doc type alias - ((TestPublishedContent) doc.Children.ElementAt(0)).DocumentTypeAlias = "DontMatch"; - - var dt = doc.ChildrenAsTable("Child"); - - Assert.AreEqual(11, dt.Columns.Count); - Assert.AreEqual(2, dt.Rows.Count); - Assert.AreEqual("value7", dt.Rows[0]["Property 1"]); - Assert.AreEqual("value8", dt.Rows[0]["Property 2"]); - Assert.AreEqual("value9", dt.Rows[0]["Property 4"]); - Assert.AreEqual("value10", dt.Rows[1]["Property 1"]); - Assert.AreEqual("value11", dt.Rows[1]["Property 2"]); - Assert.AreEqual("value12", dt.Rows[1]["Property 4"]); - } - - [Test] - public void To_DataTable_No_Rows() - { - var doc = GetContent(false, 1); - var dt = doc.ChildrenAsTable(); - //will return an empty data table - Assert.AreEqual(0, dt.Columns.Count); - Assert.AreEqual(0, dt.Rows.Count); - } - - private IPublishedContent GetContent(bool createChildren, int indexVals) - { - var d = new TestPublishedContent - { - CreateDate = DateTime.Now, - CreatorId = 1, - CreatorName = "Shannon", - DocumentTypeAlias = createChildren? "Parent" : "Child", - DocumentTypeId = 2, - Id = 3, - SortOrder = 4, - TemplateId = 5, - UpdateDate = DateTime.Now, - Path = "-1,3", - UrlName = "home-page", - Name = "Page" + Guid.NewGuid().ToString(), - Version = Guid.NewGuid(), - WriterId = 1, - WriterName = "Shannon", - Parent = null, - Level = 1, - Properties = new Collection( - new List() - { - new PropertyResult("property1", "value" + indexVals, Guid.NewGuid(), PropertyResultType.UserProperty), - new PropertyResult("property2", "value" + (indexVals + 1), Guid.NewGuid(), PropertyResultType.UserProperty) - }), - Children = new List() - }; - if (createChildren) - { - d.Children = new List() - { - GetContent(false, indexVals + 3), - GetContent(false, indexVals + 6), - GetContent(false, indexVals + 9) - }; - } - if (!createChildren) - { - //create additional columns, used to test the different columns for child nodes - d.Properties.Add(new PropertyResult("property4", "value" + (indexVals + 2), Guid.NewGuid(), PropertyResultType.UserProperty)); - } - else - { - d.Properties.Add(new PropertyResult("property3", "value" + (indexVals + 2), Guid.NewGuid(), PropertyResultType.UserProperty)); - } - return d; - } - - - private class TestPublishedContent : IPublishedContent - { - public string Url { get; set; } - public PublishedItemType ItemType { get; set; } - - IPublishedContent IPublishedContent.Parent - { - get { return Parent; } - } - IEnumerable IPublishedContent.Children - { - get { return Children; } - } - public IPublishedContent Parent { get; set; } - public int Id { get; set; } - public int TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public string UrlName { get; set; } - public string DocumentTypeAlias { get; set; } - public int DocumentTypeId { get; set; } - public string WriterName { get; set; } - public string CreatorName { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public ICollection Properties { get; set; } - - public object this[string propertyAlias] - { - get { return GetProperty(propertyAlias).Value; } - } - - public IEnumerable Children { get; set; } - public IPublishedContentProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTestBase.cs deleted file mode 100644 index 4db386e3a4..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTestBase.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.IO; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; - -namespace Umbraco.Tests.PublishedContent -{ - /// - /// Abstract base class for tests for published content and published media - /// - public abstract class PublishedContentTestBase : BaseRoutingTest - { - public override void Initialize() - { - base.Initialize(); - - UmbracoSettings.SettingsFilePath = Core.IO.IOHelper.MapPath(Core.IO.SystemDirectories.Config + Path.DirectorySeparatorChar, false); - - //need to specify a custom callback for unit tests - PublishedContentHelper.GetDataTypeCallback = (docTypeAlias, propertyAlias) => - { - if (propertyAlias == "content") - { - //return the rte type id - return Guid.Parse(Constants.PropertyEditors.TinyMCEv3); - } - return Guid.Empty; - }; - - var rCtx = GetRoutingContext("/test", 1234); - UmbracoContext.Current = rCtx.UmbracoContext; - - } - - protected override void FreezeResolution() - { - PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( - new[] - { - typeof(DatePickerPropertyEditorValueConverter), - typeof(TinyMcePropertyEditorValueConverter), - typeof(YesNoPropertyEditorValueConverter) - }); - - PublishedContentCacheResolver.Current = new PublishedContentCacheResolver(new PublishedContentCache()); - PublishedMediaCacheResolver.Current = new PublishedMediaCacheResolver(new PublishedMediaCache()); - - base.FreezeResolution(); - } - - public override void TearDown() - { - base.TearDown(); - - UmbracoContext.Current = null; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTests.cs deleted file mode 100644 index 79940a82f2..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedContentTests.cs +++ /dev/null @@ -1,491 +0,0 @@ -using System.Linq; -using System.Web; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; - -namespace Umbraco.Tests.PublishedContent -{ - /// - /// Tests the methods on IPublishedContent using the DefaultPublishedContentStore - /// - [TestFixture] - public class PublishedContentTests : PublishedContentTestBase - { - protected override DatabaseBehavior DatabaseTestBehavior - { - get { return DatabaseBehavior.NoDatabasePerFixture; } - } - - protected override string GetXmlContent(int templateId) - { - return @" - - - - -]> - - - - - 1 - - - This is some content]]> - - - - - - - - - - - - - 1 - - - - - - - - - -"; - } - - internal IPublishedContent GetNode(int id) - { - var ctx = GetUmbracoContext("/test", 1234); - var doc = ctx.ContentCache.GetById(id); - Assert.IsNotNull(doc); - return doc; - } - - [Test] - public void Is_Last_From_Where_Filter_Dynamic_Linq() - { - var doc = GetNode(1173); - - foreach (var d in doc.Children.Where("Visible")) - { - if (d.Id != 1178) - { - Assert.IsFalse(d.IsLast()); - } - else - { - Assert.IsTrue(d.IsLast()); - } - } - } - - [Test] - public void Is_Last_From_Where_Filter() - { - var doc = GetNode(1173); - - foreach (var d in doc.Children.Where(x => x.IsVisible())) - { - if (d.Id != 1178) - { - Assert.IsFalse(d.IsLast()); - } - else - { - Assert.IsTrue(d.IsLast()); - } - } - } - - [Test] - public void Is_Last_From_Take() - { - var doc = GetNode(1173); - - foreach (var d in doc.Children.Take(3)) - { - if (d.Id != 1178) - { - Assert.IsFalse(d.IsLast()); - } - else - { - Assert.IsTrue(d.IsLast()); - } - } - } - - [Test] - public void Is_Last_From_Skip() - { - var doc = GetNode(1173); - - foreach (var d in doc.Children.Skip(1)) - { - if (d.Id != 1176) - { - Assert.IsFalse(d.IsLast()); - } - else - { - Assert.IsTrue(d.IsLast()); - } - } - } - - [Test] - public void Is_Last_From_Concat() - { - var doc = GetNode(1173); - - - foreach (var d in doc.Children.Concat(new[] { GetNode(1175), GetNode(4444) })) - { - if (d.Id != 4444) - { - Assert.IsFalse(d.IsLast()); - } - else - { - Assert.IsTrue(d.IsLast()); - } - } - } - - [Test] - public void Descendants_Ordered_Properly() - { - var doc = GetNode(1046); - - var currentLevel = 0; - var lastSortOrder = 0; - var levelChangesAt = new[] { 1046, 1173, 1174 }; - - foreach (var d in doc.DescendantsOrSelf()) - { - if (levelChangesAt.Contains(d.Id)) - { - Assert.Greater(d.Level, currentLevel); - currentLevel = d.Level; - } - else - { - Assert.AreEqual(currentLevel, d.Level); - Assert.Greater(d.SortOrder, lastSortOrder); - } - lastSortOrder = d.SortOrder; - } - } - - [Test] - public void Test_Get_Recursive_Val() - { - var doc = GetNode(1174); - var rVal = doc.GetRecursiveValue("testRecursive"); - var nullVal = doc.GetRecursiveValue("DoNotFindThis"); - Assert.AreEqual("This is the recursive val", rVal); - Assert.AreEqual("", nullVal); - } - - [Test] - public void Get_Property_Value_Uses_Converter() - { - var doc = GetNode(1173); - - var propVal = doc.GetPropertyValue("content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal.GetType())); - Assert.AreEqual("
This is some content
", propVal.ToString()); - - var propVal2 = doc.GetPropertyValue("content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal2.GetType())); - Assert.AreEqual("
This is some content
", propVal2.ToString()); - } - - [Test] - public void Complex_Linq() - { - var doc = GetNode(1173); - - var result = doc.Ancestors().OrderBy(x => x.Level) - .Single() - .Descendants() - .FirstOrDefault(x => x.GetPropertyValue("selectedNodes", "").Split(',').Contains("1173")); - - Assert.IsNotNull(result); - } - - [Test] - public void Index() - { - var doc = GetNode(1173); - Assert.AreEqual(0, doc.Index()); - doc = GetNode(1176); - Assert.AreEqual(3, doc.Index()); - doc = GetNode(1177); - Assert.AreEqual(1, doc.Index()); - doc = GetNode(1178); - Assert.AreEqual(2, doc.Index()); - } - - [Test] - public void Is_First() - { - var doc = GetNode(1046); //test root nodes - Assert.IsTrue(doc.IsFirst()); - doc = GetNode(1172); - Assert.IsFalse(doc.IsFirst()); - doc = GetNode(1173); //test normal nodes - Assert.IsTrue(doc.IsFirst()); - doc = GetNode(1175); - Assert.IsFalse(doc.IsFirst()); - } - - [Test] - public void Is_Not_First() - { - var doc = GetNode(1046); //test root nodes - Assert.IsFalse(doc.IsNotFirst()); - doc = GetNode(1172); - Assert.IsTrue(doc.IsNotFirst()); - doc = GetNode(1173); //test normal nodes - Assert.IsFalse(doc.IsNotFirst()); - doc = GetNode(1175); - Assert.IsTrue(doc.IsNotFirst()); - } - - [Test] - public void Is_Position() - { - var doc = GetNode(1046); //test root nodes - Assert.IsTrue(doc.IsPosition(0)); - doc = GetNode(1172); - Assert.IsTrue(doc.IsPosition(1)); - doc = GetNode(1173); //test normal nodes - Assert.IsTrue(doc.IsPosition(0)); - doc = GetNode(1175); - Assert.IsTrue(doc.IsPosition(1)); - } - - [Test] - public void Children_GroupBy_DocumentTypeAlias() - { - var doc = GetNode(1046); - - var found1 = doc.Children.GroupBy("DocumentTypeAlias"); - - Assert.AreEqual(2, found1.Count()); - Assert.AreEqual(2, found1.Single(x => x.Key.ToString() == "Home").Count()); - Assert.AreEqual(1, found1.Single(x => x.Key.ToString() == "CustomDocument").Count()); - } - - [Test] - public void Children_Where_DocumentTypeAlias() - { - var doc = GetNode(1046); - - var found1 = doc.Children.Where("DocumentTypeAlias == \"CustomDocument\""); - var found2 = doc.Children.Where("DocumentTypeAlias == \"Home\""); - - Assert.AreEqual(1, found1.Count()); - Assert.AreEqual(2, found2.Count()); - } - - [Test] - public void Children_Order_By_Update_Date() - { - var doc = GetNode(1173); - - var ordered = doc.Children.OrderBy("UpdateDate"); - - var correctOrder = new[] { 1178, 1177, 1174, 1176 }; - for (var i = 0; i < correctOrder.Length; i++) - { - Assert.AreEqual(correctOrder[i], ordered.ElementAt(i).Id); - } - - } - - [Test] - public void HasProperty() - { - var doc = GetNode(1173); - - var hasProp = doc.HasProperty(Constants.Conventions.Content.UrlAlias); - - Assert.AreEqual(true, (bool)hasProp); - - } - - - [Test] - public void HasValue() - { - var doc = GetNode(1173); - - var hasValue = doc.HasValue(Constants.Conventions.Content.UrlAlias); - var noValue = doc.HasValue("blahblahblah"); - - Assert.IsTrue(hasValue); - Assert.IsFalse(noValue); - } - - - [Test] - public void Ancestors_Where_Visible() - { - var doc = GetNode(1174); - - var whereVisible = doc.Ancestors().Where("Visible"); - - Assert.AreEqual(1, whereVisible.Count()); - - } - - [Test] - public void Visible() - { - var hidden = GetNode(1046); - var visible = GetNode(1173); - - Assert.IsFalse(hidden.IsVisible()); - Assert.IsTrue(visible.IsVisible()); - } - - - [Test] - public void Ancestor_Or_Self() - { - var doc = GetNode(1173); - - var result = doc.AncestorOrSelf(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int)1046, (int)result.Id); - } - - [Test] - public void Ancestors_Or_Self() - { - var doc = GetNode(1174); - - var result = doc.AncestorsOrSelf(); - - Assert.IsNotNull(result); - - Assert.AreEqual(3, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1174, 1173, 1046 })); - } - - [Test] - public void Ancestors() - { - var doc = GetNode(1174); - - var result = doc.Ancestors(); - - Assert.IsNotNull(result); - - Assert.AreEqual(2, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1046 })); - } - - [Test] - public void Descendants_Or_Self() - { - var doc = GetNode(1046); - - var result = doc.DescendantsOrSelf(); - - Assert.IsNotNull(result); - - Assert.AreEqual(8, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1046, 1173, 1174, 1176, 1175 })); - } - - [Test] - public void Descendants() - { - var doc = GetNode(1046); - - var result = doc.Descendants(); - - Assert.IsNotNull(result); - - Assert.AreEqual(7, result.Count()); - Assert.IsTrue(result.Select(x => ((dynamic)x).Id).ContainsAll(new dynamic[] { 1173, 1174, 1176, 1175, 4444 })); - } - - [Test] - public void Up() - { - var doc = GetNode(1173); - - var result = doc.Up(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int)1046, (int)result.Id); - } - - [Test] - public void Down() - { - var doc = GetNode(1173); - - var result = doc.Down(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int)1174, (int)result.Id); - } - - [Test] - public void Next() - { - var doc = GetNode(1173); - - var result = doc.Next(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int)1175, (int)result.Id); - } - - [Test] - public void Next_Without_Sibling() - { - var doc = GetNode(1176); - - Assert.IsNull(doc.Next()); - } - - [Test] - public void Previous_Without_Sibling() - { - var doc = GetNode(1173); - - Assert.IsNull(doc.Previous()); - } - - [Test] - public void Previous() - { - var doc = GetNode(1176); - - var result = doc.Previous(); - - Assert.IsNotNull(result); - - Assert.AreEqual((int)1178, (int)result.Id); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedMediaTests.cs deleted file mode 100644 index 36cba1ad3e..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/PublishedMediaTests.cs +++ /dev/null @@ -1,387 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text.RegularExpressions; -using System.Xml.Linq; -using System.Xml.XPath; -using Examine; -using Examine.LuceneEngine; -using Examine.LuceneEngine.Providers; -using Lucene.Net.Analysis.Standard; -using Lucene.Net.Store; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.UmbracoExamine; -using Umbraco.Web; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; -using UmbracoExamine; -using UmbracoExamine.DataServices; -using umbraco.BusinessLogic; -using System.Linq; - -namespace Umbraco.Tests.PublishedContent -{ - /// - /// Tests the typed extension methods on IPublishedContent using the DefaultPublishedMediaStore - /// - [TestFixture, RequiresSTA] - public class PublishedMediaTests : PublishedContentTestBase - { - - public override void Initialize() - { - base.Initialize(); - UmbracoExamineSearcher.DisableInitializationCheck = true; - BaseUmbracoIndexer.DisableInitializationCheck = true; - UmbracoSettings.ForceSafeAliases = true; - UmbracoSettings.UmbracoLibraryCacheDuration = 1800; - UmbracoSettings.ForceSafeAliases = true; - } - - public override void TearDown() - { - base.TearDown(); - UmbracoExamineSearcher.DisableInitializationCheck = null; - BaseUmbracoIndexer.DisableInitializationCheck = null; - } - - /// - /// Shared with PublishMediaStoreTests - /// - /// - /// - /// - internal static IPublishedContent GetNode(int id, UmbracoContext umbracoContext) - { - var ctx = umbracoContext; - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(), ctx); - var doc = cache.GetById(id); - Assert.IsNotNull(doc); - return doc; - } - - private IPublishedContent GetNode(int id) - { - return GetNode(id, GetUmbracoContext("/test", 1234)); - } - - [Test] - public void Ensure_Children_Sorted_With_Examine() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootChildren = publishedMedia.Children().ToArray(); - var currSort = 0; - for (var i = 0; i < rootChildren.Count(); i++) - { - Assert.GreaterOrEqual(rootChildren[i].SortOrder, currSort); - currSort = rootChildren[i].SortOrder; - } - } - - - - - - } - - - [Test] - public void Do_Not_Find_In_Recycle_Bin() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); - - //ensure it is found - var publishedMedia = cache.GetById(3113); - Assert.IsNotNull(publishedMedia); - - //move item to recycle bin - var newXml = XElement.Parse(@" - - 115 - 268 - 10726 - jpg - "); - indexer.ReIndexNode(newXml, "media"); - - //ensure it still exists in the index (raw examine search) - var criteria = searcher.CreateSearchCriteria(); - var filter = criteria.Id(3113); - var found = searcher.Search(filter.Compile()); - Assert.IsNotNull(found); - Assert.AreEqual(1, found.TotalItemCount); - - //ensure it does not show up in the published media store - var recycledMedia = cache.GetById(3113); - Assert.IsNull(recycledMedia); - - } - - } - - [Test] - public void Children_With_Examine() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootChildren = publishedMedia.Children(); - Assert.IsTrue(rootChildren.Select(x => x.Id).ContainsAll(new[] { 2222, 1113, 1114, 1115, 1116 })); - - var publishedChild1 = cache.GetById(2222); - var subChildren = publishedChild1.Children(); - Assert.IsTrue(subChildren.Select(x => x.Id).ContainsAll(new[] { 2112 })); - } - } - - [Test] - public void Descendants_With_Examine() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootDescendants = publishedMedia.Descendants(); - Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(new[] { 2112, 2222, 1113, 1114, 1115, 1116 })); - - var publishedChild1 = cache.GetById(2222); - var subDescendants = publishedChild1.Descendants(); - Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(new[] { 2112, 3113 })); - } - } - - [Test] - public void DescendantsOrSelf_With_Examine() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var ctx = GetUmbracoContext("/test", 1234); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(1111); - var rootDescendants = publishedMedia.DescendantsOrSelf(); - Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(new[] { 1111, 2112, 2222, 1113, 1114, 1115, 1116 })); - - var publishedChild1 = cache.GetById(2222); - var subDescendants = publishedChild1.DescendantsOrSelf(); - Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(new[] { 2222, 2112, 3113 })); - } - } - - [Test] - public void Ancestors_With_Examine() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - var ctx = GetUmbracoContext("/test", 1234); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(3113); - var ancestors = publishedMedia.Ancestors(); - Assert.IsTrue(ancestors.Select(x => x.Id).ContainsAll(new[] { 2112, 2222, 1111 })); - } - - } - - [Test] - public void AncestorsOrSelf_With_Examine() - { - using (var luceneDir = new RAMDirectory()) - { - var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir); - indexer.RebuildIndex(); - var ctx = GetUmbracoContext("/test", 1234); - var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir); - var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(searcher, indexer), ctx); - - //we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace - var publishedMedia = cache.GetById(3113); - var ancestors = publishedMedia.AncestorsOrSelf(); - Assert.IsTrue(ancestors.Select(x => x.Id).ContainsAll(new[] { 3113, 2112, 2222, 1111 })); - } - } - - [Test] - public void Children_Without_Examine() - { - var user = new User(0); - var mType = global::umbraco.cms.businesslogic.media.MediaType.MakeNew(user, "TestMediaType"); - var mRoot = global::umbraco.cms.businesslogic.media.Media.MakeNew("MediaRoot", mType, user, -1); - - var mChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child1", mType, user, mRoot.Id); - var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot.Id); - var mChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child3", mType, user, mRoot.Id); - - var mSubChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild3", mType, user, mChild1.Id); - - var publishedMedia = GetNode(mRoot.Id); - var rootChildren = publishedMedia.Children(); - Assert.IsTrue(rootChildren.Select(x => x.Id).ContainsAll(new[] { mChild1.Id, mChild2.Id, mChild3.Id })); - - var publishedChild1 = GetNode(mChild1.Id); - var subChildren = publishedChild1.Children(); - Assert.IsTrue(subChildren.Select(x => x.Id).ContainsAll(new[] { mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - } - - [Test] - public void Descendants_Without_Examine() - { - var user = new User(0); - var mType = global::umbraco.cms.businesslogic.media.MediaType.MakeNew(user, "TestMediaType"); - var mRoot = global::umbraco.cms.businesslogic.media.Media.MakeNew("MediaRoot", mType, user, -1); - - var mChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child1", mType, user, mRoot.Id); - var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot.Id); - var mChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child3", mType, user, mRoot.Id); - - var mSubChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild3", mType, user, mChild1.Id); - - var publishedMedia = GetNode(mRoot.Id); - var rootDescendants = publishedMedia.Descendants(); - Assert.IsTrue(rootDescendants.Select(x => x.Id).ContainsAll(new[] { mChild1.Id, mChild2.Id, mChild3.Id, mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - - var publishedChild1 = GetNode(mChild1.Id); - var subDescendants = publishedChild1.Descendants(); - Assert.IsTrue(subDescendants.Select(x => x.Id).ContainsAll(new[] { mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - } - - [Test] - public void DescendantsOrSelf_Without_Examine() - { - var user = new User(0); - var mType = global::umbraco.cms.businesslogic.media.MediaType.MakeNew(user, "TestMediaType"); - var mRoot = global::umbraco.cms.businesslogic.media.Media.MakeNew("MediaRoot", mType, user, -1); - - var mChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child1", mType, user, mRoot.Id); - var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot.Id); - var mChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child3", mType, user, mRoot.Id); - - var mSubChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild3", mType, user, mChild1.Id); - - var publishedMedia = GetNode(mRoot.Id); - var rootDescendantsOrSelf = publishedMedia.DescendantsOrSelf(); - Assert.IsTrue(rootDescendantsOrSelf.Select(x => x.Id).ContainsAll( - new[] { mRoot.Id, mChild1.Id, mChild2.Id, mChild3.Id, mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - - var publishedChild1 = GetNode(mChild1.Id); - var subDescendantsOrSelf = publishedChild1.DescendantsOrSelf(); - Assert.IsTrue(subDescendantsOrSelf.Select(x => x.Id).ContainsAll( - new[] { mChild1.Id, mSubChild1.Id, mSubChild2.Id, mSubChild3.Id })); - } - - [Test] - public void Parent_Without_Examine() - { - var user = new User(0); - var mType = global::umbraco.cms.businesslogic.media.MediaType.MakeNew(user, "TestMediaType"); - var mRoot = global::umbraco.cms.businesslogic.media.Media.MakeNew("MediaRoot", mType, user, -1); - - var mChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child1", mType, user, mRoot.Id); - var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot.Id); - var mChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child3", mType, user, mRoot.Id); - - var mSubChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild3", mType, user, mChild1.Id); - - var publishedRoot = GetNode(mRoot.Id); - Assert.AreEqual(null, publishedRoot.Parent); - - var publishedChild1 = GetNode(mChild1.Id); - Assert.AreEqual(mRoot.Id, publishedChild1.Parent.Id); - - var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.AreEqual(mChild1.Id, publishedSubChild1.Parent.Id); - } - - - [Test] - public void Ancestors_Without_Examine() - { - var user = new User(0); - var mType = global::umbraco.cms.businesslogic.media.MediaType.MakeNew(user, "TestMediaType"); - var mRoot = global::umbraco.cms.businesslogic.media.Media.MakeNew("MediaRoot", mType, user, -1); - - var mChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child1", mType, user, mRoot.Id); - var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot.Id); - var mChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child3", mType, user, mRoot.Id); - - var mSubChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild3", mType, user, mChild1.Id); - - var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.IsTrue(publishedSubChild1.Ancestors().Select(x => x.Id).ContainsAll(new[] { mChild1.Id, mRoot.Id })); - } - - [Test] - public void AncestorsOrSelf_Without_Examine() - { - var user = new User(0); - var mType = global::umbraco.cms.businesslogic.media.MediaType.MakeNew(user, "TestMediaType"); - var mRoot = global::umbraco.cms.businesslogic.media.Media.MakeNew("MediaRoot", mType, user, -1); - - var mChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child1", mType, user, mRoot.Id); - var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot.Id); - var mChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child3", mType, user, mRoot.Id); - - var mSubChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild1", mType, user, mChild1.Id); - var mSubChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild2", mType, user, mChild1.Id); - var mSubChild3 = global::umbraco.cms.businesslogic.media.Media.MakeNew("SubChild3", mType, user, mChild1.Id); - - var publishedSubChild1 = GetNode(mSubChild1.Id); - Assert.IsTrue(publishedSubChild1.AncestorsOrSelf().Select(x => x.Id).ContainsAll( - new[] { mSubChild1.Id, mChild1.Id, mRoot.Id })); - } - } - - -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContent/StronglyTypedQueryTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContent/StronglyTypedQueryTests.cs deleted file mode 100644 index e437829956..0000000000 --- a/src/Umbraco.Tests/PublishedCache/PublishedContent/StronglyTypedQueryTests.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; -using Umbraco.Web.Models; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class StronglyTypedQueryTests : PublishedContentTestBase - { - public override void Initialize() - { - base.Initialize(); - } - - public override void TearDown() - { - base.TearDown(); - } - - protected override DatabaseBehavior DatabaseTestBehavior - { - get { return DatabaseBehavior.NoDatabasePerFixture; } - } - - protected override string GetXmlContent(int templateId) - { - return @" - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - } - - internal IPublishedContent GetNode(int id) - { - var ctx = UmbracoContext.Current; - var doc = ctx.ContentCache.GetById(id); - Assert.IsNotNull(doc); - return doc; - } - - - [Test] - public void Type_Test() - { - var doc = GetNode(1); - var result = doc.NewsArticles(TraversalType.Descendants).ToArray(); - Assert.AreEqual("John doe", result[0].ArticleAuthor); - Assert.AreEqual("John Smith", result[1].ArticleAuthor); - } - - - [Test] - public void As_Test() - { - var doc = GetNode(1); - var result = doc.AsHome(); - Assert.AreEqual("Test site", result.SiteName); - - Assert.Throws(() => doc.AsContentPage()); - } - - } - - //NOTE: Some of these class will be moved in to the core once all this is working the way we want - - #region Gen classes & supporting classes - - //TOOD: SD: This class could be the way that the UmbracoHelper deals with looking things up in the background, we might not - // even expose it publicly but it could handle any caching (per request) that might be required when looking up any objects... - // though we might not need it at all, not sure yet. - // However, what we need to do is implement the GetDocumentsByType method of the IPublishedStore, see the TODO there. - // It might be nicer to have a QueryContext on the UmbracoHelper (we can still keep the Content and TypedContent, etc... - // methods, but these would just wrap the QueryContext attached to it. Other methods on the QueryContext will be - // ContentByType, TypedContentByType, etc... then we can also have extension methods like below for strongly typed - // access like: GetAllHomes, GetAllNewsArticles, etc... - - //public class QueryDataContext - //{ - // private readonly IPublishedContentStore _contentStore; - // private readonly UmbracoContext _umbracoContext; - - // internal QueryDataContext(IPublishedContentStore contentStore, UmbracoContext umbracoContext) - // { - // _contentStore = contentStore; - // _umbracoContext = umbracoContext; - // } - - // public IPublishedContent GetDocumentById(int id) - // { - // return _contentStore.GetDocumentById(_umbracoContext, id); - // } - - // public IEnumerable GetByDocumentType(string alias) - // { - - // } - //} - - public enum TraversalType - { - Children, - Ancestors, - AncestorsOrSelf, - Descendants, - DescendantsOrSelf - } - - public static class StronglyTypedQueryExtensions - { - private static IEnumerable GetEnumerable(this IPublishedContent content, string docTypeAlias, TraversalType traversalType = TraversalType.Children) - { - switch (traversalType) - { - case TraversalType.Children: - return content.Children.Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.Ancestors: - return content.Ancestors().Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.AncestorsOrSelf: - return content.AncestorsOrSelf().Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.Descendants: - return content.Descendants().Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.DescendantsOrSelf: - return content.DescendantsOrSelf().Where(x => x.DocumentTypeAlias == docTypeAlias); - default: - throw new ArgumentOutOfRangeException("traversalType"); - } - } - - private static T AsDocumentType(this IPublishedContent content, string alias, Func creator) - { - if (content.DocumentTypeAlias == alias) return creator(content); - throw new InvalidOperationException("The content type cannot be cast to " + typeof(T).FullName + " since it is type: " + content.DocumentTypeAlias); - } - - public static HomeContentItem AsHome(this IPublishedContent content) - { - return content.AsDocumentType("Home", x => new HomeContentItem(x)); - } - - public static IEnumerable Homes(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("Home", traversalType).Select(x => new HomeContentItem(x)); - } - - public static NewsArticleContentItem AsNewsArticle(this IPublishedContent content) - { - return content.AsDocumentType("NewsArticle", x => new NewsArticleContentItem(x)); - } - - public static IEnumerable NewsArticles(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("NewsArticle", traversalType).Select(x => new NewsArticleContentItem(x)); - } - - public static NewsLandingPageContentItem AsNewsLandingPage(this IPublishedContent content) - { - return content.AsDocumentType("NewsLandingPage", x => new NewsLandingPageContentItem(x)); - } - - public static IEnumerable NewsLandingPages(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("NewsLandingPage", traversalType).Select(x => new NewsLandingPageContentItem(x)); - } - - public static ContentPageContentItem AsContentPage(this IPublishedContent content) - { - return content.AsDocumentType("ContentPage", x => new ContentPageContentItem(x)); - } - - public static IEnumerable ContentPages(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("ContentPage", traversalType).Select(x => new ContentPageContentItem(x)); - } - } - - public class PublishedContentWrapper : IPublishedContent, IOwnerCollectionAware - { - protected IPublishedContent WrappedContent { get; private set; } - - public PublishedContentWrapper(IPublishedContent content) - { - WrappedContent = content; - } - - public string Url - { - get { return WrappedContent.Url; } - } - - public PublishedItemType ItemType - { - get { return WrappedContent.ItemType; } - } - - public IPublishedContent Parent - { - get { return WrappedContent.Parent; } - } - - public int Id - { - get { return WrappedContent.Id; } - } - public int TemplateId - { - get { return WrappedContent.TemplateId; } - } - public int SortOrder - { - get { return WrappedContent.SortOrder; } - } - public string Name - { - get { return WrappedContent.Name; } - } - public string UrlName - { - get { return WrappedContent.UrlName; } - } - public string DocumentTypeAlias - { - get { return WrappedContent.DocumentTypeAlias; } - } - public int DocumentTypeId - { - get { return WrappedContent.DocumentTypeId; } - } - public string WriterName - { - get { return WrappedContent.WriterName; } - } - public string CreatorName - { - get { return WrappedContent.CreatorName; } - } - public int WriterId - { - get { return WrappedContent.WriterId; } - } - public int CreatorId - { - get { return WrappedContent.CreatorId; } - } - public string Path - { - get { return WrappedContent.Path; } - } - public DateTime CreateDate - { - get { return WrappedContent.CreateDate; } - } - public DateTime UpdateDate - { - get { return WrappedContent.UpdateDate; } - } - public Guid Version - { - get { return WrappedContent.Version; } - } - public int Level - { - get { return WrappedContent.Level; } - } - public ICollection Properties - { - get { return WrappedContent.Properties; } - } - - public object this[string propertyAlias] - { - get { return GetProperty(propertyAlias).Value; } - } - - public IEnumerable Children - { - get { return WrappedContent.Children; } - } - public IPublishedContentProperty GetProperty(string alias) - { - return WrappedContent.GetProperty(alias); - } - - private IEnumerable _ownersCollection; - - /// - /// Need to get/set the owner collection when an item is returned from the result set of a query - /// - /// - /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 - /// - IEnumerable IOwnerCollectionAware.OwnersCollection - { - get - { - var publishedContentBase = WrappedContent as IOwnerCollectionAware; - if (publishedContentBase != null) - { - return publishedContentBase.OwnersCollection; - } - - //if the owners collection is null, we'll default to it's siblings - if (_ownersCollection == null) - { - //get the root docs if parent is null - _ownersCollection = this.Siblings(); - } - return _ownersCollection; - } - set - { - var publishedContentBase = WrappedContent as IOwnerCollectionAware; - if (publishedContentBase != null) - { - publishedContentBase.OwnersCollection = value; - } - else - { - _ownersCollection = value; - } - } - } - } - - public partial class HomeContentItem : ContentPageContentItem - { - public HomeContentItem(IPublishedContent content) - : base(content) - { - } - - public string SiteName - { - get { return WrappedContent.GetPropertyValue("siteName"); } - } - public string SiteDescription - { - get { return WrappedContent.GetPropertyValue("siteDescription"); } - } - } - - public partial class NewsLandingPageContentItem : ContentPageContentItem - { - public NewsLandingPageContentItem(IPublishedContent content) - : base(content) - { - } - - public string PageTitle - { - get { return WrappedContent.GetPropertyValue("pageTitle"); } - } - } - - public partial class NewsArticleContentItem : PublishedContentWrapper - { - public NewsArticleContentItem(IPublishedContent content) - : base(content) - { - } - - public string ArticleContent - { - get { return WrappedContent.GetPropertyValue("articleContent"); } - } - public DateTime ArticleDate - { - get { return WrappedContent.GetPropertyValue("articleDate"); } - } - public string ArticleAuthor - { - get { return WrappedContent.GetPropertyValue("articleAuthor"); } - } - } - - public partial class ContentPageContentItem : PublishedContentWrapper - { - public ContentPageContentItem(IPublishedContent content) - : base(content) - { - } - - public string BodyContent - { - get { return WrappedContent.GetPropertyValue("bodyContent"); } - } - } - - #endregion -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs index d1abe13d54..f8658734cc 100644 --- a/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs +++ b/src/Umbraco.Tests/PublishedCache/PublishedContentCacheTests.cs @@ -4,6 +4,9 @@ using System.Xml; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.PropertyEditors; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -15,11 +18,12 @@ using umbraco.BusinessLogic; namespace Umbraco.Tests.PublishedCache { [TestFixture] - public class PublishContentCacheTests + public class PublishContentCacheTests : BaseWebTest { private FakeHttpContextFactory _httpContextFactory; private UmbracoContext _umbracoContext; private ContextualPublishedContentCache _cache; + private XmlDocument _xml; private string GetLegacyXml() { @@ -67,26 +71,20 @@ namespace Umbraco.Tests.PublishedCache } [SetUp] - public void SetUp() - { - TestHelper.SetupLog4NetForTests(); + public override void Initialize() + { + base.Initialize(); - //create the app context - ApplicationContext.Current = new ApplicationContext(false); - - _httpContextFactory = new FakeHttpContextFactory("~/Home"); - //ensure the StateHelper is using our custom context - StateHelper.HttpContext = _httpContextFactory.HttpContext; + _httpContextFactory = new FakeHttpContextFactory("~/Home"); + //ensure the StateHelper is using our custom context + StateHelper.HttpContext = _httpContextFactory.HttpContext; UmbracoSettings.UseLegacyXmlSchema = false; + _xml = new XmlDocument(); + _xml.LoadXml(GetXml()); var cache = new PublishedContentCache { - GetXmlDelegate = (context, preview) => - { - var doc = new XmlDocument(); - doc.LoadXml(GetXml()); - return doc; - } + GetXmlDelegate = (context, preview) => _xml }; _umbracoContext = new UmbracoContext( @@ -95,30 +93,22 @@ namespace Umbraco.Tests.PublishedCache new PublishedCaches(cache, new PublishedMediaCache())); _cache = _umbracoContext.ContentCache; - } + } - private void SetupForLegacy() + private void SetupForLegacy() { - Umbraco.Core.Configuration.UmbracoSettings.UseLegacyXmlSchema = true; - - var cache = _umbracoContext.ContentCache.InnerCache as PublishedContentCache; - if (cache == null) throw new Exception("Unsupported IPublishedContentCache, only the Xml one is supported."); - - cache.GetXmlDelegate = (context, preview) => - { - var doc = new XmlDocument(); - doc.LoadXml(GetLegacyXml()); - return doc; - }; + UmbracoSettings.UseLegacyXmlSchema = true; + _xml = new XmlDocument(); + _xml.LoadXml(GetLegacyXml()); } - [TearDown] - public void TearDown() - { - UmbracoSettings.Reset(); - } + protected override void FreezeResolution() + { + PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); + base.FreezeResolution(); + } - [Test] + [Test] public void Has_Content_LegacySchema() { SetupForLegacy(); diff --git a/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs index a1d922e86d..b2ac31c91a 100644 --- a/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/PublishedCache/PublishedMediaCacheTests.cs @@ -6,6 +6,7 @@ using Examine; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.PublishedContent; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -16,22 +17,15 @@ using umbraco.BusinessLogic; namespace Umbraco.Tests.PublishedCache { [TestFixture] - public class PublishMediaCacheTests : PublishedContentTestBase + public class PublishMediaCacheTests : BaseWebTest { - public override void Initialize() - { - base.Initialize(); - } - - - - - public override void TearDown() - { - base.TearDown(); - } - - [Test] + protected override void FreezeResolution() + { + PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); + base.FreezeResolution(); + } + + [Test] public void Get_Root_Docs() { var user = new User(0); diff --git a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs index e0321e08d5..2b8f6dd958 100644 --- a/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs +++ b/src/Umbraco.Tests/PublishedContent/DynamicDocumentTestsBase.cs @@ -6,6 +6,8 @@ using System.Web; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Dynamics; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.PublishedContent @@ -18,7 +20,36 @@ namespace Umbraco.Tests.PublishedContent get { return DatabaseBehavior.NoDatabasePerFixture; } } - protected override string GetXmlContent(int templateId) + public override void Initialize() + { + // required so we can access property.Value + //PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver(); + + base.Initialize(); + + // need to specify a custom callback for unit tests + // AutoPublishedContentTypes generates properties automatically + // when they are requested, but we must declare those that we + // explicitely want to be here... + + var propertyTypes = new[] + { + // AutoPublishedContentType will auto-generate other properties + new PublishedPropertyType("umbracoNaviHide", Guid.Empty, 0, 0), + new PublishedPropertyType("selectedNodes", Guid.Empty, 0, 0), + new PublishedPropertyType("umbracoUrlAlias", Guid.Empty, 0, 0), + new PublishedPropertyType("content", Guid.Parse(Constants.PropertyEditors.TinyMCEv3), 0, 0), + new PublishedPropertyType("testRecursive", Guid.Empty, 0, 0), + new PublishedPropertyType("siteTitle", Guid.Empty, 0, 0), + new PublishedPropertyType("creatorName", Guid.Empty, 0, 0), + new PublishedPropertyType("blah", Guid.Empty, 0, 0), // ugly error when that one is missing... + }; + var type = new AutoPublishedContentType(0, "anything", propertyTypes); + PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; + + } + + protected override string GetXmlContent(int templateId) { return @" + + // need to specify a custom callback for unit tests + // AutoPublishedContentTypes generates properties automatically + var type = new AutoPublishedContentType(0, "anything", new PublishedPropertyType[] {}); + PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; + + // need to specify a different callback for testing + PublishedContentExtensions.GetPropertyAliasesAndNames = s => { var userFields = new Dictionary() { @@ -118,12 +125,13 @@ namespace Umbraco.Tests.PublishedContent private IPublishedContent GetContent(bool createChildren, int indexVals) { + var contentTypeAlias = createChildren ? "Parent" : "Child"; var d = new TestPublishedContent { CreateDate = DateTime.Now, CreatorId = 1, CreatorName = "Shannon", - DocumentTypeAlias = createChildren? "Parent" : "Child", + DocumentTypeAlias = contentTypeAlias, DocumentTypeId = 2, Id = 3, SortOrder = 4, @@ -137,8 +145,8 @@ namespace Umbraco.Tests.PublishedContent WriterName = "Shannon", Parent = null, Level = 1, - Properties = new Collection( - new List() + Properties = new Collection( + new List() { new PropertyResult("property1", "value" + indexVals, Guid.NewGuid(), PropertyResultType.UserProperty), new PropertyResult("property2", "value" + (indexVals + 1), Guid.NewGuid(), PropertyResultType.UserProperty) @@ -166,50 +174,80 @@ namespace Umbraco.Tests.PublishedContent return d; } + // fixme - why can't we just use SolidPublishedContent here? + private class TestPublishedContent : IPublishedContent + { + public string Url { get; set; } + public PublishedItemType ItemType { get; set; } - private class TestPublishedContent : IPublishedContent - { - public string Url { get; set; } - public PublishedItemType ItemType { get; set; } + IPublishedContent IPublishedContent.Parent + { + get { return Parent; } + } - IPublishedContent IPublishedContent.Parent - { - get { return Parent; } - } - IEnumerable IPublishedContent.Children - { - get { return Children; } - } - public IPublishedContent Parent { get; set; } - public int Id { get; set; } - public int TemplateId { get; set; } - public int SortOrder { get; set; } - public string Name { get; set; } - public string UrlName { get; set; } - public string DocumentTypeAlias { get; set; } - public int DocumentTypeId { get; set; } - public string WriterName { get; set; } - public string CreatorName { get; set; } - public int WriterId { get; set; } - public int CreatorId { get; set; } - public string Path { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public Guid Version { get; set; } - public int Level { get; set; } - public ICollection Properties { get; set; } + IEnumerable IPublishedContent.Children + { + get { return Children; } + } - public object this[string propertyAlias] - { - get { return GetProperty(propertyAlias).Value; } - } + public IPublishedContent Parent { get; set; } + public int Id { get; set; } + public int TemplateId { get; set; } + public int SortOrder { get; set; } + public string Name { get; set; } + public string UrlName { get; set; } + public string DocumentTypeAlias { get; set; } + public int DocumentTypeId { get; set; } + public string WriterName { get; set; } + public string CreatorName { get; set; } + public int WriterId { get; set; } + public int CreatorId { get; set; } + public string Path { get; set; } + public DateTime CreateDate { get; set; } + public DateTime UpdateDate { get; set; } + public Guid Version { get; set; } + public int Level { get; set; } + public bool IsDraft { get; set; } + public int GetIndex() { throw new NotImplementedException();} + + public ICollection Properties { get; set; } - public IEnumerable Children { get; set; } - public IPublishedContentProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - } + public object this[string propertyAlias] + { + get { return GetProperty(propertyAlias).RawValue; } // fixme - why not just .Value? + } + public IEnumerable Children { get; set; } + + public IPublishedProperty GetProperty(string alias) + { + return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + + public IPublishedProperty GetProperty(string alias, bool recurse) + { + var property = GetProperty(alias); + if (recurse == false) return property; + + IPublishedContent content = this; + while (content != null && (property == null || property.HasValue == false)) + { + content = content.Parent; + property = content == null ? null : content.GetProperty(alias); + } + + return property; + } + + public IEnumerable ContentSet + { + get { throw new NotImplementedException(); } + } + + public PublishedContentType ContentType + { + get { throw new NotImplementedException(); } + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs new file mode 100644 index 0000000000..4477410864 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -0,0 +1,227 @@ +using System.Linq; +using System.Collections.ObjectModel; +using Lucene.Net.Documents; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.ObjectResolution; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web; +using Umbraco.Tests.TestHelpers; +using umbraco.BusinessLogic; + +namespace Umbraco.Tests.PublishedContent +{ + [TestFixture] + public class PublishedContentMoreTests + { + // read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet + // and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx + // and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx + + private PluginManager _pluginManager; + + [SetUp] + public void Setup() + { + // this is so the model factory looks into the test assembly + _pluginManager = PluginManager.Current; + PluginManager.Current = new PluginManager(false) + { + AssembliesToScan = _pluginManager.AssembliesToScan + .Union(new[] { typeof (PublishedContentMoreTests).Assembly}) + }; + + PropertyValueConvertersResolver.Current = + new PropertyValueConvertersResolver(); + PublishedContentModelFactoryResolver.Current = + new PublishedContentModelFactoryResolver(); + Resolution.Freeze(); + + var caches = CreatePublishedContent(); + + ApplicationContext.Current = new ApplicationContext(false) { IsReady = true }; + var factory = new FakeHttpContextFactory("http://umbraco.local/"); + StateHelper.HttpContext = factory.HttpContext; + var context = new UmbracoContext( + factory.HttpContext, + ApplicationContext.Current, + caches); + UmbracoContext.Current = context; + } + + [TearDown] + public void TearDown() + { + PluginManager.Current = _pluginManager; + ApplicationContext.Current.DisposeIfDisposable(); + ApplicationContext.Current = null; + } + + [Test] + public void First() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + Assert.AreEqual("Content 1", content.Name); + } + + [Test] + public void DefaultContentSetIsSiblings() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + Assert.AreEqual(0, content.Index()); + Assert.IsTrue(content.IsFirst()); + } + + [Test] + public void RunOnLatestContentSet() + { + // get first content + var content = UmbracoContext.Current.ContentCache.GetAtRoot().First(); + var id = content.Id; + Assert.IsTrue(content.IsFirst()); + + // reverse => should be last, but set has not changed => still first + content = UmbracoContext.Current.ContentCache.GetAtRoot().Reverse().First(x => x.Id == id); + Assert.IsTrue(content.IsFirst()); + Assert.IsFalse(content.IsLast()); + + // reverse + new set => now it's last + content = UmbracoContext.Current.ContentCache.GetAtRoot().Reverse().ToContentSet().First(x => x.Id == id); + Assert.IsFalse(content.IsFirst()); + Assert.IsTrue(content.IsLast()); + + // reverse that set => should be first, but no new set => still last + content = UmbracoContext.Current.ContentCache.GetAtRoot().Reverse().ToContentSet().Reverse().First(x => x.Id == id); + Assert.IsFalse(content.IsFirst()); + Assert.IsTrue(content.IsLast()); + } + + [Test] + public void Distinct() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot() + .Distinct() + .Distinct() + .ToContentSet() + .First(); + + Assert.AreEqual("Content 1", content.Name); + Assert.IsTrue(content.IsFirst()); + Assert.IsFalse(content.IsLast()); + + content = content.Next(); + Assert.AreEqual("Content 2", content.Name); + Assert.IsFalse(content.IsFirst()); + Assert.IsFalse(content.IsLast()); + + content = content.Next(); + Assert.AreEqual("Content 2Sub", content.Name); + Assert.IsFalse(content.IsFirst()); + Assert.IsTrue(content.IsLast()); + } + + [Test] + public void Position() + { + var content = UmbracoContext.Current.ContentCache.GetAtRoot() + .Where(x => x.GetPropertyValue("prop1") == 1234) + .ToContentSet() + .ToArray(); + + Assert.IsTrue(content.First().IsFirst()); + Assert.IsFalse(content.First().IsLast()); + Assert.IsFalse(content.First().Next().IsFirst()); + Assert.IsFalse(content.First().Next().IsLast()); + Assert.IsFalse(content.First().Next().Next().IsFirst()); + Assert.IsTrue(content.First().Next().Next().IsLast()); + } + + static SolidPublishedCaches CreatePublishedContent() + { + var caches = new SolidPublishedCaches(); + var cache = caches.ContentCache; + + var props = new[] + { + new PublishedPropertyType("prop1", System.Guid.Empty, 1, 1), + }; + + var contentType1 = new PublishedContentType(1, "ContentType1", props); + var contentType2 = new PublishedContentType(2, "ContentType2", props); + var contentType2s = new PublishedContentType(3, "ContentType2Sub", props); + + cache.Add(new SolidPublishedContent(contentType1) + { + Id = 1, + SortOrder = 0, + Name = "Content 1", + UrlName = "content-1", + Path = "/1", + Level = 1, + Url = "/content-1", + ParentId = -1, + ChildIds = new int[] {}, + Properties = new Collection + { + new SolidPublishedProperty + { + Alias = "prop1", + HasValue = true, + Value = 1234, + RawValue = "1234" + } + } + }); + + cache.Add(new SolidPublishedContent(contentType2) + { + Id = 2, + SortOrder = 1, + Name = "Content 2", + UrlName = "content-2", + Path = "/2", + Level = 1, + Url = "/content-2", + ParentId = -1, + ChildIds = new int[] { }, + Properties = new Collection + { + new SolidPublishedProperty + { + Alias = "prop1", + HasValue = true, + Value = 1234, + RawValue = "1234" + } + } + }); + + cache.Add(new SolidPublishedContent(contentType2s) + { + Id = 3, + SortOrder = 2, + Name = "Content 2Sub", + UrlName = "content-2sub", + Path = "/3", + Level = 1, + Url = "/content-2sub", + ParentId = -1, + ChildIds = new int[] { }, + Properties = new Collection + { + new SolidPublishedProperty + { + Alias = "prop1", + HasValue = true, + Value = 1234, + RawValue = "1234" + } + } + }); + + return caches; + } + } +} diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index b4e44dc216..de887c8fd7 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -1,7 +1,10 @@ using System; using System.IO; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -19,16 +22,14 @@ namespace Umbraco.Tests.PublishedContent { base.Initialize(); - //need to specify a custom callback for unit tests - PublishedContentHelper.GetDataTypeCallback = (docTypeAlias, propertyAlias) => + // need to specify a custom callback for unit tests + var propertyTypes = new[] { - if (propertyAlias.InvariantEquals("content")) - { - //return the rte type id - return Guid.Parse(Constants.PropertyEditors.TinyMCEv3); - } - return Guid.Empty; + // AutoPublishedContentType will auto-generate other properties + new PublishedPropertyType("content", Guid.Parse(Constants.PropertyEditors.TinyMCEv3), 0, 0), }; + var type = new AutoPublishedContentType(0, "anything", propertyTypes); + PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; var rCtx = GetRoutingContext("/test", 1234); UmbracoContext.Current = rCtx.UmbracoContext; @@ -37,17 +38,20 @@ namespace Umbraco.Tests.PublishedContent protected override void FreezeResolution() { - PropertyEditorValueConvertersResolver.Current = new PropertyEditorValueConvertersResolver( + PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver( new[] { - typeof(DatePickerPropertyEditorValueConverter), - typeof(TinyMcePropertyEditorValueConverter), - typeof(YesNoPropertyEditorValueConverter) + typeof(DatePickerValueConverter), + typeof(TinyMceValueConverter), + typeof(YesNoValueConverter) }); PublishedCachesResolver.Current = new PublishedCachesResolver(new PublishedCaches( new PublishedContentCache(), new PublishedMediaCache())); + if (PublishedContentModelFactoryResolver.HasCurrent == false) + PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); + base.FreezeResolution(); } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs new file mode 100644 index 0000000000..a2cb4c5dac --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; +using Umbraco.Web.PublishedCache; + +namespace Umbraco.Tests.PublishedContent +{ + class SolidPublishedCaches : IPublishedCaches + { + public readonly SolidPublishedContentCache ContentCache = new SolidPublishedContentCache(); + + public ContextualPublishedContentCache CreateContextualContentCache(UmbracoContext context) + { + return new ContextualPublishedContentCache(ContentCache, context); + } + + public ContextualPublishedMediaCache CreateContextualMediaCache(UmbracoContext context) + { + return null; + } + } + + class SolidPublishedContentCache : IPublishedContentCache + { + private readonly Dictionary _content = new Dictionary(); + + public void Add(SolidPublishedContent content) + { + _content[content.Id] = PublishedContentModelFactory.CreateModel(content); + } + + public void Clear() + { + _content.Clear(); + } + + public void ContentHasChanged(UmbracoContext umbracoContext) + { + throw new NotImplementedException(); + } + + public IPublishedContent GetByRoute(UmbracoContext umbracoContext, bool preview, string route, bool? hideTopLevelNode = null) + { + throw new NotImplementedException(); + } + + public string GetRouteById(UmbracoContext umbracoContext, bool preview, int contentId) + { + throw new NotImplementedException(); + } + + public IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int contentId) + { + return _content.ContainsKey(contentId) ? _content[contentId] : null; + } + + public IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) + { + return _content.Values.Where(x => x.Parent == null); + } + + public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, System.Xml.XPath.XPathExpression xpath, Core.Xml.XPathVariable[] vars) + { + throw new NotImplementedException(); + } + + public System.Xml.XPath.XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview) + { + throw new NotImplementedException(); + } + + public bool XPathNavigatorIsNavigable + { + get { throw new NotImplementedException(); } + } + + public bool HasContent(UmbracoContext umbracoContext, bool preview) + { + return _content.Count > 0; + } + } + + class SolidPublishedContent : IPublishedContent + { + #region Constructor + + public SolidPublishedContent(PublishedContentType contentType) + { + // initialize boring stuff + TemplateId = 0; + WriterName = CreatorName = string.Empty; + WriterId = CreatorId = 0; + CreateDate = UpdateDate = DateTime.Now; + Version = Guid.Empty; + IsDraft = false; + + ContentType = contentType; + DocumentTypeAlias = contentType.Alias; + DocumentTypeId = contentType.Id; + } + + #endregion + + #region Content + + public int Id { get; set; } + public int TemplateId { get; set; } + public int SortOrder { get; set; } + public string Name { get; set; } + public string UrlName { get; set; } + public string DocumentTypeAlias { get; private set; } + public int DocumentTypeId { get; private set; } + public string WriterName { get; set; } + public string CreatorName { get; set; } + public int WriterId { get; set; } + public int CreatorId { get; set; } + public string Path { get; set; } + public DateTime CreateDate { get; set; } + public DateTime UpdateDate { get; set; } + public Guid Version { get; set; } + public int Level { get; set; } + public string Url { get; set; } + + public PublishedItemType ItemType { get { return PublishedItemType.Content; } } + public bool IsDraft { get; set; } + + public int GetIndex() + { + var index = this.Siblings().FindIndex(x => x.Id == Id); + if (index < 0) + throw new IndexOutOfRangeException(""); // fixme + return index; + } + + #endregion + + #region Tree + + public int ParentId { get; set; } + public IEnumerable ChildIds { get; set; } + + public IPublishedContent Parent { get { return UmbracoContext.Current.ContentCache.GetById(ParentId); } } + public IEnumerable Children { get { return ChildIds.Select(id => UmbracoContext.Current.ContentCache.GetById(id)); } } + + #endregion + + #region ContentSet + + public IEnumerable ContentSet { get { return this.Siblings(); } } + + #endregion + + #region ContentType + + public PublishedContentType ContentType { get; private set; } + + #endregion + + #region Properties + + public ICollection Properties { get; set; } + + public IPublishedProperty GetProperty(string alias) + { + return Properties.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); + } + + public IPublishedProperty GetProperty(string alias, bool recurse) + { + var property = GetProperty(alias); + if (recurse == false) return property; + + IPublishedContent content = this; + while (content != null && (property == null || property.HasValue == false)) + { + content = content.Parent; + property = content == null ? null : content.GetProperty(alias); + } + + return property; + } + + public object this[string alias] + { + get + { + var property = GetProperty(alias); + return property == null || property.HasValue == false ? null : property.Value; + } + } + + #endregion + } + + class SolidPublishedProperty : IPublishedProperty + { + public SolidPublishedProperty() + { + // initialize boring stuff + } + + public string Alias { get; set; } + public object RawValue { get; set; } + public object Value { get; set; } + public bool HasValue { get; set; } + public object XPathValue { get; set; } + } + + class PublishedContentStrong1 : PublishedContentExtended + { + public PublishedContentStrong1(IPublishedContent content) + : base(content) + { } + + public int StrongValue { get { return (int)this["strongValue"]; } } + } + + class PublishedContentStrong1Sub : PublishedContentStrong1 + { + public PublishedContentStrong1Sub(IPublishedContent content) + : base(content) + { } + + public int AnotherValue { get { return (int)this["anotherValue"]; } } + } + + class PublishedContentStrong2 : PublishedContentExtended + { + public PublishedContentStrong2(IPublishedContent content) + : base(content) + { } + + public int StrongValue { get { return (int)this["strongValue"]; } } + } + + class AutoPublishedContentType : PublishedContentType + { + private static readonly PublishedPropertyType Default = new PublishedPropertyType("*", Guid.Empty, 0, 0); + + public AutoPublishedContentType(int id, string alias, IEnumerable propertyTypes) + : base(id, alias, propertyTypes) + { } + + public override PublishedPropertyType GetPropertyType(string alias) + { + var propertyType = base.GetPropertyType(alias); + return propertyType ?? Default; + } + } +} diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 8582a894d5..06ac9dc824 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -1,8 +1,12 @@ +using System; +using System.Collections.Generic; using System.Linq; using System.Web; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -19,7 +23,32 @@ namespace Umbraco.Tests.PublishedContent get { return DatabaseBehavior.NoDatabasePerFixture; } } - protected override string GetXmlContent(int templateId) + public override void Initialize() + { + // required so we can access property.Value + //PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver(); + + base.Initialize(); + + // need to specify a custom callback for unit tests + // AutoPublishedContentTypes generates properties automatically + // when they are requested, but we must declare those that we + // explicitely want to be here... + + var propertyTypes = new[] + { + // AutoPublishedContentType will auto-generate other properties + new PublishedPropertyType("umbracoNaviHide", Guid.Parse(Constants.PropertyEditors.TrueFalse), 0, 0), + new PublishedPropertyType("selectedNodes", Guid.Empty, 0, 0), + new PublishedPropertyType("umbracoUrlAlias", Guid.Empty, 0, 0), + new PublishedPropertyType("content", Guid.Parse(Constants.PropertyEditors.TinyMCEv3), 0, 0), + new PublishedPropertyType("testRecursive", Guid.Empty, 0, 0), + }; + var type = new AutoPublishedContentType(0, "anything", propertyTypes); + PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; + } + + protected override string GetXmlContent(int templateId) { return @" x.IsVisible())) + var items = doc + .Children + .Where(x => x.IsVisible()) + .ToContentSet(); + + Assert.AreEqual(3, items.Count()); + + foreach (var d in items) { - if (d.Id != 1178) + switch (d.Id) { - Assert.IsFalse(d.IsLast()); - } - else - { - Assert.IsTrue(d.IsLast()); + case 1174: + Assert.IsTrue(d.IsFirst()); + Assert.IsFalse(d.IsLast()); + break; + case 1177: + Assert.IsFalse(d.IsFirst()); + Assert.IsFalse(d.IsLast()); + break; + case 1178: + Assert.IsFalse(d.IsFirst()); + Assert.IsTrue(d.IsLast()); + break; + default: + Assert.Fail("Invalid id."); + break; } } } @@ -143,15 +191,17 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); - foreach (var d in doc.Children.Take(3)) + var items = doc.Children.Take(3).ToContentSet(); + + foreach (var item in items) { - if (d.Id != 1178) + if (item.Id != 1178) { - Assert.IsFalse(d.IsLast()); + Assert.IsFalse(item.IsLast()); } else { - Assert.IsTrue(d.IsLast()); + Assert.IsTrue(item.IsLast()); } } } @@ -179,16 +229,19 @@ namespace Umbraco.Tests.PublishedContent { var doc = GetNode(1173); + var items = doc.Children + .Concat(new[] { GetNode(1175), GetNode(4444) }) + .ToContentSet(); - foreach (var d in doc.Children.Concat(new[] { GetNode(1175), GetNode(4444) })) + foreach (var item in items) { - if (d.Id != 4444) + if (item.Id != 4444) { - Assert.IsFalse(d.IsLast()); + Assert.IsFalse(item.IsLast()); } else { - Assert.IsTrue(d.IsLast()); + Assert.IsTrue(item.IsLast()); } } } @@ -234,15 +287,15 @@ namespace Umbraco.Tests.PublishedContent var doc = GetNode(1173); var propVal = doc.GetPropertyValue("content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal.GetType())); + Assert.IsInstanceOf(typeof(IHtmlString), propVal); Assert.AreEqual("
This is some content
", propVal.ToString()); var propVal2 = doc.GetPropertyValue("content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal2.GetType())); - Assert.AreEqual("
This is some content
", propVal2.ToString()); + Assert.IsInstanceOf(typeof(IHtmlString), propVal2); + Assert.AreEqual("
This is some content
", propVal2.ToString()); var propVal3 = doc.GetPropertyValue("Content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal3.GetType())); + Assert.IsInstanceOf(typeof(IHtmlString), propVal3); Assert.AreEqual("
This is some content
", propVal3.ToString()); } @@ -361,7 +414,6 @@ namespace Umbraco.Tests.PublishedContent } - [Test] public void HasValue() { @@ -374,14 +426,12 @@ namespace Umbraco.Tests.PublishedContent Assert.IsFalse(noValue); } - [Test] public void Ancestors_Where_Visible() { var doc = GetNode(1174); var whereVisible = doc.Ancestors().Where("Visible"); - Assert.AreEqual(1, whereVisible.Count()); } @@ -396,7 +446,6 @@ namespace Umbraco.Tests.PublishedContent Assert.IsTrue(visible.IsVisible()); } - [Test] public void Ancestor_Or_Self() { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index efa50075bf..8919afe0ba 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -76,7 +76,7 @@ namespace Umbraco.Tests.PublishedContent var mType = MockedContentTypes.CreateImageMediaType(); //lets add an RTE to this mType.PropertyGroups.First().PropertyTypes.Add( - new PropertyType(new Guid(), DataTypeDatabaseType.Nvarchar) + new PropertyType(Guid.Parse(Constants.PropertyEditors.TinyMCEv3), DataTypeDatabaseType.Nvarchar) { Alias = "content", Name = "Rich Text", @@ -90,15 +90,15 @@ namespace Umbraco.Tests.PublishedContent var publishedMedia = GetNode(media.Id); var propVal = publishedMedia.GetPropertyValue("content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal.GetType())); + Assert.IsInstanceOf(propVal); Assert.AreEqual("
This is some content
", propVal.ToString()); var propVal2 = publishedMedia.GetPropertyValue("content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal2.GetType())); + Assert.IsInstanceOf(propVal2); Assert.AreEqual("
This is some content
", propVal2.ToString()); var propVal3 = publishedMedia.GetPropertyValue("Content"); - Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(propVal3.GetType())); + Assert.IsInstanceOf(propVal3); Assert.AreEqual("
This is some content
", propVal3.ToString()); } diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs index d606be3c5b..f5df693982 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels @@ -28,14 +29,11 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels /// by casting the typed model to IPublishedContent, so the properties doesn't show up by default: /// ie. ((IPublishedContent)textpage).Url /// - public abstract class TypedModelBase : IPublishedContent + public abstract class TypedModelBase : PublishedContentWrapped // IPublishedContent { - private readonly IPublishedContent _publishedContent; - protected TypedModelBase(IPublishedContent publishedContent) - { - _publishedContent = publishedContent; - } + : base(publishedContent) + { } protected readonly Func Property = MethodBase.GetCurrentMethod; protected readonly Func ContentTypeAlias = MethodBase.GetCurrentMethod; @@ -50,7 +48,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels protected T Resolve(string propertyTypeAlias) { - return _publishedContent.GetPropertyValue(propertyTypeAlias); + return Content.GetPropertyValue(propertyTypeAlias); } protected T Resolve(MethodBase methodBase, T ifCannotConvert) @@ -61,7 +59,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels protected T Resolve(string propertyTypeAlias, T ifCannotConvert) { - return _publishedContent.GetPropertyValue(propertyTypeAlias, false, ifCannotConvert); + return Content.GetPropertyValue(propertyTypeAlias, false, ifCannotConvert); } protected T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) @@ -72,7 +70,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels protected T Resolve(string propertyTypeAlias, bool recursive, T ifCannotConvert) { - return _publishedContent.GetPropertyValue(propertyTypeAlias, recursive, ifCannotConvert); + return Content.GetPropertyValue(propertyTypeAlias, recursive, ifCannotConvert); } #endregion @@ -83,7 +81,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels if (constructorInfo == null) throw new Exception("No valid constructor found"); - return (T) constructorInfo.Invoke(new object[] {_publishedContent.Parent}); + return (T) constructorInfo.Invoke(new object[] {Content.Parent}); } protected IEnumerable Children(MethodBase methodBase) where T : TypedModelBase @@ -100,7 +98,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels string singularizedDocTypeAlias = docTypeAlias.ToSingular(); - return _publishedContent.Children.Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + return Content.Children.Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) .Select(x => (T)constructorInfo.Invoke(new object[] { x })); } @@ -118,7 +116,7 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels string singularizedDocTypeAlias = docTypeAlias.ToSingular(); - return _publishedContent.Ancestors().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + return Content.Ancestors().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) .Select(x => (T)constructorInfo.Invoke(new object[] { x })); } @@ -136,42 +134,10 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels string singularizedDocTypeAlias = docTypeAlias.ToSingular(); - return _publishedContent.Descendants().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + return Content.Descendants().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) .Select(x => (T)constructorInfo.Invoke(new object[] { x })); } #endregion - - #region IPublishedContent - int IPublishedContent.Id { get { return _publishedContent.Id; } } - int IPublishedContent.TemplateId { get { return _publishedContent.TemplateId; } } - int IPublishedContent.SortOrder { get { return _publishedContent.SortOrder; } } - string IPublishedContent.Name { get { return _publishedContent.Name; } } - string IPublishedContent.UrlName { get { return _publishedContent.UrlName; } } - string IPublishedContent.DocumentTypeAlias { get { return _publishedContent.DocumentTypeAlias; } } - int IPublishedContent.DocumentTypeId { get { return _publishedContent.DocumentTypeId; } } - string IPublishedContent.WriterName { get { return _publishedContent.WriterName; } } - string IPublishedContent.CreatorName { get { return _publishedContent.CreatorName; } } - int IPublishedContent.WriterId { get { return _publishedContent.WriterId; } } - int IPublishedContent.CreatorId { get { return _publishedContent.CreatorId; } } - string IPublishedContent.Path { get { return _publishedContent.Path; } } - DateTime IPublishedContent.CreateDate { get { return _publishedContent.CreateDate; } } - DateTime IPublishedContent.UpdateDate { get { return _publishedContent.UpdateDate; } } - Guid IPublishedContent.Version { get { return _publishedContent.Version; } } - int IPublishedContent.Level { get { return _publishedContent.Level; } } - string IPublishedContent.Url { get { return _publishedContent.Url; } } - PublishedItemType IPublishedContent.ItemType { get { return _publishedContent.ItemType; } } - IPublishedContent IPublishedContent.Parent { get { return _publishedContent.Parent; } } - IEnumerable IPublishedContent.Children { get { return _publishedContent.Children; } } - - ICollection IPublishedContent.Properties { get { return _publishedContent.Properties; } } - - object IPublishedContent.this[string propertyAlias] { get { return _publishedContent[propertyAlias]; } } - - IPublishedContentProperty IPublishedContent.GetProperty(string alias) - { - return _publishedContent.GetProperty(alias); - } - #endregion } /// diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs deleted file mode 100644 index cd974fa534..0000000000 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedQueryTests.cs +++ /dev/null @@ -1,434 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using NUnit.Framework; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; -using Umbraco.Web.Models; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.PublishedCache.XmlPublishedCache; - -namespace Umbraco.Tests.PublishedContent -{ - [TestFixture] - public class StronglyTypedQueryTests : PublishedContentTestBase - { - public override void Initialize() - { - base.Initialize(); - } - - public override void TearDown() - { - base.TearDown(); - } - - protected override DatabaseBehavior DatabaseTestBehavior - { - get { return DatabaseBehavior.NoDatabasePerFixture; } - } - - protected override string GetXmlContent(int templateId) - { - return @" - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - } - - internal IPublishedContent GetNode(int id) - { - var ctx = UmbracoContext.Current; - var doc = ctx.ContentCache.GetById(id); - Assert.IsNotNull(doc); - return doc; - } - - - [Test] - public void Type_Test() - { - var doc = GetNode(1); - var result = doc.NewsArticles(TraversalType.Descendants).ToArray(); - Assert.AreEqual("John doe", result[0].ArticleAuthor); - Assert.AreEqual("John Smith", result[1].ArticleAuthor); - } - - - [Test] - public void As_Test() - { - var doc = GetNode(1); - var result = doc.AsHome(); - Assert.AreEqual("Test site", result.SiteName); - - Assert.Throws(() => doc.AsContentPage()); - } - - } - - //NOTE: Some of these class will be moved in to the core once all this is working the way we want - - #region Gen classes & supporting classes - - //TOOD: SD: This class could be the way that the UmbracoHelper deals with looking things up in the background, we might not - // even expose it publicly but it could handle any caching (per request) that might be required when looking up any objects... - // though we might not need it at all, not sure yet. - // However, what we need to do is implement the GetDocumentsByType method of the IPublishedStore, see the TODO there. - // It might be nicer to have a QueryContext on the UmbracoHelper (we can still keep the Content and TypedContent, etc... - // methods, but these would just wrap the QueryContext attached to it. Other methods on the QueryContext will be - // ContentByType, TypedContentByType, etc... then we can also have extension methods like below for strongly typed - // access like: GetAllHomes, GetAllNewsArticles, etc... - - //public class QueryDataContext - //{ - // private readonly IPublishedContentStore _contentStore; - // private readonly UmbracoContext _umbracoContext; - - // internal QueryDataContext(IPublishedContentStore contentStore, UmbracoContext umbracoContext) - // { - // _contentStore = contentStore; - // _umbracoContext = umbracoContext; - // } - - // public IPublishedContent GetDocumentById(int id) - // { - // return _contentStore.GetDocumentById(_umbracoContext, id); - // } - - // public IEnumerable GetByDocumentType(string alias) - // { - - // } - //} - - public enum TraversalType - { - Children, - Ancestors, - AncestorsOrSelf, - Descendants, - DescendantsOrSelf - } - - public static class StronglyTypedQueryExtensions - { - private static IEnumerable GetEnumerable(this IPublishedContent content, string docTypeAlias, TraversalType traversalType = TraversalType.Children) - { - switch (traversalType) - { - case TraversalType.Children: - return content.Children.Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.Ancestors: - return content.Ancestors().Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.AncestorsOrSelf: - return content.AncestorsOrSelf().Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.Descendants: - return content.Descendants().Where(x => x.DocumentTypeAlias == docTypeAlias); - case TraversalType.DescendantsOrSelf: - return content.DescendantsOrSelf().Where(x => x.DocumentTypeAlias == docTypeAlias); - default: - throw new ArgumentOutOfRangeException("traversalType"); - } - } - - private static T AsDocumentType(this IPublishedContent content, string alias, Func creator) - { - if (content.DocumentTypeAlias == alias) return creator(content); - throw new InvalidOperationException("The content type cannot be cast to " + typeof(T).FullName + " since it is type: " + content.DocumentTypeAlias); - } - - public static HomeContentItem AsHome(this IPublishedContent content) - { - return content.AsDocumentType("Home", x => new HomeContentItem(x)); - } - - public static IEnumerable Homes(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("Home", traversalType).Select(x => new HomeContentItem(x)); - } - - public static NewsArticleContentItem AsNewsArticle(this IPublishedContent content) - { - return content.AsDocumentType("NewsArticle", x => new NewsArticleContentItem(x)); - } - - public static IEnumerable NewsArticles(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("NewsArticle", traversalType).Select(x => new NewsArticleContentItem(x)); - } - - public static NewsLandingPageContentItem AsNewsLandingPage(this IPublishedContent content) - { - return content.AsDocumentType("NewsLandingPage", x => new NewsLandingPageContentItem(x)); - } - - public static IEnumerable NewsLandingPages(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("NewsLandingPage", traversalType).Select(x => new NewsLandingPageContentItem(x)); - } - - public static ContentPageContentItem AsContentPage(this IPublishedContent content) - { - return content.AsDocumentType("ContentPage", x => new ContentPageContentItem(x)); - } - - public static IEnumerable ContentPages(this IPublishedContent content, TraversalType traversalType = TraversalType.Children) - { - return content.GetEnumerable("ContentPage", traversalType).Select(x => new ContentPageContentItem(x)); - } - } - - [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] - public class PublishedContentWrapper : IPublishedContent, IOwnerCollectionAware - { - protected IPublishedContent WrappedContent { get; private set; } - - public PublishedContentWrapper(IPublishedContent content) - { - WrappedContent = content; - } - - public string Url - { - get { return WrappedContent.Url; } - } - - public PublishedItemType ItemType - { - get { return WrappedContent.ItemType; } - } - - public IPublishedContent Parent - { - get { return WrappedContent.Parent; } - } - - public int Id - { - get { return WrappedContent.Id; } - } - public int TemplateId - { - get { return WrappedContent.TemplateId; } - } - public int SortOrder - { - get { return WrappedContent.SortOrder; } - } - public string Name - { - get { return WrappedContent.Name; } - } - public string UrlName - { - get { return WrappedContent.UrlName; } - } - public string DocumentTypeAlias - { - get { return WrappedContent.DocumentTypeAlias; } - } - public int DocumentTypeId - { - get { return WrappedContent.DocumentTypeId; } - } - public string WriterName - { - get { return WrappedContent.WriterName; } - } - public string CreatorName - { - get { return WrappedContent.CreatorName; } - } - public int WriterId - { - get { return WrappedContent.WriterId; } - } - public int CreatorId - { - get { return WrappedContent.CreatorId; } - } - public string Path - { - get { return WrappedContent.Path; } - } - public DateTime CreateDate - { - get { return WrappedContent.CreateDate; } - } - public DateTime UpdateDate - { - get { return WrappedContent.UpdateDate; } - } - public Guid Version - { - get { return WrappedContent.Version; } - } - public int Level - { - get { return WrappedContent.Level; } - } - public ICollection Properties - { - get { return WrappedContent.Properties; } - } - - public object this[string propertyAlias] - { - get { return GetProperty(propertyAlias).Value; } - } - - public IEnumerable Children - { - get { return WrappedContent.Children; } - } - public IPublishedContentProperty GetProperty(string alias) - { - return WrappedContent.GetProperty(alias); - } - - private IEnumerable _ownersCollection; - - /// - /// Need to get/set the owner collection when an item is returned from the result set of a query - /// - /// - /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 - /// - IEnumerable IOwnerCollectionAware.OwnersCollection - { - get - { - var publishedContentBase = WrappedContent as IOwnerCollectionAware; - if (publishedContentBase != null) - { - return publishedContentBase.OwnersCollection; - } - - //if the owners collection is null, we'll default to it's siblings - if (_ownersCollection == null) - { - //get the root docs if parent is null - _ownersCollection = this.Siblings(); - } - return _ownersCollection; - } - set - { - var publishedContentBase = WrappedContent as IOwnerCollectionAware; - if (publishedContentBase != null) - { - publishedContentBase.OwnersCollection = value; - } - else - { - _ownersCollection = value; - } - } - } - } - - public partial class HomeContentItem : ContentPageContentItem - { - public HomeContentItem(IPublishedContent content) - : base(content) - { - } - - public string SiteName - { - get { return WrappedContent.GetPropertyValue("siteName"); } - } - public string SiteDescription - { - get { return WrappedContent.GetPropertyValue("siteDescription"); } - } - } - - public partial class NewsLandingPageContentItem : ContentPageContentItem - { - public NewsLandingPageContentItem(IPublishedContent content) - : base(content) - { - } - - public string PageTitle - { - get { return WrappedContent.GetPropertyValue("pageTitle"); } - } - } - - public partial class NewsArticleContentItem : PublishedContentWrapper - { - public NewsArticleContentItem(IPublishedContent content) - : base(content) - { - } - - public string ArticleContent - { - get { return WrappedContent.GetPropertyValue("articleContent"); } - } - public DateTime ArticleDate - { - get { return WrappedContent.GetPropertyValue("articleDate"); } - } - public string ArticleAuthor - { - get { return WrappedContent.GetPropertyValue("articleAuthor"); } - } - } - - public partial class ContentPageContentItem : PublishedContentWrapper - { - public ContentPageContentItem(IPublishedContent content) - : base(content) - { - } - - public string BodyContent - { - get { return WrappedContent.GetPropertyValue("bodyContent"); } - } - } - - #endregion -} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index d85d9813f5..be5474139b 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -12,11 +12,13 @@ using SQLCE4Umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Tests.Stubs; @@ -179,6 +181,12 @@ namespace Umbraco.Tests.TestHelpers MappingResolver.Current = new MappingResolver( () => PluginManager.Current.ResolveAssignedMapperTypes()); + if (PropertyValueConvertersResolver.HasCurrent == false) + PropertyValueConvertersResolver.Current = new PropertyValueConvertersResolver(); + + if (PublishedContentModelFactoryResolver.HasCurrent == false) + PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); + base.FreezeResolution(); } diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index 95e89468c3..064e48dd7f 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -7,11 +7,13 @@ using SQLCE4Umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; using Umbraco.Core.Services; +using Umbraco.Tests.PublishedContent; using Umbraco.Tests.Stubs; using Umbraco.Web; using Umbraco.Web.Routing; @@ -27,7 +29,12 @@ namespace Umbraco.Tests.TestHelpers [SetUp] public override void Initialize() { - base.Initialize(); + base.Initialize(); + + // need to specify a custom callback for unit tests + // AutoPublishedContentTypes generates properties automatically + var type = new AutoPublishedContentType(0, "anything", new PublishedPropertyType[] {}); + PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; } [TearDown] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e6c3e0f03e..0003de7785 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -186,6 +186,8 @@ + + @@ -252,9 +254,9 @@ + - @@ -296,6 +298,7 @@ + diff --git a/src/Umbraco.Tests/Views/dummy.txt b/src/Umbraco.Tests/Views/dummy.txt deleted file mode 100644 index 9c01dce8f4..0000000000 --- a/src/Umbraco.Tests/Views/dummy.txt +++ /dev/null @@ -1 +0,0 @@ -This file is just here to make sure the directory gets created. \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index f1a97dc767..1474c1a16b 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -129,7 +129,6 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); //clear static object cache global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); - PublishedContentHelper.ClearPropertyTypeCache(); base.RefreshAll(); } @@ -250,7 +249,6 @@ namespace Umbraco.Web.Cache //clears the dictionary object cache of the legacy ContentType global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); - PublishedContentHelper.ClearPropertyTypeCache(); //need to recursively clear the cache for each child content type foreach (var descendant in payload.DescendantPayloads) diff --git a/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs b/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs index d8e976cf8c..2901962f07 100644 --- a/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs +++ b/src/Umbraco.Web/ContextualPublishedCacheExtensions.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web public static dynamic GetDynamicById(this ContextualPublishedContentCache cache, int contentId) { var content = cache.GetById(contentId); - return content == null ? new DynamicNull() : new DynamicPublishedContent(content).AsDynamic(); + return content == null ? DynamicNull.Null : new DynamicPublishedContent(content).AsDynamic(); } /// @@ -34,7 +34,7 @@ namespace Umbraco.Web public static dynamic GetDynamicSingleByXPath(this ContextualPublishedContentCache cache, string xpath, params XPathVariable[] vars) { var content = cache.GetSingleByXPath(xpath, vars); - return content == null ? new DynamicNull() : new DynamicPublishedContent(content).AsDynamic(); + return content == null ? DynamicNull.Null : new DynamicPublishedContent(content).AsDynamic(); } /// @@ -47,7 +47,7 @@ namespace Umbraco.Web public static dynamic GetDynamicSingleByXPath(this ContextualPublishedContentCache cache, XPathExpression xpath, params XPathVariable[] vars) { var content = cache.GetSingleByXPath(xpath, vars); - return content == null ? new DynamicNull() : new DynamicPublishedContent(content).AsDynamic(); + return content == null ? DynamicNull.Null : new DynamicPublishedContent(content).AsDynamic(); } /// diff --git a/src/Umbraco.Web/Dynamics/ExtensionMethods.cs b/src/Umbraco.Web/Dynamics/ExtensionMethods.cs index 316b4951bf..dd3d2f69cd 100644 --- a/src/Umbraco.Web/Dynamics/ExtensionMethods.cs +++ b/src/Umbraco.Web/Dynamics/ExtensionMethods.cs @@ -1,32 +1,31 @@ using System; +using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Models; using Umbraco.Web.Models; namespace Umbraco.Web.Dynamics { internal static class ExtensionMethods { - - - public static DynamicPublishedContentList Random(this DynamicPublishedContentList all, int min, int max) + public static DynamicPublishedContentList Random(this DynamicPublishedContentList source, int min, int max) { - //get a random number generator - Random r = new Random(); - //choose the number of elements to be returned between Min and Max - int Number = r.Next(min, max); - //Call the other method - return Random(all, Number); - } - public static DynamicPublishedContentList Random(this DynamicPublishedContentList all, int max) - { - //Randomly order the items in the set by a Guid, Take the correct number, and return this wrapped in a new DynamicNodeList - return new DynamicPublishedContentList(all.Items.OrderBy(x => Guid.NewGuid()).Take(max)); + return Random(source, new Random().Next(min, max)); } - public static DynamicPublishedContent Random(this DynamicPublishedContentList all) + public static DynamicPublishedContentList Random(this DynamicPublishedContentList source, int max) { - return all.Items.OrderBy(x => Guid.NewGuid()).First(); + return new DynamicPublishedContentList(source.OrderByRandom().Take(max)); } + public static DynamicPublishedContent Random(this DynamicPublishedContentList source) + { + return new DynamicPublishedContent(source.OrderByRandom().First()); + } + + private static IEnumerable OrderByRandom(this DynamicPublishedContentList source) + { + return source.Items.OrderBy(x => Guid.NewGuid()); + } } } diff --git a/src/Umbraco.Web/ExamineExtensions.cs b/src/Umbraco.Web/ExamineExtensions.cs index e24cf1c9b0..4ce5daf5cb 100644 --- a/src/Umbraco.Web/ExamineExtensions.cs +++ b/src/Umbraco.Web/ExamineExtensions.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml; using Examine; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; -using Umbraco.Web.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache; namespace Umbraco.Web @@ -15,25 +14,39 @@ namespace Umbraco.Web /// internal static class ExamineExtensions { - internal static IEnumerable ConvertSearchResultToPublishedContent( - this IEnumerable results, + internal static PublishedContentSet ConvertSearchResultToPublishedContent(this IEnumerable results, ContextualPublishedCache cache) { //TODO: The search result has already returned a result which SHOULD include all of the data to create an IPublishedContent, - // however thsi is currently not the case: + // however this is currently not the case: // http://examine.codeplex.com/workitem/10350 - var list = new List(); + var list = new List(); + var set = new PublishedContentSet(list); foreach (var result in results.OrderByDescending(x => x.Score)) { - var doc = cache.GetById(result.Id); - if (doc == null) continue; //skip if this doesn't exist in the cache - doc.Properties.Add( - new PropertyResult("examineScore", result.Score.ToString(), Guid.Empty, PropertyResultType.CustomProperty)); - list.Add(doc); + var content = cache.GetById(result.Id); + if (content == null) continue; // skip if this doesn't exist in the cache + + // need to extend the content as we're going to add a property to it, + // and we should not ever do it to the content we get from the cache, + // precisely because it is cached and shared by all requests. + + // but we cannot wrap it because we need to respect the type that was + // returned by the cache, in case the cache can create real types. + // so we have to ask it to please extend itself. + + list.Add(content); + var extend = set.MapContent(content); + + var property = new PropertyResult("examineScore", + result.Score, Guid.Empty, + PropertyResultType.CustomProperty); + extend.AddProperty(property); } - return list; + + return set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Macros/PartialViewMacroController.cs b/src/Umbraco.Web/Macros/PartialViewMacroController.cs index b858d1c283..e275464b20 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroController.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroController.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Macros public PartialViewResult Index() { var model = new PartialViewMacroModel( - _currentPage.ConvertFromNode(), + _umbracoContext.ContentCache.GetById(_currentPage.Id), //_currentPage.ConvertFromNode(), _macro.Id, _macro.Alias, _macro.Name, diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs index 2d17ee11c0..1dbc960253 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs @@ -1,18 +1,19 @@ -using System; +// TODO in v7, #define FIX_GET_PROPERTY_VALUE (see GetPropertyValue region) +#undef FIX_GET_PROPERTY_VALUE + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Dynamic; using System.Linq; +using System.Runtime.CompilerServices; using System.Web; -using Umbraco.Core.Configuration; using Umbraco.Core.Dynamics; using Umbraco.Core.Models; using Umbraco.Core; -using Umbraco.Core.PropertyEditors; using System.Reflection; -using System.Xml.Linq; -using umbraco.cms.businesslogic; +using Umbraco.Core.Models.PublishedContent; using ContentType = umbraco.cms.businesslogic.ContentType; namespace Umbraco.Web.Models @@ -22,12 +23,33 @@ namespace Umbraco.Web.Models /// The base dynamic model for views /// [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] - public class DynamicPublishedContent : DynamicObject, IPublishedContent, IOwnerCollectionAware + public class DynamicPublishedContent : DynamicObject, IPublishedContent { protected internal IPublishedContent PublishedContent { get; private set; } - private DynamicPublishedContentList _cachedChildren; - private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary(); - + + public IEnumerable ContentSet + { + get + { + // fixme - what's this? + throw new NotImplementedException("What shall we do?"); + /* + if (_contentSet != null) return _contentSet; + + // siblings = parent.Children + var parent = PublishedContent.Parent; + var dynamicParent = parent == null ? null : parent.AsDynamicOrNull(); + if (dynamicParent != null) return dynamicParent.Children; + + // silbings = content at root + var atRoot = new DynamicPublishedContentList(UmbracoContext.Current.ContentCache.GetAtRoot()); + return atRoot; + */ + } + } + + public PublishedContentType ContentType { get { return PublishedContent.ContentType; } } + #region Constructors public DynamicPublishedContent(IPublishedContent content) @@ -35,60 +57,26 @@ namespace Umbraco.Web.Models if (content == null) throw new ArgumentNullException("content"); PublishedContent = content; } + + //internal DynamicPublishedContent(IPublishedContent content, IEnumerable contentSet) + //{ + // PublishedContent = content; + // _contentSet = contentSet; + //} #endregion - private IEnumerable _ownersCollection; + // these two here have leaked in v6 and so we cannot remove them anymore + // without breaking compatibility but... TODO: remove them in v7 + public DynamicPublishedContentList ChildrenAsList { get { return Children; } } + public int parentId { get { return PublishedContent.Parent.Id; } } + + #region DynamicObject + + // fixme - so everywhere else we use a basic dictionary but here we use a concurrent one? why? + private readonly ConcurrentDictionary _cachedMemberOutput = new ConcurrentDictionary(); /// - /// Need to get/set the owner collection when an item is returned from the result set of a query - /// - /// - /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 - /// - IEnumerable IOwnerCollectionAware.OwnersCollection - { - get - { - var publishedContentBase = PublishedContent as IOwnerCollectionAware; - if (publishedContentBase != null) - { - return publishedContentBase.OwnersCollection; - } - - //if the owners collection is null, we'll default to it's siblings - if (_ownersCollection == null) - { - //get the root docs if parent is null - _ownersCollection = this.Siblings(); - } - return _ownersCollection; - } - set - { - var publishedContentBase = PublishedContent as IOwnerCollectionAware; - if (publishedContentBase != null) - { - publishedContentBase.OwnersCollection = value; - } - else - { - _ownersCollection = value; - } - } - } - - public dynamic AsDynamic() - { - return this; - } - - public bool HasProperty(string name) - { - return PublishedContent.HasProperty(name); - } - - /// /// Attempts to call a method on the dynamic object /// /// @@ -130,10 +118,11 @@ namespace Umbraco.Web.Models } //this is the result of an extension method execution gone wrong so we return dynamic null + //fixme - throws a NullRef, wrong order of checks?! if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod && attempt.Exception != null && attempt.Exception is TargetInvocationException) { - result = new DynamicNull(); + result = DynamicNull.Null; return true; } @@ -148,6 +137,10 @@ namespace Umbraco.Web.Models /// protected virtual Attempt TryGetCustomMember(GetMemberBinder binder) { + // FIXME that one makes NO SENSE + // why not let the NORMAL BINDER do the work?! + // see below, that should be enough! + if (binder.Name.InvariantEquals("ChildrenAsList") || binder.Name.InvariantEquals("Children")) { return Attempt.Succeed(Children); @@ -195,10 +188,9 @@ namespace Umbraco.Web.Models /// protected virtual Attempt TryGetDocumentProperty(GetMemberBinder binder) { - var reflectedProperty = GetReflectedProperty(binder.Name); var result = reflectedProperty != null - ? reflectedProperty.Value + ? reflectedProperty.RawValue // fixme - why use the raw value? : null; return Attempt.If(result != null, result); @@ -212,43 +204,15 @@ namespace Umbraco.Web.Models protected virtual Attempt TryGetUserProperty(GetMemberBinder binder) { var name = binder.Name; - var recursive = false; + var recurse = false; if (name.StartsWith("_")) { name = name.Substring(1, name.Length - 1); - recursive = true; + recurse = true; } - var userProperty = GetUserProperty(name, recursive); - - if (userProperty == null) - { - return Attempt.Fail(); - } - - var result = userProperty.Value; - - if (PublishedContent.DocumentTypeAlias == null && userProperty.Alias == null) - { - throw new InvalidOperationException("No node alias or property alias available. Unable to look up the datatype of the property you are trying to fetch."); - } - - //get the data type id for the current property - var dataType = PublishedContentHelper.GetDataType( - ApplicationContext.Current, - userProperty.DocumentTypeAlias, - userProperty.Alias, - ItemType); - - //convert the string value to a known type - var converted = Umbraco.Core.PublishedContentHelper.ConvertPropertyValue(result, dataType, userProperty.DocumentTypeAlias, userProperty.Alias); - if (converted.Success) - { - result = converted.Result; - } - - return Attempt.Succeed(result); - + var value = PublishedContent.GetPropertyValue(name, recurse); + return Attempt.SucceedIf(value != null, value); } /// @@ -311,7 +275,7 @@ namespace Umbraco.Web.Models //and will make it false //which means backwards equality (&& property != true) will pass //forwwards equality (&& property or && property == true) will fail - result = new DynamicNull(); + result = DynamicNull.Null; //alwasy return true if we haven't thrown an exception though I'm wondering if we return 'false' if .Net throws an exception for us?? return true; @@ -342,20 +306,19 @@ namespace Umbraco.Web.Models var context = this; var prop = GetPropertyInternal(alias, PublishedContent); - while (prop == null || !prop.HasValue()) + while (prop == null || !prop.HasValue) { var parent = ((IPublishedContent) context).Parent; if (parent == null) break; // Update the context before attempting to retrieve the property again. - context = parent.AsDynamicPublishedContent(); + context = parent.AsDynamicOrNull(); prop = context.GetPropertyInternal(alias, context.PublishedContent); } return prop; } - private PropertyResult GetPropertyInternal(string alias, IPublishedContent content, bool checkUserProperty = true) { if (alias.IsNullOrWhiteSpace()) throw new ArgumentNullException("alias"); @@ -366,17 +329,17 @@ namespace Umbraco.Web.Models { var prop = content.GetProperty(alias); - return prop == null - ? null - : new PropertyResult(prop, PropertyResultType.UserProperty) - { - DocumentTypeAlias = content.DocumentTypeAlias, - DocumentId = content.Id - }; + // get wrap the result in a PropertyResult - just so it's an IHtmlString - ?! + return prop == null + ? null + : new PropertyResult(prop, PropertyResultType.UserProperty); } //reflect + // FIXME but if it exists here, then the original BINDER should have found it ALREADY? + // and if it's just a casing issue then there's BindingFlags.IgnoreCase ?!! + Func> getMember = memberAlias => { @@ -408,91 +371,128 @@ namespace Umbraco.Web.Models } return !attempt.Success - ? null - : new PropertyResult(alias, attempt.Result, Guid.Empty, PropertyResultType.ReflectedProperty) - { - DocumentTypeAlias = content.DocumentTypeAlias, - DocumentId = content.Id - }; + ? null + : new PropertyResult(alias, attempt.Result, Guid.Empty, PropertyResultType.ReflectedProperty); } - + #endregion - //public DynamicNode Media(string propertyAlias) - //{ - // if (_n != null) - // { - // IProperty prop = _n.GetProperty(propertyAlias); - // if (prop != null) - // { - // int mediaNodeId; - // if (int.TryParse(prop.Value, out mediaNodeId)) - // { - // return _razorLibrary.Value.MediaById(mediaNodeId); - // } - // } - // return null; - // } - // return null; - //} - //public bool IsProtected - //{ - // get - // { - // if (_n != null) - // { - // return umbraco.library.IsProtected(_n.Id, _n.Path); - // } - // return false; - // } - //} - //public bool HasAccess - //{ - // get - // { - // if (_n != null) - // { - // return umbraco.library.HasAccess(_n.Id, _n.Path); - // } - // return true; - // } - //} + #region Explicit IPublishedContent implementation - //public string Media(string propertyAlias, string mediaPropertyAlias) - //{ - // if (_n != null) - // { - // IProperty prop = _n.GetProperty(propertyAlias); - // if (prop == null && propertyAlias.Substring(0, 1).ToUpper() == propertyAlias.Substring(0, 1)) - // { - // prop = _n.GetProperty(propertyAlias.Substring(0, 1).ToLower() + propertyAlias.Substring((1))); - // } - // if (prop != null) - // { - // int mediaNodeId; - // if (int.TryParse(prop.Value, out mediaNodeId)) - // { - // umbraco.cms.businesslogic.media.Media media = new cms.businesslogic.media.Media(mediaNodeId); - // if (media != null) - // { - // Property mprop = media.getProperty(mediaPropertyAlias); - // // check for nicer support of Pascal Casing EVEN if alias is camelCasing: - // if (prop == null && mediaPropertyAlias.Substring(0, 1).ToUpper() == mediaPropertyAlias.Substring(0, 1)) - // { - // mprop = media.getProperty(mediaPropertyAlias.Substring(0, 1).ToLower() + mediaPropertyAlias.Substring((1))); - // } - // if (mprop != null) - // { - // return string.Format("{0}", mprop.Value); - // } - // } - // } - // } - // } - // return null; - //} + IPublishedContent IPublishedContent.Parent + { + get { return PublishedContent.Parent; } + } + + int IPublishedContent.Id + { + get { return PublishedContent.Id; } + } + + int IPublishedContent.TemplateId + { + get { return PublishedContent.TemplateId; } + } + + int IPublishedContent.SortOrder + { + get { return PublishedContent.SortOrder; } + } + + string IPublishedContent.Name + { + get { return PublishedContent.Name; } + } + + string IPublishedContent.UrlName + { + get { return PublishedContent.UrlName; } + } + + string IPublishedContent.DocumentTypeAlias + { + get { return PublishedContent.DocumentTypeAlias; } + } + + int IPublishedContent.DocumentTypeId + { + get { return PublishedContent.DocumentTypeId; } + } + + string IPublishedContent.WriterName + { + get { return PublishedContent.WriterName; } + } + + string IPublishedContent.CreatorName + { + get { return PublishedContent.CreatorName; } + } + + int IPublishedContent.WriterId + { + get { return PublishedContent.WriterId; } + } + + int IPublishedContent.CreatorId + { + get { return PublishedContent.CreatorId; } + } + + string IPublishedContent.Path + { + get { return PublishedContent.Path; } + } + + DateTime IPublishedContent.CreateDate + { + get { return PublishedContent.CreateDate; } + } + + DateTime IPublishedContent.UpdateDate + { + get { return PublishedContent.UpdateDate; } + } + + Guid IPublishedContent.Version + { + get { return PublishedContent.Version; } + } + + int IPublishedContent.Level + { + get { return PublishedContent.Level; } + } + + bool IPublishedContent.IsDraft + { + get { return PublishedContent.IsDraft; } + } + + int IPublishedContent.GetIndex() + { + return PublishedContent.GetIndex(); + } + + ICollection IPublishedContent.Properties + { + get { return PublishedContent.Properties; } + } + + IEnumerable IPublishedContent.Children + { + get { return PublishedContent.Children; } + } + + IPublishedProperty IPublishedContent.GetProperty(string alias) + { + return PublishedContent.GetProperty(alias); + } + + #endregion + + #region IPublishedContent implementation - #region Standard Properties public int TemplateId { get { return PublishedContent.TemplateId; } @@ -508,16 +508,6 @@ namespace Umbraco.Web.Models get { return PublishedContent.Name; } } - public bool Visible - { - get { return PublishedContent.IsVisible(); } - } - - public bool IsVisible() - { - return PublishedContent.IsVisible(); - } - public string UrlName { get { return PublishedContent.UrlName; } @@ -588,7 +578,13 @@ namespace Umbraco.Web.Models get { return PublishedContent.ItemType; } } - public IEnumerable Properties + // see note in IPublishedContent + //public bool Published + //{ + // get { return PublishedContent.Published; } + //} + + public IEnumerable Properties { get { return PublishedContent.Properties; } } @@ -598,629 +594,717 @@ namespace Umbraco.Web.Models get { return PublishedContent[propertyAlias]; } } - public DynamicPublishedContentList Children - { - get - { - if (_cachedChildren == null) - { - var children = PublishedContent.Children; - //testing, think this must be a special case for the root node ? - if (!children.Any() && PublishedContent.Id == 0) - { - _cachedChildren = new DynamicPublishedContentList(new List { new DynamicPublishedContent(this.PublishedContent) }); - } - else - { - _cachedChildren = new DynamicPublishedContentList(PublishedContent.Children.Select(x => new DynamicPublishedContent(x))); - } - } - return _cachedChildren; - } - } - #endregion + #endregion - public string GetTemplateAlias() + #region GetProperty + + // enhanced versions of the extension methods that exist for IPublishedContent, + // here we support the recursive (_) and reflected (@) syntax + + public IPublishedProperty GetProperty(string alias) + { + return alias.StartsWith("_") + ? GetProperty(alias.Substring(1), true) + : GetProperty(alias, false); + } + + public IPublishedProperty GetProperty(string alias, bool recurse) + { + if (alias.StartsWith("@")) return GetReflectedProperty(alias.Substring(1)); + + // get wrap the result in a PropertyResult - just so it's an IHtmlString - ?! + var property = PublishedContent.GetProperty(alias, recurse); + return property == null ? null : new PropertyResult(property, PropertyResultType.UserProperty); + } + + #endregion + + // IPublishedContent extension methods: + // + // all these methods are IPublishedContent extension methods so they should in + // theory apply to DynamicPublishedContent since it is an IPublishedContent and + // we look for extension methods. But that lookup has to be pretty slow. + // Duplicating the methods here makes things much faster. + + #region IPublishedContent extension methods - Template + + public string GetTemplateAlias() { return PublishedContentExtensions.GetTemplateAlias(this); } + + #endregion - #region Search + #region IPublishedContent extension methods - HasProperty - public DynamicPublishedContentList Search(string term, bool useWildCards = true, string searchProvider = null) + public bool HasProperty(string name) + { + return PublishedContent.HasProperty(name); + } + + #endregion + + #region IPublishedContent extension methods - HasValue + + public bool HasValue(string alias) { - return new DynamicPublishedContentList( - PublishedContentExtensions.Search(this, term, useWildCards, searchProvider)); + return PublishedContent.HasValue(alias); } - public DynamicPublishedContentList SearchDescendants(string term, bool useWildCards = true, string searchProvider = null) - { - return new DynamicPublishedContentList( - PublishedContentExtensions.SearchDescendants(this, term, useWildCards, searchProvider)); - } - - public DynamicPublishedContentList SearchChildren(string term, bool useWildCards = true, string searchProvider = null) - { - return new DynamicPublishedContentList( - PublishedContentExtensions.SearchChildren(this, term, useWildCards, searchProvider)); - } - - public DynamicPublishedContentList Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - return new DynamicPublishedContentList( - PublishedContentExtensions.Search(this, criteria, searchProvider)); - } - - #endregion - - #region GetProperty methods which can be used with the dynamic object - - public IPublishedContentProperty GetProperty(string alias) - { - var prop = GetProperty(alias, false); - if (prop == null && alias.StartsWith("_")) - { - //if it is prefixed and the first result failed, try to get it by recursive - var recursiveAlias = alias.Substring(1, alias.Length - 1); - return GetProperty(recursiveAlias, true); - } - return prop; - } - public IPublishedContentProperty GetProperty(string alias, bool recursive) - { - return alias.StartsWith("@") - ? GetReflectedProperty(alias.TrimStart('@')) - : GetUserProperty(alias, recursive); - } - public string GetPropertyValue(string alias) - { - return GetPropertyValue(alias, false); - } - public string GetPropertyValue(string alias, string fallback) - { - var prop = GetPropertyValue(alias); - return !prop.IsNullOrWhiteSpace() ? prop : fallback; - } - public string GetPropertyValue(string alias, bool recursive) - { - var p = alias.StartsWith("@") - ? GetReflectedProperty(alias.TrimStart('@')) - : GetUserProperty(alias, recursive); - return p == null ? null : p.ValueAsString; - } - public string GetPropertyValue(string alias, bool recursive, string fallback) - { - var prop = GetPropertyValue(alias, recursive); - return !prop.IsNullOrWhiteSpace() ? prop : fallback; - } - - #endregion - - #region HasValue - public bool HasValue(string alias) - { - return this.PublishedContent.HasValue(alias); - } public bool HasValue(string alias, bool recursive) { - return this.PublishedContent.HasValue(alias, recursive); + return PublishedContent.HasValue(alias, recursive); } + public IHtmlString HasValue(string alias, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.HasValue(alias, valueIfTrue, valueIfFalse); + return PublishedContent.HasValue(alias, valueIfTrue, valueIfFalse); } + public IHtmlString HasValue(string alias, bool recursive, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.HasValue(alias, recursive, valueIfTrue, valueIfFalse); + return PublishedContent.HasValue(alias, recursive, valueIfTrue, valueIfFalse); } + public IHtmlString HasValue(string alias, string valueIfTrue) { - return this.PublishedContent.HasValue(alias, valueIfTrue); + return PublishedContent.HasValue(alias, valueIfTrue); } + public IHtmlString HasValue(string alias, bool recursive, string valueIfTrue) { - return this.PublishedContent.HasValue(alias, recursive, valueIfTrue); + return PublishedContent.HasValue(alias, recursive, valueIfTrue); } + #endregion - #region Explicit IPublishedContent implementation + #region IPublishedContent extension methods - GetPropertyValue - IPublishedContent IPublishedContent.Parent - { - get { return PublishedContent.Parent; } - } + // for whatever reason, some methods returning strings were created in DynamicPublishedContent + // and are now considered a "feature" as of v6. So we can't have the proper GetPropertyValue + // methods returning objects, too. And we don't want to change it in v6 as that would be a + // breaking change. - int IPublishedContent.Id - { - get { return PublishedContent.Id; } - } +#if FIX_GET_PROPERTY_VALUE - int IPublishedContent.TemplateId - { - get { return PublishedContent.TemplateId; } - } + public object GetPropertyValue(string alias) + { + return PublishedContent.GetPropertyValue(alias); + } - int IPublishedContent.SortOrder - { - get { return PublishedContent.SortOrder; } - } + public object GetPropertyValue(string alias, string defaultValue) + { + return PublishedContent.GetPropertyValue(alias, defaultValue); + } - string IPublishedContent.Name - { - get { return PublishedContent.Name; } - } + public object GetPropertyValue(string alias, object defaultValue) + { + return PublishedContent.GetPropertyValue(alias, defaultValue); + } - string IPublishedContent.UrlName - { - get { return PublishedContent.UrlName; } - } + public object GetPropertyValue(string alias, bool recurse) + { + return PublishedContent.GetPropertyValue(alias, recurse); + } - string IPublishedContent.DocumentTypeAlias - { - get { return PublishedContent.DocumentTypeAlias; } - } + public object GetPropertyValue(string alias, bool recurse, object defaultValue) + { + return PublishedContent.GetPropertyValue(alias, recurse, defaultValue); + } - int IPublishedContent.DocumentTypeId - { - get { return PublishedContent.DocumentTypeId; } - } +#else - string IPublishedContent.WriterName - { - get { return PublishedContent.WriterName; } - } + public string GetPropertyValue(string alias) + { + return GetPropertyValue(alias, false); + } - string IPublishedContent.CreatorName - { - get { return PublishedContent.CreatorName; } - } + public string GetPropertyValue(string alias, string defaultValue) + { + var value = GetPropertyValue(alias); + return value.IsNullOrWhiteSpace() ? defaultValue : value; + } - int IPublishedContent.WriterId - { - get { return PublishedContent.WriterId; } - } + public string GetPropertyValue(string alias, bool recurse, string defaultValue) + { + var value = GetPropertyValue(alias, recurse); + return value.IsNullOrWhiteSpace() ? defaultValue : value; + } - int IPublishedContent.CreatorId - { - get { return PublishedContent.CreatorId; } - } + public string GetPropertyValue(string alias, bool recursive) + { + var property = GetProperty(alias, recursive); + if (property == null || property.Value == null) return null; + return property.Value.ToString(); + } - string IPublishedContent.Path - { - get { return PublishedContent.Path; } - } +#endif - DateTime IPublishedContent.CreateDate - { - get { return PublishedContent.CreateDate; } - } + #endregion - DateTime IPublishedContent.UpdateDate - { - get { return PublishedContent.UpdateDate; } - } + #region IPublishedContent extension methods - GetPropertyValue - Guid IPublishedContent.Version - { - get { return PublishedContent.Version; } - } + public T GetPropertyValue(string alias) + { + return PublishedContent.GetPropertyValue(alias); + } - int IPublishedContent.Level - { - get { return PublishedContent.Level; } - } + public T GetPropertyValue(string alias, T defaultValue) + { + return PublishedContent.GetPropertyValue(alias, defaultValue); + } - ICollection IPublishedContent.Properties - { - get { return PublishedContent.Properties; } - } + public T GetPropertyValue(string alias, bool recurse) + { + return PublishedContent.GetPropertyValue(alias, recurse); + } - IEnumerable IPublishedContent.Children - { - get { return PublishedContent.Children; } - } + public T GetPropertyValue(string alias, bool recurse, T defaultValue) + { + return PublishedContent.GetPropertyValue(alias, recurse, defaultValue); + } - IPublishedContentProperty IPublishedContent.GetProperty(string alias) - { - return PublishedContent.GetProperty(alias); - } - #endregion + #endregion - - #region Index/Position - public int Position() - { - return Umbraco.Web.PublishedContentExtensions.Position(this); - } - public int Index() - { - return Umbraco.Web.PublishedContentExtensions.Index(this); - } - #endregion + #region IPublishedContent extension methods - Search - #region Is Helpers + public DynamicPublishedContentList Search(string term, bool useWildCards = true, string searchProvider = null) + { + return new DynamicPublishedContentList(PublishedContent.Search(term, useWildCards, searchProvider)); + } + + public DynamicPublishedContentList SearchDescendants(string term, bool useWildCards = true, string searchProvider = null) + { + return new DynamicPublishedContentList(PublishedContent.SearchDescendants(term, useWildCards, searchProvider)); + } + + public DynamicPublishedContentList SearchChildren(string term, bool useWildCards = true, string searchProvider = null) + { + return new DynamicPublishedContentList(PublishedContent.SearchChildren(term, useWildCards, searchProvider)); + } + + public DynamicPublishedContentList Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + { + return new DynamicPublishedContentList(PublishedContent.Search(criteria, searchProvider)); + } + + #endregion + + // fixme + #region IPublishedContent extension methods - ToContentSet + #endregion + + // fixme + #region IPublishedContent extension methods - AsDynamic + + public dynamic AsDynamic() + { + return this; + } + + public dynamic AsDynamicOrNull() + { + return this; + } + + #endregion + + #region IPublishedContente extension methods - ContentSet + + public int Position() + { + return Index(); + } + + public int Index() + { + return PublishedContent.GetIndex(); + } + + #endregion + + #region IPublishedContent extension methods - IsSomething: misc + + public bool Visible + { + get { return PublishedContent.IsVisible(); } + } + + public bool IsVisible() + { + return PublishedContent.IsVisible(); + } public bool IsDocumentType(string docTypeAlias) { - return this.PublishedContent.IsDocumentType(docTypeAlias); + return PublishedContent.IsDocumentType(docTypeAlias); } public bool IsNull(string alias, bool recursive) { - return this.PublishedContent.IsNull(alias, recursive); + return PublishedContent.IsNull(alias, recursive); } + public bool IsNull(string alias) { - return this.PublishedContent.IsNull(alias, false); + return PublishedContent.IsNull(alias, false); } - public bool IsFirst() + + #endregion + + #region IPublishedContent extension methods - IsSomething: position in set + + public bool IsFirst() { - return this.PublishedContent.IsFirst(); + return PublishedContent.IsFirst(); } + public HtmlString IsFirst(string valueIfTrue) { - return this.PublishedContent.IsFirst(valueIfTrue); + return PublishedContent.IsFirst(valueIfTrue); } + public HtmlString IsFirst(string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsFirst(valueIfTrue, valueIfFalse); + return PublishedContent.IsFirst(valueIfTrue, valueIfFalse); } + public bool IsNotFirst() { - return this.PublishedContent.IsNotFirst(); + return PublishedContent.IsNotFirst(); } + public HtmlString IsNotFirst(string valueIfTrue) { - return this.PublishedContent.IsNotFirst(valueIfTrue); + return PublishedContent.IsNotFirst(valueIfTrue); } + public HtmlString IsNotFirst(string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsNotFirst(valueIfTrue, valueIfFalse); + return PublishedContent.IsNotFirst(valueIfTrue, valueIfFalse); } + public bool IsPosition(int index) { - return this.PublishedContent.IsPosition(index); + return PublishedContent.IsPosition(index); } + public HtmlString IsPosition(int index, string valueIfTrue) { - return this.PublishedContent.IsPosition(index, valueIfTrue); + return PublishedContent.IsPosition(index, valueIfTrue); } + public HtmlString IsPosition(int index, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsPosition(index, valueIfTrue, valueIfFalse); + return PublishedContent.IsPosition(index, valueIfTrue, valueIfFalse); } + public bool IsModZero(int modulus) { - return this.PublishedContent.IsModZero(modulus); + return PublishedContent.IsModZero(modulus); } + public HtmlString IsModZero(int modulus, string valueIfTrue) { - return this.PublishedContent.IsModZero(modulus, valueIfTrue); + return PublishedContent.IsModZero(modulus, valueIfTrue); } + public HtmlString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsModZero(modulus, valueIfTrue, valueIfFalse); + return PublishedContent.IsModZero(modulus, valueIfTrue, valueIfFalse); } public bool IsNotModZero(int modulus) { - return this.PublishedContent.IsNotModZero(modulus); + return PublishedContent.IsNotModZero(modulus); } + public HtmlString IsNotModZero(int modulus, string valueIfTrue) { - return this.PublishedContent.IsNotModZero(modulus, valueIfTrue); + return PublishedContent.IsNotModZero(modulus, valueIfTrue); } + public HtmlString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsNotModZero(modulus, valueIfTrue, valueIfFalse); + return PublishedContent.IsNotModZero(modulus, valueIfTrue, valueIfFalse); } + public bool IsNotPosition(int index) { - return this.PublishedContent.IsNotPosition(index); + return PublishedContent.IsNotPosition(index); } + public HtmlString IsNotPosition(int index, string valueIfTrue) { - return this.PublishedContent.IsNotPosition(index, valueIfTrue); + return PublishedContent.IsNotPosition(index, valueIfTrue); } + public HtmlString IsNotPosition(int index, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsNotPosition(index, valueIfTrue, valueIfFalse); + return PublishedContent.IsNotPosition(index, valueIfTrue, valueIfFalse); } + public bool IsLast() { - return this.PublishedContent.IsLast(); + return PublishedContent.IsLast(); } + public HtmlString IsLast(string valueIfTrue) { - return this.PublishedContent.IsLast(valueIfTrue); + return PublishedContent.IsLast(valueIfTrue); } + public HtmlString IsLast(string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsLast(valueIfTrue, valueIfFalse); + return PublishedContent.IsLast(valueIfTrue, valueIfFalse); } + public bool IsNotLast() { - return this.PublishedContent.IsNotLast(); + return PublishedContent.IsNotLast(); } + public HtmlString IsNotLast(string valueIfTrue) { - return this.PublishedContent.IsNotLast(valueIfTrue); + return PublishedContent.IsNotLast(valueIfTrue); } + public HtmlString IsNotLast(string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsNotLast(valueIfTrue, valueIfFalse); + return PublishedContent.IsNotLast(valueIfTrue, valueIfFalse); } + public bool IsEven() { - return this.PublishedContent.IsEven(); + return PublishedContent.IsEven(); } + public HtmlString IsEven(string valueIfTrue) { - return this.PublishedContent.IsEven(valueIfTrue); + return PublishedContent.IsEven(valueIfTrue); } + public HtmlString IsEven(string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsEven(valueIfTrue, valueIfFalse); + return PublishedContent.IsEven(valueIfTrue, valueIfFalse); } + public bool IsOdd() { - return this.PublishedContent.IsOdd(); + return PublishedContent.IsOdd(); } + public HtmlString IsOdd(string valueIfTrue) { - return this.PublishedContent.IsOdd(valueIfTrue); + return PublishedContent.IsOdd(valueIfTrue); } + public HtmlString IsOdd(string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsOdd(valueIfTrue, valueIfFalse); + return PublishedContent.IsOdd(valueIfTrue, valueIfFalse); } - public bool IsEqual(DynamicPublishedContent other) + + #endregion + + #region IPublishedContent extension methods - IsSomething: equality + + public bool IsEqual(DynamicPublishedContent other) { - return this.PublishedContent.IsEqual(other); + return PublishedContent.IsEqual(other); } + public HtmlString IsEqual(DynamicPublishedContent other, string valueIfTrue) { - return this.PublishedContent.IsEqual(other, valueIfTrue); + return PublishedContent.IsEqual(other, valueIfTrue); } + public HtmlString IsEqual(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsEqual(other, valueIfTrue, valueIfFalse); + return PublishedContent.IsEqual(other, valueIfTrue, valueIfFalse); } + public bool IsNotEqual(DynamicPublishedContent other) { - return this.PublishedContent.IsNotEqual(other); + return PublishedContent.IsNotEqual(other); } + public HtmlString IsNotEqual(DynamicPublishedContent other, string valueIfTrue) { - return this.PublishedContent.IsNotEqual(other, valueIfTrue); + return PublishedContent.IsNotEqual(other, valueIfTrue); } + public HtmlString IsNotEqual(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsNotEqual(other, valueIfTrue, valueIfFalse); + return PublishedContent.IsNotEqual(other, valueIfTrue, valueIfFalse); } - public bool IsDescendant(DynamicPublishedContent other) + + #endregion + + #region IPublishedContent extension methods - IsSomething: ancestors and descendants + + public bool IsDescendant(DynamicPublishedContent other) { - return this.PublishedContent.IsDescendant(other); + return PublishedContent.IsDescendant(other); } + public HtmlString IsDescendant(DynamicPublishedContent other, string valueIfTrue) { - return this.PublishedContent.IsDescendant(other, valueIfTrue); + return PublishedContent.IsDescendant(other, valueIfTrue); } + public HtmlString IsDescendant(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsDescendant(other, valueIfTrue, valueIfFalse); + return PublishedContent.IsDescendant(other, valueIfTrue, valueIfFalse); } + public bool IsDescendantOrSelf(DynamicPublishedContent other) { - return this.PublishedContent.IsDescendantOrSelf(other); + return PublishedContent.IsDescendantOrSelf(other); } + public HtmlString IsDescendantOrSelf(DynamicPublishedContent other, string valueIfTrue) { - return this.PublishedContent.IsDescendantOrSelf(other, valueIfTrue); + return PublishedContent.IsDescendantOrSelf(other, valueIfTrue); } + public HtmlString IsDescendantOrSelf(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsDescendantOrSelf(other, valueIfTrue, valueIfFalse); + return PublishedContent.IsDescendantOrSelf(other, valueIfTrue, valueIfFalse); } + public bool IsAncestor(DynamicPublishedContent other) { - return this.PublishedContent.IsAncestor(other); + return PublishedContent.IsAncestor(other); } + public HtmlString IsAncestor(DynamicPublishedContent other, string valueIfTrue) { - return this.PublishedContent.IsAncestor(other, valueIfTrue); + return PublishedContent.IsAncestor(other, valueIfTrue); } + public HtmlString IsAncestor(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsAncestor(other, valueIfTrue, valueIfFalse); + return PublishedContent.IsAncestor(other, valueIfTrue, valueIfFalse); } + public bool IsAncestorOrSelf(DynamicPublishedContent other) { - return this.PublishedContent.IsAncestorOrSelf(other); + return PublishedContent.IsAncestorOrSelf(other); } + public HtmlString IsAncestorOrSelf(DynamicPublishedContent other, string valueIfTrue) { - return this.PublishedContent.IsAncestorOrSelf(other, valueIfTrue); + return PublishedContent.IsAncestorOrSelf(other, valueIfTrue); } + public HtmlString IsAncestorOrSelf(DynamicPublishedContent other, string valueIfTrue, string valueIfFalse) { - return this.PublishedContent.IsAncestorOrSelf(other, valueIfTrue, valueIfFalse); - } + return PublishedContent.IsAncestorOrSelf(other, valueIfTrue, valueIfFalse); + } + #endregion - #region Traversal + // all these methods wrap whatever PublishedContent returns in a new + // DynamicPublishedContentList, for dynamic usage. + + #region Ancestors + + public DynamicPublishedContentList Ancestors(int level) + { + return new DynamicPublishedContentList(PublishedContent.Ancestors(level)); + } + + public DynamicPublishedContentList Ancestors(string contentTypeAlias) + { + return new DynamicPublishedContentList(PublishedContent.Ancestors(contentTypeAlias)); + } + + public DynamicPublishedContentList Ancestors() + { + return new DynamicPublishedContentList(PublishedContent.Ancestors()); + } + + public DynamicPublishedContentList Ancestors(Func func) + { + return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(false, func)); + } + + public DynamicPublishedContent AncestorOrSelf() + { + return PublishedContent.AncestorOrSelf().AsDynamicOrNull(); + } + + public DynamicPublishedContent AncestorOrSelf(int level) + { + return PublishedContent.AncestorOrSelf(level).AsDynamicOrNull(); + } + + public DynamicPublishedContent AncestorOrSelf(string contentTypeAlias) + { + return PublishedContent.AncestorOrSelf(contentTypeAlias).AsDynamicOrNull(); + } + + public DynamicPublishedContent AncestorOrSelf(Func func) + { + return PublishedContent.AncestorsOrSelf(true, func).FirstOrDefault().AsDynamicOrNull(); + } + + public DynamicPublishedContentList AncestorsOrSelf(Func func) + { + return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(true, func)); + } + + public DynamicPublishedContentList AncestorsOrSelf() + { + return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf()); + } + + public DynamicPublishedContentList AncestorsOrSelf(string contentTypeAlias) + { + return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(contentTypeAlias)); + } + + public DynamicPublishedContentList AncestorsOrSelf(int level) + { + return new DynamicPublishedContentList(PublishedContent.AncestorsOrSelf(level)); + } + + #endregion + + #region Descendants + + public DynamicPublishedContentList Descendants(string contentTypeAlias) + { + return new DynamicPublishedContentList(PublishedContent.Descendants(contentTypeAlias)); + } + public DynamicPublishedContentList Descendants(int level) + { + return new DynamicPublishedContentList(PublishedContent.Descendants(level)); + } + public DynamicPublishedContentList Descendants() + { + return new DynamicPublishedContentList(PublishedContent.Descendants()); + } + public DynamicPublishedContentList DescendantsOrSelf(int level) + { + return new DynamicPublishedContentList(PublishedContent.DescendantsOrSelf(level)); + } + public DynamicPublishedContentList DescendantsOrSelf(string contentTypeAlias) + { + return new DynamicPublishedContentList(PublishedContent.DescendantsOrSelf(contentTypeAlias)); + } + public DynamicPublishedContentList DescendantsOrSelf() + { + return new DynamicPublishedContentList(PublishedContent.DescendantsOrSelf()); + } + + #endregion + + #region Traversal + public DynamicPublishedContent Up() { - return Umbraco.Web.PublishedContentExtensions.Up(this).AsDynamicPublishedContent(); + return PublishedContent.Up().AsDynamicOrNull(); } + public DynamicPublishedContent Up(int number) { - return Umbraco.Web.PublishedContentExtensions.Up(this, number).AsDynamicPublishedContent(); + return PublishedContent.Up(number).AsDynamicOrNull(); } - public DynamicPublishedContent Up(string nodeTypeAlias) + + public DynamicPublishedContent Up(string contentTypeAlias) { - return Umbraco.Web.PublishedContentExtensions.Up(this, nodeTypeAlias).AsDynamicPublishedContent(); + return PublishedContent.Up(contentTypeAlias).AsDynamicOrNull(); } + public DynamicPublishedContent Down() { - return Umbraco.Web.PublishedContentExtensions.Down(this).AsDynamicPublishedContent(); + return PublishedContent.Down().AsDynamicOrNull(); } + public DynamicPublishedContent Down(int number) { - return Umbraco.Web.PublishedContentExtensions.Down(this, number).AsDynamicPublishedContent(); + return PublishedContent.Down(number).AsDynamicOrNull(); } - public DynamicPublishedContent Down(string nodeTypeAlias) + + public DynamicPublishedContent Down(string contentTypeAlias) { - return Umbraco.Web.PublishedContentExtensions.Down(this, nodeTypeAlias).AsDynamicPublishedContent(); + return PublishedContent.Down(contentTypeAlias).AsDynamicOrNull(); } + public DynamicPublishedContent Next() { - return Umbraco.Web.PublishedContentExtensions.Next(this).AsDynamicPublishedContent(); + return PublishedContent.Next().AsDynamicOrNull(); } + public DynamicPublishedContent Next(int number) { - return Umbraco.Web.PublishedContentExtensions.Next(this, number).AsDynamicPublishedContent(); + return PublishedContent.Next(number).AsDynamicOrNull(); } - public DynamicPublishedContent Next(string nodeTypeAlias) + + public DynamicPublishedContent Next(string contentTypeAlias) { - return Umbraco.Web.PublishedContentExtensions.Next(this, nodeTypeAlias).AsDynamicPublishedContent(); + return PublishedContent.Next(contentTypeAlias).AsDynamicOrNull(); } public DynamicPublishedContent Previous() { - return Umbraco.Web.PublishedContentExtensions.Previous(this).AsDynamicPublishedContent(); + return PublishedContent.Previous().AsDynamicOrNull(); } + public DynamicPublishedContent Previous(int number) { - return Umbraco.Web.PublishedContentExtensions.Previous(this, number).AsDynamicPublishedContent(); + return PublishedContent.Previous(number).AsDynamicOrNull(); } - public DynamicPublishedContent Previous(string nodeTypeAlias) + + public DynamicPublishedContent Previous(string contentTypeAlias) { - return Umbraco.Web.PublishedContentExtensions.Previous(this, nodeTypeAlias).AsDynamicPublishedContent(); + return PublishedContent.Previous(contentTypeAlias).AsDynamicOrNull(); } + public DynamicPublishedContent Sibling(int number) { - return Umbraco.Web.PublishedContentExtensions.Previous(this, number).AsDynamicPublishedContent(); + return PublishedContent.Previous(number).AsDynamicOrNull(); } - public DynamicPublishedContent Sibling(string nodeTypeAlias) + + public DynamicPublishedContent Sibling(string contentTypeAlias) { - return Umbraco.Web.PublishedContentExtensions.Previous(this, nodeTypeAlias).AsDynamicPublishedContent(); + return PublishedContent.Previous(contentTypeAlias).AsDynamicOrNull(); } + #endregion - #region Ancestors, Descendants and Parent - #region Ancestors - public DynamicPublishedContentList Ancestors(int level) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.Ancestors(this, level)); - } - public DynamicPublishedContentList Ancestors(string nodeTypeAlias) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.Ancestors(this, nodeTypeAlias)); - } - public DynamicPublishedContentList Ancestors() - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.Ancestors(this)); - } - public DynamicPublishedContentList Ancestors(Func func) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.Ancestors(this, func)); - } - public DynamicPublishedContent AncestorOrSelf() - { - return Umbraco.Web.PublishedContentExtensions.AncestorOrSelf(this).AsDynamicPublishedContent(); - } - public DynamicPublishedContent AncestorOrSelf(int level) - { - return Umbraco.Web.PublishedContentExtensions.AncestorOrSelf(this, level).AsDynamicPublishedContent(); - } - public DynamicPublishedContent AncestorOrSelf(string nodeTypeAlias) - { - return Umbraco.Web.PublishedContentExtensions.AncestorOrSelf(this, nodeTypeAlias).AsDynamicPublishedContent(); - } - public DynamicPublishedContent AncestorOrSelf(Func func) - { - return Umbraco.Web.PublishedContentExtensions.AncestorOrSelf(this, func).AsDynamicPublishedContent(); - } - public DynamicPublishedContentList AncestorsOrSelf(Func func) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.AncestorsOrSelf(this, func)); - } - public DynamicPublishedContentList AncestorsOrSelf() - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.AncestorsOrSelf(this)); - } - public DynamicPublishedContentList AncestorsOrSelf(string nodeTypeAlias) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.AncestorsOrSelf(this, nodeTypeAlias)); - } - public DynamicPublishedContentList AncestorsOrSelf(int level) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.AncestorsOrSelf(this, level)); - } - #endregion - #region Descendants - public DynamicPublishedContentList Descendants(string nodeTypeAlias) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.Descendants(this, nodeTypeAlias)); - } - public DynamicPublishedContentList Descendants(int level) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.Descendants(this, level)); - } - public DynamicPublishedContentList Descendants() - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.Descendants(this)); - } - public DynamicPublishedContentList DescendantsOrSelf(int level) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.DescendantsOrSelf(this, level)); - } - public DynamicPublishedContentList DescendantsOrSelf(string nodeTypeAlias) - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.DescendantsOrSelf(this, nodeTypeAlias)); - } - public DynamicPublishedContentList DescendantsOrSelf() - { - return new DynamicPublishedContentList( - Umbraco.Web.PublishedContentExtensions.DescendantsOrSelf(this)); - } - #endregion + #region Parent public DynamicPublishedContent Parent { get { - if (PublishedContent.Parent != null) - { - return PublishedContent.Parent.AsDynamicPublishedContent(); - } - if (PublishedContent != null && PublishedContent.Id == 0) - { - return this; - } - return null; + return PublishedContent.Parent != null ? PublishedContent.Parent.AsDynamicOrNull() : null; } } #endregion - #region Where + #region Children - public HtmlString Where(string predicate, string valueIfTrue) + // we want to cache the dynamic list of children here + // whether PublishedContent.Children itself is cached, is not our concern + + private DynamicPublishedContentList _children; + + public DynamicPublishedContentList Children + { + get { return _children ?? (_children = new DynamicPublishedContentList(PublishedContent.Children)); } + } + + #endregion + + // fixme - cleanup + + #region Where + + public HtmlString Where(string predicate, string valueIfTrue) { return Where(predicate, valueIfTrue, string.Empty); } @@ -1247,6 +1331,5 @@ namespace Umbraco.Web.Models } #endregion - - } + } } diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs index d937a31bbe..548fc38342 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs @@ -7,66 +7,97 @@ using Umbraco.Core.Dynamics; using System.Collections; using System.Reflection; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Dynamics; namespace Umbraco.Web.Models { /// - /// A collection of DynamicPublishedContent items + /// Represents a collection of DynamicPublishedContent items. /// - /// - /// Implements many of the dynamic methods required for execution against this list. It also ensures - /// that the correct OwnersCollection properties is assigned to the underlying PublishedContentBase object - /// of the DynamicPublishedContent item (so long as the IPublishedContent item is actually PublishedContentBase). - /// All relates to this issue here: http://issues.umbraco.org/issue/U4-1797 - /// public class DynamicPublishedContentList : DynamicObject, IEnumerable, IEnumerable { - internal List Items { get; set; } + private readonly List _content; + private readonly PublishedContentSet _contentSet; + internal readonly List Items; + + #region Constructor public DynamicPublishedContentList() { + _content = new List(); + _contentSet = new PublishedContentSet(_content); Items = new List(); } - public DynamicPublishedContentList(IEnumerable items) - { - var list = items.ToList(); - //set the owners list for each item - list.ForEach(x => SetOwnersList(x, this)); - Items = list; - } public DynamicPublishedContentList(IEnumerable items) { - var list = items.Select(x => new DynamicPublishedContent(x)).ToList(); - //set the owners list for each item - list.ForEach(x => SetOwnersList(x, this)); - Items = list; + _content = items.ToList(); + _contentSet = new PublishedContentSet(_content); + Items = _contentSet.Select(x => new DynamicPublishedContent(x)).ToList(); } - private static void SetOwnersList(IPublishedContent content, IEnumerable list) + public DynamicPublishedContentList(IEnumerable items) { - var publishedContentBase = content as IOwnerCollectionAware; - if (publishedContentBase != null) - { - publishedContentBase.OwnersCollection = list; - } + _content = items.Select(x => x.PublishedContent).ToList(); + _contentSet = new PublishedContentSet(_content); + Items = _contentSet.Select(x => new DynamicPublishedContent(x)).ToList(); } + #endregion + + #region IList (well, part of it) + + /// + /// Adds an item to the collection. + /// + /// The item to add. + public void Add(DynamicPublishedContent dynamicContent) + { + var content = dynamicContent.PublishedContent; + _content.Add(content); + _contentSet.SourceChanged(); + + var setContent = _contentSet.MapContent(content); + Items.Add(new DynamicPublishedContent(setContent)); + } + + /// + /// Removes an item from the collection. + /// + /// The item to remove. + public void Remove(DynamicPublishedContent dynamicContent) + { + if (Items.Contains(dynamicContent) == false) return; + Items.Remove(dynamicContent); + _content.Remove(dynamicContent.PublishedContent); + _contentSet.SourceChanged(); + } + + #endregion + + #region DynamicObject + + // because we want to return DynamicNull and not null, we need to implement the index property + // via the dynamic getter and not natively - otherwise it's not possible to return DynamicNull + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { - int index = (int)indexes[0]; - try - { - result = this.Items.ElementAt(index); - return true; - } - catch (IndexOutOfRangeException) - { - result = new DynamicNull(); - return true; - } + result = DynamicNull.Null; + + if (indexes.Length != 1) + return false; + + var index = indexes[0] as int?; + if (index.HasValue == false) + return false; + + if (index >= 0 && index < Items.Count) + result = Items[index.Value]; + + return true; } + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { //TODO: Nowhere here are we checking if args is the correct length! @@ -86,7 +117,7 @@ namespace Umbraco.Web.Models { string predicate = firstArg == null ? "" : firstArg.ToString(); var values = predicate.IsNullOrWhiteSpace() ? new object[] {} : args.Skip(1).ToArray(); - var single = this.Single(predicate, values); + var single = Single(predicate, values); result = new DynamicPublishedContent(single); return true; } @@ -94,9 +125,9 @@ namespace Umbraco.Web.Models { string predicate = firstArg == null ? "" : firstArg.ToString(); var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var single = this.SingleOrDefault(predicate, values); + var single = SingleOrDefault(predicate, values); if (single == null) - result = new DynamicNull(); + result = DynamicNull.Null; else result = new DynamicPublishedContent(single); return true; @@ -105,7 +136,7 @@ namespace Umbraco.Web.Models { string predicate = firstArg == null ? "" : firstArg.ToString(); var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var first = this.First(predicate, values); + var first = First(predicate, values); result = new DynamicPublishedContent(first); return true; } @@ -113,9 +144,9 @@ namespace Umbraco.Web.Models { string predicate = firstArg == null ? "" : firstArg.ToString(); var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var first = this.FirstOrDefault(predicate, values); + var first = FirstOrDefault(predicate, values); if (first == null) - result = new DynamicNull(); + result = DynamicNull.Null; else result = new DynamicPublishedContent(first); return true; @@ -124,7 +155,7 @@ namespace Umbraco.Web.Models { string predicate = firstArg == null ? "" : firstArg.ToString(); var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var last = this.Last(predicate, values); + var last = Last(predicate, values); result = new DynamicPublishedContent(last); return true; } @@ -132,9 +163,9 @@ namespace Umbraco.Web.Models { string predicate = firstArg == null ? "" : firstArg.ToString(); var values = predicate.IsNullOrWhiteSpace() ? new object[] { } : args.Skip(1).ToArray(); - var last = this.LastOrDefault(predicate, values); + var last = LastOrDefault(predicate, values); if (last == null) - result = new DynamicNull(); + result = DynamicNull.Null; else result = new DynamicPublishedContent(last); return true; @@ -145,14 +176,14 @@ namespace Umbraco.Web.Models var values = args.Skip(1).ToArray(); //TODO: We are pre-resolving the where into a ToList() here which will have performance impacts if there where clauses // are nested! We should somehow support an QueryableDocumentList! - result = new DynamicPublishedContentList(this.Where(predicate, values).ToList()); + result = new DynamicPublishedContentList(Where(predicate, values).ToList()); return true; } if (name == "OrderBy") { //TODO: We are pre-resolving the where into a ToList() here which will have performance impacts if there where clauses // are nested! We should somehow support an QueryableDocumentList! - result = new DynamicPublishedContentList(this.OrderBy(firstArg.ToString()).ToList()); + result = new DynamicPublishedContentList(OrderBy(firstArg.ToString()).ToList()); return true; } if (name == "Take") @@ -167,24 +198,24 @@ namespace Umbraco.Web.Models } if (name == "InGroupsOf") { - int groupSize = 0; + int groupSize; if (int.TryParse(firstArg.ToString(), out groupSize)) { result = InGroupsOf(groupSize); return true; } - result = new DynamicNull(); + result = DynamicNull.Null; return true; } if (name == "GroupedInto") { - int groupCount = 0; + int groupCount; if (int.TryParse(firstArg.ToString(), out groupCount)) { result = GroupedInto(groupCount); return true; } - result = new DynamicNull(); + result = DynamicNull.Null; return true; } if (name == "GroupBy") @@ -199,14 +230,20 @@ namespace Umbraco.Web.Models } if (name == "Union") { - if ((firstArg as IEnumerable) != null) + // check DynamicPublishedContentList before IEnumerable<...> because DynamicPublishedContentList + // is IEnumerable<...> so ... the check on DynamicPublishedContentList would never be reached. + + var firstArgAsDynamicPublishedContentList = firstArg as DynamicPublishedContentList; + if (firstArgAsDynamicPublishedContentList != null) { - result = new DynamicPublishedContentList(this.Items.Union(firstArg as IEnumerable)); + result = new DynamicPublishedContentList(Items.Union((firstArgAsDynamicPublishedContentList).Items)); return true; } - if ((firstArg as DynamicPublishedContentList) != null) + + var firstArgAsIEnumerable = firstArg as IEnumerable; + if (firstArgAsIEnumerable != null) { - result = new DynamicPublishedContentList(this.Items.Union((firstArg as DynamicPublishedContentList).Items)); + result = new DynamicPublishedContentList(Items.Union(firstArgAsIEnumerable)); return true; } } @@ -214,7 +251,7 @@ namespace Umbraco.Web.Models { if ((firstArg as IEnumerable) != null) { - result = new DynamicPublishedContentList(this.Items.Except(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); + result = new DynamicPublishedContentList(Items.Except(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); return true; } } @@ -222,13 +259,13 @@ namespace Umbraco.Web.Models { if ((firstArg as IEnumerable) != null) { - result = new DynamicPublishedContentList(this.Items.Intersect(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); + result = new DynamicPublishedContentList(Items.Intersect(firstArg as IEnumerable, new DynamicPublishedContentIdEqualityComparer())); return true; } } if (name == "Distinct") { - result = new DynamicPublishedContentList(this.Items.Distinct(new DynamicPublishedContentIdEqualityComparer())); + result = new DynamicPublishedContentList(Items.Distinct(new DynamicPublishedContentIdEqualityComparer())); return true; } if (name == "Pluck" || name == "Select") @@ -275,7 +312,7 @@ namespace Umbraco.Web.Models if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod && attempt.Exception != null && attempt.Exception is TargetInvocationException) { - result = new DynamicNull(); + result = DynamicNull.Null; return true; } @@ -283,6 +320,11 @@ namespace Umbraco.Web.Models return false; } + + #endregion + + #region Linq and stuff + private T Aggregate(IEnumerable data, string name) where T : struct { switch (name) @@ -323,7 +365,7 @@ namespace Umbraco.Web.Models object firstItem = query.FirstOrDefault(); if (firstItem == null) { - result = new DynamicNull(); + result = DynamicNull.Null; } else { @@ -465,7 +507,7 @@ namespace Umbraco.Web.Models } public IQueryable OrderBy(string key) { - return ((IQueryable)Items.AsQueryable()).OrderBy(key, () => typeof(DynamicPublishedContentListOrdering)); + return ((IQueryable)Items.AsQueryable()).OrderBy(key, () => typeof(DynamicPublishedContentListOrdering)); } public DynamicGrouping GroupBy(string key) { @@ -474,10 +516,9 @@ namespace Umbraco.Web.Models } public DynamicGrouping GroupedInto(int groupCount) { - int groupSize = (int)Math.Ceiling(((decimal)Items.Count() / groupCount)); + var groupSize = (int)Math.Ceiling(((decimal)Items.Count() / groupCount)); return new DynamicGrouping( - this - .Items + Items .Select((node, index) => new KeyValuePair(index, node)) .GroupBy(kv => (object)(kv.Key / groupSize)) .Select(item => new Grouping() @@ -489,8 +530,7 @@ namespace Umbraco.Web.Models public DynamicGrouping InGroupsOf(int groupSize) { return new DynamicGrouping( - this - .Items + Items .Select((node, index) => new KeyValuePair(index, node)) .GroupBy(kv => (object)(kv.Key / groupSize)) .Select(item => new Grouping() @@ -506,38 +546,24 @@ namespace Umbraco.Web.Models return DynamicQueryable.Select(Items.AsQueryable(), predicate, values); } - /// - /// Allows the adding of an item from the collection - /// - /// - public void Add(DynamicPublishedContent publishedContent) - { - SetOwnersList(publishedContent, this); - this.Items.Add(publishedContent); - } + #endregion + + #region Dynamic - /// - /// Allows the removal of an item from the collection - /// - /// - public void Remove(DynamicPublishedContent publishedContent) - { - if (this.Items.Contains(publishedContent)) - { - //set owners list to null - SetOwnersList(publishedContent, null); - this.Items.Remove(publishedContent); - } - } public bool IsNull() { return false; } + public bool HasValue() { return true; } + #endregion + + #region Enumerate inner IPublishedContent items + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -551,6 +577,8 @@ namespace Umbraco.Web.Models IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); - } + } + + #endregion } } diff --git a/src/Umbraco.Web/Models/IOwnerCollectionAware.cs b/src/Umbraco.Web/Models/IOwnerCollectionAware.cs deleted file mode 100644 index a224f5c227..0000000000 --- a/src/Umbraco.Web/Models/IOwnerCollectionAware.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Web.Models -{ - /// - /// An interface describing that the object should be aware of it's containing collection - /// - internal interface IOwnerCollectionAware - { - IEnumerable OwnersCollection { get; set; } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 12501c6fac..3db343c8db 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -1,62 +1,36 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; -using System.Linq; -using System.Text; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Web.Templates; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models { /// - /// An abstract base class to use for IPublishedContent which ensures that the Url and Indexed property return values - /// are consistently returned. + /// Provide an abstract base class for IPublishedContent implementations. /// - /// - /// This also ensures that we have an OwnersCollection property so that the IsFirst/IsLast/Index helper methods work - /// when referenced inside the result of a collection. http://issues.umbraco.org/issue/U4-1797 - /// + /// This base class does which (a) consitently resolves and caches the Url, (b) provides an implementation + /// for this[alias], and (c) provides basic content set management. [DebuggerDisplay("Content Id: {Id}, Name: {Name}")] - public abstract class PublishedContentBase : IPublishedContent, IOwnerCollectionAware - { - private string _url; - private readonly Dictionary _resolvePropertyValues = new Dictionary(); - private IEnumerable _ownersCollection; + public abstract class PublishedContentBase : IPublishedContent + { + #region Content - /// - /// Need to get/set the owner collection when an item is returned from the result set of a query - /// - /// - /// Based on this issue here: http://issues.umbraco.org/issue/U4-1797 - /// - IEnumerable IOwnerCollectionAware.OwnersCollection - { - get - { - //if the owners collection is null, we'll default to it's siblings - if (_ownersCollection == null) - { - //get the root docs if parent is null - _ownersCollection = this.Siblings(); - } - return _ownersCollection; - } - set { _ownersCollection = value; } - } + private string _url; /// - /// Returns the Url for this content item + /// Gets the url of the content. /// /// - /// If this item type is media, the Url that is returned is the Url computed by the NiceUrlProvider, otherwise if it is media - /// the Url returned is the value found in the 'umbracoFile' property. + /// If this content is Content, the url that is returned is the one computed by the NiceUrlProvider, otherwise if + /// this content is Media, the url returned is the value found in the 'umbracoFile' property. /// public virtual string Url { get { + // should be thread-safe although it won't prevent url from being resolved more than once if (_url != null) return _url; @@ -64,20 +38,21 @@ namespace Umbraco.Web.Models { case PublishedItemType.Content: if (UmbracoContext.Current == null) - throw new InvalidOperationException("Cannot resolve a Url for a content item with a null UmbracoContext.Current reference"); + 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 with a null UmbracoContext.Current.NiceUrlProvider reference"); - _url= UmbracoContext.Current.UrlProvider.GetUrl(this.Id); + throw new InvalidOperationException("Cannot resolve a Url for a content item when UmbracoContext.Current.UrlProvider is null."); + _url= UmbracoContext.Current.UrlProvider.GetUrl(Id); break; case PublishedItemType.Media: var prop = GetProperty(Constants.Conventions.Media.File); if (prop == null) - throw new NotSupportedException("Cannot retreive a Url for a media item if there is no 'umbracoFile' property defined"); + throw new NotSupportedException("Cannot resolve a Url for a media item when there is no 'umbracoFile' property defined."); _url = prop.Value.ToString(); break; default: throw new ArgumentOutOfRangeException(); } + return _url; } } @@ -99,32 +74,126 @@ namespace Umbraco.Web.Models public abstract DateTime UpdateDate { get; } public abstract Guid Version { get; } public abstract int Level { get; } - public abstract ICollection Properties { get; } - /// - /// Returns the property value for the property alias specified - /// - /// - /// - /// - /// Ensures that the value is executed through the IPropertyEditorValueConverters and that all internal links are are to date - /// - public virtual object this[string propertyAlias] + public abstract bool IsDraft { get; } + + public int GetIndex() + { + var index = this.Siblings().FindIndex(x => x.Id == Id); + if (index < 0) + throw new IndexOutOfRangeException("Could not find content in the content set."); + return index; + } + + #endregion + + #region Tree + + /// + /// Gets the parent of the content. + /// + public abstract IPublishedContent Parent { get; } + + /// + /// Gets the children of the content. + /// + /// Children are sorted by their sortOrder. + public abstract IEnumerable Children { get; } + + #endregion + + #region ContentSet + + public virtual IEnumerable ContentSet + { + // the default content set of a content is its siblings + get { return this.Siblings(); } + } + + #endregion + + #region ContentType + + public abstract PublishedContentType ContentType { get; } + + #endregion + + #region Properties + + /// + /// Gets the properties of the content. + /// + public abstract ICollection Properties { get; } + + /// + /// Gets the value of a property identified by its alias. + /// + /// The property alias. + /// The value of the property identified by the alias. + /// + /// If GetProperty(alias) is null then returns null else return GetProperty(alias).Value. + /// So if the property has no value, returns the default value for that property type. + /// This one is defined here really because we cannot define index extension methods, but all it should do is: + /// var p = GetProperty(alias); return p == null ? null : p.Value; and nothing else. + /// The recursive syntax (eg "_title") is _not_ supported here. + /// The alias is case-insensitive. + /// + public virtual object this[string alias] { get { - //check this instance's cache, this is better for performance because resolving a value can - //have performance impacts since it has to resolve Urls and IPropertyEditorValueConverter's as well. - if (_resolvePropertyValues.ContainsKey(propertyAlias)) - return _resolvePropertyValues[propertyAlias]; - _resolvePropertyValues.Add(propertyAlias, this.GetPropertyValue(propertyAlias)); - return _resolvePropertyValues[propertyAlias]; + // no cache here: GetProperty should be fast, and .Value cache should be managed by the property. + var property = GetProperty(alias); + return property == null ? null : property.Value; } } - public abstract IPublishedContentProperty GetProperty(string alias); - public abstract IPublishedContent Parent { get; } - public abstract IEnumerable Children { get; } + /// + /// Gets a property identified by its alias. + /// + /// The property alias. + /// The property identified by the alias. + /// + /// If no property with the specified alias exists, returns null. + /// The returned property may have no value (ie HasValue is false). + /// The alias is case-insensitive. + /// + public abstract IPublishedProperty GetProperty(string alias); + /// + /// Gets a property identified by its alias. + /// + /// The property alias. + /// A value indicating whether to navigate the tree upwards until a property with a value is found. + /// The property identified by the alias. + /// + /// Navigate the tree upwards and look for a property with that alias and with a value (ie HasValue is true). + /// If found, return the property. If no property with that alias is found, having a value or not, return null. Otherwise + /// return the first property that was found with the alias but had no value (ie HasValue is false). + /// The alias is case-insensitive. + /// + public virtual IPublishedProperty GetProperty(string alias, bool recurse) + { + var property = GetProperty(alias); + if (recurse == false) return property; + + IPublishedContent content = this; + var firstNonNullProperty = property; + while (content != null && (property == null || property.HasValue == false)) + { + content = content.Parent; + property = content == null ? null : content.GetProperty(alias); + if (firstNonNullProperty == null && property != null) firstNonNullProperty = property; + } + + // if we find a content with the property with a value, return that property + // if we find no content with the property, return null + // if we find a content with the property without a value, return that property + // have to save that first property while we look further up, hence firstNonNullProperty + + return property != null && property.HasValue ? property : firstNonNullProperty; + } + + #endregion } } diff --git a/src/Umbraco.Web/Models/PublishedContentTypeCaching.cs b/src/Umbraco.Web/Models/PublishedContentTypeCaching.cs new file mode 100644 index 0000000000..2b6ea34610 --- /dev/null +++ b/src/Umbraco.Web/Models/PublishedContentTypeCaching.cs @@ -0,0 +1,76 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; + +namespace Umbraco.Web.Models +{ + // note + // this is probably how we should refresh the Core.Models.PublishedContentType cache, by subscribing to + // events from the ContentTypeCacheRefresher - however as of may 1st, 2013 that eventing system is not + // fully operational and Shannon prefers that the refresh code is hard-wired into the refresher. so this + // is commented out and the refresher calls PublishedContentType.Clear() directly. + // TODO refactor this when the refresher is ready + // FIXME should use the right syntax NOW + + class PublishedContentTypeCaching2 : ApplicationEventHandler + { + protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) + { + ContentTypeCacheRefresher.CacheUpdated += ContentTypeCacheUpdated; + DataTypeCacheRefresher.CacheUpdated += DataTypeCacheUpdated; + base.ApplicationInitialized(umbracoApplication, applicationContext); + } + + private static void ContentTypeCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e) + { + switch (e.MessageType) + { + case MessageType.RefreshAll: + PublishedContentType.ClearAll(); + break; + case MessageType.RefreshById: + case MessageType.RemoveById: + PublishedContentType.ClearContentType((int)e.MessageObject); + break; + case MessageType.RefreshByInstance: + case MessageType.RemoveByInstance: + PublishedContentType.ClearContentType(((IContentType)e.MessageObject).Id); + break; + case MessageType.RefreshByJson: + var jsonPayload = (string)e.MessageObject; + // TODO ?? FUCK! this is what we get now what? + break; + default: + throw new ArgumentOutOfRangeException("e", "Unknown message type."); + } + } + + private static void DataTypeCacheUpdated(DataTypeCacheRefresher sender, CacheRefresherEventArgs e) + { + switch (e.MessageType) + { + case MessageType.RefreshAll: + PublishedContentType.ClearAll(); + break; + case MessageType.RefreshById: + case MessageType.RemoveById: + PublishedContentType.ClearDataType((int)e.MessageObject); + break; + case MessageType.RefreshByInstance: + case MessageType.RemoveByInstance: + PublishedContentType.ClearDataType(((IDataTypeDefinition)e.MessageObject).Id); + break; + case MessageType.RefreshByJson: + var jsonPayload = (string)e.MessageObject; + // TODO ?? + break; + default: + throw new ArgumentOutOfRangeException("e", "Unknown message type."); + } + } + } +} diff --git a/src/Umbraco.Web/Models/XmlPublishedContent.cs b/src/Umbraco.Web/Models/XmlPublishedContent.cs deleted file mode 100644 index 867fc8c144..0000000000 --- a/src/Umbraco.Web/Models/XmlPublishedContent.cs +++ /dev/null @@ -1,368 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Xml; -using System.Xml.Serialization; -using System.Xml.XPath; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Models; -using Umbraco.Web.Routing; - -namespace Umbraco.Web.Models -{ - - /// - /// Represents an IPublishedContent which is created based on an Xml structure - /// - [Serializable] - [XmlType(Namespace = "http://umbraco.org/webservices/")] - internal class XmlPublishedContent : PublishedContentBase - { - /// - /// Constructor - /// - /// - public XmlPublishedContent(XmlNode xmlNode) - { - _pageXmlNode = xmlNode; - InitializeStructure(); - Initialize(); - } - - /// - /// Constructor - /// - /// - /// - internal XmlPublishedContent(XmlNode xmlNode, bool disableInitializing) - { - _pageXmlNode = xmlNode; - InitializeStructure(); - if (!disableInitializing) - Initialize(); - } - - private bool _initialized = false; - private readonly ICollection _children = new Collection(); - private IPublishedContent _parent = null; - private int _id; - private int _template; - private string _name; - private string _docTypeAlias; - private int _docTypeId; - private string _writerName; - private string _creatorName; - private int _writerId; - private int _creatorId; - private string _urlName; - private string _path; - private DateTime _createDate; - private DateTime _updateDate; - private Guid _version; - private readonly Collection _properties = new Collection(); - private readonly XmlNode _pageXmlNode; - private int _sortOrder; - private int _level; - - public override IEnumerable Children - { - get - { - if (!_initialized) - Initialize(); - return _children.OrderBy(x => x.SortOrder); - } - } - - public override IPublishedContentProperty GetProperty(string alias) - { - return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); - } - - /// - /// returns 'Content' as the ItemType - /// - public override PublishedItemType ItemType - { - get { return PublishedItemType.Content; } - } - - public override IPublishedContent Parent - { - get - { - if (!_initialized) - Initialize(); - return _parent; - } - } - - public override int Id - { - get - { - if (!_initialized) - Initialize(); - return _id; - } - } - - public override int TemplateId - { - get - { - if (!_initialized) - Initialize(); - return _template; - } - } - - public override int SortOrder - { - get - { - if (!_initialized) - Initialize(); - return _sortOrder; - } - } - - public override string Name - { - get - { - if (!_initialized) - Initialize(); - return _name; - } - } - - public override string DocumentTypeAlias - { - get - { - if (!_initialized) - Initialize(); - return _docTypeAlias; - } - } - - public override int DocumentTypeId - { - get - { - if (!_initialized) - Initialize(); - return _docTypeId; - } - } - - public override string WriterName - { - get - { - if (!_initialized) - Initialize(); - return _writerName; - } - } - - public override string CreatorName - { - get - { - if (!_initialized) - Initialize(); - return _creatorName; - } - } - - public override int WriterId - { - get - { - if (!_initialized) - Initialize(); - return _writerId; - } - } - - public override int CreatorId - { - get - { - if (!_initialized) - Initialize(); - return _creatorId; - } - } - - - public override string Path - { - get - { - if (!_initialized) - Initialize(); - return _path; - } - } - - public override DateTime CreateDate - { - get - { - if (!_initialized) - Initialize(); - return _createDate; - } - } - - public override DateTime UpdateDate - { - get - { - if (!_initialized) - Initialize(); - return _updateDate; - } - } - - public override Guid Version - { - get - { - if (!_initialized) - Initialize(); - return _version; - } - } - - public override string UrlName - { - get - { - if (!_initialized) - Initialize(); - return _urlName; - } - } - - public override int Level - { - get - { - if (!_initialized) - Initialize(); - return _level; - } - } - - public override ICollection Properties - { - get - { - if (!_initialized) - Initialize(); - return _properties; - } - } - - - private void InitializeStructure() - { - // Load parent if it exists and is a node - - if (_pageXmlNode != null && _pageXmlNode.SelectSingleNode("..") != null) - { - XmlNode parent = _pageXmlNode.SelectSingleNode(".."); - if (parent != null && (parent.Name == "node" || (parent.Attributes != null && parent.Attributes.GetNamedItem("isDoc") != null))) - _parent = new XmlPublishedContent(parent, true); - } - } - - private void Initialize() - { - if (_pageXmlNode != null) - { - _initialized = true; - if (_pageXmlNode.Attributes != null) - { - _id = int.Parse(_pageXmlNode.Attributes.GetNamedItem("id").Value); - if (_pageXmlNode.Attributes.GetNamedItem("template") != null) - _template = int.Parse(_pageXmlNode.Attributes.GetNamedItem("template").Value); - if (_pageXmlNode.Attributes.GetNamedItem("sortOrder") != null) - _sortOrder = int.Parse(_pageXmlNode.Attributes.GetNamedItem("sortOrder").Value); - if (_pageXmlNode.Attributes.GetNamedItem("nodeName") != null) - _name = _pageXmlNode.Attributes.GetNamedItem("nodeName").Value; - if (_pageXmlNode.Attributes.GetNamedItem("writerName") != null) - _writerName = _pageXmlNode.Attributes.GetNamedItem("writerName").Value; - if (_pageXmlNode.Attributes.GetNamedItem("urlName") != null) - _urlName = _pageXmlNode.Attributes.GetNamedItem("urlName").Value; - // Creatorname is new in 2.1, so published xml might not have it! - try - { - _creatorName = _pageXmlNode.Attributes.GetNamedItem("creatorName").Value; - } - catch - { - _creatorName = _writerName; - } - - //Added the actual userID, as a user cannot be looked up via full name only... - if (_pageXmlNode.Attributes.GetNamedItem("creatorID") != null) - _creatorId = int.Parse(_pageXmlNode.Attributes.GetNamedItem("creatorID").Value); - if (_pageXmlNode.Attributes.GetNamedItem("writerID") != null) - _writerId = int.Parse(_pageXmlNode.Attributes.GetNamedItem("writerID").Value); - - if (UmbracoSettings.UseLegacyXmlSchema) - { - if (_pageXmlNode.Attributes.GetNamedItem("nodeTypeAlias") != null) - _docTypeAlias = _pageXmlNode.Attributes.GetNamedItem("nodeTypeAlias").Value; - } - else - { - _docTypeAlias = _pageXmlNode.Name; - } - - if (_pageXmlNode.Attributes.GetNamedItem("nodeType") != null) - _docTypeId = int.Parse(_pageXmlNode.Attributes.GetNamedItem("nodeType").Value); - if (_pageXmlNode.Attributes.GetNamedItem("path") != null) - _path = _pageXmlNode.Attributes.GetNamedItem("path").Value; - if (_pageXmlNode.Attributes.GetNamedItem("version") != null) - _version = new Guid(_pageXmlNode.Attributes.GetNamedItem("version").Value); - if (_pageXmlNode.Attributes.GetNamedItem("createDate") != null) - _createDate = DateTime.Parse(_pageXmlNode.Attributes.GetNamedItem("createDate").Value); - if (_pageXmlNode.Attributes.GetNamedItem("updateDate") != null) - _updateDate = DateTime.Parse(_pageXmlNode.Attributes.GetNamedItem("updateDate").Value); - if (_pageXmlNode.Attributes.GetNamedItem("level") != null) - _level = int.Parse(_pageXmlNode.Attributes.GetNamedItem("level").Value); - - } - - // load data - var dataXPath = UmbracoSettings.UseLegacyXmlSchema ? "data" : "* [not(@isDoc)]"; - foreach (XmlNode n in _pageXmlNode.SelectNodes(dataXPath)) - _properties.Add(new XmlPublishedContentProperty(n)); - - // load children - var childXPath = UmbracoSettings.UseLegacyXmlSchema ? "node" : "* [@isDoc]"; - var nav = _pageXmlNode.CreateNavigator(); - var expr = nav.Compile(childXPath); - expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); - var iterator = nav.Select(expr); - while (iterator.MoveNext()) - { - _children.Add( - new XmlPublishedContent(((IHasXmlNode)iterator.Current).GetNode(), true) - ); - } - } - // else - // throw new ArgumentNullException("Node xml source is null"); - } - - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/XmlPublishedContentProperty.cs b/src/Umbraco.Web/Models/XmlPublishedContentProperty.cs deleted file mode 100644 index 386c75ec39..0000000000 --- a/src/Umbraco.Web/Models/XmlPublishedContentProperty.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Xml; -using System.Xml.Serialization; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.IO; -using Umbraco.Core.Models; -using Umbraco.Web.Templates; - -namespace Umbraco.Web.Models -{ - - /// - /// Represents an IDocumentProperty which is created based on an Xml structure. - /// - [Serializable] - [XmlType(Namespace = "http://umbraco.org/webservices/")] - public class XmlPublishedContentProperty : IPublishedContentProperty - { - private readonly Guid _version; - private readonly string _alias; - private readonly string _value; - - public string Alias - { - get { return _alias; } - } - - private string _parsedValue; - - /// - /// Returns the value of a property from the XML cache - /// - /// - /// This ensures that the result has any {localLink} syntax parsed and that urls are resolved correctly. - /// This also ensures that the parsing is only done once as the result is cached in a private field of this object. - /// - public object Value - { - get - { - if (_parsedValue == null) - { - _parsedValue = TemplateUtilities.ResolveUrlsFromTextString( - TemplateUtilities.ParseInternalLinks( - _value)); - } - return _parsedValue; - } - } - - public Guid Version - { - get { return _version; } - } - - public XmlPublishedContentProperty() - { - - } - - public XmlPublishedContentProperty(XmlNode propertyXmlData) - { - if (propertyXmlData != null) - { - // For backward compatibility with 2.x (the version attribute has been removed from 3.0 data nodes) - if (propertyXmlData.Attributes.GetNamedItem("versionID") != null) - _version = new Guid(propertyXmlData.Attributes.GetNamedItem("versionID").Value); - _alias = UmbracoSettings.UseLegacyXmlSchema ? - propertyXmlData.Attributes.GetNamedItem("alias").Value : - propertyXmlData.Name; - _value = XmlHelper.GetNodeValue(propertyXmlData); - } - else - throw new ArgumentNullException("Property xml source is null"); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs deleted file mode 100644 index ee59ea6e94..0000000000 --- a/src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Web; -using Umbraco.Core; -using Umbraco.Core.Macros; -using Umbraco.Core.PropertyEditors; - -namespace Umbraco.Web.PropertyEditors -{ - /// - /// A value converter for TinyMCE that will ensure any macro content is rendered properly even when - /// used dynamically. - /// - internal class RteMacroRenderingPropertyEditorValueConverter : TinyMcePropertyEditorValueConverter - { - - /// - /// Return IHtmlString so devs doesn't need to decode html - /// - /// - /// - public override Attempt ConvertPropertyValue(object value) - { - //we're going to send the string through the macro parser and create the output string. - var sb = new StringBuilder(); - var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); - MacroTagParser.ParseMacros( - value.ToString(), - //callback for when text block is found - textBlock => sb.Append(textBlock), - //callback for when macro syntax is found - (macroAlias, macroAttributes) => sb.Append(umbracoHelper.RenderMacro( - macroAlias, - //needs to be explicitly casted to Dictionary - macroAttributes.ConvertTo(x => (string)x, x => (object)x)).ToString())); - - return Attempt.Succeed(new HtmlString(sb.ToString())); - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs b/src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs new file mode 100644 index 0000000000..20cac816ea --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/RteMacroRenderingValueConverter.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Web; +using Umbraco.Core; +using Umbraco.Core.Macros; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// A value converter for TinyMCE that will ensure any macro content is rendered properly even when + /// used dynamically. + /// + // because that version of RTE converter parses {locallink} and executes macros, when going from + // data to source, its source value has to be cached at the request level, because we have no idea + // what the macros may depend on actually. An so, object and xpath need to follow... request, too. + // note: the TinyMceValueConverter is NOT inherited, so the PropertyValueCache attribute here is not + // actually required (since Request is default) but leave it here to be absolutely explicit. + [PropertyValueType(typeof(IHtmlString))] + [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Request)] + internal class RteMacroRenderingValueConverter : TinyMceValueConverter + { + string RenderRteMacros(string source) + { + // fixme - not taking 'preview' into account here + // but we should, when running the macros... how?! + + var sb = new StringBuilder(); + var umbracoHelper = new UmbracoHelper(UmbracoContext.Current); + MacroTagParser.ParseMacros( + source, + //callback for when text block is found + textBlock => sb.Append(textBlock), + //callback for when macro syntax is found + (macroAlias, macroAttributes) => sb.Append(umbracoHelper.RenderMacro( + macroAlias, + //needs to be explicitly casted to Dictionary + macroAttributes.ConvertTo(x => (string)x, x => (object)x)).ToString())); + return sb.ToString(); + } + + public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + sourceString = TextValueConverterHelper.ParseStringValueSource(sourceString); // fixme - must handle preview + sourceString = RenderRteMacros(sourceString); // fixme - must handle preview + + return sourceString; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/TextValueConverterHelper.cs b/src/Umbraco.Web/PropertyEditors/TextValueConverterHelper.cs new file mode 100644 index 0000000000..63843d6d0e --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TextValueConverterHelper.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Web.Templates; + +namespace Umbraco.Web.PropertyEditors +{ + class TextValueConverterHelper + { + // ensures string value sources are parsed for {localLink} and urls are resolved correctly + // fixme - but then that one should get "previewing" too? + public static string ParseStringValueSource(string stringValueSource) + { + return TemplateUtilities.ResolveUrlsFromTextString(TemplateUtilities.ParseInternalLinks(stringValueSource)); + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs index b5c3d850d2..944b85c576 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCache.cs @@ -24,6 +24,15 @@ namespace Umbraco.Web.PublishedCache UmbracoContext = umbracoContext; } + /// + /// Informs the contextual cache that content has changed. + /// + /// The contextual cache may, although that is not mandatory, provide an immutable snapshot of + /// the content over the duration of the context. If you make changes to the content and do want to have + /// the cache update its snapshot, you have to explicitely ask it to do so by calling ContentHasChanged. + public virtual void ContentHasChanged() + { } + /// /// Gets a content identified by its unique identifier. /// diff --git a/src/Umbraco.Web/PublishedCache/ContextualPublishedCacheOfT.cs b/src/Umbraco.Web/PublishedCache/ContextualPublishedCacheOfT.cs index d759153419..6e897c2c2e 100644 --- a/src/Umbraco.Web/PublishedCache/ContextualPublishedCacheOfT.cs +++ b/src/Umbraco.Web/PublishedCache/ContextualPublishedCacheOfT.cs @@ -12,6 +12,8 @@ namespace Umbraco.Web.PublishedCache /// Provides access to cached contents in a specified context. /// /// The type of the underlying published cache. + /// The type differenciates between the content cache and the media cache, + /// ie it will be either IPublishedContentCache or IPublishedMediaCache. public abstract class ContextualPublishedCache : ContextualPublishedCache where T : IPublishedCache { diff --git a/src/Umbraco.Web/PublishedCache/PublishedCachesResolver.cs b/src/Umbraco.Web/PublishedCache/PublishedCachesResolver.cs index 129b876a4f..d060ac554b 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedCachesResolver.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedCachesResolver.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.PublishedCache /// /// The caches. /// For developers, at application startup. - public void SetCache(IPublishedCaches caches) + public void SetCaches(IPublishedCaches caches) { Value = caches; } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs index 6b82f278be..455bd22e13 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedContentCache.cs @@ -17,6 +17,7 @@ using umbraco.presentation.preview; namespace Umbraco.Web.PublishedCache.XmlPublishedCache { + // fixme - does not implement the content model factory internal class PublishedContentCache : IPublishedContentCache { #region Routes cache @@ -235,14 +236,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache #region Converters - private static IPublishedContent ConvertToDocument(XmlNode xmlNode) + private static IPublishedContent ConvertToDocument(XmlNode xmlNode, bool isPreviewing) { - return xmlNode == null ? null : new Models.XmlPublishedContent(xmlNode); - } + return xmlNode == null ? null : new XmlPublishedContent(xmlNode, isPreviewing); + } - private static IEnumerable ConvertToDocuments(XmlNodeList xmlNodes) + private static IEnumerable ConvertToDocuments(XmlNodeList xmlNodes, bool isPreviewing) { - return xmlNodes.Cast().Select(xmlNode => new Models.XmlPublishedContent(xmlNode)); + return xmlNodes.Cast().Select(xmlNode => new XmlPublishedContent(xmlNode, isPreviewing)); } #endregion @@ -251,12 +252,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId) { - return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture))); + return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview); } public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) { - return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments)); + return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments), preview); } public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars) @@ -268,7 +269,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var node = vars == null ? xml.SelectSingleNode(xpath) : xml.SelectSingleNode(xpath, vars); - return ConvertToDocument(node); + return ConvertToDocument(node, preview); } public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars) @@ -279,7 +280,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var node = vars == null ? xml.SelectSingleNode(xpath) : xml.SelectSingleNode(xpath, vars); - return ConvertToDocument(node); + return ConvertToDocument(node, preview); } public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, params XPathVariable[] vars) @@ -291,7 +292,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var nodes = vars == null ? xml.SelectNodes(xpath) : xml.SelectNodes(xpath, vars); - return ConvertToDocuments(nodes); + return ConvertToDocuments(nodes, preview); } public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, params XPathVariable[] vars) @@ -302,7 +303,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache var nodes = vars == null ? xml.SelectNodes(xpath) : xml.SelectNodes(xpath, vars); - return ConvertToDocuments(nodes); + return ConvertToDocuments(nodes, preview); } public virtual bool HasContent(UmbracoContext umbracoContext, bool preview) diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 03c44f34c9..39bae8b2bf 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -12,6 +12,7 @@ using Umbraco.Core; using Umbraco.Core.Dynamics; using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; using Umbraco.Web.Models; using UmbracoExamine; @@ -27,7 +28,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly. /// - internal class PublishedMediaCache : IPublishedMediaCache + // fixme - does not implement the content model factory + internal class PublishedMediaCache : IPublishedMediaCache { public PublishedMediaCache() { @@ -314,7 +316,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// /// /// - private IPublishedContentProperty GetProperty(DictionaryPublishedContent dd, string alias) + private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias) { if (dd.LoadedFromExamine) { @@ -326,7 +328,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache //is cached so will be quicker to look up. if (dd.Properties.Any(x => x.Alias == UmbracoContentIndexer.NodeTypeAliasFieldName)) { - var aliasesAndNames = ContentType.GetAliasesAndNames(dd.Properties.First(x => x.Alias.InvariantEquals(UmbracoContentIndexer.NodeTypeAliasFieldName)).Value.ToString()); + var aliasesAndNames = ContentType.GetAliasesAndNames(dd.Properties.First(x => x.Alias.InvariantEquals(UmbracoContentIndexer.NodeTypeAliasFieldName)).RawValue.ToString()); if (aliasesAndNames != null) { if (!aliasesAndNames.ContainsKey(alias)) @@ -466,12 +468,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache /// internal class DictionaryPublishedContent : PublishedContentBase { + // note: I'm not sure this class fully complies with IPublishedContent rules especially + // I'm not sure that _properties contains all properties including those without a value, + // neither that GetProperty will return a property without a value vs. null... @zpqrtbnk public DictionaryPublishedContent( IDictionary valueDictionary, Func getParent, Func> getChildren, - Func getProperty, + Func getProperty, bool fromExamine) { if (valueDictionary == null) throw new ArgumentNullException("valueDictionary"); @@ -509,15 +514,27 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } }, "parentID"); - _properties = new Collection(); + _contentType = PublishedContentType.Get(PublishedItemType.Media, _documentTypeAlias); + _properties = new Collection(); //loop through remaining values that haven't been applied foreach (var i in valueDictionary.Where(x => !_keysAdded.Contains(x.Key))) { - //this is taken from examine - _properties.Add(i.Key.InvariantStartsWith("__") - ? new PropertyResult(i.Key, i.Value, Guid.Empty, PropertyResultType.CustomProperty) - : new PropertyResult(i.Key, i.Value, Guid.Empty, PropertyResultType.UserProperty)); + IPublishedProperty property; + + if (i.Key.InvariantStartsWith("__")) + { + // no type for tha tone, dunno how to convert + property = new PropertyResult(i.Key, i.Value, Guid.Empty, PropertyResultType.CustomProperty); + } + else + { + // use property type to ensure proper conversion + var propertyType = _contentType.GetPropertyType(i.Key); + property = new XmlPublishedProperty(propertyType, false, i.Value); // false :: never preview a media + } + + _properties.Add(property); } } @@ -546,7 +563,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private readonly Func _getParent; private readonly Func> _getChildren; - private readonly Func _getProperty; + private readonly Func _getProperty; /// /// Returns 'Media' as the item type @@ -646,7 +663,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache get { return _level; } } - public override ICollection Properties + public override bool IsDraft + { + get { return false; } + } + + public override ICollection Properties { get { return _properties; } } @@ -656,11 +678,49 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache get { return _getChildren(this); } } - public override IPublishedContentProperty GetProperty(string alias) + public override IPublishedProperty GetProperty(string alias) { return _getProperty(this, alias); } + public override PublishedContentType ContentType + { + get { return _contentType; } + } + + // override to implement cache + // cache at context level, ie once for the whole request + // but cache is not shared by requests because we wouldn't know how to clear it + public override IPublishedProperty GetProperty(string alias, bool recurse) + { + if (recurse == false) return GetProperty(alias); + + IPublishedProperty property; + string key = null; + var cache = UmbracoContextCache.Current; + + if (cache != null) + { + key = string.Format("RECURSIVE_PROPERTY::{0}::{1}", Id, alias.ToLowerInvariant()); + object o; + if (cache.TryGetValue(key, out o)) + { + property = o as IPublishedProperty; + if (property == null) + throw new InvalidOperationException("Corrupted cache."); + return property; + } + } + + // else get it for real, no cache + property = base.GetProperty(alias, true); + + if (cache != null) + cache[key] = property; + + return property; + } + private readonly List _keysAdded = new List(); private int _id; private int _templateId; @@ -678,7 +738,8 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache private DateTime _updateDate; private Guid _version; private int _level; - private readonly ICollection _properties; + private readonly ICollection _properties; + private readonly PublishedContentType _contentType; private void ValidateAndSetProperty(IDictionary valueDictionary, Action setProperty, params string[] potentialKeys) { @@ -691,6 +752,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache setProperty(valueDictionary[key]); _keysAdded.Add(key); } - } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/UmbracoContextCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/UmbracoContextCache.cs new file mode 100644 index 0000000000..6b82a7ee3d --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/UmbracoContextCache.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.CompilerServices; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + static class UmbracoContextCache + { + static readonly ConditionalWeakTable> Caches + = new ConditionalWeakTable>(); + + public static ConcurrentDictionary Current + { + get + { + var umbracoContext = UmbracoContext.Current; + + // will get or create a value + // a ConditionalWeakTable is thread-safe + // does not prevent the context from being disposed, and then the dictionary will be disposed too + return umbracoContext == null ? null : Caches.GetOrCreateValue(umbracoContext); + } + } + } +} diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs new file mode 100644 index 0000000000..302546f550 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -0,0 +1,448 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Xml; +using System.Xml.Serialization; +using System.Xml.XPath; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + + /// + /// Represents an IPublishedContent which is created based on an Xml structure. + /// + [Serializable] + [XmlType(Namespace = "http://umbraco.org/webservices/")] + internal class XmlPublishedContent : PublishedContentBase + { + /// + /// Initializes a new instance of the XmlPublishedContent class with an Xml node. + /// + /// The Xml node. + /// A value indicating whether the published content is being previewed. + public XmlPublishedContent(XmlNode xmlNode, bool isPreviewing) + { + _xmlNode = xmlNode; + _isPreviewing = isPreviewing; + InitializeStructure(); + Initialize(); + InitializeChildren(); + } + + /// + /// Initializes a new instance of the XmlPublishedContent class with an Xml node, + /// and a value indicating whether to lazy-initialize the instance. + /// + /// The Xml node. + /// A value indicating whether the published content is being previewed. + /// A value indicating whether to lazy-initialize the instance. + /// Lazy-initializationg is NOT thread-safe. + internal XmlPublishedContent(XmlNode xmlNode, bool isPreviewing, bool lazyInitialize) + { + _xmlNode = xmlNode; + _isPreviewing = isPreviewing; + InitializeStructure(); + if (lazyInitialize == false) + { + Initialize(); + InitializeChildren(); + } + } + + private readonly XmlNode _xmlNode; + + private bool _initialized; + private bool _childrenInitialized; + + private readonly ICollection _children = new Collection(); + private IPublishedContent _parent; + + private int _id; + private int _template; + private string _name; + private string _docTypeAlias; + private int _docTypeId; + private string _writerName; + private string _creatorName; + private int _writerId; + private int _creatorId; + private string _urlName; + private string _path; + private DateTime _createDate; + private DateTime _updateDate; + private Guid _version; + private IPublishedProperty[] _properties; + private int _sortOrder; + private int _level; + private bool _isDraft; + private readonly bool _isPreviewing; + private PublishedContentType _contentType; + + public override IEnumerable Children + { + get + { + if (_initialized == false) + Initialize(); + if (_childrenInitialized == false) + InitializeChildren(); + return _children.OrderBy(x => x.SortOrder); + } + } + + public override IPublishedProperty GetProperty(string alias) + { + return Properties.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } + + // override to implement cache + // cache at context level, ie once for the whole request + // but cache is not shared by requests because we wouldn't know how to clear it + public override IPublishedProperty GetProperty(string alias, bool recurse) + { + if (recurse == false) return GetProperty(alias); + + var cache = UmbracoContextCache.Current; + + if (cache == null) + return base.GetProperty(alias, true); + + var key = string.Format("RECURSIVE_PROPERTY::{0}::{1}", Id, alias.ToLowerInvariant()); + var value = cache.GetOrAdd(key, k => base.GetProperty(alias, true)); + if (value == null) + return null; + + var property = value as IPublishedProperty; + if (property == null) + throw new InvalidOperationException("Corrupted cache."); + + return property; + } + + public override PublishedItemType ItemType + { + get { return PublishedItemType.Content; } + } + + public override IPublishedContent Parent + { + get + { + if (_initialized == false) + Initialize(); + return _parent; + } + } + + public override int Id + { + get + { + if (_initialized == false) + Initialize(); + return _id; + } + } + + public override int TemplateId + { + get + { + if (_initialized == false) + Initialize(); + return _template; + } + } + + public override int SortOrder + { + get + { + if (_initialized == false) + Initialize(); + return _sortOrder; + } + } + + public override string Name + { + get + { + if (_initialized == false) + Initialize(); + return _name; + } + } + + public override string DocumentTypeAlias + { + get + { + if (_initialized == false) + Initialize(); + return _docTypeAlias; + } + } + + public override int DocumentTypeId + { + get + { + if (_initialized == false) + Initialize(); + return _docTypeId; + } + } + + public override string WriterName + { + get + { + if (_initialized == false) + Initialize(); + return _writerName; + } + } + + public override string CreatorName + { + get + { + if (_initialized == false) + Initialize(); + return _creatorName; + } + } + + public override int WriterId + { + get + { + if (_initialized == false) + Initialize(); + return _writerId; + } + } + + public override int CreatorId + { + get + { + if (_initialized == false) + Initialize(); + return _creatorId; + } + } + + public override string Path + { + get + { + if (_initialized == false) + Initialize(); + return _path; + } + } + + public override DateTime CreateDate + { + get + { + if (_initialized == false) + Initialize(); + return _createDate; + } + } + + public override DateTime UpdateDate + { + get + { + if (_initialized == false) + Initialize(); + return _updateDate; + } + } + + public override Guid Version + { + get + { + if (_initialized == false) + Initialize(); + return _version; + } + } + + public override string UrlName + { + get + { + if (_initialized == false) + Initialize(); + return _urlName; + } + } + + public override int Level + { + get + { + if (_initialized == false) + Initialize(); + return _level; + } + } + + public override bool IsDraft + { + get + { + if (_initialized == false) + Initialize(); + return _isDraft; + } + } + + public override ICollection Properties + { + get + { + if (_initialized == false) + Initialize(); + return _properties; + } + } + + public override PublishedContentType ContentType + { + get + { + if (_initialized == false) + Initialize(); + return _contentType; + } + } + + private void InitializeStructure() + { + // load parent if it exists and is a node + + var parent = _xmlNode == null ? null : _xmlNode.ParentNode; + if (parent == null) return; + + if (parent.Name == "node" || (parent.Attributes != null && parent.Attributes.GetNamedItem("isDoc") != null)) + _parent = new XmlPublishedContent(parent, _isPreviewing, true); + } + + private void Initialize() + { + if (_xmlNode == null) return; + + if (_xmlNode.Attributes != null) + { + _id = int.Parse(_xmlNode.Attributes.GetNamedItem("id").Value); + if (_xmlNode.Attributes.GetNamedItem("template") != null) + _template = int.Parse(_xmlNode.Attributes.GetNamedItem("template").Value); + if (_xmlNode.Attributes.GetNamedItem("sortOrder") != null) + _sortOrder = int.Parse(_xmlNode.Attributes.GetNamedItem("sortOrder").Value); + if (_xmlNode.Attributes.GetNamedItem("nodeName") != null) + _name = _xmlNode.Attributes.GetNamedItem("nodeName").Value; + if (_xmlNode.Attributes.GetNamedItem("writerName") != null) + _writerName = _xmlNode.Attributes.GetNamedItem("writerName").Value; + if (_xmlNode.Attributes.GetNamedItem("urlName") != null) + _urlName = _xmlNode.Attributes.GetNamedItem("urlName").Value; + // Creatorname is new in 2.1, so published xml might not have it! + try + { + _creatorName = _xmlNode.Attributes.GetNamedItem("creatorName").Value; + } + catch + { + _creatorName = _writerName; + } + + //Added the actual userID, as a user cannot be looked up via full name only... + if (_xmlNode.Attributes.GetNamedItem("creatorID") != null) + _creatorId = int.Parse(_xmlNode.Attributes.GetNamedItem("creatorID").Value); + if (_xmlNode.Attributes.GetNamedItem("writerID") != null) + _writerId = int.Parse(_xmlNode.Attributes.GetNamedItem("writerID").Value); + + if (UmbracoSettings.UseLegacyXmlSchema) + { + if (_xmlNode.Attributes.GetNamedItem("nodeTypeAlias") != null) + _docTypeAlias = _xmlNode.Attributes.GetNamedItem("nodeTypeAlias").Value; + } + else + { + _docTypeAlias = _xmlNode.Name; + } + + if (_xmlNode.Attributes.GetNamedItem("nodeType") != null) + _docTypeId = int.Parse(_xmlNode.Attributes.GetNamedItem("nodeType").Value); + if (_xmlNode.Attributes.GetNamedItem("path") != null) + _path = _xmlNode.Attributes.GetNamedItem("path").Value; + if (_xmlNode.Attributes.GetNamedItem("version") != null) + _version = new Guid(_xmlNode.Attributes.GetNamedItem("version").Value); + if (_xmlNode.Attributes.GetNamedItem("createDate") != null) + _createDate = DateTime.Parse(_xmlNode.Attributes.GetNamedItem("createDate").Value); + if (_xmlNode.Attributes.GetNamedItem("updateDate") != null) + _updateDate = DateTime.Parse(_xmlNode.Attributes.GetNamedItem("updateDate").Value); + if (_xmlNode.Attributes.GetNamedItem("level") != null) + _level = int.Parse(_xmlNode.Attributes.GetNamedItem("level").Value); + + _isDraft = (_xmlNode.Attributes.GetNamedItem("isDraft") != null); + } + + // load data + var dataXPath = UmbracoSettings.UseLegacyXmlSchema ? "data" : "* [not(@isDoc)]"; + var nodes = _xmlNode.SelectNodes(dataXPath); + + _contentType = PublishedContentType.Get(PublishedItemType.Content, _docTypeAlias); + + var propertyNodes = new Dictionary(); + if (nodes != null) + foreach (XmlNode n in nodes) + { + var alias = UmbracoSettings.UseLegacyXmlSchema + ? n.Attributes.GetNamedItem("alias").Value + : n.Name; + propertyNodes[alias.ToLowerInvariant()] = n; + } + + _properties = _contentType.PropertyTypes.Select(p => + { + XmlNode n; + return propertyNodes.TryGetValue(p.Alias.ToLowerInvariant(), out n) + ? new XmlPublishedProperty(p, _isPreviewing, n) + : new XmlPublishedProperty(p, _isPreviewing); + }).Cast().ToArray(); + + // warn: this is not thread-safe... + _initialized = true; + } + + private void InitializeChildren() + { + if (_xmlNode == null) return; + + // load children + var childXPath = UmbracoSettings.UseLegacyXmlSchema ? "node" : "* [@isDoc]"; + var nav = _xmlNode.CreateNavigator(); + var expr = nav.Compile(childXPath); + expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); + var iterator = nav.Select(expr); + while (iterator.MoveNext()) + _children.Add(new XmlPublishedContent(((IHasXmlNode)iterator.Current).GetNode(), _isPreviewing, true)); + + // warn: this is not thread-safe + _childrenInitialized = true; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs new file mode 100644 index 0000000000..55242e07b6 --- /dev/null +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedProperty.cs @@ -0,0 +1,67 @@ +using System; +using System.Xml; +using System.Xml.Serialization; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Models; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + + /// + /// Represents an IDocumentProperty which is created based on an Xml structure. + /// + [Serializable] + [XmlType(Namespace = "http://umbraco.org/webservices/")] + internal class XmlPublishedProperty : PublishedPropertyBase + { + private readonly string _xmlValue; // the raw, xml node value + private readonly Lazy _sourceValue; + private readonly Lazy _value; + private readonly Lazy _xpathValue; + private readonly bool _isPreviewing; + + /// + /// Gets the raw value of the property. + /// + public override object RawValue { get { return _xmlValue; } } + + // in the Xml cache, everything is a string, and to have a value + // you want to have a non-null, non-empty string. + public override bool HasValue + { + get { return _xmlValue.Trim().Length > 0; } + } + + public override object Value { get { return _value.Value; } } + public override object XPathValue { get { return _xpathValue.Value; } } + + public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, XmlNode propertyXmlData) + : this(propertyType, isPreviewing) + { + if (propertyXmlData == null) + throw new ArgumentNullException("propertyXmlData", "Property xml source is null"); + _xmlValue = XmlHelper.GetNodeValue(propertyXmlData); + } + + public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing, string propertyData) + : this(propertyType, isPreviewing) + { + if (propertyData == null) + throw new ArgumentNullException("propertyData"); + _xmlValue = propertyData; + } + + public XmlPublishedProperty(PublishedPropertyType propertyType, bool isPreviewing) + : base(propertyType) + { + _xmlValue = string.Empty; + _isPreviewing = isPreviewing; + + _sourceValue = new Lazy(() => PropertyType.ConvertDataToSource(_xmlValue, _isPreviewing)); + _value = new Lazy(() => PropertyType.ConvertSourceToObject(_sourceValue.Value, _isPreviewing)); + _xpathValue = new Lazy(() => PropertyType.ConvertSourceToXPath(_sourceValue.Value, _isPreviewing)); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 29d7e1f22c..0d06154bac 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1,272 +1,458 @@ +// fixme - should define - ok for now +// axes navigation is broken in many ways... but fixes would not be 100% +// backward compatible... so keep them for v7 or whenever appropriate. +#undef FIX_AXES + using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Data; using System.Linq; using System.Web; using Examine.LuceneEngine.SearchCriteria; -using Umbraco.Core.Dynamics; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Models; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; -using Umbraco.Web.Templates; -using umbraco; -using umbraco.cms.businesslogic; using Umbraco.Core; -using umbraco.cms.businesslogic.template; -using umbraco.interfaces; +using Umbraco.Web.PropertyEditors; using ContentType = umbraco.cms.businesslogic.ContentType; -using Template = umbraco.cms.businesslogic.template.Template; namespace Umbraco.Web { - /// - /// Extension methods for IPublishedContent - /// - /// - /// These methods exist in the web project as we need access to web based classes like NiceUrl provider - /// which is why they cannot exist in the Core project. - /// + /// + /// Provides extension methods for IPublishedContent. + /// public static class PublishedContentExtensions - { + { + #region Urls - /// - /// Converts an INode to an IPublishedContent item + /// + /// Gets the url for the content. /// - /// - /// - internal static IPublishedContent ConvertFromNode(this INode node) - { - var umbHelper = new UmbracoHelper(UmbracoContext.Current); - return umbHelper.TypedContent(node.Id); - } - - /// - /// Gets the NiceUrl for the content item - /// - /// - /// + /// The content. + /// The url for the content. [Obsolete("NiceUrl() is obsolete, use the Url() method instead")] - public static string NiceUrl(this IPublishedContent doc) + public static string NiceUrl(this IPublishedContent content) { - return doc.Url(); + return content.Url(); } /// - /// Gets the Url for the content item + /// Gets the url for the content. /// - /// - /// - public static string Url(this IPublishedContent doc) + /// 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) { - switch (doc.ItemType) - { - case PublishedItemType.Content: - var umbHelper = new UmbracoHelper(UmbracoContext.Current); - return umbHelper.NiceUrl(doc.Id); - case PublishedItemType.Media: - var prop = doc.GetProperty(Constants.Conventions.Media.File); - if (prop == null) - throw new NotSupportedException("Cannot retreive a Url for a media item if there is no 'umbracoFile' property defined"); - return prop.Value.ToString(); - default: - throw new ArgumentOutOfRangeException(); - } + return content.Url; } /// - /// Gets the NiceUrlWithDomain for the content item + /// Gets the absolute url for the content. /// - /// - /// - [Obsolete("NiceUrlWithDomain() is obsolete, use the UrlWithDomain() method instead")] - public static string NiceUrlWithDomain(this IPublishedContent doc) + /// 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 doc.UrlWithDomain(); + return content.UrlAbsolute(); } /// - /// Gets the UrlWithDomain for the content item + /// Gets the absolute url for the content. /// - /// - /// - public static string UrlWithDomain(this IPublishedContent doc) + /// The content. + /// The absolute url for the content. + //[Obsolete("UrlWithDomain() is obsolete, use the UrlAbsolute() method instead.")] + public static string UrlWithDomain(this IPublishedContent content) { - switch (doc.ItemType) - { - case PublishedItemType.Content: - var umbHelper = new UmbracoHelper(UmbracoContext.Current); - return umbHelper.NiceUrlWithDomain(doc.Id); - case PublishedItemType.Media: - throw new NotSupportedException("NiceUrlWithDomain is not supported for media types"); - default: - throw new ArgumentOutOfRangeException(); - } + 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); + 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 doc) - { - var template = Template.GetTemplate(doc.TemplateId); - return template != null ? template.Alias : string.Empty; + public static string GetTemplateAlias(this IPublishedContent content) + { + var template = ApplicationContext.Current.Services.FileService.GetTemplate(content.TemplateId); + return template == null ? string.Empty : template.Alias; } - #region GetPropertyValue + #endregion - /// - /// if the val is a string, ensures all internal local links are parsed + #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) + { + // FIXME that is very wrong, we want the TYPE that was used when creating the IPublishedContent else caching issues!!!! + var contentType = PublishedContentType.Get(content.ItemType, content.DocumentTypeAlias); + return 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 + + /// + /// Provides a shortcut to GetPropertyValue{T}. + /// + /// The content. + /// The property alias. + /// The value of the content's property identified by the alias. + public static T V(this IPublishedContent content, string alias) + { + return content.GetPropertyValue(alias); + } + + /// + /// Provides a shortcut to GetPropertyValue{T} with recursion. + /// + /// The content. + /// The property alias. + /// The value of the content's property identified by the alias. + public static T Vr(this IPublishedContent content, string alias) + { + return content.GetPropertyValue(alias, true); + } + + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. /// - /// - /// - internal static object GetValueWithParsedLinks(object val) + /// 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) { - //if it is a string send it through the url parser - var text = val as string; - if (text != null) - { - return TemplateUtilities.ResolveUrlsFromTextString( - TemplateUtilities.ParseInternalLinks(text)); - } - //its not a string - return val; + return content.GetPropertyValue(alias, false, false, default(T)); } - public static object GetPropertyValue(this IPublishedContent doc, string alias) - { - return doc.GetPropertyValue(alias, false); - } - public static object GetPropertyValue(this IPublishedContent doc, string alias, string fallback) - { - var prop = doc.GetPropertyValue(alias); - return (prop != null && !Convert.ToString(prop).IsNullOrWhiteSpace()) ? prop : fallback; - } - public static object GetPropertyValue(this IPublishedContent doc, string alias, bool recursive) - { - var p = doc.GetProperty(alias, recursive); - if (p == null) return null; + /// + /// 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); + } - //Here we need to put the value through the IPropertyEditorValueConverter's - //get the data type id for the current property - var dataType = PublishedContentHelper.GetDataType( - ApplicationContext.Current, doc.DocumentTypeAlias, alias, - doc.ItemType); + /// + /// 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)); + } - //convert the string value to a known type - var converted = PublishedContentHelper.ConvertPropertyValue(p.Value, dataType, doc.DocumentTypeAlias, alias); - return converted.Success - ? GetValueWithParsedLinks(converted.Result) - : GetValueWithParsedLinks(p.Value); - } - public static object GetPropertyValue(this IPublishedContent doc, string alias, bool recursive, string fallback) - { - var prop = doc.GetPropertyValue(alias, recursive); - return (prop != null && !Convert.ToString(prop).IsNullOrWhiteSpace()) ? prop : fallback; - } + /// + /// 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); + } - /// - /// Returns the property as the specified type, if the property is not found or does not convert - /// then the default value of type T is returned. - /// - /// - /// - /// - /// - public static T GetPropertyValue(this IPublishedContent doc, string alias) - { - return doc.GetPropertyValue(alias, default(T)); - } + 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; - public static T GetPropertyValue(this IPublishedContent prop, string alias, bool recursive, T ifCannotConvert) - { - var p = prop.GetProperty(alias, recursive); - if (p == null) - return ifCannotConvert; - - //before we try to convert it manually, lets see if the PropertyEditorValueConverter does this for us - //Here we need to put the value through the IPropertyEditorValueConverter's - //get the data type id for the current property - var dataType = PublishedContentHelper.GetDataType(ApplicationContext.Current, prop.DocumentTypeAlias, alias, prop.ItemType); - //convert the value to a known type - var converted = PublishedContentHelper.ConvertPropertyValue(p.Value, dataType, prop.DocumentTypeAlias, alias); - object parsedLinksVal; - if (converted.Success) - { - parsedLinksVal = GetValueWithParsedLinks(converted.Result); - - //if its successful, check if its the correct type and return it - if (parsedLinksVal is T) - { - return (T)parsedLinksVal; - } - //if that's not correct, try converting the converted type - var reConverted = converted.Result.TryConvertTo(); - if (reConverted.Success) - { - return reConverted.Result; - } - } - - //first, parse links if possible - parsedLinksVal = GetValueWithParsedLinks(p.Value); - //last, if all the above has failed, we'll just try converting the raw value straight to 'T' - var manualConverted = parsedLinksVal.TryConvertTo(); - if (manualConverted.Success) - return manualConverted.Result; - return ifCannotConvert; - } - - public static T GetPropertyValue(this IPublishedContent prop, string alias, T ifCannotConvert) - { - return prop.GetPropertyValue(alias, false, ifCannotConvert); + 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 d, string term, bool useWildCards = true, string searchProvider = null) + + 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)) + if (string.IsNullOrEmpty(searchProvider) == false) searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; var t = term.Escape().Value; if (useWildCards) t = term.MultipleCharacterWildcard().Value; - string luceneQuery = "+__Path:(" + d.Path.Replace("-", "\\-") + "*) +" + t; + var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); - return d.Search(crit, searcher); + return content.Search(crit, searcher); } - public static IEnumerable SearchDescendants(this IPublishedContent d, string term, bool useWildCards = true, string searchProvider = null) + public static IEnumerable SearchDescendants(this IPublishedContent content, string term, bool useWildCards = true, string searchProvider = null) { - return d.Search(term, useWildCards, searchProvider); + return content.Search(term, useWildCards, searchProvider); } - public static IEnumerable SearchChildren(this IPublishedContent d, string term, bool useWildCards = true, string searchProvider = null) + 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)) + if (string.IsNullOrEmpty(searchProvider) == false) searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; var t = term.Escape().Value; if (useWildCards) t = term.MultipleCharacterWildcard().Value; - string luceneQuery = "+parentID:" + d.Id.ToString() + " +" + t; + var luceneQuery = "+parentID:" + content.Id + " +" + t; var crit = searcher.CreateSearchCriteria().RawQuery(luceneQuery); - return d.Search(crit, searcher); + return content.Search(crit, searcher); } - public static IEnumerable Search(this IPublishedContent d, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + 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) @@ -275,147 +461,48 @@ namespace Umbraco.Web var results = s.Search(criteria); return results.ConvertSearchResultToPublishedContent(UmbracoContext.Current.ContentCache); } + #endregion - - #region Linq Wrapping Extensions + #region ToContentSet - //NOTE: These are all purely required to fix this issue: http://issues.umbraco.org/issue/U4-1797 which requires that any - // content item knows about it's containing collection. - - public static IEnumerable Where(this IEnumerable source, Func predicate) + /// + /// 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 { - var internalResult = Enumerable.Where(source, predicate); - return new DynamicPublishedContentList(internalResult); + return new PublishedContentSet(source); } - public static IEnumerable Where(this IEnumerable source, Func predicate) + /// + /// 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 { - var internalResult = Enumerable.Where(source, predicate); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Take(this IEnumerable source, int count) - { - var internalResult = Enumerable.Take(source, count); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable TakeWhile(this IEnumerable source, Func predicate) - { - var internalResult = Enumerable.TakeWhile(source, predicate); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable TakeWhile(this IEnumerable source, Func predicate) - { - var internalResult = Enumerable.TakeWhile(source, predicate); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Skip(this IEnumerable source, int count) - { - var internalResult = Enumerable.Skip(source, count); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable SkipWhile(this IEnumerable source, Func predicate) - { - var internalResult = Enumerable.SkipWhile(source, predicate); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable SkipWhile(this IEnumerable source, Func predicate) - { - var internalResult = Enumerable.SkipWhile(source, predicate); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Concat(this IEnumerable first, IEnumerable second) - { - var internalResult = Enumerable.Concat(first, second); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Distinct(this IEnumerable source) - { - var internalResult = Enumerable.Distinct(source); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Distinct(this IEnumerable source, IEqualityComparer comparer) - { - var internalResult = Enumerable.Distinct(source, comparer); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Union(this IEnumerable first, IEnumerable second) - { - var internalResult = Enumerable.Union(first, second); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Union(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) - { - var internalResult = Enumerable.Union(first, second, comparer); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Intersect(this IEnumerable first, IEnumerable second) - { - var internalResult = Enumerable.Intersect(first, second); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Intersect(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) - { - var internalResult = Enumerable.Intersect(first, second, comparer); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Except(this IEnumerable first, IEnumerable second) - { - var internalResult = Enumerable.Except(first, second); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Except(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) - { - var internalResult = Enumerable.Except(first, second, comparer); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable Reverse(this IEnumerable source) - { - var internalResult = Enumerable.Reverse(source); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable DefaultIfEmpty(this IEnumerable source) - { - var internalResult = Enumerable.DefaultIfEmpty(source); - return new DynamicPublishedContentList(internalResult); - } - - public static IEnumerable DefaultIfEmpty(this IEnumerable source, IPublishedContent defaultValue) - { - var internalResult = Enumerable.DefaultIfEmpty(source, defaultValue); - return new DynamicPublishedContentList(internalResult); + return new PublishedContentOrderedSet(source); } #endregion - #region Dynamic Linq Extensions - public static IQueryable OrderBy(this IEnumerable list, string predicate) + // TODO cleanup... do we really want dynamics here? + + public static IQueryable OrderBy(this IEnumerable source, string predicate) { - var dList = new DynamicPublishedContentList(list); + var dList = new DynamicPublishedContentList(source); return dList.OrderBy(predicate); } public static IQueryable Where(this IEnumerable list, string predicate) { + // fixme - but wait... ?! var dList = new DynamicPublishedContentList(list); //we have to wrap the result in another DynamicPublishedContentList so that the OwnersList get's set on //the individual items. See: http://issues.umbraco.org/issue/U4-1797 @@ -436,363 +523,414 @@ namespace Umbraco.Web return dList.Select(predicate); } - #endregion + 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 dynamic AsDynamic(this IPublishedContent doc) - { - if (doc == null) throw new ArgumentNullException("doc"); - var dd = new DynamicPublishedContent(doc); - return dd.AsDynamic(); - } + 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); + } - /// - /// Converts a IPublishedContent to a DynamicPublishedContent and tests for null - /// - /// - /// - internal static DynamicPublishedContent AsDynamicPublishedContent(this IPublishedContent content) + 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) - return null; + if (content == null) throw new ArgumentNullException("content"); return new DynamicPublishedContent(content); } - #region Where - - public static HtmlString Where(this IPublishedContent doc, string predicate, string valueIfTrue) + // content CAN be null + internal static DynamicPublishedContent AsDynamicOrNull(this IPublishedContent content) { - if (doc == null) throw new ArgumentNullException("doc"); - return doc.Where(predicate, valueIfTrue, string.Empty); + return content == null ? null : new DynamicPublishedContent(content); } - public static HtmlString Where(this IPublishedContent doc, string predicate, string valueIfTrue, string valueIfFalse) - { - if (doc == null) throw new ArgumentNullException("doc"); - if (doc.Where(predicate)) - { - return new HtmlString(valueIfTrue); - } - return new HtmlString(valueIfFalse); - } - - public static bool Where(this IPublishedContent doc, string predicate) - { - if (doc == null) throw new ArgumentNullException("doc"); - var dynamicDocumentList = new DynamicPublishedContentList(); - dynamicDocumentList.Add(doc.AsDynamicPublishedContent()); - var filtered = dynamicDocumentList.Where(predicate); - if (filtered.Count() == 1) - { - //this node matches the predicate - return true; - } - return false; - } + #endregion - #endregion + #region ContentSet - #region Position/Index public static int Position(this IPublishedContent content) { - return content.Index(); - } - public static int Index(this IPublishedContent content) - { - var container = content.GetOwnersList().ToList(); - int currentIndex = container.FindIndex(n => n.Id == content.Id); - if (currentIndex != -1) - { - return currentIndex; - } - else - { - throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicDocumentList but could not retrieve the index for it's position in the list", content.Id)); - } + return content.GetIndex(); } - /// - /// Return the owners collection of the current content item. - /// - /// - /// - /// - /// If the content item is of type PublishedContentBase we will have a property called OwnersCollection which will - /// be the collection of a resultant set (i.e. from a where clause, a call to Children(), etc...) otherwise it will - /// be the item's siblings. All relates to this issue: http://issues.umbraco.org/issue/U4-1797 - /// - private static IEnumerable GetOwnersList(this IPublishedContent content) + public static int Index(this IPublishedContent content) { - //Here we need to type check, we need to see if we have a real OwnersCollection list based on the result set - // of a query, otherwise we can only lookup among the item's siblings. All related to this issue here: - // http://issues.umbraco.org/issue/U4-1797 + return content.GetIndex(); + } - var publishedContentBase = content as IOwnerCollectionAware; - var ownersList = publishedContentBase != null - ? publishedContentBase.OwnersCollection - : content.Siblings(); - return ownersList; - } + 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; + } + + // fixme - remove - now IPublishedContent.Index() is native + //public static int Index(this IPublishedContent content) + //{ + // // fast: check if content knows its index + // var withIndex = content as IPublishedContentWithIndex; + // if (withIndex != null && withIndex.Index.HasValue) return withIndex.Index.Value; + + // // slow: find content in the content set + // var index = content.Index(content.ContentSet); + // if (withIndex != null) withIndex.Index = index; + // return index; + //} + + //private static int Index(this IPublishedContent content, IEnumerable set) + //{ + // var index = set.FindIndex(n => n.Id == content.Id); + // if (index >= 0) return index; + + // throw new IndexOutOfRangeException("Could not find content in the content set."); + //} #endregion - #region Is Helpers + #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". + + var umbracoNaviHide = content.GetProperty(Constants.Conventions.Content.NaviHide); + + // fixme - works but not using the proper converters? + if (umbracoNaviHide == null || umbracoNaviHide.HasValue == false) return true; + return umbracoNaviHide.GetValue() == false; + } public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) { return content.DocumentTypeAlias == docTypeAlias; } - public static bool IsNull(this IPublishedContent content, string alias, bool recursive) + public static bool IsNull(this IPublishedContent content, string alias, bool recurse) { - var prop = content.GetProperty(alias, recursive); - if (prop == null) return true; - return ((PropertyResult)prop).HasValue(); + return content.HasValue(alias, recurse) == false; } + public static bool IsNull(this IPublishedContent content, string alias) { - return content.IsNull(alias, false); - } + return content.HasValue(alias) == false; + } - #region Position in list + #endregion + + #region IsSomething: position in set public static bool IsFirst(this IPublishedContent content) { - return content.IsHelper(n => n.Index() == 0); + return content.GetIndex() == 0; } + public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue) { - return content.IsHelper(n => n.Index() == 0, valueIfTrue); + return content.IsFirst(valueIfTrue, string.Empty); } + public static HtmlString IsFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() == 0, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsFirst() ? valueIfTrue : valueIfFalse); } + public static bool IsNotFirst(this IPublishedContent content) { - return !content.IsHelper(n => n.Index() == 0); + return content.IsFirst() == false; } + public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue) { - return content.IsHelper(n => n.Index() != 0, valueIfTrue); + return content.IsNotFirst(valueIfTrue, string.Empty); } + public static HtmlString IsNotFirst(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() != 0, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsNotFirst() ? valueIfTrue : valueIfFalse); } + public static bool IsPosition(this IPublishedContent content, int index) { - return content.IsHelper(n => n.Index() == index); + return content.GetIndex() == index; } + public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue) { - return content.IsHelper(n => n.Index() == index, valueIfTrue); + return content.IsPosition(index, valueIfTrue, string.Empty); } + public static HtmlString IsPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() == index, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsPosition(index) ? valueIfTrue : valueIfFalse); } + public static bool IsModZero(this IPublishedContent content, int modulus) { - return content.IsHelper(n => n.Index() % modulus == 0); + return content.GetIndex() % modulus == 0; } + public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue) { - return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue); + return content.IsModZero(modulus, valueIfTrue, string.Empty); } + public static HtmlString IsModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() % modulus == 0, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsModZero(modulus) ? valueIfTrue : valueIfFalse); } + public static bool IsNotModZero(this IPublishedContent content, int modulus) { - return content.IsHelper(n => n.Index() % modulus != 0); + return content.IsModZero(modulus) == false; } + public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue) { - return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue); + return content.IsNotModZero(modulus, valueIfTrue, string.Empty); } + public static HtmlString IsNotModZero(this IPublishedContent content, int modulus, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() % modulus != 0, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsNotModZero(modulus) ? valueIfTrue : valueIfFalse); } + public static bool IsNotPosition(this IPublishedContent content, int index) { - return !content.IsHelper(n => n.Index() == index); + return content.IsPosition(index) == false; } + public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue) { - return content.IsHelper(n => n.Index() != index, valueIfTrue); + return content.IsNotPosition(index, valueIfTrue, string.Empty); } + public static HtmlString IsNotPosition(this IPublishedContent content, int index, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() != index, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsNotPosition(index) ? valueIfTrue : valueIfFalse); } + public static bool IsLast(this IPublishedContent content) { - var ownersList = content.GetOwnersList(); - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1); + return content.GetIndex() == content.ContentSet.Count() - 1; } + public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue) { - var ownersList = content.GetOwnersList(); - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1, valueIfTrue); + return content.IsLast(valueIfTrue, string.Empty); } + public static HtmlString IsLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { - var ownersList = content.GetOwnersList(); - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() == count - 1, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsLast() ? valueIfTrue : valueIfFalse); } + public static bool IsNotLast(this IPublishedContent content) { - var ownersList = content.GetOwnersList(); - var count = ownersList.Count(); - return !content.IsHelper(n => n.Index() == count - 1); + return content.IsLast() == false; } + public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue) { - var ownersList = content.GetOwnersList(); - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() != count - 1, valueIfTrue); + return content.IsNotLast(valueIfTrue, string.Empty); } + public static HtmlString IsNotLast(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { - var ownersList = content.GetOwnersList(); - var count = ownersList.Count(); - return content.IsHelper(n => n.Index() != count - 1, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsNotLast() ? valueIfTrue : valueIfFalse); } + public static bool IsEven(this IPublishedContent content) { - return content.IsHelper(n => n.Index() % 2 == 0); + return content.GetIndex() % 2 == 0; } + public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue) { - return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue); + return content.IsEven(valueIfTrue, string.Empty); } + public static HtmlString IsEven(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() % 2 == 0, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsEven() ? valueIfTrue : valueIfFalse); } + public static bool IsOdd(this IPublishedContent content) { - return content.IsHelper(n => n.Index() % 2 == 1); + return content.GetIndex() % 2 == 1; } + public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue) { - return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue); + return content.IsOdd(valueIfTrue, string.Empty); } + public static HtmlString IsOdd(this IPublishedContent content, string valueIfTrue, string valueIfFalse) { - return content.IsHelper(n => n.Index() % 2 == 1, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsOdd() ? valueIfTrue : valueIfFalse); } + #endregion - + + #region IsSomething: equality + public static bool IsEqual(this IPublishedContent content, IPublishedContent other) { - return content.IsHelper(n => n.Id == other.Id); - } - public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) - { - return content.IsHelper(n => n.Id == other.Id, valueIfTrue); + 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 content.IsHelper(n => n.Id == other.Id, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsEqual(other) ? valueIfTrue : valueIfFalse); } + public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) { - return content.IsHelper(n => n.Id != other.Id); + return content.IsEqual(other) == false; } - public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue) + + 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 content.IsHelper(n => n.Id != other.Id, valueIfTrue); + return new HtmlString(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse); } - public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) - { - return content.IsHelper(n => n.Id != other.Id, valueIfTrue, valueIfFalse); - } - public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) - { - var ancestors = content.Ancestors(); - return content.IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.Id == other.Id) != null); + + #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) { - var ancestors = content.Ancestors(); - return content.IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.Id == other.Id) != null, valueIfTrue); + return content.IsDescendant(other, valueIfTrue, string.Empty); } + public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { - var ancestors = content.Ancestors(); - return content.IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.Id == other.Id) != null, valueIfTrue, valueIfFalse); + return new HtmlString(content.IsDescendant(other) ? valueIfTrue : valueIfFalse); } + public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) { - var ancestors = content.AncestorsOrSelf(); - return content.IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.Id == other.Id) != null); - } + return content.AncestorsOrSelf().Any(x => x.Id == other.Id); + } + public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue) { - var ancestors = content.AncestorsOrSelf(); - return content.IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.Id == other.Id) != null, valueIfTrue); - } + return content.IsDescendantOrSelf(other, valueIfTrue, string.Empty); + } + public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { - var ancestors = content.AncestorsOrSelf(); - return content.IsHelper(n => ancestors.FirstOrDefault(ancestor => ancestor.Id == other.Id) != null, valueIfTrue, valueIfFalse); - } + return new HtmlString(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse); + } + public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) { - var descendants = content.Descendants(); - return content.IsHelper(n => descendants.FirstOrDefault(descendant => descendant.Id == other.Id) != null); + // 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) { - var descendants = content.Descendants(); - return content.IsHelper(n => descendants.FirstOrDefault(descendant => descendant.Id == other.Id) != null, valueIfTrue); - } + return content.IsAncestor(other, valueIfTrue, string.Empty); + } + public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { - var descendants = content.Descendants(); - return content.IsHelper(n => descendants.FirstOrDefault(descendant => descendant.Id == other.Id) != null, valueIfTrue, valueIfFalse); - } + return new HtmlString(content.IsAncestor(other) ? valueIfTrue : valueIfFalse); + } + public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) { - var descendants = content.DescendantsOrSelf(); - return content.IsHelper(n => descendants.FirstOrDefault(descendant => descendant.Id == other.Id) != null); - } + // 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) { - var descendants = content.DescendantsOrSelf(); - return content.IsHelper(n => descendants.FirstOrDefault(descendant => descendant.Id == other.Id) != null, valueIfTrue); - } + return content.IsAncestorOrSelf(other, valueIfTrue, string.Empty); + } + public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse) { - var descendants = content.DescendantsOrSelf(); - return content.IsHelper(n => descendants.FirstOrDefault(descendant => descendant.Id == other.Id) != null, valueIfTrue, valueIfFalse); - } - private static bool IsHelper(this IPublishedContent content, Func test) - { - return test(content); - } - private static HtmlString IsHelper(this IPublishedContent content, Func test, string valueIfTrue) - { - return content.IsHelper(test, valueIfTrue, string.Empty); - } - private static HtmlString IsHelper(this IPublishedContent content, Func test, string valueIfTrue, string valueIfFalse) - { - return test(content) ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse); - } + return new HtmlString(content.IsAncestorOrSelf(other) ? valueIfTrue : valueIfFalse); + } - #endregion + #endregion - #region Ancestors + #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. + + // SO, here we want to walk up the tree. which is what AncestorOrSelf does but NOT what AncestorsOrSelf does since + // it reverses the list, so basically ancestors are NOT XPath-compliant in Umbraco at the moment -- but fixing that + // would be a breaking change. Defining FIX_AXES would fix the situation. public static IEnumerable Ancestors(this IPublishedContent content) { - return content.AncestorsOrSelf(false, n => true); + return content.AncestorsOrSelf(false, null); } public static IEnumerable Ancestors(this IPublishedContent content, int level) @@ -800,51 +938,14 @@ namespace Umbraco.Web return content.AncestorsOrSelf(false, n => n.Level <= level); } - public static IEnumerable Ancestors(this IPublishedContent content, string nodeTypeAlias) + public static IEnumerable Ancestors(this IPublishedContent content, string contentTypeAlias) { - return content.AncestorsOrSelf(false, n => n.DocumentTypeAlias == nodeTypeAlias); - } - - internal static IEnumerable Ancestors(this IPublishedContent content, Func func) - { - return content.AncestorsOrSelf(false, func); - } - - public static IPublishedContent AncestorOrSelf(this IPublishedContent content) - { - //TODO: Why is this query like this?? - return content.AncestorOrSelf(node => node.Level == 1); - } - - public static IPublishedContent AncestorOrSelf(this IPublishedContent content, int level) - { - return content.AncestorOrSelf(node => node.Level == level); - } - - public static IPublishedContent AncestorOrSelf(this IPublishedContent content, string nodeTypeAlias) - { - return content.AncestorOrSelf(node => node.DocumentTypeAlias == nodeTypeAlias); - } - - internal static IPublishedContent AncestorOrSelf(this IPublishedContent content, Func func) - { - if (func(content)) - return content; - - while (content.Level > 1) // while we have a parent, consider the parent - { - content = content.Parent; - - if (func(content)) - return content; - } - - return null; + return content.AncestorsOrSelf(false, n => n.DocumentTypeAlias == contentTypeAlias); } public static IEnumerable AncestorsOrSelf(this IPublishedContent content) { - return content.AncestorsOrSelf(true, n => true); + return content.AncestorsOrSelf(true, null); } public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int level) @@ -852,307 +953,583 @@ namespace Umbraco.Web return content.AncestorsOrSelf(true, n => n.Level <= level); } - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, string nodeTypeAlias) + public static IEnumerable AncestorsOrSelf(this IPublishedContent content, string contentTypeAlias) { - return content.AncestorsOrSelf(true, n => n.DocumentTypeAlias == nodeTypeAlias); + return content.AncestorsOrSelf(true, n => n.DocumentTypeAlias == contentTypeAlias); } - internal static IEnumerable AncestorsOrSelf(this IPublishedContent content, Func func) - { - return content.AncestorsOrSelf(true, func); - } - - internal static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func func) + public static IPublishedContent Ancestor(this IPublishedContent content) { + return content.Parent; + } + + public static IPublishedContent Ancestor(this IPublishedContent content, int level) + { + return content.EnumerateAncestors(false).FirstOrDefault(x => x.Level <= level); + } + + public static IPublishedContent Ancestor(this IPublishedContent content, string contentTypeAlias) + { + return content.EnumerateAncestors(false).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); + } + + // note: that one makes no sense and should return self -- but fixing that + // would be a breaking change. Defining FIX_AXES would fix the situation. + public static IPublishedContent AncestorOrSelf(this IPublishedContent content) + { +#if FIX_AXES + return content; +#else + return content.EnumerateAncestors(true).FirstOrDefault(x => x.Level == 1); +#endif + } + + public static IPublishedContent AncestorOrSelf(this IPublishedContent content, int level) + { + return content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= level); + } + + public static IPublishedContent AncestorOrSelf(this IPublishedContent content, string contentTypeAlias) + { + return content.EnumerateAncestors(true).FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAlias); + } + + // broken until we defined FIX_AXES + internal static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func func) + { +#if FIX_AXES + return content.EnumerateAncestors(orSelf).Where(x => func == null || func(x)); +#else var ancestors = new List(); - if (orSelf && func(content)) + if (orSelf && (func == null || func(content))) ancestors.Add(content); while (content.Level > 1) // while we have a parent, consider the parent { content = content.Parent; - if (func(content)) + if ((func == null || func(content))) ancestors.Add(content); } ancestors.Reverse(); return ancestors; +#endif + } + + internal static IEnumerable EnumerateAncestors(this IPublishedContent content, bool orSelf) + { + if (orSelf) yield return content; + while ((content = content.Parent) != null) + yield return content; } #endregion - #region Descendants - public static IEnumerable Descendants(this IPublishedContent content, string nodeTypeAlias) - { - return content.Descendants(p => p.DocumentTypeAlias == nodeTypeAlias); - } - public static IEnumerable Descendants(this IPublishedContent content, int level) - { - return content.Descendants(p => p.Level >= level); - } - public static IEnumerable Descendants(this IPublishedContent content) - { - return content.Descendants(n => true); - } - private static IEnumerable Descendants(this IPublishedContent content, Func func) - { - //return content.Children.Map(func, (IPublishedContent n) => n.Children); - return content.Children.FlattenList(x => x.Children).Where(func) - .OrderBy(x => x.Level) //ensure its sorted by level and then by sort order - .ThenBy(x => x.SortOrder); - } - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, int level) - { - return content.DescendantsOrSelf(p => p.Level >= level); - } - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, string nodeTypeAlias) - { - return content.DescendantsOrSelf(p => p.DocumentTypeAlias == nodeTypeAlias); - } - public static IEnumerable DescendantsOrSelf(this IPublishedContent content) - { - return content.DescendantsOrSelf(p => true); - } - internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, Func func) - { - if (content != null) - { - var thisNode = new List(); - if (func(content)) - { - thisNode.Add(content); - } - //var flattenedNodes = content.Children.Map(func, (IPublishedContent n) => n.Children); - var flattenedNodes = content.Children.FlattenList(n => n.Children).Where(func); + #region Axes: descendants, descendants-or-self - return thisNode.Concat(flattenedNodes) - .Select(dynamicBackingItem => new DynamicPublishedContent(dynamicBackingItem)) - .OrderBy(x => x.Level) //ensure its sorted by level and then by sort order - .ThenBy(x => x.SortOrder); - } - return Enumerable.Empty(); - } - #endregion + // 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. + + // SO, here we want to implement a depth-first enumeration of children. Which is what EnumerateDescendants does, but NOT what + // DescendantsOrSelf does, so basically descendants are NOT XPath-compliant in Umbraco at the moment -- but fixing that + // would be a breaking change. Defining FIX_AXES would fix the situation. - #region Traversal + 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 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 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 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); + } + + // broken until we defined FIX_AXES + internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, bool orSelf, Func func) + { +#if FIX_AXES + return content.EnumerateDescendants(orSelf).Where(x => func == null || func(x)); +#else + var init = (orSelf && (func == null || func(content))) ? new[] { content } : new IPublishedContent[] { }; + + var descendants = init + .Union(content.Children + .FlattenList(x => x.Children) + .Where(x => func == null || func(x)) + ) + .OrderBy(x => x.Level) + .ThenBy(x => x.SortOrder); + + return descendants; +#endif + } + + internal static IEnumerable EnumerateDescendants(this IPublishedContent content, bool orSelf) + { + 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 public static IPublishedContent Up(this IPublishedContent content) { - return content.Up(0); + return content.Parent; } + public static IPublishedContent Up(this IPublishedContent content, int number) { - if (number == 0) - { - return content.Parent; - } - while ((content = content.Parent) != null && --number >= 0) ; - return content; + if (number < 0) + throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); +#if (!FIX_AXES) + number += 1; // legacy is zero-based ie zero == parent +#endif + return number == 0 ? content : content.EnumerateAncestors(false).Skip(number).FirstOrDefault(); } - public static IPublishedContent Up(this IPublishedContent content, string nodeTypeAlias) + + public static IPublishedContent Up(this IPublishedContent content, string contentTypeAlias) { - if (string.IsNullOrEmpty(nodeTypeAlias)) - { - return content.Parent; - } - while ((content = content.Parent) != null && content.DocumentTypeAlias != nodeTypeAlias) ; - return content; + return string.IsNullOrEmpty(contentTypeAlias) + ? content.Parent + : content.Ancestor(contentTypeAlias); } + + // down pseudo-axe ~ children (not descendants) + public static IPublishedContent Down(this IPublishedContent content) { - return content.Down(0); + return content.Children.FirstOrDefault(); } + public static IPublishedContent Down(this IPublishedContent content, int number) { - var children = content.Children; - if (number == 0) - { - return children.First(); - } - var working = content; - while (number-- >= 0) - { - working = children.First(); - children = new DynamicPublishedContentList(working.Children); - } - return working; + if (number < 0) + throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); +#if (!FIX_AXES) + number += 1; // legacy is zero-based ie zero == first child +#endif + 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 nodeTypeAlias) + + public static IPublishedContent Down(this IPublishedContent content, string contentTypeAlias) { - if (string.IsNullOrEmpty(nodeTypeAlias)) - { - var children = content.Children; - return children.First(); - } - return content.Descendants(nodeTypeAlias).FirstOrDefault(); + 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 + public static IPublishedContent Next(this IPublishedContent content) { - return content.Next(0); - } - public static IPublishedContent Next(this IPublishedContent content, int number) - { - var ownersList = content.GetOwnersList(); + return content.Move(+1); + } - var container = ownersList.ToList(); - var currentIndex = container.FindIndex(n => n.Id == content.Id); - if (currentIndex != -1) - { - return container.ElementAtOrDefault(currentIndex + (number + 1)); - } - throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); - } - - public static IPublishedContent Next(this IPublishedContent content, string nodeTypeAlias) + public static IPublishedContent Next(this IPublishedContent content, int number) { - var ownersList = content.GetOwnersList(); + if (number < 0) + throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); +#if (!FIX_AXES) + number += 1; // legacy is zero-based ie zero == next, whereas zero should be current +#endif + return number == 0 ? content : content.Move(+number); + } - var container = ownersList.ToList(); - var currentIndex = container.FindIndex(n => n.Id == content.Id); - if (currentIndex != -1) - { - var newIndex = container.FindIndex(currentIndex, n => n.DocumentTypeAlias == nodeTypeAlias); - return newIndex != -1 - ? container.ElementAt(newIndex) - : null; - } - throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); - } - public static IPublishedContent Previous(this IPublishedContent content) + public static IPublishedContent Next(this IPublishedContent content, string contentTypeAlias) + { + return content.Move(+1, contentTypeAlias); + } + + public static IPublishedContent Next(this IPublishedContent content, string contentTypeAlias, bool wrap) + { + var axis = content.ContentSet.ToArray(); + var currentIndex = content.GetIndex(); + var nextIndex = axis.Skip(currentIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + if (nextIndex >= 0 && nextIndex < axis.Length) return axis.ElementAt(currentIndex + nextIndex); + if (wrap == false) return null; + nextIndex = axis.Take(currentIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + return axis.ElementAtOrDefault(nextIndex); + } + + // previous pseudo-axe ~ preceding within the content set + + public static IPublishedContent Previous(this IPublishedContent content) { - return content.Previous(0); - } + return content.Move(-1); + } + public static IPublishedContent Previous(this IPublishedContent content, int number) { - var ownersList = content.GetOwnersList(); + if (number < 0) + throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); +#if (!FIX_AXES) + number = -number; // legacy wants negative numbers, should be positive + number += 1; // legacy is zero-based ie zero == previous, whereas zero should be current +#endif + return content.Move(-number); + } - var container = ownersList.ToList(); - var currentIndex = container.FindIndex(n => n.Id == content.Id); - if (currentIndex != -1) - { - return container.ElementAtOrDefault(currentIndex + (number - 1)); - } - throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); - } - public static IPublishedContent Previous(this IPublishedContent content, string nodeTypeAlias) + public static IPublishedContent Previous(this IPublishedContent content, string contentTypeAlias) { - var ownersList = content.GetOwnersList(); + return content.Move(-1, contentTypeAlias); + } - var container = ownersList.ToList(); - int currentIndex = container.FindIndex(n => n.Id == content.Id); - if (currentIndex != -1) - { - var previousNodes = container.Take(currentIndex).ToList(); - int newIndex = previousNodes.FindIndex(n => n.DocumentTypeAlias == nodeTypeAlias); - if (newIndex != -1) - { - return container.ElementAt(newIndex); - } - return null; - } - throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); - } + public static IPublishedContent Previous(this IPublishedContent content, string contentTypeAlias, bool wrap) + { + var axis = content.ContentSet.ToArray(); + var currentIndex = content.GetIndex(); + var reversed = axis.Reverse().ToArray(); + var revIndex = reversed.Length - currentIndex; + var prevIndex = reversed.Skip(revIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + if (prevIndex >= 0 && prevIndex < reversed.Length) return reversed.ElementAt(prevIndex); + if (wrap == false) return null; + prevIndex = reversed.Take(revIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + return reversed.ElementAtOrDefault(prevIndex); + } + + [Obsolete("Obsolete, use FollowingSibling or PrecedingSibling instead.")] public static IPublishedContent Sibling(this IPublishedContent content, int number) - { - var siblings = content.Siblings(); - - var container = siblings.ToList(); - var currentIndex = container.FindIndex(n => n.Id == content.Id); - if (currentIndex != -1) - { - return container.ElementAtOrDefault(currentIndex + number); - } - throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); + { + if (number < 0) + throw new ArgumentOutOfRangeException("number", "Must be greater than, or equal to, zero."); + number += 1; // legacy is zero-based + return content.Move(content.Siblings(), +number); } - public static IPublishedContent Sibling(this IPublishedContent content, string nodeTypeAlias) - { - var siblings = content.Siblings(); - var container = siblings.ToList(); - var currentIndex = container.FindIndex(n => n.Id == content.Id); - if (currentIndex != -1) - { - var workingIndex = currentIndex + 1; - while (workingIndex != currentIndex) - { - var working = container.ElementAtOrDefault(workingIndex); - if (working != null && working.DocumentTypeAlias == nodeTypeAlias) - { - return working; - } - workingIndex++; - if (workingIndex > container.Count) - { - workingIndex = 0; - } - } - return null; - } - throw new IndexOutOfRangeException(string.Format("Node {0} belongs to a DynamicNodeList but could not retrieve the index for it's position in the list", content.Id)); - } - - /// - /// Return the items siblings - /// - /// - /// + // 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. + var siblings = content.Siblings().ToArray(); + var index = content.GetIndex(siblings); + + var nextIndex = siblings.Skip(index).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + var c = siblings.ElementAtOrDefault(nextIndex); + if (c != null) return c; + nextIndex = siblings.Take(index).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + return siblings.ElementAtOrDefault(nextIndex); + + // but that is not consistent with the previous method, which does + // not loop if number is greater than the number of siblings. so really + // we should fix with the following code, although that's a breaking + // change + //return content.Move(content.Siblings(), +1, contentTypeAlias); + } + + // following-sibling, preceding-sibling axes + + public static IPublishedContent FollowingSibling(this IPublishedContent content) + { + return content.Move(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 content.Move(content.Siblings(), +number); + } + + // contentTypeAlias is case-insensitive + public static IPublishedContent FollowingSibling(this IPublishedContent content, string contentTypeAlias) + { + return content.Move(content.Siblings(), +1, contentTypeAlias); + } + + // 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) + { + var axis = content.Siblings().ToArray(); + var currentIndex = content.GetIndex(axis); + var nextIndex = axis.Skip(currentIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + if (nextIndex >= 0 && nextIndex < axis.Length) return axis.ElementAt(currentIndex + nextIndex); + if (wrap == false) return null; + nextIndex = axis.Take(currentIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + return axis.ElementAtOrDefault(nextIndex); + } + + public static IPublishedContent PrecedingSibling(this IPublishedContent content) + { + return content.Move(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 content.Move(content.Siblings(), -number); + } + + // contentTypeAlias is case-insensitive + public static IPublishedContent PrecedingSibling(this IPublishedContent content, string contentTypeAlias) + { + return content.Move(content.Siblings(), -1, contentTypeAlias); + } + + // 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) + { + var axis = content.Siblings().ToArray(); + var currentIndex = content.GetIndex(axis); + var reversed = axis.Reverse().ToArray(); + var revIndex = reversed.Length - currentIndex; + var prevIndex = reversed.Skip(revIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + if (prevIndex >= 0 && prevIndex < reversed.Length) return reversed.ElementAt(prevIndex); + if (wrap == false) return null; + prevIndex = reversed.Take(revIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + return reversed.ElementAtOrDefault(prevIndex); + } + + // following, preceding axes - NOT IMPLEMENTED + + // utilities + + static IPublishedContent Move(this IPublishedContent content, int number) + { + return number == 0 + ? content + : content.ContentSet.ElementAtOrDefault(content.GetIndex() + number); + } + + static IPublishedContent Move(this IPublishedContent content, IEnumerable axis, int number) + { + if (number == 0) return content; + + var set = axis.ToArray(); + return set.ElementAtOrDefault(content.GetIndex(set) + number); + } + + // contentTypeAlias is case-insensitive + static IPublishedContent Move(this IPublishedContent content, int number, string contentTypeAlias) + { + if (number == 0) return content.DocumentTypeAlias.InvariantEquals(contentTypeAlias) ? content : null; + + return Move(content.ContentSet.ToArray(), content.GetIndex(), number, contentTypeAlias); + } + + // contentTypeAlias is case-insensitive + static IPublishedContent Move(this IPublishedContent content, IEnumerable axis, int number, string contentTypeAlias) + { + if (number == 0) return content.DocumentTypeAlias.InvariantEquals(contentTypeAlias) ? content : null; + + var set = axis.ToArray(); + return Move(set, content.GetIndex(set), number, contentTypeAlias); + } + + // contentTypeAlias is case-insensitive + static IPublishedContent Move(IPublishedContent[] axis, int currentIndex, int number, string contentTypeAlias) + { + if (number >= 0) + { + // forward + var nextIndex = axis.Skip(currentIndex).FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + return axis.ElementAtOrDefault(currentIndex + nextIndex); + } + + // backward + var prev = axis.Take(currentIndex).Reverse().ToArray(); + var prevIndex = prev.FindIndex(x => x.DocumentTypeAlias.InvariantEquals(contentTypeAlias)); + return prev.ElementAtOrDefault(prevIndex); + } + public static IEnumerable Siblings(this IPublishedContent content) { - //get the root docs if parent is null - return content.Parent == null + // 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 ? UmbracoContext.Current.ContentCache.GetAtRoot() : content.Parent.Children; - } + + // make sure we run it once + return siblings.ToArray(); + } #endregion - /// - /// Method to return the Children of the content item + #region Axes: parent + + // Parent is native + + #endregion + + #region Axes: child + + /// + /// Gets the children of the content. /// - /// - /// + /// The content. + /// The children of the content. /// - /// This method exists for consistency, it is the same as calling content.Children as a property. + /// 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 p) + public static IEnumerable Children(this IPublishedContent content) { - return p.Children.OrderBy(x => x.SortOrder); + 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 ChildrenOfType(this IPublishedContent content) + { + return content.Children().OfType(); + } + + /// + /// 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); } /// - /// Returns a DataTable object for the IPublishedContent - /// - /// - /// - /// - public static DataTable ChildrenAsTable(this IPublishedContent d, string nodeTypeAliasFilter = "") + /// 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 = "") { - return GenerateDataTable(d, nodeTypeAliasFilter); - } - - /// - /// Generates the DataTable for the IPublishedContent - /// - /// - /// - /// - private static DataTable GenerateDataTable(IPublishedContent node, string nodeTypeAliasFilter = "") - { - var firstNode = nodeTypeAliasFilter.IsNullOrWhiteSpace() - ? node.Children.Any() - ? node.Children.ElementAt(0) + var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() + ? content.Children.Any() + ? content.Children.ElementAt(0) : null - : node.Children.FirstOrDefault(x => x.DocumentTypeAlias == nodeTypeAliasFilter); + : content.Children.FirstOrDefault(x => x.DocumentTypeAlias == contentTypeAliasFilter); if (firstNode == null) return new DataTable(); //no children found - var urlProvider = UmbracoContext.Current.RoutingContext.UrlProvider; - //use new utility class to create table so that we don't have to maintain code in many places, just one - var dt = Umbraco.Core.DataTableExtensions.GenerateDataTable( + 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 @@ -1161,34 +1538,35 @@ namespace Umbraco.Web () => { //create all row data - var tableData = Umbraco.Core.DataTableExtensions.CreateTableData(); + var tableData = Core.DataTableExtensions.CreateTableData(); //loop through each child and create row data for it - foreach (var n in node.Children.OrderBy(x => x.SortOrder)) + foreach (var n in content.Children.OrderBy(x => x.SortOrder)) { - if (!nodeTypeAliasFilter.IsNullOrWhiteSpace()) + if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) { - if (n.DocumentTypeAlias != nodeTypeAliasFilter) + 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", urlProvider.GetUrl(n.Id)} + 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 IPublishedContentProperty p in n.Properties where p.Value != null select p) + foreach (var p in from IPublishedProperty p in n.Properties where p.RawValue != null select p) { - userVals[p.Alias] = p.Value; + userVals[p.Alias] = p.RawValue; // use the raw, unprocessed value } //add the row data - Umbraco.Core.DataTableExtensions.AddRowData(tableData, standardVals, userVals); + Core.DataTableExtensions.AddRowData(tableData, standardVals, userVals); } return tableData; } @@ -1196,7 +1574,11 @@ namespace Umbraco.Web return dt; } - private static Func> _getPropertyAliasesAndNames; + #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 @@ -1220,7 +1602,7 @@ namespace Umbraco.Web {"WriterName", "WriterName"}, {"Url", "Url"} }; - foreach (var f in userFields.Where(f => !allFields.ContainsKey(f.Key))) + foreach (var f in userFields.Where(f => allFields.ContainsKey(f.Key) == false)) { allFields.Add(f.Key, f.Value); } @@ -1228,6 +1610,8 @@ namespace Umbraco.Web }); } set { _getPropertyAliasesAndNames = value; } - } - } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedContentPropertyExtension.cs b/src/Umbraco.Web/PublishedContentPropertyExtension.cs new file mode 100644 index 0000000000..ee4dd83e57 --- /dev/null +++ b/src/Umbraco.Web/PublishedContentPropertyExtension.cs @@ -0,0 +1,61 @@ +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.PropertyEditors; + +namespace Umbraco.Web +{ + /// + /// Provides extension methods for IPublishedProperty. + /// + public static class PublishedPropertyExtension + { + #region GetValue + + public static T GetValue(this IPublishedProperty property) + { + return property.GetValue(false, default(T)); + } + + public static T GetValue(this IPublishedProperty property, T defaultValue) + { + return property.GetValue(true, defaultValue); + } + + internal static T GetValue(this IPublishedProperty property, bool withDefaultValue, T defaultValue) + { + if (property.HasValue == false && withDefaultValue) return defaultValue; + + // else we use .Value so we give the converter a chance to handle the default value differently + // eg for IEnumerable it may return Enumerable.Empty instead of null + + var value = property.Value; + + // if value is null (strange but why not) it still is OK to call TryConvertTo + // because it's an extension method (hence no NullRef) which will return a + // failed attempt. So, no need to care for value being null here. + + // if already the requested type, return + if (value is T) return (T)value; + + // if can convert to requested type, return + var convert = value.TryConvertTo(); + if (convert.Success) return convert.Result; + + // at that point, the code tried with the raw value + // that makes no sense because it sort of is unpredictable, + // you wouldn't know when the converters run or don't run. + // so, it's commented out now. + + // try with the raw value + //var source = property.ValueSource; + //if (source is string) source = TextValueConverterHelper.ParseStringValueSource((string)source); + //if (source is T) return (T)source; + //convert = source.TryConvertTo(); + //if (convert.Success) return convert.Result; + + return defaultValue; + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index dc9d11b478..2687d752a6 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -294,10 +294,12 @@ + + @@ -344,9 +346,9 @@ - + @@ -376,6 +378,7 @@ + @@ -397,6 +400,7 @@ AssignDomain2.aspx + ASPXCodeBehind @@ -454,7 +458,7 @@ - + @@ -632,8 +636,8 @@ - - + + @@ -1832,7 +1836,9 @@ ASPXCodeBehind - + + ASPXCodeBehind + ASPXCodeBehind @@ -2085,4 +2091,4 @@ - + diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 17751b4077..6e45375c33 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Web; using Umbraco.Core; using Umbraco.Core.Services; @@ -30,7 +31,6 @@ namespace Umbraco.Web private static readonly object Locker = new object(); private bool _replacing; - private PreviewContent _previewContent; /// /// Used if not running in a web application (no real HttpContext) @@ -366,7 +366,6 @@ namespace Umbraco.Web { Security.DisposeIfDisposable(); Security = null; - _previewContent = null; _umbracoContext = null; //ensure not to dispose this! Application = null; diff --git a/src/Umbraco.Web/UmbracoContextExtensions.cs b/src/Umbraco.Web/UmbracoContextExtensions.cs new file mode 100644 index 0000000000..cba1193cdb --- /dev/null +++ b/src/Umbraco.Web/UmbracoContextExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Umbraco.Web +{ + /// + /// Provides extension methods for . + /// + public static class UmbracoContextExtensions + { + /// + /// Informs the context that content has changed. + /// + /// The context. + /// + /// The contextual caches may, although that is not mandatory, provide an immutable snapshot of + /// the content over the duration of the context. If you make changes to the content and do want to have + /// the caches update their snapshot, you have to explicitely ask them to do so by calling ContentHasChanged. + /// The context informs the contextual caches that content has changed. + /// + public static void ContentHasChanged(this UmbracoContext context) + { + context.ContentCache.ContentHasChanged(); + context.MediaCache.ContentHasChanged(); + } + } +} diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index c0b59819c4..b381a50326 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -435,10 +435,19 @@ namespace Umbraco.Web /// String with a friendly url from a node public string NiceUrl(int nodeId) { - var urlProvider = UmbracoContext.Current.UrlProvider; - return urlProvider.GetUrl(nodeId); + return Url(nodeId); } + /// + /// Gets the url of a content identified by its identifier. + /// + /// The content identifier. + /// The url for the content. + public string Url(int contentId) + { + return UmbracoContext.Current.UrlProvider.GetUrl(contentId); + } + /// /// This method will always add the domain to the path if the hostnames are set up correctly. /// @@ -446,10 +455,19 @@ namespace Umbraco.Web /// String with a friendly url with full domain from a node public string NiceUrlWithDomain(int nodeId) { - var urlProvider = UmbracoContext.Current.UrlProvider; - return urlProvider.GetUrl(nodeId, true); + return UrlAbsolute(nodeId); } + /// + /// Gets the absolute url of a content identified by its identifier. + /// + /// The content identifier. + /// The absolute url for the content. + public string UrlAbsolute(int contentId) + { + return UmbracoContext.Current.UrlProvider.GetUrl(contentId, true); + } + #endregion #region Content @@ -521,27 +539,27 @@ namespace Umbraco.Web public dynamic Content(object id) { - return DocumentById(id, _umbracoContext.ContentCache, new DynamicNull()); + return DocumentById(id, _umbracoContext.ContentCache, DynamicNull.Null); } public dynamic Content(int id) { - return DocumentById(id, _umbracoContext.ContentCache, new DynamicNull()); + return DocumentById(id, _umbracoContext.ContentCache, DynamicNull.Null); } public dynamic Content(string id) { - return DocumentById(id, _umbracoContext.ContentCache, new DynamicNull()); + return DocumentById(id, _umbracoContext.ContentCache, DynamicNull.Null); } public dynamic ContentSingleAtXPath(string xpath, params XPathVariable[] vars) { - return DocumentByXPath(xpath, vars, _umbracoContext.ContentCache, new DynamicNull()); + return DocumentByXPath(xpath, vars, _umbracoContext.ContentCache, DynamicNull.Null); } public dynamic ContentSingleAtXPath(XPathExpression xpath, params XPathVariable[] vars) { - return DocumentByXPath(xpath, vars, _umbracoContext.ContentCache, new DynamicNull()); + return DocumentByXPath(xpath, vars, _umbracoContext.ContentCache, DynamicNull.Null); } public dynamic Content(params object[] ids) @@ -655,17 +673,17 @@ namespace Umbraco.Web public dynamic Media(object id) { - return DocumentById(id, _umbracoContext.MediaCache, new DynamicNull()); + return DocumentById(id, _umbracoContext.MediaCache, DynamicNull.Null); } public dynamic Media(int id) { - return DocumentById(id, _umbracoContext.MediaCache, new DynamicNull()); + return DocumentById(id, _umbracoContext.MediaCache, DynamicNull.Null); } public dynamic Media(string id) { - return DocumentById(id, _umbracoContext.MediaCache, new DynamicNull()); + return DocumentById(id, _umbracoContext.MediaCache, DynamicNull.Null); } public dynamic Media(params object[] ids) @@ -862,7 +880,7 @@ namespace Umbraco.Web /// private dynamic DocumentByIds(ContextualPublishedCache cache, params object[] ids) { - var dNull = new DynamicNull(); + var dNull = DynamicNull.Null; var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) .Where(x => !TypeHelper.IsTypeAssignableFrom(x)) .Cast(); @@ -871,7 +889,7 @@ namespace Umbraco.Web private dynamic DocumentByIds(ContextualPublishedCache cache, params int[] ids) { - var dNull = new DynamicNull(); + var dNull = DynamicNull.Null; var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) .Where(x => !TypeHelper.IsTypeAssignableFrom(x)) .Cast(); @@ -880,7 +898,7 @@ namespace Umbraco.Web private dynamic DocumentByIds(ContextualPublishedCache cache, params string[] ids) { - var dNull = new DynamicNull(); + var dNull = DynamicNull.Null; var nodes = ids.Select(eachId => DocumentById(eachId, cache, dNull)) .Where(x => !TypeHelper.IsTypeAssignableFrom(x)) .Cast(); diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 35869fb76b..68a3fbd821 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -280,10 +280,10 @@ namespace Umbraco.Web UmbracoApiControllerResolver.Current = new UmbracoApiControllerResolver( PluginManager.Current.ResolveUmbracoApiControllers()); - //the base creates the PropertyEditorValueConvertersResolver but we want to modify it in the web app and replace - //the TinyMcePropertyEditorValueConverter with the RteMacroRenderingPropertyEditorValueConverter - PropertyEditorValueConvertersResolver.Current.RemoveType(); - PropertyEditorValueConvertersResolver.Current.AddType(); + // CoreBootManager configures TinyMceValueConverter + // we want to replace it with RteMacroRenderingValueConverter, which will convert macros, etc + PropertyValueConvertersResolver.Current.RemoveType(); + PropertyValueConvertersResolver.Current.AddType(); PublishedCachesResolver.Current = new PublishedCachesResolver(new PublishedCaches( new PublishedCache.XmlPublishedCache.PublishedContentCache(), diff --git a/src/Umbraco.Web/umbraco.presentation/item.cs b/src/Umbraco.Web/umbraco.presentation/item.cs index 1cf5e8ae31..118ebe6f2a 100644 --- a/src/Umbraco.Web/umbraco.presentation/item.cs +++ b/src/Umbraco.Web/umbraco.presentation/item.cs @@ -5,6 +5,7 @@ using System.Xml; using StackExchange.Profiling; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Web; using Umbraco.Core.Profiling; namespace umbraco @@ -68,8 +69,9 @@ namespace umbraco } else { - var recursiveVal = publishedContent.GetRecursiveValue(_fieldName); - _fieldContent = recursiveVal.IsNullOrWhiteSpace() ? _fieldContent : recursiveVal; + var pval = publishedContent.GetPropertyValue(_fieldName, true); + var rval = pval == null ? string.Empty : pval.ToString(); + _fieldContent = rval.IsNullOrWhiteSpace() ? _fieldContent : rval; } } else diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index 466731bb92..107433a3c9 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -362,9 +362,10 @@ namespace umbraco return doc.CreatorName; } + // the legacy library returns the string value from the xml cache - which means a string + // that has not be converted at all -- use RawValue here. var prop = doc.GetProperty(alias); - return prop == null ? string.Empty : prop.Value.ToString(); - + return prop == null ? string.Empty : prop.RawValue.ToString(); } /// diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index 58fc1a4d94..6fe510044e 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -275,7 +275,7 @@ namespace umbraco { if (!_elements.ContainsKey(p.Alias)) { - _elements[p.Alias] = p.Value; + _elements[p.Alias] = p.Value; } } } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs index 179998201a..0362095d4f 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicBackingItem.cs @@ -146,7 +146,7 @@ namespace umbraco.MacroEngines } if (result != null) { - return new PropertyResult(alias, string.Format("{0}", result), Guid.Empty) { ContextAlias = content.NodeTypeAlias, ContextId = content.Id }; + return new PropertyResult(alias, string.Format("{0}", result)) { ContextAlias = content.NodeTypeAlias, ContextId = content.Id }; } } } @@ -188,7 +188,7 @@ namespace umbraco.MacroEngines } if (result != null) { - return new PropertyResult(alias, string.Format("{0}", result), Guid.Empty) { ContextAlias = content.NodeTypeAlias, ContextId = content.Id }; + return new PropertyResult(alias, string.Format("{0}", result)) { ContextAlias = content.NodeTypeAlias, ContextId = content.Id }; } } } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNull.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNull.cs index 5628a755ff..c06e6ae8b7 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNull.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNull.cs @@ -16,7 +16,7 @@ namespace umbraco.MacroEngines [Obsolete("This class has been superceded by Umbraco.Core.Dynamics.DynamicNull")] public class DynamicNull : DynamicObject, IEnumerable, IHtmlString { - private readonly Umbraco.Core.Dynamics.DynamicNull _inner = new Umbraco.Core.Dynamics.DynamicNull(); + private readonly Umbraco.Core.Dynamics.DynamicNull _inner = Umbraco.Core.Dynamics.DynamicNull.Null; public IEnumerator GetEnumerator() { diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/ExamineBackedMedia.cs b/src/umbraco.MacroEngines/RazorDynamicNode/ExamineBackedMedia.cs index 92c2f84323..f6351623ff 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/ExamineBackedMedia.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/ExamineBackedMedia.cs @@ -141,7 +141,7 @@ namespace umbraco.MacroEngines } Values.Add(result.Current.Name, value); propertyExists = true; - return new PropertyResult(alias, value, Guid.Empty); + return new PropertyResult(alias, value); } } } @@ -376,7 +376,7 @@ namespace umbraco.MacroEngines return Values .Where(kvp => !internalProperties.Contains(kvp.Key)) .ToList() - .ConvertAll(kvp => new PropertyResult(kvp.Key, kvp.Value, Guid.Empty)) + .ConvertAll(kvp => new PropertyResult(kvp.Key, kvp.Value)) .Cast() .ToList(); } @@ -473,7 +473,7 @@ namespace umbraco.MacroEngines || Values.TryGetValue(alias, out value)) { propertyExists = true; - return new PropertyResult(alias, value, Guid.Empty); + return new PropertyResult(alias, value); } propertyExists = false; diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/PropertyResult.cs b/src/umbraco.MacroEngines/RazorDynamicNode/PropertyResult.cs index 76fb729cec..9e98c1f866 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/PropertyResult.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/PropertyResult.cs @@ -10,31 +10,29 @@ namespace umbraco.MacroEngines { public class PropertyResult : IProperty, IHtmlString { - private string _alias; - private string _value; - private Guid _version; + private readonly string _alias; + private readonly string _value; public PropertyResult(IProperty source) { - if (source != null) - { - this._alias = source.Alias; - this._value = source.Value; - this._version = source.Version; - } + if (source == null) return; + + _alias = source.Alias; + _value = source.Value; } - public PropertyResult(string alias, string value, Guid version) + + public PropertyResult(string alias, string value) { - this._alias = alias; - this._value = value; - this._version = version; + _alias = alias; + _value = value; } + public PropertyResult(Property source) { - this._alias = source.PropertyType.Alias; - this._value = string.Format("{0}", source.Value); - this._version = source.VersionId; + _alias = source.PropertyType.Alias; + _value = source.Value.ToString(); } + public string Alias { get { return _alias; } @@ -47,13 +45,14 @@ namespace umbraco.MacroEngines public Guid Version { - get { return _version; } + get { return Guid.Empty; } } public bool IsNull() { return Value == null; } + public bool HasValue() { return !string.IsNullOrWhiteSpace(Value); @@ -62,9 +61,9 @@ namespace umbraco.MacroEngines public int ContextId { get; set; } public string ContextAlias { get; set; } + // implements IHtmlString.ToHtmlString public string ToHtmlString() { - //Like a boss return Value; } } diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs b/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs index 1dabc444ac..ad0b3c4b55 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/PublishedContentExtensions.cs @@ -12,15 +12,15 @@ using Property = umbraco.NodeFactory.Property; namespace umbraco.MacroEngines.Library { - /// - /// Extension methods for converting DynamicPublishedContent to INode - /// + /// + /// Provides extension methods for IPublishedContent. + /// + /// These are dedicated to converting DynamicPublishedContent to INode. internal static class PublishedContentExtensions - { - - internal static IProperty ConvertToNodeProperty(this IPublishedContentProperty prop) + { + internal static IProperty ConvertToNodeProperty(this IPublishedProperty prop) { - return new PropertyResult(prop.Alias, prop.Value.ToString(), prop.Version); + return new PropertyResult(prop.Alias, prop.Value.ToString()); } internal static INode ConvertToNode(this IPublishedContent doc) @@ -30,7 +30,7 @@ namespace umbraco.MacroEngines.Library } /// - /// Internal custom INode class used for conversions from DynamicPublishedContent + /// Internal custom INode class used for conversions from DynamicPublishedContent. /// private class ConvertedNode : INode {