diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index 11b1870b90..426df0c341 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -22,7 +22,6 @@
the latter would pick anything below 3.0.0 and that includes prereleases such as 3.0.0-alpha, and we do
not want this to happen as the alpha of the next major is, really, the next major already.
-->
-
diff --git a/src/Umbraco.Core/BindingRedirects.cs b/src/Umbraco.Core/BindingRedirects.cs
index 17e187b7ae..e698ffa646 100644
--- a/src/Umbraco.Core/BindingRedirects.cs
+++ b/src/Umbraco.Core/BindingRedirects.cs
@@ -1,10 +1,10 @@
using System;
using System.Reflection;
-using System.Text.RegularExpressions;
using System.Web;
using Umbraco.Core;
-[assembly: PreApplicationStartMethod(typeof(BindingRedirects), "Initialize")]
+// no binding redirect for now = de-activate
+//[assembly: PreApplicationStartMethod(typeof(BindingRedirects), "Initialize")]
namespace Umbraco.Core
{
@@ -18,7 +18,7 @@ namespace Umbraco.Core
// this only gets called when an assembly can't be resolved
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
-
+
///
/// This is used to do an assembly binding redirect via code - normally required due to signature changes in assemblies
///
@@ -27,14 +27,19 @@ namespace Umbraco.Core
///
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
+ // When an assembly can't be resolved. In here we can do magic with the assembly name and try loading another.
+
+ // keep here for reference - we don't use AutoMapper
+ /*
//AutoMapper:
+ // this is used for loading a signed assembly of AutoMapper (v. 3.1+) without having to recompile old code.
// ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again
// do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stackoverflow
if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null"))
return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005"));
+ */
return null;
-
}
}
}
diff --git a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs
index b1a12ec411..fecab66d84 100644
--- a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs
+++ b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.Entities;
-using Umbraco.Core.Scoping;
namespace Umbraco.Core.Cache
{
@@ -13,11 +12,6 @@ namespace Umbraco.Core.Cache
public static NoCacheRepositoryCachePolicy Instance { get; } = new NoCacheRepositoryCachePolicy();
- public IRepositoryCachePolicy Scoped(IAppPolicyCache runtimeCache, IScope scope)
- {
- throw new NotImplementedException();
- }
-
public TEntity Get(TId id, Func performGet, Func> performGetAll)
{
return performGet(id);
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs
index ed875ec973..165fa10be7 100644
--- a/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs
+++ b/src/Umbraco.Core/Composing/CompositionExtensions/CoreMappingProfiles.cs
@@ -1,4 +1,4 @@
-using AutoMapper;
+using Umbraco.Core.Mapping;
using Umbraco.Core.Models.Identity;
namespace Umbraco.Core.Composing.CompositionExtensions
@@ -8,7 +8,9 @@ namespace Umbraco.Core.Composing.CompositionExtensions
{
public static Composition ComposeCoreMappingProfiles(this Composition composition)
{
- composition.Register();
+ composition.RegisterUnique();
+ composition.WithCollectionBuilder()
+ .Add();
return composition;
}
}
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs
index 03f13f96cb..23dc9e67c6 100644
--- a/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs
+++ b/src/Umbraco.Core/Composing/CompositionExtensions/Repositories.cs
@@ -46,6 +46,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions
composition.RegisterUnique();
composition.RegisterUnique();
composition.RegisterUnique();
+ composition.RegisterUnique();
return composition;
}
diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs
index 1b16ad2b8e..f12bf0dd63 100644
--- a/src/Umbraco.Core/Composing/Current.cs
+++ b/src/Umbraco.Core/Composing/Current.cs
@@ -4,6 +4,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Dictionary;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
+using Umbraco.Core.Mapping;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PackageActions;
using Umbraco.Core.Packaging;
@@ -103,6 +104,9 @@ namespace Umbraco.Core.Composing
#region Getters
+ public static UmbracoMapper Mapper
+ => _factory.GetInstance();
+
public static IShortStringHelper ShortStringHelper
=> _shortStringHelper ?? (_shortStringHelper = _factory?.TryGetInstance()
?? new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(Configs.Settings())));
diff --git a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs
index 3698b7e801..9261423bbc 100644
--- a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs
@@ -9,6 +9,10 @@ namespace Umbraco.Core.Composing
/// The type of the builder.
/// The type of the collection.
/// The type of the items.
+ ///
+ /// A set collection builder is the most basic collection builder,
+ /// where items are not ordered.
+ ///
public abstract class SetCollectionBuilderBase : CollectionBuilderBase
where TBuilder : SetCollectionBuilderBase
where TCollection : class, IBuilderCollection
diff --git a/src/Umbraco.Core/Mapping/IMapDefinition.cs b/src/Umbraco.Core/Mapping/IMapDefinition.cs
new file mode 100644
index 0000000000..ffea07c3eb
--- /dev/null
+++ b/src/Umbraco.Core/Mapping/IMapDefinition.cs
@@ -0,0 +1,13 @@
+namespace Umbraco.Core.Mapping
+{
+ ///
+ /// Defines maps for .
+ ///
+ public interface IMapDefinition
+ {
+ ///
+ /// Defines maps.
+ ///
+ void DefineMaps(UmbracoMapper mapper);
+ }
+}
diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs
new file mode 100644
index 0000000000..e2438515f0
--- /dev/null
+++ b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.Mapping
+{
+ public class MapDefinitionCollection : BuilderCollectionBase
+ {
+ public MapDefinitionCollection(IEnumerable items)
+ : base(items)
+ { }
+ }
+}
diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs
new file mode 100644
index 0000000000..15d94e58a0
--- /dev/null
+++ b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs
@@ -0,0 +1,11 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.Mapping
+{
+ public class MapDefinitionCollectionBuilder : SetCollectionBuilderBase
+ {
+ protected override MapDefinitionCollectionBuilder This => this;
+
+ protected override Lifetime CollectionLifetime => Lifetime.Transient;
+ }
+}
diff --git a/src/Umbraco.Core/Mapping/MapperContext.cs b/src/Umbraco.Core/Mapping/MapperContext.cs
new file mode 100644
index 0000000000..a7044a05b9
--- /dev/null
+++ b/src/Umbraco.Core/Mapping/MapperContext.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Mapping
+{
+ ///
+ /// Represents a mapper context.
+ ///
+ public class MapperContext
+ {
+ private readonly UmbracoMapper _mapper;
+ private IDictionary _items;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MapperContext(UmbracoMapper mapper)
+ {
+ _mapper = mapper;
+ }
+
+ ///
+ /// Gets a value indicating whether the context has items.
+ ///
+ public bool HasItems => _items != null;
+
+ ///
+ /// Gets the context items.
+ ///
+ public IDictionary Items => _items ?? (_items = new Dictionary());
+
+ #region Map
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ public TTarget Map(object source)
+ => _mapper.Map(source, this);
+
+ // let's say this is a bad (dangerous) idea, and leave it out for now
+ /*
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// A mapper context preparation method.
+ /// The target object.
+ public TTarget Map(object source, Action f)
+ {
+ f(this);
+ return _mapper.Map(source, this);
+ }
+ */
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ public TTarget Map(TSource source)
+ => _mapper.Map(source, this);
+
+ // let's say this is a bad (dangerous) idea, and leave it out for now
+ /*
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// A mapper context preparation method.
+ /// The target object.
+ public TTarget Map(TSource source, Action f)
+ {
+ f(this);
+ return _mapper.Map(source, this);
+ }
+ */
+
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// The target object.
+ public TTarget Map(TSource source, TTarget target)
+ => _mapper.Map(source, target, this);
+
+ // let's say this is a bad (dangerous) idea, and leave it out for now
+ /*
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// A mapper context preparation method.
+ /// The target object.
+ public TTarget Map(TSource source, TTarget target, Action f)
+ {
+ f(this);
+ return _mapper.Map(source, target, this);
+ }
+ */
+
+ ///
+ /// Maps an enumerable of source objects to a new list of target objects.
+ ///
+ /// The type of the source objects.
+ /// The type of the target objects.
+ /// The source objects.
+ /// A list containing the target objects.
+ public List MapEnumerable(IEnumerable source)
+ {
+ return source.Select(Map).ToList();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Umbraco.Core/Mapping/UmbracoMapper.cs b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
new file mode 100644
index 0000000000..0831edab4e
--- /dev/null
+++ b/src/Umbraco.Core/Mapping/UmbracoMapper.cs
@@ -0,0 +1,424 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Mapping
+{
+ // notes:
+ // AutoMapper maps null to empty arrays, lists, etc
+
+ // TODO:
+ // when mapping from TSource, and no map is found, consider the actual source.GetType()?
+ // when mapping to TTarget, and no map is found, consider the actual target.GetType()?
+ // not sure we want to add magic to this simple mapper class, though
+
+ ///
+ /// Umbraco Mapper.
+ ///
+ ///
+ /// When a map is defined from TSource to TTarget, the mapper automatically knows how to map
+ /// from IEnumerable{TSource} to IEnumerable{TTarget} (using a List{TTarget}) and to TTarget[].
+ /// When a map is defined from TSource to TTarget, the mapper automatically uses that map
+ /// for any source type that inherits from, or implements, TSource.
+ /// When a map is defined from TSource to TTarget, the mapper can map to TTarget exclusively
+ /// and cannot re-use that map for types that would inherit from, or implement, TTarget.
+ /// When using the Map{TSource, TTarget}(TSource source, ...) overloads, TSource is explicit. When
+ /// using the Map{TTarget}(object source, ...) TSource is defined as source.GetType().
+ /// In both cases, TTarget is explicit and not typeof(target).
+ ///
+ public class UmbracoMapper
+ {
+ private readonly Dictionary>> _ctors
+ = new Dictionary>>();
+
+ private readonly Dictionary>> _maps
+ = new Dictionary>>();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public UmbracoMapper(MapDefinitionCollection profiles)
+ {
+ foreach (var profile in profiles)
+ profile.DefineMaps(this);
+ }
+
+ #region Define
+
+ private static TTarget ThrowCtor(TSource source, MapperContext context)
+ => throw new InvalidOperationException($"Don't know how to create {typeof(TTarget).FullName} instances.");
+
+ private static void Identity(TSource source, TTarget target, MapperContext context)
+ { }
+
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ public void Define()
+ => Define(ThrowCtor, Identity);
+
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ /// A mapping method.
+ public void Define(Action map)
+ => Define(ThrowCtor, map);
+
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ /// A constructor method.
+ public void Define(Func ctor)
+ => Define(ctor, Identity);
+
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ /// A constructor method.
+ /// A mapping method.
+ public void Define(Func ctor, Action map)
+ {
+ var sourceType = typeof(TSource);
+ var targetType = typeof(TTarget);
+
+ var sourceCtors = DefineCtors(sourceType);
+ if (ctor != null)
+ sourceCtors[targetType] = (source, context) => ctor((TSource)source, context);
+
+ var sourceMaps = DefineMaps(sourceType);
+ sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context);
+ }
+
+ private Dictionary> DefineCtors(Type sourceType)
+ {
+ if (!_ctors.TryGetValue(sourceType, out var sourceCtor))
+ sourceCtor = _ctors[sourceType] = new Dictionary>();
+ return sourceCtor;
+ }
+
+ private Dictionary> DefineMaps(Type sourceType)
+ {
+ if (!_maps.TryGetValue(sourceType, out var sourceMap))
+ sourceMap = _maps[sourceType] = new Dictionary>();
+ return sourceMap;
+ }
+
+ #endregion
+
+ #region Map
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ public TTarget Map(object source)
+ => Map(source, new MapperContext(this));
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// A mapper context preparation method.
+ /// The target object.
+ public TTarget Map(object source, Action f)
+ {
+ var context = new MapperContext(this);
+ f(context);
+ return Map(source, context);
+ }
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// A mapper context.
+ /// The target object.
+ public TTarget Map(object source, MapperContext context)
+ => Map(source, source?.GetType(), context);
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ public TTarget Map(TSource source)
+ => Map(source, new MapperContext(this));
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// A mapper context preparation method.
+ /// The target object.
+ public TTarget Map(TSource source, Action f)
+ {
+ var context = new MapperContext(this);
+ f(context);
+ return Map(source, context);
+ }
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// A mapper context.
+ /// The target object.
+ public TTarget Map(TSource source, MapperContext context)
+ => Map(source, typeof(TSource), context);
+
+ private TTarget Map(object source, Type sourceType, MapperContext context)
+ {
+ if (source == null)
+ throw new ArgumentNullException(nameof(source));
+
+ var targetType = typeof(TTarget);
+
+ var ctor = GetCtor(sourceType, targetType);
+ var map = GetMap(sourceType, targetType);
+
+ // if there is a direct constructor, map
+ if (ctor != null && map != null)
+ {
+ var target = ctor(source, context);
+ map(source, target, context);
+ return (TTarget)target;
+ }
+
+ // otherwise, see if we can deal with enumerable
+
+ var ienumerableOfT = typeof(IEnumerable<>);
+
+ bool IsIEnumerableOfT(Type type) =>
+ type.IsGenericType &&
+ type.GenericTypeArguments.Length == 1 &&
+ type.GetGenericTypeDefinition() == ienumerableOfT;
+
+ // try to get source as an IEnumerable
+ var sourceIEnumerable = IsIEnumerableOfT(sourceType) ? sourceType : sourceType.GetInterfaces().FirstOrDefault(IsIEnumerableOfT);
+
+ // if source is an IEnumerable and target is T[] or IEnumerable, we can create a map
+ if (sourceIEnumerable != null && IsEnumerableOrArrayOfType(targetType))
+ {
+ var sourceGenericArg = sourceIEnumerable.GenericTypeArguments[0];
+ var targetGenericArg = GetEnumerableOrArrayTypeArgument(targetType);
+
+ ctor = GetCtor(sourceGenericArg, targetGenericArg);
+ map = GetMap(sourceGenericArg, targetGenericArg);
+
+ // if there is a constructor for the underlying type, create & invoke the map
+ if (ctor != null && map != null)
+ {
+ // register (for next time) and do it now (for this time)
+ object NCtor(object s, MapperContext c) => MapEnumerableInternal((IEnumerable) s, targetGenericArg, ctor, map, c);
+ DefineCtors(sourceType)[targetType] = NCtor;
+ DefineMaps(sourceType)[targetType] = Identity;
+ return (TTarget) NCtor(source, context);
+ }
+
+ throw new InvalidOperationException($"Don't know how to map {sourceGenericArg.FullName} to {targetGenericArg.FullName}, so don't know how to map {sourceType.FullName} to {targetType.FullName}.");
+ }
+
+ throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
+ }
+
+ private TTarget MapEnumerableInternal(IEnumerable source, Type targetGenericArg, Func ctor, Action map, MapperContext context)
+ {
+ var targetList = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericArg));
+
+ foreach (var sourceItem in source)
+ {
+ var targetItem = ctor(sourceItem, context);
+ map(sourceItem, targetItem, context);
+ targetList.Add(targetItem);
+ }
+
+ object target = targetList;
+
+ if (typeof(TTarget).IsArray)
+ {
+ var elementType = typeof(TTarget).GetElementType();
+ if (elementType == null) throw new Exception("panic");
+ var targetArray = Array.CreateInstance(elementType, targetList.Count);
+ targetList.CopyTo(targetArray, 0);
+ target = targetArray;
+ }
+
+ return (TTarget) target;
+ }
+
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// The target object.
+ public TTarget Map(TSource source, TTarget target)
+ => Map(source, target, new MapperContext(this));
+
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// A mapper context preparation method.
+ /// The target object.
+ public TTarget Map(TSource source, TTarget target, Action f)
+ {
+ var context = new MapperContext(this);
+ f(context);
+ return Map(source, target, context);
+ }
+
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// A mapper context.
+ /// The target object.
+ public TTarget Map(TSource source, TTarget target, MapperContext context)
+ {
+ var sourceType = typeof(TSource);
+ var targetType = typeof(TTarget);
+
+ var map = GetMap(sourceType, targetType);
+
+ // if there is a direct map, map
+ if (map != null)
+ {
+ map(source, target, context);
+ return target;
+ }
+
+ // we cannot really map to an existing enumerable - give up
+
+ throw new InvalidOperationException($"Don't know how to map {typeof(TSource).FullName} to {typeof(TTarget).FullName}.");
+ }
+
+ private Func GetCtor(Type sourceType, Type targetType)
+ {
+ if (_ctors.TryGetValue(sourceType, out var sourceCtor) && sourceCtor.TryGetValue(targetType, out var ctor))
+ return ctor;
+
+ ctor = null;
+ foreach (var (stype, sctors) in _ctors)
+ {
+ if (!stype.IsAssignableFrom(sourceType)) continue;
+ if (!sctors.TryGetValue(targetType, out ctor)) continue;
+
+ sourceCtor = sctors;
+ break;
+ }
+
+ if (ctor == null) return null;
+
+ _ctors[sourceType] = sourceCtor;
+ return ctor;
+ }
+
+ private Action GetMap(Type sourceType, Type targetType)
+ {
+ if (_maps.TryGetValue(sourceType, out var sourceMap) && sourceMap.TryGetValue(targetType, out var map))
+ return map;
+
+ map = null;
+ foreach (var (stype, smap) in _maps)
+ {
+ if (!stype.IsAssignableFrom(sourceType)) continue;
+
+ // TODO: consider looking for assignable types for target too?
+ if (!smap.TryGetValue(targetType, out map)) continue;
+
+ sourceMap = smap;
+ break;
+ }
+
+ if (map == null) return null;
+
+ _maps[sourceType] = sourceMap;
+ return map;
+ }
+
+ private static bool IsEnumerableOrArrayOfType(Type type)
+ {
+ if (type.IsArray && type.GetArrayRank() == 1) return true;
+ if (type.IsGenericType && type.GenericTypeArguments.Length == 1) return true;
+ return false;
+ }
+
+ private static Type GetEnumerableOrArrayTypeArgument(Type type)
+ {
+ if (type.IsArray) return type.GetElementType();
+ if (type.IsGenericType) return type.GenericTypeArguments[0];
+ throw new Exception("panic");
+ }
+
+ ///
+ /// Maps an enumerable of source objects to a new list of target objects.
+ ///
+ /// The type of the source objects.
+ /// The type of the target objects.
+ /// The source objects.
+ /// A list containing the target objects.
+ public List MapEnumerable(IEnumerable source)
+ {
+ return source.Select(Map).ToList();
+ }
+
+ ///
+ /// Maps an enumerable of source objects to a new list of target objects.
+ ///
+ /// The type of the source objects.
+ /// The type of the target objects.
+ /// The source objects.
+ /// A mapper context preparation method.
+ /// A list containing the target objects.
+ public List MapEnumerable(IEnumerable source, Action f)
+ {
+ var context = new MapperContext(this);
+ f(context);
+ return source.Select(x => Map(x, context)).ToList();
+ }
+
+ ///
+ /// Maps an enumerable of source objects to a new list of target objects.
+ ///
+ /// The type of the source objects.
+ /// The type of the target objects.
+ /// The source objects.
+ /// A mapper context.
+ /// A list containing the target objects.
+ public List MapEnumerable(IEnumerable source, MapperContext context)
+ {
+ return source.Select(x => Map(x, context)).ToList();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
index d8283fd112..eab7afe308 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs
@@ -47,7 +47,7 @@ namespace Umbraco.Core.Migrations.Install
typeof (LogDto),
typeof (MacroDto),
typeof (MacroPropertyDto),
- typeof (MemberTypeDto),
+ typeof (MemberPropertyTypeDto),
typeof (MemberDto),
typeof (Member2MemberGroupDto),
typeof (PropertyTypeGroupDto),
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs
index 4e1a7d1470..2b5f481769 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_7_9_0/AddIsSensitiveMemberTypeColumn.cs
@@ -13,8 +13,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_7_9_0
{
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
- if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberType) && x.ColumnName.InvariantEquals("isSensitive")) == false)
- AddColumn("isSensitive");
+ if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.MemberPropertyType) && x.ColumnName.InvariantEquals("isSensitive")) == false)
+ AddColumn("isSensitive");
}
}
}
diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs
index ed8a098299..d4e35cccdf 100644
--- a/src/Umbraco.Core/Models/ContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBase.cs
@@ -210,9 +210,7 @@ namespace Umbraco.Core.Models
return Variations.ValidateVariation(culture, segment, false, true, false);
}
- ///
- /// List of PropertyGroups available on this ContentType
- ///
+ ///
///
/// A PropertyGroup corresponds to a Tab in the UI
/// Marked DoNotClone because we will manually deal with cloning and the event handlers
@@ -230,9 +228,7 @@ namespace Umbraco.Core.Models
}
}
- ///
- /// Gets all property types, across all property groups.
- ///
+ ///
[IgnoreDataMember]
[DoNotClone]
public IEnumerable PropertyTypes
@@ -243,12 +239,7 @@ namespace Umbraco.Core.Models
}
}
- ///
- /// Gets or sets the property types that are not in a group.
- ///
- ///
- /// Marked DoNotClone because we will manually deal with cloning and the event handlers
- ///
+ ///
[DoNotClone]
public IEnumerable NoGroupPropertyTypes
{
diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs
index 7497c100bc..ff61a15979 100644
--- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs
@@ -43,9 +43,7 @@ namespace Umbraco.Core.Models
}
}
- ///
- /// Gets the property groups for the entire composition.
- ///
+ ///
[IgnoreDataMember]
public IEnumerable CompositionPropertyGroups
{
@@ -76,9 +74,7 @@ namespace Umbraco.Core.Models
}
}
- ///
- /// Gets the property types for the entire composition.
- ///
+ ///
[IgnoreDataMember]
public IEnumerable CompositionPropertyTypes
{
diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs
index 0b035c1703..ca18985941 100644
--- a/src/Umbraco.Core/Models/CultureImpact.cs
+++ b/src/Umbraco.Core/Models/CultureImpact.cs
@@ -25,7 +25,6 @@ namespace Umbraco.Core.Models
if (content == null) throw new ArgumentNullException(nameof(content));
if (savingCultures == null) throw new ArgumentNullException(nameof(savingCultures));
if (savingCultures.Length == 0) throw new ArgumentException(nameof(savingCultures));
- if (defaultCulture == null) throw new ArgumentNullException(nameof(defaultCulture));
var cultureForInvariantErrors = savingCultures.Any(x => x.InvariantEquals(defaultCulture))
//the default culture is being flagged for saving so use it
diff --git a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs
index a827f7af14..5cce9777cb 100644
--- a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs
+++ b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Models
/// * when the setter performs additional required logic other than just setting the underlying field
///
///
- internal class DoNotCloneAttribute : Attribute
+ public class DoNotCloneAttribute : Attribute
{
}
diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs
index 215c8532c1..5f1fe6ed49 100644
--- a/src/Umbraco.Core/Models/IContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/IContentTypeBase.cs
@@ -96,17 +96,17 @@ namespace Umbraco.Core.Models
IEnumerable AllowedContentTypes { get; set; }
///
- /// Gets or Sets a collection of Property Groups
+ /// Gets or sets the local property groups.
///
PropertyGroupCollection PropertyGroups { get; set; }
///
- /// Gets all property types, across all property groups.
+ /// Gets all local property types belonging to a group, across all local property groups.
///
IEnumerable PropertyTypes { get; }
///
- /// Gets or sets the property types that are not in a group.
+ /// Gets or sets the local property types that do not belong to a group.
///
IEnumerable NoGroupPropertyTypes { get; set; }
diff --git a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
index 0eb53428d1..8dc056d555 100644
--- a/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
+++ b/src/Umbraco.Core/Models/Identity/BackOfficeIdentityUser.cs
@@ -41,7 +41,7 @@ namespace Umbraco.Core.Models.Identity
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture));
- var user = new BackOfficeIdentityUser();
+ var user = new BackOfficeIdentityUser(Array.Empty());
user.DisableChangeTracking();
user._userName = username;
user._email = email;
@@ -54,16 +54,19 @@ namespace Umbraco.Core.Models.Identity
return user;
}
- private BackOfficeIdentityUser()
+ private BackOfficeIdentityUser(IReadOnlyUserGroup[] groups)
{
- _startMediaIds = new int[] { };
- _startContentIds = new int[] { };
- _groups = new IReadOnlyUserGroup[] { };
- _allowedSections = new string[] { };
+ _startMediaIds = Array.Empty();
+ _startContentIds = Array.Empty();
+ _allowedSections = Array.Empty();
_culture = Current.Configs.Global().DefaultUILanguage; // TODO: inject
- _groups = new IReadOnlyUserGroup[0];
+
+ // must initialize before setting groups
_roles = new ObservableCollection>();
_roles.CollectionChanged += _roles_CollectionChanged;
+
+ // use the property setters - they do more than just setting a field
+ Groups = groups;
}
///
@@ -72,19 +75,10 @@ namespace Umbraco.Core.Models.Identity
///
///
public BackOfficeIdentityUser(int userId, IEnumerable groups)
+ : this(groups.ToArray())
{
- _startMediaIds = new int[] { };
- _startContentIds = new int[] { };
- _groups = new IReadOnlyUserGroup[] { };
- _allowedSections = new string[] { };
- _culture = Current.Configs.Global().DefaultUILanguage; // TODO: inject
- _groups = groups.ToArray();
- _roles = new ObservableCollection>(_groups.Select(x => new IdentityUserRole
- {
- RoleId = x.Alias,
- UserId = userId.ToString()
- }));
- _roles.CollectionChanged += _roles_CollectionChanged;
+ // use the property setters - they do more than just setting a field
+ Id = userId;
}
///
@@ -226,6 +220,8 @@ namespace Umbraco.Core.Models.Identity
//so they recalculate
_allowedSections = null;
+ _groups = value;
+
//now clear all roles and re-add them
_roles.CollectionChanged -= _roles_CollectionChanged;
_roles.Clear();
diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs
new file mode 100644
index 0000000000..57e1c9ee5c
--- /dev/null
+++ b/src/Umbraco.Core/Models/Identity/IdentityMapDefinition.cs
@@ -0,0 +1,80 @@
+using System;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Mapping;
+using Umbraco.Core.Models.Membership;
+using Umbraco.Core.Services;
+
+namespace Umbraco.Core.Models.Identity
+{
+ public class IdentityMapDefinition : IMapDefinition
+ {
+ private readonly ILocalizedTextService _textService;
+ private readonly IEntityService _entityService;
+ private readonly IGlobalSettings _globalSettings;
+
+ public IdentityMapDefinition(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings)
+ {
+ _textService = textService;
+ _entityService = entityService;
+ _globalSettings = globalSettings;
+ }
+
+ public void DefineMaps(UmbracoMapper mapper)
+ {
+ mapper.Define(
+ (source, context) =>
+ {
+ var target = new BackOfficeIdentityUser(source.Id, source.Groups);
+ target.DisableChangeTracking();
+ return target;
+ },
+ (source, target, context) =>
+ {
+ Map(source, target);
+ target.ResetDirtyProperties(true);
+ target.EnableChangeTracking();
+ });
+ }
+
+ // Umbraco.Code.MapAll -Id -Groups -LockoutEnabled -PhoneNumber -PhoneNumberConfirmed -TwoFactorEnabled
+ private void Map(IUser source, BackOfficeIdentityUser target)
+ {
+ // well, the ctor has been fixed
+ /*
+ // these two are already set in ctor but BackOfficeIdentityUser ctor is CompletelyBroken
+ target.Id = source.Id;
+ target.Groups = source.Groups.ToArray();
+ */
+
+ target.CalculatedMediaStartNodeIds = source.CalculateMediaStartNodeIds(_entityService);
+ target.CalculatedContentStartNodeIds = source.CalculateContentStartNodeIds(_entityService);
+ target.Email = source.Email;
+ target.UserName = source.Username;
+ target.LastPasswordChangeDateUtc = source.LastPasswordChangeDate.ToUniversalTime();
+ target.LastLoginDateUtc = source.LastLoginDate.ToUniversalTime();
+ target.EmailConfirmed = source.EmailConfirmedDate.HasValue;
+ target.Name = source.Name;
+ target.AccessFailedCount = source.FailedPasswordAttempts;
+ target.PasswordHash = GetPasswordHash(source.RawPasswordValue);
+ target.StartContentIds = source.StartContentIds;
+ target.StartMediaIds = source.StartMediaIds;
+ target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); // project CultureInfo to string
+ target.IsApproved = source.IsApproved;
+ target.SecurityStamp = source.SecurityStamp;
+ target.LockoutEndDateUtc = source.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null;
+
+ // this was in AutoMapper but does not have a setter anyways
+ //target.AllowedSections = source.AllowedSections.ToArray(),
+
+ // these were marked as ignored for AutoMapper but don't have a setter anyways
+ //target.Logins =;
+ //target.Claims =;
+ //target.Roles =;
+ }
+
+ private static string GetPasswordHash(string storedPass)
+ {
+ return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs
deleted file mode 100644
index 81069bd74c..0000000000
--- a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System;
-using System.Linq;
-using AutoMapper;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Security;
-using Umbraco.Core.Services;
-
-namespace Umbraco.Core.Models.Identity
-{
- public class IdentityMapperProfile : Profile
- {
- public IdentityMapperProfile(ILocalizedTextService textService, IEntityService entityService, IGlobalSettings globalSettings)
- {
- CreateMap()
- .BeforeMap((src, dest) =>
- {
- dest.DisableChangeTracking();
- })
- .ConstructUsing(src => new BackOfficeIdentityUser(src.Id, src.Groups))
- .ForMember(dest => dest.LastLoginDateUtc, opt => opt.MapFrom(src => src.LastLoginDate.ToUniversalTime()))
- .ForMember(user => user.LastPasswordChangeDateUtc, expression => expression.MapFrom(user => user.LastPasswordChangeDate.ToUniversalTime()))
- .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email))
- .ForMember(dest => dest.EmailConfirmed, opt => opt.MapFrom(src => src.EmailConfirmedDate.HasValue))
- .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
- .ForMember(dest => dest.LockoutEndDateUtc, opt => opt.MapFrom(src => src.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?) null))
- .ForMember(dest => dest.IsApproved, opt => opt.MapFrom(src => src.IsApproved))
- .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => src.Username))
- .ForMember(dest => dest.PasswordHash, opt => opt.MapFrom(user => GetPasswordHash(user.RawPasswordValue)))
- .ForMember(dest => dest.Culture, opt => opt.MapFrom(src => src.GetUserCulture(textService, globalSettings)))
- .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
- .ForMember(dest => dest.StartMediaIds, opt => opt.MapFrom(src => src.StartMediaIds))
- .ForMember(dest => dest.StartContentIds, opt => opt.MapFrom(src => src.StartContentIds))
- .ForMember(dest => dest.AccessFailedCount, opt => opt.MapFrom(src => src.FailedPasswordAttempts))
- .ForMember(dest => dest.CalculatedContentStartNodeIds, opt => opt.MapFrom(src => src.CalculateContentStartNodeIds(entityService)))
- .ForMember(dest => dest.CalculatedMediaStartNodeIds, opt => opt.MapFrom(src => src.CalculateMediaStartNodeIds(entityService)))
- .ForMember(dest => dest.AllowedSections, opt => opt.MapFrom(src => src.AllowedSections.ToArray()))
- .ForMember(dest => dest.LockoutEnabled, opt => opt.Ignore())
- .ForMember(dest => dest.Logins, opt => opt.Ignore())
- .ForMember(dest => dest.EmailConfirmed, opt => opt.Ignore())
- .ForMember(dest => dest.PhoneNumber, opt => opt.Ignore())
- .ForMember(dest => dest.PhoneNumberConfirmed, opt => opt.Ignore())
- .ForMember(dest => dest.TwoFactorEnabled, opt => opt.Ignore())
- .ForMember(dest => dest.Roles, opt => opt.Ignore())
- .ForMember(dest => dest.Claims, opt => opt.Ignore())
- .AfterMap((src, dest) =>
- {
- dest.ResetDirtyProperties(true);
- dest.EnableChangeTracking();
- });
- }
-
- private static string GetPasswordHash(string storedPass)
- {
- return storedPass.StartsWith(Constants.Security.EmptyPasswordPrefix) ? null : storedPass;
- }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
index 272158551d..f430a8894c 100644
--- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
+++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs
@@ -57,7 +57,7 @@ namespace Umbraco.Core
public const string MacroProperty = /*TableNamePrefix*/ "cms" + "MacroProperty";
public const string Member = /*TableNamePrefix*/ "cms" + "Member";
- public const string MemberType = /*TableNamePrefix*/ "cms" + "MemberType";
+ public const string MemberPropertyType = /*TableNamePrefix*/ "cms" + "MemberType";
public const string Member2MemberGroup = /*TableNamePrefix*/ "cms" + "Member2MemberGroup";
public const string Access = TableNamePrefix + "Access";
diff --git a/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs
index 4f3a67aa91..e7a14a26e2 100644
--- a/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/ContentTypeDto.cs
@@ -54,6 +54,7 @@ namespace Umbraco.Core.Persistence.Dtos
public byte Variations { get; set; }
[ResultColumn]
+ [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")]
public NodeDto NodeDto { get; set; }
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs
index d270c7b732..24f0e07295 100644
--- a/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/DataTypeDto.cs
@@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Dtos
public string Configuration { get; set; }
[ResultColumn]
- [Reference(ReferenceType.OneToOne, ColumnName = "DataTypeId")]
+ [Reference(ReferenceType.OneToOne, ColumnName = "NodeId")]
public NodeDto NodeDto { get; set; }
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/MemberPropertyTypeDto.cs
similarity index 88%
rename from src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs
rename to src/Umbraco.Core/Persistence/Dtos/MemberPropertyTypeDto.cs
index 545f92bb82..7186455489 100644
--- a/src/Umbraco.Core/Persistence/Dtos/MemberTypeDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/MemberPropertyTypeDto.cs
@@ -3,10 +3,10 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Persistence.Dtos
{
- [TableName(Constants.DatabaseSchema.Tables.MemberType)]
+ [TableName(Constants.DatabaseSchema.Tables.MemberPropertyType)]
[PrimaryKey("pk")]
[ExplicitColumns]
- internal class MemberTypeDto
+ internal class MemberPropertyTypeDto
{
[Column("pk")]
[PrimaryKeyColumn]
diff --git a/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs
deleted file mode 100644
index c4ea6a10fd..0000000000
--- a/src/Umbraco.Core/Persistence/Dtos/MemberTypeReadOnlyDto.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.Collections.Generic;
-using NPoco;
-
-namespace Umbraco.Core.Persistence.Dtos
-{
- [TableName(Constants.DatabaseSchema.Tables.Node)]
- [PrimaryKey("id")]
- [ExplicitColumns]
- internal class MemberTypeReadOnlyDto
- {
- /* from umbracoNode */
- [Column("id")]
- public int NodeId { get; set; }
-
- [Column("trashed")]
- public bool Trashed { get; set; }
-
- [Column("parentID")]
- public int ParentId { get; set; }
-
- [Column("nodeUser")]
- public int? UserId { get; set; }
-
- [Column("level")]
- public short Level { get; set; }
-
- [Column("path")]
- public string Path { get; set; }
-
- [Column("sortOrder")]
- public int SortOrder { get; set; }
-
- [Column("uniqueID")]
- public Guid? UniqueId { get; set; }
-
- [Column("text")]
- public string Text { get; set; }
-
- [Column("nodeObjectType")]
- public Guid? NodeObjectType { get; set; }
-
- [Column("createDate")]
- public DateTime CreateDate { get; set; }
-
- /* cmsContentType */
- [Column("pk")]
- public int PrimaryKey { get; set; }
-
- [Column("alias")]
- public string Alias { get; set; }
-
- [Column("icon")]
- public string Icon { get; set; }
-
- [Column("thumbnail")]
- public string Thumbnail { get; set; }
-
- [Column("description")]
- public string Description { get; set; }
-
- [Column("isContainer")]
- public bool IsContainer { get; set; }
-
- [Column("allowAtRoot")]
- public bool AllowAtRoot { get; set; }
-
- /* PropertyTypes */
- // TODO: Add PropertyTypeDto (+MemberTypeDto and DataTypeDto as one) ReadOnly list
- [ResultColumn]
- [Reference(ReferenceType.Many, ReferenceMemberName = "ContentTypeId")]
- public List PropertyTypes { get; set; }
-
- /* PropertyTypeGroups */
- // TODO: Add PropertyTypeGroupDto ReadOnly list
- [ResultColumn]
- [Reference(ReferenceType.Many, ReferenceMemberName = "ContentTypeNodeId")]
- public List PropertyTypeGroups { get; set; }
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeCommonDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeCommonDto.cs
new file mode 100644
index 0000000000..040123353e
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeCommonDto.cs
@@ -0,0 +1,18 @@
+using NPoco;
+
+namespace Umbraco.Core.Persistence.Dtos
+{
+ // this is PropertyTypeDto + the special property type fields for members
+ // it is used for querying everything needed for a property type, at once
+ internal class PropertyTypeCommonDto : PropertyTypeDto
+ {
+ [Column("memberCanEdit")]
+ public bool CanEdit { get; set; }
+
+ [Column("viewOnProfile")]
+ public bool ViewOnProfile { get; set; }
+
+ [Column("isSensitive")]
+ public bool IsSensitive { get; set; }
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs
index 7a04a6d0d9..54cfee0ffa 100644
--- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs
@@ -67,16 +67,28 @@ namespace Umbraco.Core.Persistence.Factories
public static IMemberType BuildMemberTypeEntity(ContentTypeDto dto)
{
- throw new NotImplementedException();
+ var contentType = new MemberType(dto.NodeDto.ParentId);
+ try
+ {
+ contentType.DisableChangeTracking();
+ BuildCommonEntity(contentType, dto, false);
+ contentType.ResetDirtyProperties(false);
+ }
+ finally
+ {
+ contentType.EnableChangeTracking();
+ }
+
+ return contentType;
}
- public static IEnumerable BuildMemberTypeDtos(IMemberType entity)
+ public static IEnumerable BuildMemberPropertyTypeDtos(IMemberType entity)
{
var memberType = entity as MemberType;
if (memberType == null || memberType.PropertyTypes.Any() == false)
- return Enumerable.Empty();
+ return Enumerable.Empty();
- var dtos = memberType.PropertyTypes.Select(x => new MemberTypeDto
+ var dtos = memberType.PropertyTypes.Select(x => new MemberPropertyTypeDto
{
NodeId = entity.Id,
PropertyTypeId = x.Id,
@@ -91,7 +103,7 @@ namespace Umbraco.Core.Persistence.Factories
#region Common
- private static void BuildCommonEntity(ContentTypeBase entity, ContentTypeDto dto)
+ private static void BuildCommonEntity(ContentTypeBase entity, ContentTypeDto dto, bool setVariations = true)
{
entity.Id = dto.NodeDto.NodeId;
entity.Key = dto.NodeDto.UniqueId;
@@ -102,6 +114,7 @@ namespace Umbraco.Core.Persistence.Factories
entity.SortOrder = dto.NodeDto.SortOrder;
entity.Description = dto.Description;
entity.CreateDate = dto.NodeDto.CreateDate;
+ entity.UpdateDate = dto.NodeDto.CreateDate;
entity.Path = dto.NodeDto.Path;
entity.Level = dto.NodeDto.Level;
entity.CreatorId = dto.NodeDto.UserId ?? Constants.Security.UnknownUserId;
@@ -109,7 +122,9 @@ namespace Umbraco.Core.Persistence.Factories
entity.IsContainer = dto.IsContainer;
entity.IsElement = dto.IsElement;
entity.Trashed = dto.NodeDto.Trashed;
- entity.Variations = (ContentVariation) dto.Variations;
+
+ if (setVariations)
+ entity.Variations = (ContentVariation) dto.Variations;
}
public static ContentTypeDto BuildContentTypeDto(IContentTypeBase entity)
diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs
deleted file mode 100644
index c7ce98a89c..0000000000
--- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs
+++ /dev/null
@@ -1,194 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Umbraco.Core.Models;
-using Umbraco.Core.Persistence.Dtos;
-using Umbraco.Core.Persistence.Repositories.Implement;
-
-namespace Umbraco.Core.Persistence.Factories
-{
- internal static class MemberTypeReadOnlyFactory
- {
- public static IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving)
- {
- var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
- needsSaving = false;
-
- var memberType = new MemberType(dto.ParentId);
-
- try
- {
- memberType.DisableChangeTracking();
-
- memberType.Alias = dto.Alias;
- memberType.AllowedAsRoot = dto.AllowAtRoot;
- memberType.CreateDate = dto.CreateDate;
- memberType.CreatorId = dto.UserId.HasValue ? dto.UserId.Value : 0;
- memberType.Description = dto.Description;
- memberType.Icon = dto.Icon;
- memberType.Id = dto.NodeId;
- memberType.IsContainer = dto.IsContainer;
- memberType.Key = dto.UniqueId.Value;
- memberType.Level = dto.Level;
- memberType.Name = dto.Text;
- memberType.Path = dto.Path;
- memberType.SortOrder = dto.SortOrder;
- memberType.Thumbnail = dto.Thumbnail;
- memberType.Trashed = dto.Trashed;
- memberType.UpdateDate = dto.CreateDate;
- memberType.AllowedContentTypes = Enumerable.Empty();
-
- var propertyTypeGroupCollection = GetPropertyTypeGroupCollection(dto, memberType, standardPropertyTypes);
- memberType.PropertyGroups = propertyTypeGroupCollection;
-
- var propertyTypes = GetPropertyTypes(dto, memberType, standardPropertyTypes);
-
- //By Convention we add 9 standard PropertyTypes - This is only here to support loading of types that didn't have these conventions before.
- foreach (var standardPropertyType in standardPropertyTypes)
- {
- if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue;
-
- // beware!
- // means that we can return a memberType "from database" that has some property types
- // that do *not* come from the database and therefore are incomplete eg have no key,
- // no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation
- needsSaving = true;
-
- //Add the standard PropertyType to the current list
- propertyTypes.Add(standardPropertyType.Value);
-
- //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType
- memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key,
- new MemberTypePropertyProfileAccess(false, false, false));
- }
- memberType.NoGroupPropertyTypes = propertyTypes;
-
- return memberType;
- }
- finally
- {
- memberType.EnableChangeTracking();
- }
- }
-
- private static PropertyGroupCollection GetPropertyTypeGroupCollection(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps)
- {
- // see PropertyGroupFactory, repeating code here...
-
- var propertyGroups = new PropertyGroupCollection();
- foreach (var groupDto in dto.PropertyTypeGroups.Where(x => x.Id.HasValue))
- {
- var group = new PropertyGroup(MemberType.SupportsPublishingConst);
-
- // if the group is defined on the current member type,
- // assign its identifier, else it will be zero
- if (groupDto.ContentTypeNodeId == memberType.Id)
- {
- // note: no idea why Id is nullable here, but better check
- if (groupDto.Id.HasValue == false)
- throw new Exception("GroupDto.Id has no value.");
- group.Id = groupDto.Id.Value;
- }
-
- group.Key = groupDto.UniqueId;
- group.Name = groupDto.Text;
- group.SortOrder = groupDto.SortOrder;
- group.PropertyTypes = new PropertyTypeCollection(MemberType.SupportsPublishingConst);
-
- //Because we are likely to have a group with no PropertyTypes we need to ensure that these are excluded
- var localGroupDto = groupDto;
- var typeDtos = dto.PropertyTypes.Where(x => x.Id.HasValue && x.Id > 0 && x.PropertyTypeGroupId.HasValue && x.PropertyTypeGroupId.Value == localGroupDto.Id.Value);
- foreach (var typeDto in typeDtos)
- {
- //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
- memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
- new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
-
- var tempGroupDto = groupDto;
-
- //ensures that any built-in membership properties have their correct dbtype assigned no matter
- //what the underlying data type is
- var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty(
- typeDto.Alias,
- typeDto.DbType.EnumParse(true),
- standardProps);
-
- var propertyType = new PropertyType(
- typeDto.PropertyEditorAlias,
- propDbType.Result,
- //This flag tells the property type that it has an explicit dbtype and that it cannot be changed
- // which is what we want for the built-in properties.
- propDbType.Success,
- typeDto.Alias)
- {
- DataTypeId = typeDto.DataTypeId,
- Description = typeDto.Description,
- Id = typeDto.Id.Value,
- Name = typeDto.Name,
- Mandatory = typeDto.Mandatory,
- SortOrder = typeDto.SortOrder,
- ValidationRegExp = typeDto.ValidationRegExp,
- PropertyGroupId = new Lazy(() => tempGroupDto.Id.Value),
- CreateDate = memberType.CreateDate,
- UpdateDate = memberType.UpdateDate,
- Key = typeDto.UniqueId
- };
-
- // reset dirty initial properties (U4-1946)
- propertyType.ResetDirtyProperties(false);
- group.PropertyTypes.Add(propertyType);
- }
-
- // reset dirty initial properties (U4-1946)
- group.ResetDirtyProperties(false);
- propertyGroups.Add(group);
- }
-
- return propertyGroups;
- }
-
- private static List GetPropertyTypes(MemberTypeReadOnlyDto dto, MemberType memberType, Dictionary standardProps)
- {
- //Find PropertyTypes that does not belong to a PropertyTypeGroup
- var propertyTypes = new List();
- foreach (var typeDto in dto.PropertyTypes.Where(x => (x.PropertyTypeGroupId.HasValue == false || x.PropertyTypeGroupId.Value == 0) && x.Id.HasValue))
- {
- //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType
- memberType.MemberTypePropertyTypes.Add(typeDto.Alias,
- new MemberTypePropertyProfileAccess(typeDto.ViewOnProfile, typeDto.CanEdit, typeDto.IsSensitive));
-
- //ensures that any built-in membership properties have their correct dbtype assigned no matter
- //what the underlying data type is
- var propDbType = MemberTypeRepository.GetDbTypeForBuiltInProperty(
- typeDto.Alias,
- typeDto.DbType.EnumParse(true),
- standardProps);
-
- var propertyType = new PropertyType(
- typeDto.PropertyEditorAlias,
- propDbType.Result,
- //This flag tells the property type that it has an explicit dbtype and that it cannot be changed
- // which is what we want for the built-in properties.
- propDbType.Success,
- typeDto.Alias)
- {
- DataTypeId = typeDto.DataTypeId,
- Description = typeDto.Description,
- Id = typeDto.Id.Value,
- Mandatory = typeDto.Mandatory,
- Name = typeDto.Name,
- SortOrder = typeDto.SortOrder,
- ValidationRegExp = typeDto.ValidationRegExp,
- PropertyGroupId = null,
- CreateDate = dto.CreateDate,
- UpdateDate = dto.CreateDate,
- Key = typeDto.UniqueId
- };
-
- propertyTypes.Add(propertyType);
- }
- return propertyTypes;
- }
-
- }
-}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs
new file mode 100644
index 0000000000..e7657ac941
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Core.Persistence.Repositories
+{
+ // TODO
+ // this should be IContentTypeRepository, and what is IContentTypeRepository at the moment should
+ // become IDocumentTypeRepository - but since these interfaces are public, that would be breaking
+
+ ///
+ /// Represents the content types common repository, dealing with document, media and member types.
+ ///
+ public interface IContentTypeCommonRepository
+ {
+ ///
+ /// Gets and cache all types.
+ ///
+ IEnumerable GetAllTypes();
+
+ ///
+ /// Clears the cache.
+ ///
+ void ClearCache();
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs
index 09059089e7..fbaff4f510 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs
@@ -1,16 +1,8 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
-using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IMediaTypeRepository : IContentTypeRepositoryBase
- {
- ///
- /// Gets all entities of the specified query
- ///
- ///
- /// An enumerable list of objects
- IEnumerable GetByQuery(IQuery query);
- }
+ { }
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
new file mode 100644
index 0000000000..194a00b7f2
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs
@@ -0,0 +1,285 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NPoco;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Models;
+using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Core.Persistence.Factories;
+using Umbraco.Core.Scoping;
+
+namespace Umbraco.Core.Persistence.Repositories.Implement
+{
+ ///
+ /// Implements .
+ ///
+ internal class ContentTypeCommonRepository : IContentTypeCommonRepository
+ {
+ private const string CacheKey = "Umbraco.Core.Persistence.Repositories.Implement.ContentTypeCommonRepository::AllTypes";
+
+ private readonly AppCaches _appCaches;
+ private readonly IScopeAccessor _scopeAccessor;
+ private readonly ITemplateRepository _templateRepository;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentTypeCommonRepository(IScopeAccessor scopeAccessor, ITemplateRepository templateRepository, AppCaches appCaches)
+ {
+ _scopeAccessor = scopeAccessor;
+ _templateRepository = templateRepository;
+ _appCaches = appCaches;
+ }
+
+ private IScope AmbientScope => _scopeAccessor.AmbientScope;
+ private IUmbracoDatabase Database => AmbientScope.Database;
+ private ISqlContext SqlContext => AmbientScope.SqlContext;
+ private Sql Sql() => SqlContext.Sql();
+ //private Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args);
+ //private ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax;
+ //private IQuery Query() => SqlContext.Query();
+
+ ///
+ public IEnumerable GetAllTypes()
+ {
+ // use a 5 minutes sliding cache - same as FullDataSet cache policy
+ return _appCaches.RuntimeCache.GetCacheItem(CacheKey, GetAllTypesInternal, TimeSpan.FromMinutes(5), true);
+ }
+
+ ///
+ public void ClearCache()
+ {
+ _appCaches.RuntimeCache.Clear(CacheKey);
+ }
+
+ private IEnumerable GetAllTypesInternal()
+ {
+ var contentTypes = new Dictionary();
+
+ // get content types
+ var sql1 = Sql()
+ .Select(r => r.Select(x => x.NodeDto))
+ .From()
+ .InnerJoin().On((ct, n) => ct.NodeId == n.NodeId)
+ .OrderBy(x => x.NodeId);
+
+ var contentTypeDtos = Database.Fetch(sql1);
+
+ // get allowed content types
+ var sql2 = Sql()
+ .Select()
+ .From()
+ .OrderBy(x => x.Id);
+
+ var allowedDtos = Database.Fetch(sql2);
+
+ // prepare
+ // note: same alias could be used for media, content... but always different ids = ok
+ var aliases = contentTypeDtos.ToDictionary(x => x.NodeId, x => x.Alias);
+
+ // create
+ var allowedDtoIx = 0;
+ foreach (var contentTypeDto in contentTypeDtos)
+ {
+ // create content type
+ IContentTypeComposition contentType;
+ if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MediaType)
+ contentType = ContentTypeFactory.BuildMediaTypeEntity(contentTypeDto);
+ else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.DocumentType)
+ contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
+ else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
+ contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
+ else throw new Exception("panic");
+ contentTypes.Add(contentType.Id, contentType);
+
+ // map allowed content types
+ var allowedContentTypes = new List();
+ while (allowedDtoIx < allowedDtos.Count && allowedDtos[allowedDtoIx].Id == contentTypeDto.NodeId)
+ {
+ var allowedDto = allowedDtos[allowedDtoIx];
+ if (!aliases.TryGetValue(allowedDto.AllowedId, out var alias)) continue;
+ allowedContentTypes.Add(new ContentTypeSort(new Lazy(() => allowedDto.AllowedId), allowedDto.SortOrder, alias));
+ allowedDtoIx++;
+ }
+ contentType.AllowedContentTypes = allowedContentTypes;
+ }
+
+ MapTemplates(contentTypes);
+ MapComposition(contentTypes);
+ MapGroupsAndProperties(contentTypes);
+
+ // finalize
+ foreach (var contentType in contentTypes.Values)
+ {
+ contentType.ResetDirtyProperties(false);
+ }
+
+ return contentTypes.Values;
+ }
+
+ private void MapTemplates(Dictionary contentTypes)
+ {
+ // get templates
+ var sql1 = Sql()
+ .Select()
+ .From()
+ .OrderBy(x => x.ContentTypeNodeId);
+
+ var templateDtos = Database.Fetch(sql1);
+ //var templates = templateRepository.GetMany(templateDtos.Select(x => x.TemplateNodeId).ToArray()).ToDictionary(x => x.Id, x => x);
+ var templates = _templateRepository.GetMany().ToDictionary(x => x.Id, x => x);
+ var templateDtoIx = 0;
+
+ foreach (var c in contentTypes.Values)
+ {
+ if (!(c is ContentType contentType)) continue;
+
+ // map allowed templates
+ var allowedTemplates = new List();
+ var defaultTemplateId = 0;
+ while (templateDtoIx < templateDtos.Count && templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id)
+ {
+ var allowedDto = templateDtos[templateDtoIx];
+ if (!templates.TryGetValue(allowedDto.TemplateNodeId, out var template)) continue;
+ allowedTemplates.Add(template);
+ templateDtoIx++;
+
+ if (allowedDto.IsDefault)
+ defaultTemplateId = template.Id;
+ }
+ contentType.AllowedTemplates = allowedTemplates;
+ contentType.DefaultTemplateId = defaultTemplateId;
+ }
+ }
+
+ private void MapComposition(IDictionary contentTypes)
+ {
+ // get parent/child
+ var sql1 = Sql()
+ .Select()
+ .From()
+ .OrderBy(x => x.ChildId);
+
+ var compositionDtos = Database.Fetch(sql1);
+
+ // map
+ var compositionIx = 0;
+ foreach (var contentType in contentTypes.Values)
+ {
+ while (compositionIx < compositionDtos.Count && compositionDtos[compositionIx].ChildId == contentType.Id)
+ {
+ var parentDto = compositionDtos[compositionIx];
+ if (!contentTypes.TryGetValue(parentDto.ParentId, out var parentContentType)) continue;
+ contentType.AddContentType(parentContentType);
+ compositionIx++;
+ }
+ }
+ }
+
+ private void MapGroupsAndProperties(IDictionary contentTypes)
+ {
+ var sql1 = Sql()
+ .Select()
+ .From()
+ .InnerJoin().On((ptg, ct) => ptg.ContentTypeNodeId == ct.NodeId)
+ .OrderBy(x => x.NodeId)
+ .AndBy(x => x.SortOrder, x => x.Id);
+
+ var groupDtos = Database.Fetch(sql1);
+
+ var sql2 = Sql()
+ .Select(r => r.Select(x => x.DataTypeDto))
+ .AndSelect()
+ .From()
+ .InnerJoin().On((pt, dt) => pt.DataTypeId == dt.NodeId)
+ .InnerJoin().On((pt, ct) => pt.ContentTypeId == ct.NodeId)
+ .LeftJoin().On((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id)
+ .LeftJoin().On((pt, mpt) => pt.Id == mpt.PropertyTypeId)
+ .OrderBy(x => x.NodeId)
+ .AndBy(x => x.SortOrder, x => x.Id) // NULLs will come first or last, never mind, we deal with it below
+ .AndBy(x => x.SortOrder, x => x.Id);
+
+ var propertyDtos = Database.Fetch(sql2);
+ var builtinProperties = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
+
+ var groupIx = 0;
+ var propertyIx = 0;
+ foreach (var contentType in contentTypes.Values)
+ {
+ // only IContentType is publishing
+ var isPublishing = contentType is IContentType;
+
+ // get group-less properties (in case NULL is ordered first)
+ var noGroupPropertyTypes = new PropertyTypeCollection(isPublishing);
+ while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null)
+ {
+ noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties));
+ propertyIx++;
+ }
+
+ // get groups and their properties
+ var groupCollection = new PropertyGroupCollection();
+ while (groupIx < groupDtos.Count && groupDtos[groupIx].ContentTypeNodeId == contentType.Id)
+ {
+ var group = MapPropertyGroup(groupDtos[groupIx], isPublishing);
+ groupCollection.Add(group);
+ groupIx++;
+
+ while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == group.Id)
+ {
+ group.PropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties));
+ propertyIx++;
+ }
+ }
+ contentType.PropertyGroups = groupCollection;
+
+ // get group-less properties (in case NULL is ordered last)
+ while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null)
+ {
+ noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties));
+ propertyIx++;
+ }
+ contentType.NoGroupPropertyTypes = noGroupPropertyTypes;
+ }
+ }
+
+ private PropertyGroup MapPropertyGroup(PropertyTypeGroupDto dto, bool isPublishing)
+ {
+ return new PropertyGroup(new PropertyTypeCollection(isPublishing))
+ {
+ Id = dto.Id,
+ Name = dto.Text,
+ SortOrder = dto.SortOrder,
+ Key = dto.UniqueId
+ };
+ }
+
+ private PropertyType MapPropertyType(IContentTypeComposition contentType, PropertyTypeCommonDto dto, IDictionary builtinProperties)
+ {
+ var groupId = dto.PropertyTypeGroupId;
+
+ var readonlyStorageType = builtinProperties.TryGetValue(dto.Alias, out var propertyType);
+ var storageType = readonlyStorageType ? propertyType.ValueStorageType : Enum.Parse(dto.DataTypeDto.DbType);
+
+ if (contentType is MemberType memberType)
+ {
+ var access = new MemberTypePropertyProfileAccess(dto.ViewOnProfile, dto.CanEdit, dto.IsSensitive);
+ memberType.MemberTypePropertyTypes.Add(dto.Alias, access);
+ }
+
+ return new PropertyType(dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, dto.Alias)
+ {
+ Description = dto.Description,
+ DataTypeId = dto.DataTypeId,
+ Id = dto.Id,
+ Key = dto.UniqueId,
+ Mandatory = dto.Mandatory,
+ Name = dto.Name,
+ PropertyGroupId = groupId.HasValue ? new Lazy(() => groupId.Value) : null,
+ SortOrder = dto.SortOrder,
+ ValidationRegExp = dto.ValidationRegExp,
+ Variations = (ContentVariation)dto.Variations
+ };
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
index 095c77eb42..98ddcdcb17 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs
@@ -17,13 +17,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class ContentTypeRepository : ContentTypeRepositoryBase, IContentTypeRepository
{
- private readonly ITemplateRepository _templateRepository;
-
- public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, ITemplateRepository templateRepository)
- : base(scopeAccessor, cache, logger)
- {
- _templateRepository = templateRepository;
- }
+ public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
+ : base(scopeAccessor, cache, logger, commonRepository)
+ { }
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
@@ -32,75 +28,81 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ true);
}
+ // every GetExists method goes cachePolicy.GetSomething which in turns goes PerformGetAll,
+ // since this is a FullDataSet policy - and everything is cached
+ // so here,
+ // every PerformGet/Exists just GetMany() and then filters
+ // except PerformGetAll which is the one really doing the job
+
+ // TODO: the filtering is highly inefficient as we deep-clone everything
+ // there should be a way to GetMany(predicate) right from the cache policy!
+ // and ah, well, this all caching should be refactored + the cache refreshers
+ // should to repository.Clear() not deal with magic caches by themselves
+
protected override IContentType PerformGet(int id)
- {
- //use the underlying GetAll which will force cache all content types
- return GetMany().FirstOrDefault(x => x.Id == id);
- }
+ => GetMany().FirstOrDefault(x => x.Id == id);
protected override IContentType PerformGet(Guid id)
- {
- //use the underlying GetAll which will force cache all content types
- return GetMany().FirstOrDefault(x => x.Key == id);
- }
+ => GetMany().FirstOrDefault(x => x.Key == id);
protected override IContentType PerformGet(string alias)
- {
- //use the underlying GetAll which will force cache all content types
- return GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias));
- }
+ => GetMany().FirstOrDefault(x => x.Alias.InvariantEquals(alias));
protected override bool PerformExists(Guid id)
- {
- return GetMany().FirstOrDefault(x => x.Key == id) != null;
- }
+ => GetMany().FirstOrDefault(x => x.Key == id) != null;
protected override IEnumerable PerformGetAll(params int[] ids)
{
- if (ids.Any())
- {
- //NOTE: This logic should never be executed according to our cache policy
- return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository)
- .Where(x => ids.Contains(x.Id));
- }
-
- return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, SupportsPublishing, this, _templateRepository);
+ // the cache policy will always want everything
+ // even GetMany(ids) gets everything and filters afterwards
+ if (ids.Any()) throw new Exception("panic");
+ return CommonRepository.GetAllTypes().OfType();
}
protected override IEnumerable PerformGetAll(params Guid[] ids)
{
- // use the underlying GetAll which will force cache all content types
- return ids.Any() ? GetMany().Where(x => ids.Contains(x.Key)) : GetMany();
+ var all = GetMany();
+ return ids.Any() ? all.Where(x => ids.Contains(x.Key)) : all;
}
protected override IEnumerable PerformGetByQuery(IQuery query)
{
- var sqlClause = GetBaseQuery(false);
- var translator = new SqlTranslator(sqlClause, query);
+ var baseQuery = GetBaseQuery(false);
+ var translator = new SqlTranslator(baseQuery, query);
var sql = translator.Translate();
+ var ids = Database.Fetch(sql).Distinct().ToArray();
- var dtos = Database.Fetch(sql);
-
- return
- //This returns a lookup from the GetAll cached lookup
- (dtos.Any()
- ? GetMany(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.NodeId).ToArray())
- : Enumerable.Empty())
- //order the result by name
- .OrderBy(x => x.Name);
+ return ids.Length > 0 ? GetMany(ids).OrderBy(x => x.Name) : Enumerable.Empty();
}
- ///
- /// Gets all entities of the specified query
- ///
- ///
- /// An enumerable list of objects
+ ///
public IEnumerable GetByQuery(IQuery query)
{
var ints = PerformGetByQuery(query).ToArray();
- return ints.Any()
- ? GetMany(ints)
- : Enumerable.Empty();
+ return ints.Length > 0 ? GetMany(ints) : Enumerable.Empty();
+ }
+
+ protected IEnumerable PerformGetByQuery(IQuery query)
+ {
+ // used by DataTypeService to remove properties
+ // from content types if they have a deleted data type - see
+ // notes in DataTypeService.Delete as it's a bit weird
+
+ var sqlClause = Sql()
+ .SelectAll()
+ .From()
+ .RightJoin()
+ .On(left => left.Id, right => right.PropertyTypeGroupId)
+ .InnerJoin()
+ .On(left => left.DataTypeId, right => right.NodeId);
+
+ var translator = new SqlTranslator(sqlClause, query);
+ var sql = translator.Translate()
+ .OrderBy(x => x.PropertyTypeGroupId);
+
+ return Database
+ .FetchOneToMany(x => x.PropertyTypeDtos, sql)
+ .Select(x => x.ContentTypeNodeId).Distinct();
}
///
@@ -141,7 +143,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (aliases.Length == 0) return Enumerable.Empty();
var sql = Sql()
- .Select("cmsContentType.nodeId")
+ .Select(x => x.NodeId)
.From()
.InnerJoin()
.On(dto => dto.NodeId, dto => dto.NodeId)
@@ -156,14 +158,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
sql = isCount
? sql.SelectCount()
- : sql.Select(r => r.Select(x => x.ContentTypeDto, r1 => r1.Select(x => x.NodeDto)));
+ : sql.Select(x => x.NodeId);
sql
.From()
- .InnerJoin()
- .On(left => left.NodeId, right => right.NodeId)
- .LeftJoin()
- .On(left => left.ContentTypeNodeId, right => right.NodeId)
+ .InnerJoin().On(left => left.NodeId, right => right.NodeId)
+ .LeftJoin().On(left => left.ContentTypeNodeId, right => right.NodeId)
.Where(x => x.NodeObjectType == NodeObjectTypeId);
return sql;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 924efa1e11..591fa2b660 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -10,11 +10,9 @@ using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
-using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
-using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
@@ -28,9 +26,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository
where TEntity : class, IContentTypeComposition
{
- protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
+ protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger)
- { }
+ {
+ CommonRepository = commonRepository;
+ }
+
+ protected IContentTypeCommonRepository CommonRepository { get; }
protected abstract bool SupportsPublishing { get; }
@@ -82,38 +84,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return moveInfo;
}
- ///
- /// Returns the content type ids that match the query
- ///
- ///
- ///
- protected IEnumerable PerformGetByQuery(IQuery query)
+
+ protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType storageType, string propertyTypeAlias)
{
- // used by DataTypeDefinitionRepository to remove properties
- // from content types if they have a deleted data type - see
- // notes in DataTypeDefinitionRepository.Delete as it's a bit
- // weird
-
- var sqlClause = Sql()
- .SelectAll()
- .From()
- .RightJoin()
- .On(left => left.Id, right => right.PropertyTypeGroupId)
- .InnerJoin()
- .On(left => left.DataTypeId, right => right.NodeId);
-
- var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate()
- .OrderBy(x => x.PropertyTypeGroupId);
-
- return Database
- .FetchOneToMany(x => x.PropertyTypeDtos, sql)
- .Select(x => x.ContentTypeNodeId).Distinct();
+ return new PropertyType(propertyEditorAlias, storageType, propertyTypeAlias);
}
- protected virtual PropertyType CreatePropertyType(string propertyEditorAlias, ValueStorageType dbType, string propertyTypeAlias)
+ protected override void PersistDeletedItem(TEntity entity)
{
- return new PropertyType(propertyEditorAlias, dbType, propertyTypeAlias);
+ base.PersistDeletedItem(entity);
+ CommonRepository.ClearCache(); // always
}
protected void PersistNewBaseContentType(IContentTypeComposition entity)
@@ -228,6 +208,8 @@ AND umbracoNode.nodeObjectType = @objectType",
propertyType.PropertyEditorAlias = dataTypeDto.EditorAlias;
propertyType.ValueStorageType = dataTypeDto.DbType.EnumParse(true);
}
+
+ CommonRepository.ClearCache(); // always
}
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
@@ -532,6 +514,8 @@ AND umbracoNode.id <> @id",
if (orphanPropertyTypeIds != null)
foreach (var id in orphanPropertyTypeIds)
DeletePropertyType(entity.Id, id);
+
+ CommonRepository.ClearCache(); // always
}
private IEnumerable GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable all)
@@ -992,74 +976,6 @@ AND umbracoNode.id <> @id",
new { Id = contentTypeId, PropertyTypeId = propertyTypeId });
}
- protected IEnumerable GetAllowedContentTypeIds(int id)
- {
- var sql = Sql()
- .SelectAll()
- .From()
- .LeftJoin()
- .On(left => left.AllowedId, right => right.NodeId)
- .Where(x => x.Id == id);
-
- var allowedContentTypeDtos = Database.Fetch(sql);
- return allowedContentTypeDtos.Select(x => new ContentTypeSort(new Lazy(() => x.AllowedId), x.SortOrder, x.ContentTypeDto.Alias)).ToList();
- }
-
- protected PropertyGroupCollection GetPropertyGroupCollection(int id, DateTime createDate, DateTime updateDate)
- {
- var sql = Sql()
- .SelectAll()
- .From()
- .LeftJoin()
- .On(left => left.Id, right => right.PropertyTypeGroupId)
- .LeftJoin()
- .On(left => left.DataTypeId, right => right.NodeId)
- .Where(x => x.ContentTypeNodeId == id)
- .OrderBy(x => x.Id);
-
-
- var dtos = Database
- .Fetch(sql);
-
- var propertyGroups = PropertyGroupFactory.BuildEntity(dtos, SupportsPublishing, id, createDate, updateDate,CreatePropertyType);
-
- return new PropertyGroupCollection(propertyGroups);
- }
-
- protected PropertyTypeCollection GetPropertyTypeCollection(int id, DateTime createDate, DateTime updateDate)
- {
- var sql = Sql()
- .SelectAll()
- .From()
- .InnerJoin()
- .On(left => left.DataTypeId, right => right.NodeId)
- .Where(x => x.ContentTypeId == id);
-
- var dtos = Database.Fetch(sql);
-
- // TODO: Move this to a PropertyTypeFactory
- var list = new List();
- foreach (var dto in dtos.Where(x => x.PropertyTypeGroupId <= 0))
- {
- var propType = CreatePropertyType(dto.DataTypeDto.EditorAlias, dto.DataTypeDto.DbType.EnumParse(true), dto.Alias);
- propType.DataTypeId = dto.DataTypeId;
- propType.Description = dto.Description;
- propType.Id = dto.Id;
- propType.Key = dto.UniqueId;
- propType.Name = dto.Name;
- propType.Mandatory = dto.Mandatory;
- propType.SortOrder = dto.SortOrder;
- propType.ValidationRegExp = dto.ValidationRegExp;
- propType.CreateDate = createDate;
- propType.UpdateDate = updateDate;
- list.Add(propType);
- }
- //Reset dirty properties
- Parallel.ForEach(list, currentFile => currentFile.ResetDirtyProperties(false));
-
- return new PropertyTypeCollection(SupportsPublishing, list);
- }
-
protected void ValidateAlias(PropertyType pt)
{
if (string.IsNullOrWhiteSpace(pt.Alias))
@@ -1114,589 +1030,6 @@ AND umbracoNode.id <> @id",
}
}
- internal static class ContentTypeQueryMapper
- {
- public class AssociatedTemplate
- {
- public AssociatedTemplate(int templateId, string alias, string templateName)
- {
- TemplateId = templateId;
- Alias = alias;
- TemplateName = templateName;
- }
-
- public int TemplateId { get; set; }
- public string Alias { get; set; }
- public string TemplateName { get; set; }
-
- protected bool Equals(AssociatedTemplate other)
- {
- return TemplateId == other.TemplateId;
- }
-
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
- return Equals((AssociatedTemplate)obj);
- }
-
- public override int GetHashCode()
- {
- return TemplateId;
- }
- }
-
- public static IEnumerable GetMediaTypes(
- IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing,
- TRepo contentTypeRepository)
- where TRepo : IReadRepository
- {
- IDictionary> allParentMediaTypeIds;
- var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds)
- .ToArray();
-
- MapContentTypeChildren(mediaTypes, db, sqlSyntax, isPublishing, contentTypeRepository, allParentMediaTypeIds);
-
- return mediaTypes;
- }
-
- public static IEnumerable GetContentTypes(
- IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing,
- TRepo contentTypeRepository,
- ITemplateRepository templateRepository)
- where TRepo : IReadRepository
- {
- IDictionary> allAssociatedTemplates;
- IDictionary> allParentContentTypeIds;
- var contentTypes = MapContentTypes(db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds)
- .ToArray();
-
- if (contentTypes.Any())
- {
- MapContentTypeTemplates(
- contentTypes, db, contentTypeRepository, templateRepository, allAssociatedTemplates);
-
- MapContentTypeChildren(contentTypes, db, sqlSyntax, isPublishing, contentTypeRepository, allParentContentTypeIds);
- }
-
- return contentTypes;
- }
-
- internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes,
- IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing,
- TRepo contentTypeRepository,
- IDictionary> allParentContentTypeIds)
- where TRepo : IReadRepository
- {
- //NOTE: SQL call #2
-
- var ids = contentTypes.Select(x => x.Id).ToArray();
- IDictionary allPropGroups;
- IDictionary allPropTypes;
- MapGroupsAndProperties(ids, db, sqlSyntax, isPublishing, out allPropTypes, out allPropGroups);
-
- foreach (var contentType in contentTypes)
- {
- contentType.PropertyGroups = allPropGroups[contentType.Id];
- contentType.NoGroupPropertyTypes = allPropTypes[contentType.Id];
- }
-
- //NOTE: SQL call #3++
-
- if (allParentContentTypeIds != null)
- {
- var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray();
- if (allParentIdsAsArray.Any())
- {
- var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray();
-
- foreach (var contentType in contentTypes)
- {
- var entityId = contentType.Id;
-
- var parentContentTypes = allParentContentTypes.Where(x =>
- {
- var parentEntityId = x.Id;
-
- return allParentContentTypeIds[entityId].Contains(parentEntityId);
- });
- foreach (var parentContentType in parentContentTypes)
- {
- var result = contentType.AddContentType(parentContentType);
- //Do something if adding fails? (Should hopefully not be possible unless someone created a circular reference)
- }
-
- // reset dirty initial properties (U4-1946)
- ((EntityBase)contentType).ResetDirtyProperties(false);
- }
- }
- }
-
-
- }
-
- internal static void MapContentTypeTemplates(IContentType[] contentTypes,
- IDatabase db,
- TRepo contentTypeRepository,
- ITemplateRepository templateRepository,
- IDictionary> associatedTemplates)
- where TRepo : IReadRepository
- {
- if (associatedTemplates == null || associatedTemplates.Any() == false) return;
-
- //NOTE: SQL call #3++
- //SEE: http://issues.umbraco.org/issue/U4-5174 to fix this
-
- var templateIds = associatedTemplates.SelectMany(x => x.Value).Select(x => x.TemplateId)
- .Distinct()
- .ToArray();
-
- var templates = (templateIds.Any()
- ? templateRepository.GetMany(templateIds)
- : Enumerable.Empty()).ToArray();
-
- foreach (var contentType in contentTypes)
- {
- var entityId = contentType.Id;
-
- var associatedTemplateIds = associatedTemplates[entityId].Select(x => x.TemplateId)
- .Distinct()
- .ToArray();
-
- contentType.AllowedTemplates = (associatedTemplateIds.Any()
- ? templates.Where(x => associatedTemplateIds.Contains(x.Id))
- : Enumerable.Empty()).ToArray();
- }
-
-
- }
-
- internal static IEnumerable MapMediaTypes(IDatabase db, ISqlSyntaxProvider sqlSyntax,
- out IDictionary> parentMediaTypeIds)
- {
- if (db == null) throw new ArgumentNullException(nameof(db));
-
- var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations,
- cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
- AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
- ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey,
- umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
- umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed,
- umbracoNode.uniqueID as nUniqueId
- FROM cmsContentType
- INNER JOIN umbracoNode
- ON cmsContentType.nodeId = umbracoNode.id
- LEFT JOIN (
- SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder
- FROM cmsContentTypeAllowedContentType
- INNER JOIN cmsContentType
- ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId
- ) AllowedTypes
- ON AllowedTypes.Id = cmsContentType.nodeId
- LEFT JOIN (
- SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId
- FROM cmsContentType2ContentType
- INNER JOIN umbracoNode
- ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @"
- ) ParentTypes
- ON ParentTypes.childContentTypeId = cmsContentType.nodeId
- WHERE (umbracoNode.nodeObjectType = @nodeObjectType)
- ORDER BY ctId";
-
- var result = db.Fetch(sql, new { nodeObjectType = Constants.ObjectTypes.MediaType });
-
- if (result.Any() == false)
- {
- parentMediaTypeIds = null;
- return Enumerable.Empty();
- }
-
- parentMediaTypeIds = new Dictionary>();
- var mappedMediaTypes = new List();
-
- //loop through each result and fill in our required values, each row will contain different required data than the rest.
- // it is much quicker to iterate each result and populate instead of looking up the values over and over in the result like
- // we used to do.
- var queue = new Queue(result);
- var currAllowedContentTypes = new List();
-
- while (queue.Count > 0)
- {
- var ct = queue.Dequeue();
-
- //check for allowed content types
- int? allowedCtId = ct.ctaAllowedId;
- int? allowedCtSort = ct.ctaSortOrder;
- string allowedCtAlias = ct.ctaAlias;
- if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null)
- {
- var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias);
- if (currAllowedContentTypes.Contains(ctSort) == false)
- {
- currAllowedContentTypes.Add(ctSort);
- }
- }
-
- //always ensure there's a list for this content type
- if (parentMediaTypeIds.ContainsKey(ct.ctId) == false)
- parentMediaTypeIds[ct.ctId] = new List();
-
- //check for parent ids and assign to the outgoing collection
- int? parentId = ct.chtParentId;
- if (parentId.HasValue)
- {
- var associatedParentIds = parentMediaTypeIds[ct.ctId];
- if (associatedParentIds.Contains(parentId.Value) == false)
- associatedParentIds.Add(parentId.Value);
- }
-
- if (queue.Count == 0 || queue.Peek().ctId != ct.ctId)
- {
- //it's the last in the queue or the content type is changing (moving to the next one)
- var mediaType = CreateForMapping(ct, currAllowedContentTypes);
- mappedMediaTypes.Add(mediaType);
-
- //Here we need to reset the current variables, we're now collecting data for a different content type
- currAllowedContentTypes = new List();
- }
- }
-
- return mappedMediaTypes;
- }
-
- private static IMediaType CreateForMapping(dynamic currCt, List currAllowedContentTypes)
- {
- // * create the DTO object
- // * create the content type object
- // * map the allowed content types
- // * add to the outgoing list
-
- var contentTypeDto = new ContentTypeDto
- {
- Alias = currCt.ctAlias,
- AllowAtRoot = currCt.ctAllowAtRoot,
- Description = currCt.ctDesc,
- Icon = currCt.ctIcon,
- IsContainer = currCt.ctIsContainer,
- IsElement = currCt.ctIsElement,
- NodeId = currCt.ctId,
- PrimaryKey = currCt.ctPk,
- Thumbnail = currCt.ctThumb,
- Variations = (byte) currCt.ctVariations,
- //map the underlying node dto
- NodeDto = new NodeDto
- {
- CreateDate = currCt.nCreateDate,
- Level = (short)currCt.nLevel,
- NodeId = currCt.ctId,
- NodeObjectType = currCt.nObjectType,
- ParentId = currCt.nParentId,
- Path = currCt.nPath,
- SortOrder = currCt.nSortOrder,
- Text = currCt.nName,
- Trashed = currCt.nTrashed,
- UniqueId = currCt.nUniqueId,
- UserId = currCt.nUser
- }
- };
-
- //now create the content type object;
- var mediaType = ContentTypeFactory.BuildMediaTypeEntity(contentTypeDto);
-
- //map the allowed content types
- mediaType.AllowedContentTypes = currAllowedContentTypes;
-
- return mediaType;
- }
-
- internal static IEnumerable MapContentTypes(IDatabase db, ISqlSyntaxProvider sqlSyntax,
- out IDictionary> associatedTemplates,
- out IDictionary> parentContentTypeIds)
- {
- if (db == null) throw new ArgumentNullException(nameof(db));
-
- var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId,
- cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.variations as ctVariations,
- cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.IsElement as ctIsElement, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb,
- AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias,
- ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey,
- umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser,
- umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed,
- umbracoNode.uniqueID as nUniqueId,
- Template.alias as tAlias, Template.nodeId as tId,Template.text as tText
- FROM cmsContentType
- INNER JOIN umbracoNode
- ON cmsContentType.nodeId = umbracoNode.id
- LEFT JOIN cmsDocumentType
- ON cmsDocumentType.contentTypeNodeId = cmsContentType.nodeId
- LEFT JOIN (
- SELECT cmsContentTypeAllowedContentType.Id, cmsContentTypeAllowedContentType.AllowedId, cmsContentType.alias, cmsContentTypeAllowedContentType.SortOrder
- FROM cmsContentTypeAllowedContentType
- INNER JOIN cmsContentType
- ON cmsContentTypeAllowedContentType.AllowedId = cmsContentType.nodeId
- ) AllowedTypes
- ON AllowedTypes.Id = cmsContentType.nodeId
- LEFT JOIN (
- SELECT * FROM cmsTemplate
- INNER JOIN umbracoNode
- ON cmsTemplate.nodeId = umbracoNode.id
- ) as Template
- ON Template.nodeId = cmsDocumentType.templateNodeId
- LEFT JOIN (
- SELECT cmsContentType2ContentType.parentContentTypeId, umbracoNode.uniqueID AS parentContentTypeKey, cmsContentType2ContentType.childContentTypeId
- FROM cmsContentType2ContentType
- INNER JOIN umbracoNode
- ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @"
- ) ParentTypes
- ON ParentTypes.childContentTypeId = cmsContentType.nodeId
- WHERE (umbracoNode.nodeObjectType = @nodeObjectType)
- ORDER BY ctId";
-
- var result = db.Fetch(sql, new { nodeObjectType = Constants.ObjectTypes.DocumentType });
-
- if (result.Any() == false)
- {
- parentContentTypeIds = null;
- associatedTemplates = null;
- return Enumerable.Empty();
- }
-
- parentContentTypeIds = new Dictionary>();
- associatedTemplates = new Dictionary>();
- var mappedContentTypes = new List();
-
- var queue = new Queue(result);
- var currDefaultTemplate = -1;
- var currAllowedContentTypes = new List();
- while (queue.Count > 0)
- {
- var ct = queue.Dequeue();
-
- //check for default templates
- bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault);
- int? templateId = ct.dtTemplateId;
- if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && isDefaultTemplate.Value && templateId.HasValue)
- {
- currDefaultTemplate = templateId.Value;
- }
-
- //always ensure there's a list for this content type
- if (associatedTemplates.ContainsKey(ct.ctId) == false)
- associatedTemplates[ct.ctId] = new List();
-
- //check for associated templates and assign to the outgoing collection
- if (ct.tId != null)
- {
- var associatedTemplate = new AssociatedTemplate(ct.tId, ct.tAlias, ct.tText);
- var associatedList = associatedTemplates[ct.ctId];
-
- if (associatedList.Contains(associatedTemplate) == false)
- associatedList.Add(associatedTemplate);
- }
-
- //check for allowed content types
- int? allowedCtId = ct.ctaAllowedId;
- int? allowedCtSort = ct.ctaSortOrder;
- string allowedCtAlias = ct.ctaAlias;
- if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null)
- {
- var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias);
- if (currAllowedContentTypes.Contains(ctSort) == false)
- {
- currAllowedContentTypes.Add(ctSort);
- }
- }
-
- //always ensure there's a list for this content type
- if (parentContentTypeIds.ContainsKey(ct.ctId) == false)
- parentContentTypeIds[ct.ctId] = new List();
-
- //check for parent ids and assign to the outgoing collection
- int? parentId = ct.chtParentId;
- if (parentId.HasValue)
- {
- var associatedParentIds = parentContentTypeIds[ct.ctId];
-
- if (associatedParentIds.Contains(parentId.Value) == false)
- associatedParentIds.Add(parentId.Value);
- }
-
- if (queue.Count == 0 || queue.Peek().ctId != ct.ctId)
- {
- //it's the last in the queue or the content type is changing (moving to the next one)
- var contentType = CreateForMapping(ct, currAllowedContentTypes, currDefaultTemplate);
- mappedContentTypes.Add(contentType);
-
- //Here we need to reset the current variables, we're now collecting data for a different content type
- currDefaultTemplate = -1;
- currAllowedContentTypes = new List();
- }
- }
-
- return mappedContentTypes;
- }
-
- private static IContentType CreateForMapping(dynamic currCt, List currAllowedContentTypes, int currDefaultTemplate)
- {
- // * set the default template to the first one if a default isn't found
- // * create the DTO object
- // * create the content type object
- // * map the allowed content types
- // * add to the outgoing list
-
- var dtDto = new ContentTypeTemplateDto
- {
- //create the content type dto
- ContentTypeDto = new ContentTypeDto
- {
- Alias = currCt.ctAlias,
- AllowAtRoot = currCt.ctAllowAtRoot,
- Description = currCt.ctDesc,
- Icon = currCt.ctIcon,
- IsContainer = currCt.ctIsContainer,
- IsElement = currCt.ctIsElement,
- NodeId = currCt.ctId,
- PrimaryKey = currCt.ctPk,
- Thumbnail = currCt.ctThumb,
- Variations = (byte) currCt.ctVariations,
- //map the underlying node dto
- NodeDto = new NodeDto
- {
- CreateDate = currCt.nCreateDate,
- Level = (short)currCt.nLevel,
- NodeId = currCt.ctId,
- NodeObjectType = currCt.nObjectType,
- ParentId = currCt.nParentId,
- Path = currCt.nPath,
- SortOrder = currCt.nSortOrder,
- Text = currCt.nName,
- Trashed = currCt.nTrashed,
- UniqueId = currCt.nUniqueId,
- UserId = currCt.nUser
- }
- },
- ContentTypeNodeId = currCt.ctId,
- IsDefault = currDefaultTemplate != -1,
- TemplateNodeId = currDefaultTemplate != -1 ? currDefaultTemplate : 0,
- };
-
- //now create the content type object
- var contentType = ContentTypeFactory.BuildContentTypeEntity(dtDto.ContentTypeDto);
-
- // NOTE
- // that was done by the factory but makes little sense, moved here, so
- // now we have to reset dirty props again (as the factory does it) and yet,
- // we are not managing allowed templates... the whole thing is weird.
- ((ContentType)contentType).DefaultTemplateId = dtDto.TemplateNodeId;
- contentType.ResetDirtyProperties(false);
-
- //map the allowed content types
- contentType.AllowedContentTypes = currAllowedContentTypes;
-
- return contentType;
- }
-
- internal static void MapGroupsAndProperties(int[] contentTypeIds, IDatabase db, ISqlSyntaxProvider sqlSyntax, bool isPublishing,
- out IDictionary allPropertyTypeCollection,
- out IDictionary allPropertyGroupCollection)
- {
- allPropertyGroupCollection = new Dictionary();
- allPropertyTypeCollection = new Dictionary();
-
- // query below is not safe + pointless if array is empty
- if (contentTypeIds.Length == 0) return;
-
- var sqlGroups = @"SELECT
- pg.contenttypeNodeId AS contentTypeId,
- pg.id AS id, pg.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @",
- pg.sortOrder AS sortOrder, pg." + sqlSyntax.GetQuotedColumnName("text") + @" AS text
-FROM cmsPropertyTypeGroup pg
-WHERE pg.contenttypeNodeId IN (@ids)
-ORDER BY contentTypeId, id";
-
- var sqlProps = @"SELECT
- pt.contentTypeId AS contentTypeId,
- pt.id AS id, pt.uniqueID AS " + sqlSyntax.GetQuotedColumnName("key") + @",
- pt.propertyTypeGroupId AS groupId,
- pt.Alias AS alias, pt." + sqlSyntax.GetQuotedColumnName("Description") + @" AS " + sqlSyntax.GetQuotedColumnName("desc") + $@", pt.mandatory AS mandatory,
- pt.Name AS name, pt.sortOrder AS sortOrder, pt.validationRegExp AS regexp, pt.variations as variations,
- dt.nodeId as dataTypeId, dt.dbType as dbType, dt.propertyEditorAlias as editorAlias
-FROM cmsPropertyType pt
-INNER JOIN {Constants.DatabaseSchema.Tables.DataType} as dt ON pt.dataTypeId = dt.nodeId
-WHERE pt.contentTypeId IN (@ids)
-ORDER BY contentTypeId, groupId, id";
-
- if (contentTypeIds.Length > 2000)
- throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters");
-
- var groups = db.Fetch(sqlGroups, new { ids = contentTypeIds });
- var groupsEnumerator = groups.GetEnumerator();
- var group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null;
-
- var props = db.Fetch(sqlProps, new { ids = contentTypeIds });
- var propsEnumerator = props.GetEnumerator();
- var prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
-
- // groups are ordered by content type, group id
- // props are ordered by content type, group id, prop id
-
- foreach (var contentTypeId in contentTypeIds)
- {
- var propertyTypeCollection = allPropertyTypeCollection[contentTypeId] = new PropertyTypeCollection(isPublishing);
- var propertyGroupCollection = allPropertyGroupCollection[contentTypeId] = new PropertyGroupCollection();
-
- while (prop != null && prop.contentTypeId == contentTypeId && prop.groupId == null)
- {
- AddPropertyType(propertyTypeCollection, prop);
- prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
- }
-
- while (group != null && group.contentTypeId == contentTypeId)
- {
- var propertyGroup = new PropertyGroup(new PropertyTypeCollection(isPublishing))
- {
- Id = group.id,
- Name = group.text,
- SortOrder = group.sortOrder,
- Key = group.key
- };
- propertyGroupCollection.Add(propertyGroup);
-
- while (prop != null && prop.groupId == group.id)
- {
- AddPropertyType(propertyGroup.PropertyTypes, prop, propertyGroup);
- prop = propsEnumerator.MoveNext() ? propsEnumerator.Current : null;
- }
-
- group = groupsEnumerator.MoveNext() ? groupsEnumerator.Current : null;
- }
- }
-
- propsEnumerator.Dispose();
- groupsEnumerator.Dispose();
- }
-
- private static void AddPropertyType(PropertyTypeCollection propertyTypes, dynamic prop, PropertyGroup propertyGroup = null)
- {
- var propertyType = new PropertyType(prop.editorAlias, Enum.Parse(prop.dbType), prop.alias)
- {
- Description = prop.desc,
- DataTypeId = prop.dataTypeId,
- Id = prop.id,
- Key = prop.key,
- Mandatory = Convert.ToBoolean(prop.mandatory),
- Name = prop.name,
- PropertyGroupId = propertyGroup == null ? null : new Lazy