Netcore: Migration of Model classes from Umbraco.Infrastructure to Core (#9404)
* Migrating more model, mapping and tree classes * Migrating files from Mapping dir without Newtonsoft dependency * Migrating files from PublishedContent and Editors dirs without Newtonsoft dependency + some more of the same kind * Migrating DataType class without the usage of Newtonsoft.Json and making the corresponding changes to all classes affected * Combining 3 ContentExtensions files into 1 * Refactoring from migrating ContentExtensions * Migrating more classes * Migrating ContentRepositoryExtensions - combining it with existing file in Umbraco.Core * removing Newtonsoft json dependency & migrating file. Adding partial migration of ConfigurationEditor, so PropertyTagsExtensions can be migrated * Migrating ContentTagsExtensions, and refactoring from changes in PropertyTagsExtensions * Changes that should be reverted once ConfigurationEditor class is fully migrated * VS couldn't find Composing, so build was failing. Removing the using solves the problem * Handling a single case for deserializing a subset of an input * Small changes and added tests to JsonNetSerializer Signed-off-by: Bjarke Berg <mail@bergmania.dk> * Migrated ConfigurationEditor Signed-off-by: Bjarke Berg <mail@bergmania.dk> Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
committed by
GitHub
parent
d498c1a2cd
commit
dd5f400cf3
@@ -1,47 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing a tour.
|
||||
/// </summary>
|
||||
[DataContract(Name = "tour", Namespace = "")]
|
||||
public class BackOfficeTour
|
||||
{
|
||||
public BackOfficeTour()
|
||||
{
|
||||
RequiredSections = new List<string>();
|
||||
}
|
||||
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[DataMember(Name = "group")]
|
||||
public string Group { get; set; }
|
||||
|
||||
[DataMember(Name = "groupOrder")]
|
||||
public int GroupOrder { get; set; }
|
||||
|
||||
[DataMember(Name = "hidden")]
|
||||
public bool Hidden { get; set; }
|
||||
|
||||
[DataMember(Name = "allowDisable")]
|
||||
public bool AllowDisable { get; set; }
|
||||
|
||||
[DataMember(Name = "requiredSections")]
|
||||
public List<string> RequiredSections { get; set; }
|
||||
|
||||
[DataMember(Name = "steps")]
|
||||
public BackOfficeTourStep[] Steps { get; set; }
|
||||
|
||||
[DataMember(Name = "culture")]
|
||||
public string Culture { get; set; }
|
||||
|
||||
[DataMember(Name = "contentType")]
|
||||
public string ContentType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing the file used to load a tour.
|
||||
/// </summary>
|
||||
[DataContract(Name = "tourFile", Namespace = "")]
|
||||
public class BackOfficeTourFile
|
||||
{
|
||||
public BackOfficeTourFile()
|
||||
{
|
||||
Tours = new List<BackOfficeTour>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The file name for the tour
|
||||
/// </summary>
|
||||
[DataMember(Name = "fileName")]
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The plugin folder that the tour comes from
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is null it means it's a Core tour
|
||||
/// </remarks>
|
||||
[DataMember(Name = "pluginName")]
|
||||
public string PluginName { get; set; }
|
||||
|
||||
[DataMember(Name = "tours")]
|
||||
public IEnumerable<BackOfficeTour> Tours { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing a step in a tour.
|
||||
/// </summary>
|
||||
[DataContract(Name = "step", Namespace = "")]
|
||||
public class BackOfficeTourStep
|
||||
{
|
||||
[DataMember(Name = "title")]
|
||||
public string Title { get; set; }
|
||||
[DataMember(Name = "content")]
|
||||
public string Content { get; set; }
|
||||
[DataMember(Name = "type")]
|
||||
public string Type { get; set; }
|
||||
[DataMember(Name = "element")]
|
||||
public string Element { get; set; }
|
||||
[DataMember(Name = "elementPreventClick")]
|
||||
public bool ElementPreventClick { get; set; }
|
||||
[DataMember(Name = "backdropOpacity")]
|
||||
public float? BackdropOpacity { get; set; }
|
||||
[DataMember(Name = "event")]
|
||||
public string Event { get; set; }
|
||||
[DataMember(Name = "view")]
|
||||
public string View { get; set; }
|
||||
[DataMember(Name = "eventElement")]
|
||||
public string EventElement { get; set; }
|
||||
[DataMember(Name = "customProperties")]
|
||||
public JObject CustomProperties { get; set; }
|
||||
[DataMember(Name = "skipStepIfVisible")]
|
||||
public string SkipStepIfVisible { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// The strongly typed model for the Block List editor.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Collections.ObjectModel.ReadOnlyCollection{Umbraco.Core.Models.Blocks.BlockListItem}" />
|
||||
[DataContract(Name = "blockList", Namespace = "")]
|
||||
public class BlockListModel : ReadOnlyCollection<BlockListItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the empty <see cref="BlockListModel" />.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The empty <see cref="BlockListModel" />.
|
||||
/// </value>
|
||||
public static BlockListModel Empty { get; } = new BlockListModel();
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="BlockListModel" /> class from being created.
|
||||
/// </summary>
|
||||
private BlockListModel()
|
||||
: this(new List<BlockListItem>())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BlockListModel" /> class.
|
||||
/// </summary>
|
||||
/// <param name="list">The list to wrap.</param>
|
||||
public BlockListModel(IList<BlockListItem> list)
|
||||
: base(list)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BlockListItem" /> with the specified content key.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="BlockListItem" />.
|
||||
/// </value>
|
||||
/// <param name="contentKey">The content key.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="BlockListItem" /> with the specified content key.
|
||||
/// </returns>
|
||||
public BlockListItem this[Guid contentKey] => this.FirstOrDefault(x => x.Content.Key == contentKey);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="BlockListItem" /> with the specified content UDI.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="BlockListItem" />.
|
||||
/// </value>
|
||||
/// <param name="contentUdi">The content UDI.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="BlockListItem" /> with the specified content UDI.
|
||||
/// </returns>
|
||||
public BlockListItem this[Udi contentUdi] => contentUdi is GuidUdi guidUdi ? this.FirstOrDefault(x => x.Content.Key == guidUdi.Guid) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.Blocks
|
||||
{
|
||||
public struct ContentAndSettingsReference : IEquatable<ContentAndSettingsReference>
|
||||
{
|
||||
public ContentAndSettingsReference(Udi contentUdi, Udi settingsUdi)
|
||||
{
|
||||
ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi));
|
||||
SettingsUdi = settingsUdi;
|
||||
}
|
||||
|
||||
public Udi ContentUdi { get; }
|
||||
|
||||
public Udi SettingsUdi { get; }
|
||||
|
||||
public override bool Equals(object obj) => obj is ContentAndSettingsReference reference && Equals(reference);
|
||||
|
||||
public bool Equals(ContentAndSettingsReference other) => other != null
|
||||
&& EqualityComparer<Udi>.Default.Equals(ContentUdi, other.ContentUdi)
|
||||
&& EqualityComparer<Udi>.Default.Equals(SettingsUdi, other.SettingsUdi);
|
||||
|
||||
public override int GetHashCode() => (ContentUdi, SettingsUdi).GetHashCode();
|
||||
|
||||
public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ContentAndSettingsReference left, ContentAndSettingsReference right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Strings
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods to IContentBase to get url segments.
|
||||
/// </summary>
|
||||
public static class ContentBaseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the url segment for a specified content and culture.
|
||||
/// </summary>
|
||||
/// <param name="content">The content.</param>
|
||||
/// <param name="culture">The culture.</param>
|
||||
/// <param name="urlSegmentProviders"></param>
|
||||
/// <returns>The url segment.</returns>
|
||||
public static string GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable<IUrlSegmentProvider> urlSegmentProviders, string culture = null)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders));
|
||||
|
||||
var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, culture)).FirstOrDefault(u => u != null);
|
||||
url = url ?? new DefaultUrlSegmentProvider(shortStringHelper).GetUrlSegment(content, culture); // be safe
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods used to manipulate content variations by the document repository
|
||||
/// </summary>
|
||||
public static class ContentRepositoryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the cultures that have been flagged for unpublishing.
|
||||
/// </summary>
|
||||
/// <remarks>Gets cultures for which content.UnpublishCulture() has been invoked.</remarks>
|
||||
public static IReadOnlyList<string> GetCulturesUnpublishing(this IContent content)
|
||||
{
|
||||
if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos"))
|
||||
return Array.Empty<string>();
|
||||
|
||||
var culturesUnpublishing = content.CultureInfos.Values
|
||||
.Where(x => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + x.Culture))
|
||||
.Select(x => x.Culture);
|
||||
|
||||
return culturesUnpublishing.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies values from another document.
|
||||
/// </summary>
|
||||
public static void CopyFrom(this IContent content, IContent other, string culture = "*")
|
||||
{
|
||||
if (other.ContentTypeId != content.ContentTypeId)
|
||||
throw new InvalidOperationException("Cannot copy values from a different content type.");
|
||||
|
||||
culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull();
|
||||
|
||||
// the variation should be supported by the content type properties
|
||||
// if the content type is invariant, only '*' and 'null' is ok
|
||||
// if the content type varies, everything is ok because some properties may be invariant
|
||||
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
|
||||
|
||||
// copying from the same Id and VersionPk
|
||||
var copyingFromSelf = content.Id == other.Id && content.VersionId == other.VersionId;
|
||||
var published = copyingFromSelf;
|
||||
|
||||
// note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails
|
||||
|
||||
// clear all existing properties for the specified culture
|
||||
foreach (var property in content.Properties)
|
||||
{
|
||||
// each property type may or may not support the variation
|
||||
if (!property.PropertyType.SupportsVariation(culture, "*", wildcards: true))
|
||||
continue;
|
||||
|
||||
foreach (var pvalue in property.Values)
|
||||
if (property.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) &&
|
||||
(culture == "*" || pvalue.Culture.InvariantEquals(culture)))
|
||||
{
|
||||
property.SetValue(null, pvalue.Culture, pvalue.Segment);
|
||||
}
|
||||
}
|
||||
|
||||
// copy properties from 'other'
|
||||
var otherProperties = other.Properties;
|
||||
foreach (var otherProperty in otherProperties)
|
||||
{
|
||||
if (!otherProperty.PropertyType.SupportsVariation(culture, "*", wildcards: true))
|
||||
continue;
|
||||
|
||||
var alias = otherProperty.PropertyType.Alias;
|
||||
foreach (var pvalue in otherProperty.Values)
|
||||
{
|
||||
if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) &&
|
||||
(culture == "*" || pvalue.Culture.InvariantEquals(culture)))
|
||||
{
|
||||
var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
|
||||
content.SetValue(alias, value, pvalue.Culture, pvalue.Segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy names, too
|
||||
|
||||
if (culture == "*")
|
||||
{
|
||||
content.CultureInfos.Clear();
|
||||
content.CultureInfos = null;
|
||||
}
|
||||
|
||||
if (culture == null || culture == "*")
|
||||
content.Name = other.Name;
|
||||
|
||||
// ReSharper disable once UseDeconstruction
|
||||
foreach (var cultureInfo in other.CultureInfos)
|
||||
{
|
||||
if (culture == "*" || culture == cultureInfo.Culture)
|
||||
content.SetCultureName(cultureInfo.Name, cultureInfo.Culture);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPublishInfo(this IContent content, string culture, string name, DateTime date)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
|
||||
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture));
|
||||
|
||||
content.PublishCultureInfos.AddOrUpdate(culture, name, date);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// sets the edited cultures on the content
|
||||
public static void SetCultureEdited(this IContent content, IEnumerable<string> cultures)
|
||||
{
|
||||
if (cultures == null)
|
||||
content.EditedCultures = null;
|
||||
else
|
||||
{
|
||||
var editedCultures = new HashSet<string>(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase);
|
||||
content.EditedCultures = editedCultures.Count > 0 ? editedCultures : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the publishing values for names and properties.
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="impact"></param>
|
||||
/// <returns>A value indicating whether it was possible to publish the names and values for the specified
|
||||
/// culture(s). The method may fail if required names are not set, but it does NOT validate property data</returns>
|
||||
public static bool PublishCulture(this IContent content, CultureImpact impact)
|
||||
{
|
||||
if (impact == null) throw new ArgumentNullException(nameof(impact));
|
||||
|
||||
// the variation should be supported by the content type properties
|
||||
// if the content type is invariant, only '*' and 'null' is ok
|
||||
// if the content type varies, everything is ok because some properties may be invariant
|
||||
if (!content.ContentType.SupportsPropertyVariation(impact.Culture, "*", true))
|
||||
throw new NotSupportedException($"Culture \"{impact.Culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
|
||||
|
||||
// set names
|
||||
if (impact.ImpactsAllCultures)
|
||||
{
|
||||
foreach (var c in content.AvailableCultures) // does NOT contain the invariant culture
|
||||
{
|
||||
var name = content.GetCultureName(c);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return false;
|
||||
content.SetPublishInfo(c, name, DateTime.Now);
|
||||
}
|
||||
}
|
||||
else if (impact.ImpactsOnlyInvariantCulture)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content.Name))
|
||||
return false;
|
||||
// PublishName set by repository - nothing to do here
|
||||
}
|
||||
else if (impact.ImpactsExplicitCulture)
|
||||
{
|
||||
var name = content.GetCultureName(impact.Culture);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return false;
|
||||
content.SetPublishInfo(impact.Culture, name, DateTime.Now);
|
||||
}
|
||||
|
||||
// set values
|
||||
// property.PublishValues only publishes what is valid, variation-wise,
|
||||
// but accepts any culture arg: null, all, specific
|
||||
foreach (var property in content.Properties)
|
||||
{
|
||||
// for the specified culture (null or all or specific)
|
||||
property.PublishValues(impact.Culture);
|
||||
|
||||
// maybe the specified culture did not impact the invariant culture, so PublishValues
|
||||
// above would skip it, yet it *also* impacts invariant properties
|
||||
if (impact.ImpactsAlsoInvariantProperties)
|
||||
property.PublishValues(null);
|
||||
}
|
||||
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool UnpublishCulture(this IContent content, string culture = "*")
|
||||
{
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
// the variation should be supported by the content type properties
|
||||
if (!content.ContentType.SupportsPropertyVariation(culture, "*", true))
|
||||
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
|
||||
|
||||
|
||||
var keepProcessing = true;
|
||||
|
||||
if (culture == "*")
|
||||
{
|
||||
// all cultures
|
||||
content.ClearPublishInfos();
|
||||
}
|
||||
else
|
||||
{
|
||||
// one single culture
|
||||
keepProcessing = content.ClearPublishInfo(culture);
|
||||
}
|
||||
|
||||
|
||||
if (keepProcessing)
|
||||
{
|
||||
// property.PublishValues only publishes what is valid, variation-wise
|
||||
foreach (var property in content.Properties)
|
||||
property.UnpublishValues(culture);
|
||||
|
||||
content.PublishedState = PublishedState.Publishing;
|
||||
}
|
||||
|
||||
return keepProcessing;
|
||||
}
|
||||
|
||||
public static void ClearPublishInfos(this IContent content)
|
||||
{
|
||||
content.PublishCultureInfos = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns false if the culture is already unpublished
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
public static bool ClearPublishInfo(this IContent content, string culture)
|
||||
{
|
||||
if (culture == null) throw new ArgumentNullException(nameof(culture));
|
||||
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture));
|
||||
|
||||
var removed = content.PublishCultureInfos.Remove(culture);
|
||||
if (removed)
|
||||
{
|
||||
// set the culture to be dirty - it's been modified
|
||||
content.TouchCulture(culture);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="IContentBase"/> class, to manage tags.
|
||||
/// </summary>
|
||||
public static class ContentTagsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Assign tags.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <param name="merge">A value indicating whether to merge the tags with existing tags instead of replacing them.</param>
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void AssignTags(this IContentBase content, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, string propertyTypeAlias, IEnumerable<string> tags, bool merge = false, string culture = null)
|
||||
{
|
||||
content.GetTagProperty(propertyTypeAlias).AssignTags(propertyEditors, dataTypeService, tags, merge, culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove tags.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void RemoveTags(this IContentBase content, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, string propertyTypeAlias, IEnumerable<string> tags, string culture = null)
|
||||
{
|
||||
content.GetTagProperty(propertyTypeAlias).RemoveTags(propertyEditors, dataTypeService, tags, culture);
|
||||
}
|
||||
|
||||
// gets and validates the property
|
||||
private static IProperty GetTagProperty(this IContentBase content, string propertyTypeAlias)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException(nameof(content));
|
||||
|
||||
var property = content.Properties[propertyTypeAlias];
|
||||
if (property != null) return property;
|
||||
|
||||
throw new IndexOutOfRangeException($"Could not find a property with alias \"{propertyTypeAlias}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
[DataContract(Name = "contentTypeImportModel")]
|
||||
public class ContentTypeImportModel : INotificationModel
|
||||
{
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "notifications")]
|
||||
public List<BackOfficeNotification> Notifications { get; } = new List<BackOfficeNotification>();
|
||||
|
||||
[DataMember(Name = "tempFileName")]
|
||||
public string TempFileName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
// TODO: This should exist within Umbraco.Core not infrastructure however there's a dependency on Newtonsoft here, how can we refactor that requirement?
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="IDataType"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class DataType : TreeEntityBase, IDataType
|
||||
{
|
||||
private IDataEditor _editor;
|
||||
private ValueStorageType _databaseType;
|
||||
private object _configuration;
|
||||
private bool _hasConfiguration;
|
||||
private string _configurationJson;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataType"/> class.
|
||||
/// </summary>
|
||||
public DataType(IDataEditor editor, int parentId = -1)
|
||||
{
|
||||
_editor = editor ?? throw new ArgumentNullException(nameof(editor));
|
||||
ParentId = parentId;
|
||||
|
||||
// set a default configuration
|
||||
Configuration = _editor.GetConfigurationEditor().DefaultConfigurationObject;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public IDataEditor Editor
|
||||
{
|
||||
get => _editor;
|
||||
set
|
||||
{
|
||||
// ignore if no change
|
||||
if (_editor.Alias == value.Alias) return;
|
||||
OnPropertyChanged(nameof(Editor));
|
||||
|
||||
// try to map the existing configuration to the new configuration
|
||||
// simulate saving to db and reloading (ie go via json)
|
||||
var configuration = Configuration;
|
||||
var json = JsonConvert.SerializeObject(configuration);
|
||||
_editor = value;
|
||||
|
||||
try
|
||||
{
|
||||
Configuration = _editor.GetConfigurationEditor().FromDatabase(json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)."
|
||||
+ " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public string EditorAlias => _editor.Alias;
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public ValueStorageType DatabaseType
|
||||
{
|
||||
get => _databaseType;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _databaseType, nameof(DatabaseType));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public object Configuration
|
||||
{
|
||||
get
|
||||
{
|
||||
// if we know we have a configuration (which may be null), return it
|
||||
// if we don't have an editor, then we have no configuration, return null
|
||||
// else, use the editor to get the configuration object
|
||||
|
||||
if (_hasConfiguration) return _configuration;
|
||||
|
||||
try
|
||||
{
|
||||
_configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)."
|
||||
+ " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e);
|
||||
}
|
||||
|
||||
_hasConfiguration = true;
|
||||
_configurationJson = null;
|
||||
|
||||
return _configuration;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
// we don't support re-assigning the same object
|
||||
// configurations are kinda non-mutable, mainly because detecting changes would be a pain
|
||||
if (_configuration == value) // reference comparison
|
||||
throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", nameof(value));
|
||||
|
||||
// validate configuration type
|
||||
if (!_editor.GetConfigurationEditor().IsConfiguration(value))
|
||||
throw new ArgumentException($"Value of type {value.GetType().Name} cannot be a configuration for editor {_editor.Alias}, expecting.", nameof(value));
|
||||
|
||||
// extract database type from configuration object, if appropriate
|
||||
if (value is IConfigureValueType valueTypeConfiguration)
|
||||
DatabaseType = ValueTypes.ToStorageType(valueTypeConfiguration.ValueType);
|
||||
|
||||
// extract database type from dictionary, if appropriate
|
||||
if (value is IDictionary<string, object> dictionaryConfiguration
|
||||
&& dictionaryConfiguration.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObject)
|
||||
&& valueTypeObject is string valueTypeString
|
||||
&& ValueTypes.IsValue(valueTypeString))
|
||||
DatabaseType = ValueTypes.ToStorageType(valueTypeString);
|
||||
|
||||
_configuration = value;
|
||||
_hasConfiguration = true;
|
||||
_configurationJson = null;
|
||||
|
||||
// it's always a change
|
||||
OnPropertyChanged(nameof(Configuration));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lazily set the configuration as a serialized json string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Will be de-serialized on-demand.</para>
|
||||
/// <para>This method is meant to be used when building entities from database, exclusively.
|
||||
/// It does NOT register a property change to dirty. It ignores the fact that the configuration
|
||||
/// may contain the database type, because the datatype DTO should also contain that database
|
||||
/// type, and they should be the same.</para>
|
||||
/// <para>Think before using!</para>
|
||||
/// </remarks>
|
||||
internal void SetLazyConfiguration(string configurationJson)
|
||||
{
|
||||
_hasConfiguration = false;
|
||||
_configuration = null;
|
||||
_configurationJson = configurationJson;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a lazy configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The configuration object will be lazily de-serialized.</para>
|
||||
/// <para>This method is meant to be used when creating published datatypes, exclusively.</para>
|
||||
/// <para>Think before using!</para>
|
||||
/// </remarks>
|
||||
internal Lazy<object> GetLazyConfiguration()
|
||||
{
|
||||
// note: in both cases, make sure we capture what we need - we don't want
|
||||
// to capture a reference to this full, potentially heavy, DataType instance.
|
||||
|
||||
if (_hasConfiguration)
|
||||
{
|
||||
// if configuration has already been de-serialized, return
|
||||
var capturedConfiguration = _configuration;
|
||||
return new Lazy<object>(() => capturedConfiguration);
|
||||
}
|
||||
else
|
||||
{
|
||||
// else, create a Lazy de-serializer
|
||||
var capturedConfiguration = _configurationJson;
|
||||
var capturedEditor = _editor;
|
||||
return new Lazy<object>(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)."
|
||||
+ " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to track reference to other entities in a property value
|
||||
/// </summary>
|
||||
public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
|
||||
{
|
||||
private static readonly UmbracoEntityReference _empty = new UmbracoEntityReference(UnknownTypeUdi.Instance, string.Empty);
|
||||
|
||||
public UmbracoEntityReference(Udi udi, string relationTypeAlias)
|
||||
{
|
||||
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
|
||||
RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
|
||||
}
|
||||
|
||||
public UmbracoEntityReference(Udi udi)
|
||||
{
|
||||
Udi = udi ?? throw new ArgumentNullException(nameof(udi));
|
||||
|
||||
switch (udi.EntityType)
|
||||
{
|
||||
case Constants.UdiEntityType.Media:
|
||||
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
|
||||
break;
|
||||
default:
|
||||
RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static UmbracoEntityReference Empty() => _empty;
|
||||
|
||||
public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
|
||||
|
||||
public Udi Udi { get; }
|
||||
public string RelationTypeAlias { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is UmbracoEntityReference reference && Equals(reference);
|
||||
}
|
||||
|
||||
public bool Equals(UmbracoEntityReference other)
|
||||
{
|
||||
return EqualityComparer<Udi>.Default.Equals(Udi, other.Udi) &&
|
||||
RelationTypeAlias == other.RelationTypeAlias;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -487348478;
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<Udi>.Default.GetHashCode(Udi);
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(RelationTypeAlias);
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.Editors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model that represents uploading a local package
|
||||
/// </summary>
|
||||
[DataContract(Name = "localPackageInstallModel")]
|
||||
public class LocalPackageInstallModel : PackageInstallModel, INotificationModel
|
||||
{
|
||||
[DataMember(Name = "notifications")]
|
||||
public List<BackOfficeNotification> Notifications { get; } = new List<BackOfficeNotification>();
|
||||
|
||||
/// <summary>
|
||||
/// A flag to determine if this package is compatible to be installed
|
||||
/// </summary>
|
||||
[DataMember(Name = "isCompatible")]
|
||||
public bool IsCompatible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum umbraco version that this package is pinned to
|
||||
/// </summary>
|
||||
[DataMember(Name = "umbracoVersion")]
|
||||
public string UmbracoVersion { get; set; }
|
||||
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
[DataMember(Name = "version")]
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this is not null then it means the package is being from this version
|
||||
/// </summary>
|
||||
[DataMember(Name = "originalVersion")]
|
||||
public string OriginalVersion { get; set; }
|
||||
|
||||
[DataMember(Name = "containsUnsecureFiles")]
|
||||
public bool ContainsUnsecureFiles { get; set; }
|
||||
|
||||
[DataMember(Name = "containsTemplateConflicts")]
|
||||
public bool ContainsTemplateConflicts => ConflictingTemplateAliases != null && ConflictingTemplateAliases.Count > 0;
|
||||
|
||||
[DataMember(Name = "containsStyleSheetConflicts")]
|
||||
public bool ContainsStyleSheetConflicts => ConflictingStyleSheetNames != null && ConflictingStyleSheetNames.Count > 0;
|
||||
|
||||
[DataMember(Name = "containsMacroConflict")]
|
||||
public bool ContainsMacroConflict => ConflictingMacroAliases != null && ConflictingMacroAliases.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Key value of name + alias
|
||||
/// </summary>
|
||||
[DataMember(Name = "conflictingTemplateAliases")]
|
||||
public IDictionary<string, string> ConflictingTemplateAliases { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key value of name + alias
|
||||
/// </summary>
|
||||
[DataMember(Name = "conflictingStyleSheetNames")]
|
||||
public IDictionary<string, string> ConflictingStyleSheetNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Key value of name + alias
|
||||
/// </summary>
|
||||
[DataMember(Name = "conflictingMacroAliases")]
|
||||
public IDictionary<string, string> ConflictingMacroAliases { get; set; }
|
||||
|
||||
[DataMember(Name = "readme")]
|
||||
public string Readme { get; set; }
|
||||
|
||||
[DataMember(Name = "licenseUrl")]
|
||||
public string LicenseUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "license")]
|
||||
public string License { get; set; }
|
||||
|
||||
[DataMember(Name = "authorUrl")]
|
||||
public string AuthorUrl { get; set; }
|
||||
|
||||
[DataMember(Name = "author")]
|
||||
public string Author { get; set; }
|
||||
|
||||
[DataMember(Name = "contributors")]
|
||||
public IList<string> Contributors { get; set; }
|
||||
|
||||
[DataMember(Name = "iconUrl")]
|
||||
public string IconUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class AuditMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IAuditItem, AuditLog>((source, context) => new AuditLog(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -UserAvatars -UserName
|
||||
private void Map(IAuditItem source, AuditLog target, MapperContext context)
|
||||
{
|
||||
target.UserId = source.UserId;
|
||||
target.NodeId = source.Id;
|
||||
target.Timestamp = source.CreateDate;
|
||||
target.LogType = source.AuditType.ToString();
|
||||
target.EntityType = source.EntityType;
|
||||
target.Comment = source.Comment;
|
||||
target.Parameters = source.Parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Stylesheet = Umbraco.Core.Models.Stylesheet;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class CodeFileMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IStylesheet, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<IPartialView, CodeFileDisplay>((source, context) => new CodeFileDisplay(), Map);
|
||||
mapper.Define<IScript, CodeFileDisplay>((source, context) => new CodeFileDisplay(), Map);
|
||||
mapper.Define<IStylesheet, CodeFileDisplay>((source, context) => new CodeFileDisplay(), Map);
|
||||
mapper.Define<CodeFileDisplay, IPartialView>(Map);
|
||||
mapper.Define<CodeFileDisplay, IScript>(Map);
|
||||
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed -Udi -Icon
|
||||
private static void Map(IStylesheet source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = source.Path;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet
|
||||
private static void Map(IPartialView source, CodeFileDisplay target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.Id = source.Id.ToString();
|
||||
target.Name = source.Name;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet
|
||||
private static void Map(IScript source, CodeFileDisplay target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.Id = source.Id.ToString();
|
||||
target.Name = source.Name;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet
|
||||
private static void Map(IStylesheet source, CodeFileDisplay target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.Id = source.Id.ToString();
|
||||
target.Name = source.Name;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
|
||||
// Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path
|
||||
private static void Map(CodeFileDisplay source, IPartialView target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate -GetFileContent
|
||||
// Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path
|
||||
private static void Map(CodeFileDisplay source, IScript target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class CommonMapper
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
|
||||
private readonly ContentAppFactoryCollection _contentAppDefinitions;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
|
||||
public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_userService = userService;
|
||||
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
|
||||
_contentAppDefinitions = contentAppDefinitions;
|
||||
_localizedTextService = localizedTextService;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
}
|
||||
|
||||
public UserProfile GetOwner(IContentBase source, MapperContext context)
|
||||
{
|
||||
var profile = source.GetCreatorProfile(_userService);
|
||||
return profile == null ? null : context.Map<IProfile, UserProfile>(profile);
|
||||
}
|
||||
|
||||
public UserProfile GetCreator(IContent source, MapperContext context)
|
||||
{
|
||||
var profile = source.GetWriterProfile(_userService);
|
||||
return profile == null ? null : context.Map<IProfile, UserProfile>(profile);
|
||||
}
|
||||
|
||||
public ContentTypeBasic GetContentType(IContentBase source, MapperContext context)
|
||||
{
|
||||
var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source);
|
||||
var contentTypeBasic = context.Map<IContentTypeComposition, ContentTypeBasic>(contentType);
|
||||
return contentTypeBasic;
|
||||
}
|
||||
|
||||
public IEnumerable<ContentApp> GetContentApps(IUmbracoEntity source)
|
||||
{
|
||||
var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray();
|
||||
|
||||
// localize content app names
|
||||
foreach (var app in apps)
|
||||
{
|
||||
var localizedAppName = _localizedTextService.Localize($"apps/{app.Alias}");
|
||||
if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false)
|
||||
{
|
||||
app.Name = localizedAppName;
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ContentSavedState?"/> for an <see cref="IContent"/> item
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ContentBasicSavedStateMapper<T>
|
||||
where T : ContentPropertyBasic
|
||||
{
|
||||
private readonly ContentSavedStateMapper<T> _inner = new ContentSavedStateMapper<T>();
|
||||
|
||||
public ContentSavedState? Map(IContent source, MapperContext context)
|
||||
{
|
||||
return _inner.Map(source, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ContentSavedState"/> for an <see cref="IContent"/> item
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ContentSavedStateMapper<T>
|
||||
where T : ContentPropertyBasic
|
||||
{
|
||||
public ContentSavedState Map(IContent source, MapperContext context)
|
||||
{
|
||||
PublishedState publishedState;
|
||||
bool isEdited;
|
||||
bool isCreated;
|
||||
|
||||
if (source.ContentType.VariesByCulture())
|
||||
{
|
||||
//Get the culture from the context which will be set during the mapping operation for each variant
|
||||
var culture = context.GetCulture();
|
||||
|
||||
//a culture needs to be in the context for a variant content item
|
||||
if (culture == null)
|
||||
throw new InvalidOperationException($"No culture found in mapping operation when one is required for a culture variant");
|
||||
|
||||
publishedState = source.PublishedState == PublishedState.Unpublished //if the entire document is unpublished, then flag every variant as unpublished
|
||||
? PublishedState.Unpublished
|
||||
: source.IsCulturePublished(culture)
|
||||
? PublishedState.Published
|
||||
: PublishedState.Unpublished;
|
||||
|
||||
isEdited = source.IsCultureEdited(culture);
|
||||
isCreated = source.Id > 0 && source.IsCultureAvailable(culture);
|
||||
}
|
||||
else
|
||||
{
|
||||
publishedState = source.PublishedState == PublishedState.Unpublished
|
||||
? PublishedState.Unpublished
|
||||
: PublishedState.Published;
|
||||
|
||||
isEdited = source.Edited;
|
||||
isCreated = source.Id > 0;
|
||||
}
|
||||
|
||||
if (!isCreated)
|
||||
return ContentSavedState.NotCreated;
|
||||
|
||||
if (publishedState == PublishedState.Unpublished)
|
||||
return ContentSavedState.Draft;
|
||||
|
||||
if (publishedState == PublishedState.Published)
|
||||
return isEdited ? ContentSavedState.PublishedPendingChanges : ContentSavedState.Published;
|
||||
|
||||
throw new NotSupportedException($"PublishedState {publishedState} is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,722 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines mappings for content/media/members type mappings
|
||||
/// </summary>
|
||||
public class ContentTypeMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly CommonMapper _commonMapper;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger<ContentTypeMapDefinition> _logger;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService,
|
||||
IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService,
|
||||
ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_commonMapper = commonMapper;
|
||||
_propertyEditors = propertyEditors;
|
||||
_dataTypeService = dataTypeService;
|
||||
_fileService = fileService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_mediaTypeService = mediaTypeService;
|
||||
_memberTypeService = memberTypeService;
|
||||
_loggerFactory = loggerFactory;
|
||||
_logger = _loggerFactory.CreateLogger<ContentTypeMapDefinition>();
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_globalSettings = globalSettings.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<DocumentTypeSave, IContentType>((source, context) => new ContentType(_shortStringHelper, source.ParentId), Map);
|
||||
mapper.Define<MediaTypeSave, IMediaType>((source, context) => new MediaType(_shortStringHelper, source.ParentId), Map);
|
||||
mapper.Define<MemberTypeSave, IMemberType>((source, context) => new MemberType(_shortStringHelper, source.ParentId), Map);
|
||||
|
||||
mapper.Define<IContentType, DocumentTypeDisplay>((source, context) => new DocumentTypeDisplay(), Map);
|
||||
mapper.Define<IMediaType, MediaTypeDisplay>((source, context) => new MediaTypeDisplay(), Map);
|
||||
mapper.Define<IMemberType, MemberTypeDisplay>((source, context) => new MemberTypeDisplay(), Map);
|
||||
|
||||
mapper.Define<PropertyTypeBasic, IPropertyType>(
|
||||
(source, context) =>
|
||||
{
|
||||
var dataType = _dataTypeService.GetDataType(source.DataTypeId);
|
||||
if (dataType == null) throw new NullReferenceException("No data type found with id " + source.DataTypeId);
|
||||
return new PropertyType(_shortStringHelper, dataType, source.Alias);
|
||||
}, Map);
|
||||
|
||||
// TODO: isPublishing in ctor?
|
||||
mapper.Define<PropertyGroupBasic<PropertyTypeBasic>, PropertyGroup>((source, context) => new PropertyGroup(false), Map);
|
||||
mapper.Define<PropertyGroupBasic<MemberPropertyTypeBasic>, PropertyGroup>((source, context) => new PropertyGroup(false), Map);
|
||||
|
||||
mapper.Define<IContentTypeComposition, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
mapper.Define<IContentType, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
mapper.Define<IMediaType, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
mapper.Define<IMemberType, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
|
||||
mapper.Define<DocumentTypeSave, DocumentTypeDisplay>((source, context) => new DocumentTypeDisplay(), Map);
|
||||
mapper.Define<MediaTypeSave, MediaTypeDisplay>((source, context) => new MediaTypeDisplay(), Map);
|
||||
mapper.Define<MemberTypeSave, MemberTypeDisplay>((source, context) => new MemberTypeDisplay(), Map);
|
||||
|
||||
mapper.Define<PropertyGroupBasic<PropertyTypeBasic>, PropertyGroupDisplay<PropertyTypeDisplay>>((source, context) => new PropertyGroupDisplay<PropertyTypeDisplay>(), Map);
|
||||
mapper.Define<PropertyGroupBasic<MemberPropertyTypeBasic>, PropertyGroupDisplay<MemberPropertyTypeDisplay>>((source, context) => new PropertyGroupDisplay<MemberPropertyTypeDisplay>(), Map);
|
||||
|
||||
mapper.Define<PropertyTypeBasic, PropertyTypeDisplay>((source, context) => new PropertyTypeDisplay(), Map);
|
||||
mapper.Define<MemberPropertyTypeBasic, MemberPropertyTypeDisplay>((source, context) => new MemberPropertyTypeDisplay(), Map);
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(DocumentTypeSave source, IContentType target, MapperContext context)
|
||||
{
|
||||
MapSaveToTypeBase<DocumentTypeSave, PropertyTypeBasic>(source, target, context);
|
||||
MapComposition(source, target, alias => _contentTypeService.Get(alias));
|
||||
|
||||
target.AllowedTemplates = source.AllowedTemplates
|
||||
.Where(x => x != null)
|
||||
.Select(_fileService.GetTemplate)
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
target.SetDefaultTemplate(source.DefaultTemplate == null ? null : _fileService.GetTemplate(source.DefaultTemplate));
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MediaTypeSave source, IMediaType target, MapperContext context)
|
||||
{
|
||||
MapSaveToTypeBase<MediaTypeSave, PropertyTypeBasic>(source, target, context);
|
||||
MapComposition(source, target, alias => _mediaTypeService.Get(alias));
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MemberTypeSave source, IMemberType target, MapperContext context)
|
||||
{
|
||||
MapSaveToTypeBase<MemberTypeSave, MemberPropertyTypeBasic>(source, target, context);
|
||||
MapComposition(source, target, alias => _memberTypeService.Get(alias));
|
||||
|
||||
foreach (var propertyType in source.Groups.SelectMany(x => x.Properties))
|
||||
{
|
||||
var localCopy = propertyType;
|
||||
var destProp = target.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias));
|
||||
if (destProp == null) continue;
|
||||
target.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty);
|
||||
target.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty);
|
||||
target.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData);
|
||||
}
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(IContentType source, DocumentTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<DocumentTypeDisplay, PropertyTypeDisplay>(source, target);
|
||||
|
||||
target.AllowCultureVariant = source.VariesByCulture();
|
||||
target.AllowSegmentVariant = source.VariesBySegment();
|
||||
target.ContentApps = _commonMapper.GetContentApps(source);
|
||||
|
||||
//sync templates
|
||||
target.AllowedTemplates = context.MapEnumerable<ITemplate, EntityBasic>(source.AllowedTemplates);
|
||||
|
||||
if (source.DefaultTemplate != null)
|
||||
target.DefaultTemplate = context.Map<EntityBasic>(source.DefaultTemplate);
|
||||
|
||||
//default listview
|
||||
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content";
|
||||
|
||||
if (string.IsNullOrEmpty(source.Alias)) return;
|
||||
|
||||
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias;
|
||||
if (_dataTypeService.GetDataType(name) != null)
|
||||
target.ListViewEditorName = name;
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(IMediaType source, MediaTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MediaTypeDisplay, PropertyTypeDisplay>(source, target);
|
||||
|
||||
//default listview
|
||||
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media";
|
||||
target.IsSystemMediaType = source.IsSystemMediaType();
|
||||
|
||||
if (string.IsNullOrEmpty(source.Name)) return;
|
||||
|
||||
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name;
|
||||
if (_dataTypeService.GetDataType(name) != null)
|
||||
target.ListViewEditorName = name;
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(IMemberType source, MemberTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MemberTypeDisplay, MemberPropertyTypeDisplay>(source, target);
|
||||
|
||||
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
|
||||
foreach (var propertyType in source.PropertyTypes)
|
||||
{
|
||||
var localCopy = propertyType;
|
||||
var displayProp = target.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias));
|
||||
if (displayProp == null) continue;
|
||||
displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias);
|
||||
displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias);
|
||||
displayProp.IsSensitiveData = source.IsSensitiveProperty(localCopy.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Blueprints
|
||||
private void Map(IContentTypeBase source, ContentTypeBasic target, string entityType)
|
||||
{
|
||||
target.Udi = Udi.Create(entityType, source.Key);
|
||||
target.Alias = source.Alias;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.IconFilePath = target.IconIsClass
|
||||
? string.Empty
|
||||
: $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}";
|
||||
|
||||
target.Trashed = source.Trashed;
|
||||
target.Id = source.Id;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
target.ThumbnailFilePath = target.ThumbnailIsClass
|
||||
? string.Empty
|
||||
: _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.MemberType);
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private void Map(IContentType source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.DocumentType);
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private void Map(IMediaType source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.MediaType);
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private void Map(IMemberType source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.MemberType);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
|
||||
// Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations
|
||||
private static void Map(PropertyTypeBasic source, IPropertyType target, MapperContext context)
|
||||
{
|
||||
target.Name = source.Label;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Mandatory = source.Validation.Mandatory;
|
||||
target.MandatoryMessage = source.Validation.MandatoryMessage;
|
||||
target.ValidationRegExp = source.Validation.Pattern;
|
||||
target.ValidationRegExpMessage = source.Validation.PatternMessage;
|
||||
target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant);
|
||||
target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant);
|
||||
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
|
||||
if (source.GroupId > 0)
|
||||
target.PropertyGroupId = new Lazy<int>(() => source.GroupId, false);
|
||||
|
||||
target.Alias = source.Alias;
|
||||
target.Description = source.Description;
|
||||
target.SortOrder = source.SortOrder;
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<DocumentTypeSave, PropertyTypeBasic, DocumentTypeDisplay, PropertyTypeDisplay>(source, target, context);
|
||||
|
||||
//sync templates
|
||||
var destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias);
|
||||
//if the dest is set and it's the same as the source, then don't change
|
||||
if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false)
|
||||
{
|
||||
var templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray());
|
||||
target.AllowedTemplates = source.AllowedTemplates
|
||||
.Select(x =>
|
||||
{
|
||||
var template = templates.SingleOrDefault(t => t.Alias == x);
|
||||
return template != null
|
||||
? context.Map<EntityBasic>(template)
|
||||
: null;
|
||||
})
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
if (source.DefaultTemplate.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//if the dest is set and it's the same as the source, then don't change
|
||||
if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias)
|
||||
{
|
||||
var template = _fileService.GetTemplate(source.DefaultTemplate);
|
||||
target.DefaultTemplate = template == null ? null : context.Map<EntityBasic>(template);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target.DefaultTemplate = null;
|
||||
}
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MediaTypeSave, PropertyTypeBasic, MediaTypeDisplay, PropertyTypeDisplay>(source, target, context);
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MemberTypeSave, MemberPropertyTypeBasic, MemberTypeDisplay, MemberPropertyTypeDisplay>(source, target, context);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes
|
||||
private static void Map(PropertyGroupBasic<PropertyTypeBasic> source, PropertyGroup target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes
|
||||
private static void Map(PropertyGroupBasic<MemberPropertyTypeBasic> source, PropertyGroup target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames
|
||||
private static void Map(PropertyGroupBasic<PropertyTypeBasic> source, PropertyGroupDisplay<PropertyTypeDisplay> target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
|
||||
target.Inherited = source.Inherited;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
|
||||
target.Properties = context.MapEnumerable<PropertyTypeBasic, PropertyTypeDisplay>(source.Properties);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames
|
||||
private static void Map(PropertyGroupBasic<MemberPropertyTypeBasic> source, PropertyGroupDisplay<MemberPropertyTypeDisplay> target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
|
||||
target.Inherited = source.Inherited;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
|
||||
target.Properties = context.MapEnumerable<MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(source.Properties);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName
|
||||
private static void Map(PropertyTypeBasic source, PropertyTypeDisplay target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.AllowSegmentVariant = source.AllowSegmentVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
target.Inherited = source.Inherited;
|
||||
target.Label = source.Label;
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.Validation = source.Validation;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName
|
||||
private static void Map(MemberPropertyTypeBasic source, MemberPropertyTypeDisplay target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.AllowSegmentVariant = source.AllowSegmentVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.DataTypeKey = source.DataTypeKey;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
target.Inherited = source.Inherited;
|
||||
target.IsSensitiveData = source.IsSensitiveData;
|
||||
target.Label = source.Label;
|
||||
target.MemberCanEditProperty = source.MemberCanEditProperty;
|
||||
target.MemberCanViewProperty = source.MemberCanViewProperty;
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.Validation = source.Validation;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
|
||||
// Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType)
|
||||
private static void MapSaveToTypeBase<TSource, TSourcePropertyType>(TSource source, IContentTypeComposition target, MapperContext context)
|
||||
where TSource : ContentTypeSave<TSourcePropertyType>
|
||||
where TSourcePropertyType : PropertyTypeBasic
|
||||
{
|
||||
// TODO: not so clean really
|
||||
var isPublishing = target is IContentType;
|
||||
|
||||
var id = Convert.ToInt32(source.Id);
|
||||
if (id > 0)
|
||||
target.Id = id;
|
||||
|
||||
target.Alias = source.Alias;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
|
||||
target.AllowedAsRoot = source.AllowAsRoot;
|
||||
target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i));
|
||||
|
||||
if (!(target is IMemberType))
|
||||
{
|
||||
target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant);
|
||||
target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant);
|
||||
}
|
||||
|
||||
// handle property groups and property types
|
||||
// note that ContentTypeSave has
|
||||
// - all groups, inherited and local; only *one* occurrence per group *name*
|
||||
// - potentially including the generic properties group
|
||||
// - all properties, inherited and local
|
||||
//
|
||||
// also, see PropertyTypeGroupResolver.ResolveCore:
|
||||
// - if a group is local *and* inherited, then Inherited is true
|
||||
// and the identifier is the identifier of the *local* group
|
||||
//
|
||||
// IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some
|
||||
// unique-alias-checking, etc that is *not* compatible with re-mapping everything
|
||||
// the way we do it here, so we should exclusively do it by
|
||||
// - managing a property group's PropertyTypes collection
|
||||
// - managing the content type's PropertyTypes collection (for generic properties)
|
||||
|
||||
// handle actual groups (non-generic-properties)
|
||||
var destOrigGroups = target.PropertyGroups.ToArray(); // local groups
|
||||
var destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not
|
||||
var destGroups = new List<PropertyGroup>();
|
||||
var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray();
|
||||
foreach (var sourceGroup in sourceGroups)
|
||||
{
|
||||
// get the dest group
|
||||
var destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context);
|
||||
|
||||
// handle local properties
|
||||
var destProperties = sourceGroup.Properties
|
||||
.Where(x => x.Inherited == false)
|
||||
.Select(x => MapSaveProperty(x, destOrigProperties, context))
|
||||
.ToArray();
|
||||
|
||||
// if the group has no local properties, skip it, ie sort-of garbage-collect
|
||||
// local groups which would not have local properties anymore
|
||||
if (destProperties.Length == 0)
|
||||
continue;
|
||||
|
||||
// ensure no duplicate alias, then assign the group properties collection
|
||||
EnsureUniqueAliases(destProperties);
|
||||
destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
|
||||
destGroups.Add(destGroup);
|
||||
}
|
||||
|
||||
// ensure no duplicate name, then assign the groups collection
|
||||
EnsureUniqueNames(destGroups);
|
||||
target.PropertyGroups = new PropertyGroupCollection(destGroups);
|
||||
|
||||
// because the property groups collection was rebuilt, there is no need to remove
|
||||
// the old groups - they are just gone and will be cleared by the repository
|
||||
|
||||
// handle non-grouped (ie generic) properties
|
||||
var genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties);
|
||||
if (genericPropertiesGroup != null)
|
||||
{
|
||||
// handle local properties
|
||||
var destProperties = genericPropertiesGroup.Properties
|
||||
.Where(x => x.Inherited == false)
|
||||
.Select(x => MapSaveProperty(x, destOrigProperties, context))
|
||||
.ToArray();
|
||||
|
||||
// ensure no duplicate alias, then assign the generic properties collection
|
||||
EnsureUniqueAliases(destProperties);
|
||||
target.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
|
||||
}
|
||||
|
||||
// because all property collections were rebuilt, there is no need to remove
|
||||
// some old properties, they are just gone and will be cleared by the repository
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Blueprints -Errors -ListViewEditorName -Trashed
|
||||
private void MapTypeToDisplayBase(IContentTypeComposition source, ContentTypeCompositionDisplay target)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowAsRoot = source.AllowedAsRoot;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.IconFilePath = target.IconIsClass
|
||||
? string.Empty
|
||||
: $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}";
|
||||
target.Id = source.Id;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
target.ThumbnailFilePath = target.ThumbnailIsClass
|
||||
? string.Empty
|
||||
: _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail);
|
||||
target.Udi = MapContentTypeUdi(source);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
|
||||
target.AllowedContentTypes = source.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value);
|
||||
target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias);
|
||||
target.LockedCompositeContentTypes = MapLockedCompositions(source);
|
||||
}
|
||||
|
||||
// no MapAll - relies on the non-generic method
|
||||
private void MapTypeToDisplayBase<TTarget, TTargetPropertyType>(IContentTypeComposition source, TTarget target)
|
||||
where TTarget : ContentTypeCompositionDisplay<TTargetPropertyType>
|
||||
where TTargetPropertyType : PropertyTypeDisplay, new()
|
||||
{
|
||||
MapTypeToDisplayBase(source, target);
|
||||
|
||||
var groupsMapper = new PropertyTypeGroupMapper<TTargetPropertyType>(_propertyEditors, _dataTypeService, _shortStringHelper, _loggerFactory.CreateLogger<PropertyTypeGroupMapper<TTargetPropertyType>>());
|
||||
target.Groups = groupsMapper.Map(source);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -ListViewEditorName -Errors -LockedCompositeContentTypes
|
||||
private void MapTypeToDisplayBase(ContentTypeSave source, ContentTypeCompositionDisplay target)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowAsRoot = source.AllowAsRoot;
|
||||
target.AllowedContentTypes = source.AllowedContentTypes;
|
||||
target.Blueprints = source.Blueprints;
|
||||
target.CompositeContentTypes = source.CompositeContentTypes;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.IconFilePath = target.IconIsClass
|
||||
? string.Empty
|
||||
: $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}";
|
||||
target.Id = source.Id;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
target.ThumbnailFilePath = target.ThumbnailIsClass
|
||||
? string.Empty
|
||||
: _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail);
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = source.Udi;
|
||||
}
|
||||
|
||||
// no MapAll - relies on the non-generic method
|
||||
private void MapTypeToDisplayBase<TSource, TSourcePropertyType, TTarget, TTargetPropertyType>(TSource source, TTarget target, MapperContext context)
|
||||
where TSource : ContentTypeSave<TSourcePropertyType>
|
||||
where TSourcePropertyType : PropertyTypeBasic
|
||||
where TTarget : ContentTypeCompositionDisplay<TTargetPropertyType>
|
||||
where TTargetPropertyType : PropertyTypeDisplay
|
||||
{
|
||||
MapTypeToDisplayBase(source, target);
|
||||
|
||||
target.Groups = context.MapEnumerable<PropertyGroupBasic<TSourcePropertyType>, PropertyGroupDisplay<TTargetPropertyType>>(source.Groups);
|
||||
}
|
||||
|
||||
private IEnumerable<string> MapLockedCompositions(IContentTypeComposition source)
|
||||
{
|
||||
// get ancestor ids from path of parent if not root
|
||||
if (source.ParentId == Constants.System.Root)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
var parent = _contentTypeService.Get(source.ParentId);
|
||||
if (parent == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
var aliases = new List<string>();
|
||||
var ancestorIds = parent.Path.Split(',').Select(int.Parse);
|
||||
// loop through all content types and return ordered aliases of ancestors
|
||||
var allContentTypes = _contentTypeService.GetAll().ToArray();
|
||||
foreach (var ancestorId in ancestorIds)
|
||||
{
|
||||
var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId);
|
||||
if (ancestor != null)
|
||||
aliases.Add(ancestor.Alias);
|
||||
}
|
||||
return aliases.OrderBy(x => x);
|
||||
}
|
||||
|
||||
public static Udi MapContentTypeUdi(IContentTypeComposition source)
|
||||
{
|
||||
if (source == null) return null;
|
||||
|
||||
string udiType;
|
||||
switch (source)
|
||||
{
|
||||
case IMemberType _:
|
||||
udiType = Constants.UdiEntityType.MemberType;
|
||||
break;
|
||||
case IMediaType _:
|
||||
udiType = Constants.UdiEntityType.MediaType;
|
||||
break;
|
||||
case IContentType _:
|
||||
udiType = Constants.UdiEntityType.DocumentType;
|
||||
break;
|
||||
default:
|
||||
throw new PanicException($"Source is of type {source.GetType()} which isn't supported here");
|
||||
}
|
||||
|
||||
return Udi.Create(udiType, source.Key);
|
||||
}
|
||||
|
||||
private static PropertyGroup MapSaveGroup<TPropertyType>(PropertyGroupBasic<TPropertyType> sourceGroup, IEnumerable<PropertyGroup> destOrigGroups, MapperContext context)
|
||||
where TPropertyType : PropertyTypeBasic
|
||||
{
|
||||
PropertyGroup destGroup;
|
||||
if (sourceGroup.Id > 0)
|
||||
{
|
||||
// update an existing group
|
||||
// ensure it is still there, then map/update
|
||||
destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id);
|
||||
if (destGroup != null)
|
||||
{
|
||||
context.Map(sourceGroup, destGroup);
|
||||
return destGroup;
|
||||
}
|
||||
|
||||
// force-clear the ID as it does not match anything
|
||||
sourceGroup.Id = 0;
|
||||
}
|
||||
|
||||
// insert a new group, or update an existing group that has
|
||||
// been deleted in the meantime and we need to re-create
|
||||
// map/create
|
||||
destGroup = context.Map<PropertyGroup>(sourceGroup);
|
||||
return destGroup;
|
||||
}
|
||||
|
||||
private static IPropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable<IPropertyType> destOrigProperties, MapperContext context)
|
||||
{
|
||||
IPropertyType destProperty;
|
||||
if (sourceProperty.Id > 0)
|
||||
{
|
||||
// updating an existing property
|
||||
// ensure it is still there, then map/update
|
||||
destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id);
|
||||
if (destProperty != null)
|
||||
{
|
||||
context.Map(sourceProperty, destProperty);
|
||||
return destProperty;
|
||||
}
|
||||
|
||||
// force-clear the ID as it does not match anything
|
||||
sourceProperty.Id = 0;
|
||||
}
|
||||
|
||||
// insert a new property, or update an existing property that has
|
||||
// been deleted in the meantime and we need to re-create
|
||||
// map/create
|
||||
destProperty = context.Map<IPropertyType>(sourceProperty);
|
||||
return destProperty;
|
||||
}
|
||||
|
||||
private static void EnsureUniqueAliases(IEnumerable<IPropertyType> properties)
|
||||
{
|
||||
var propertiesA = properties.ToArray();
|
||||
var distinctProperties = propertiesA
|
||||
.Select(x => x.Alias.ToUpperInvariant())
|
||||
.Distinct()
|
||||
.Count();
|
||||
if (distinctProperties != propertiesA.Length)
|
||||
throw new InvalidOperationException("Cannot map properties due to alias conflict.");
|
||||
}
|
||||
|
||||
private static void EnsureUniqueNames(IEnumerable<PropertyGroup> groups)
|
||||
{
|
||||
var groupsA = groups.ToArray();
|
||||
var distinctProperties = groupsA
|
||||
.Select(x => x.Name.ToUpperInvariant())
|
||||
.Distinct()
|
||||
.Count();
|
||||
if (distinctProperties != groupsA.Length)
|
||||
throw new InvalidOperationException("Cannot map groups due to name conflict.");
|
||||
}
|
||||
|
||||
private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, Func<string, IContentTypeComposition> getContentType)
|
||||
{
|
||||
var current = target.CompositionAliases().ToArray();
|
||||
var proposed = source.CompositeContentTypes;
|
||||
|
||||
var remove = current.Where(x => !proposed.Contains(x));
|
||||
var add = proposed.Where(x => !current.Contains(x));
|
||||
|
||||
foreach (var alias in remove)
|
||||
target.RemoveContentType(alias);
|
||||
|
||||
foreach (var alias in add)
|
||||
{
|
||||
// TODO: Remove N+1 lookup
|
||||
var contentType = getContentType(alias);
|
||||
if (contentType != null)
|
||||
target.AddContentType(contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Language = Umbraco.Web.Models.ContentEditing.Language;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class ContentVariantMapper
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public ContentVariantMapper(ILocalizationService localizationService, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
}
|
||||
|
||||
public IEnumerable<ContentVariantDisplay> Map(IContent source, MapperContext context)
|
||||
{
|
||||
var variesByCulture = source.ContentType.VariesByCulture();
|
||||
var variesBySegment = source.ContentType.VariesBySegment();
|
||||
|
||||
IList<ContentVariantDisplay> variants = new List<ContentVariantDisplay>();
|
||||
|
||||
if (!variesByCulture && !variesBySegment)
|
||||
{
|
||||
// this is invariant so just map the IContent instance to ContentVariationDisplay
|
||||
var variantDisplay = context.Map<ContentVariantDisplay>(source);
|
||||
variants.Add(variantDisplay);
|
||||
}
|
||||
else if (variesByCulture && !variesBySegment)
|
||||
{
|
||||
var languages = GetLanguages(context);
|
||||
variants = languages
|
||||
.Select(language => CreateVariantDisplay(context, source, language, null))
|
||||
.ToList();
|
||||
}
|
||||
else if (variesBySegment && !variesByCulture)
|
||||
{
|
||||
// Segment only
|
||||
var segments = GetSegments(source);
|
||||
variants = segments
|
||||
.Select(segment => CreateVariantDisplay(context, source, null, segment))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Culture and segment
|
||||
var languages = GetLanguages(context).ToList();
|
||||
var segments = GetSegments(source).ToList();
|
||||
|
||||
if (languages.Count == 0 || segments.Count == 0)
|
||||
{
|
||||
// This should not happen
|
||||
throw new InvalidOperationException("No languages or segments available");
|
||||
}
|
||||
|
||||
variants = languages
|
||||
.SelectMany(language => segments
|
||||
.Select(segment => CreateVariantDisplay(context, source, language, segment)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return SortVariants(variants);
|
||||
}
|
||||
|
||||
private IList<ContentVariantDisplay> SortVariants(IList<ContentVariantDisplay> variants)
|
||||
{
|
||||
if (variants == null || variants.Count <= 1)
|
||||
{
|
||||
return variants;
|
||||
}
|
||||
|
||||
// Default variant first, then order by language, segment.
|
||||
return variants
|
||||
.OrderBy(v => IsDefaultLanguage(v) ? 0 : 1)
|
||||
.ThenBy(v => IsDefaultSegment(v) ? 0 : 1)
|
||||
.ThenBy(v => v?.Language?.Name)
|
||||
.ThenBy(v => v.Segment)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static bool IsDefaultSegment(ContentVariantDisplay variant)
|
||||
{
|
||||
return variant.Segment == null;
|
||||
}
|
||||
|
||||
private static bool IsDefaultLanguage(ContentVariantDisplay variant)
|
||||
{
|
||||
return variant.Language == null || variant.Language.IsDefault;
|
||||
}
|
||||
|
||||
private IEnumerable<Language> GetLanguages(MapperContext context)
|
||||
{
|
||||
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
|
||||
if (allLanguages.Count == 0)
|
||||
{
|
||||
// This should never happen
|
||||
return Enumerable.Empty<Language>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all segments assigned to the content
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns>
|
||||
/// Returns all segments assigned to the content including the default `null` segment.
|
||||
/// </returns>
|
||||
private IEnumerable<string> GetSegments(IContent content)
|
||||
{
|
||||
// The default segment (null) is always there,
|
||||
// even when there is no property data at all yet
|
||||
var segments = new List<string> { null };
|
||||
|
||||
// Add actual segments based on the property values
|
||||
segments.AddRange(content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)));
|
||||
|
||||
// Do not return a segment more than once
|
||||
return segments.Distinct();
|
||||
}
|
||||
|
||||
private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment)
|
||||
{
|
||||
context.SetCulture(language?.IsoCode);
|
||||
context.SetSegment(segment);
|
||||
|
||||
var variantDisplay = context.Map<ContentVariantDisplay>(content);
|
||||
|
||||
variantDisplay.Segment = segment;
|
||||
variantDisplay.Language = language;
|
||||
variantDisplay.Name = content.GetCultureName(language?.IsoCode);
|
||||
variantDisplay.DisplayName = GetDisplayName(language, segment);
|
||||
|
||||
return variantDisplay;
|
||||
}
|
||||
|
||||
private string GetDisplayName(Language language, string segment)
|
||||
{
|
||||
var isCultureVariant = language != null;
|
||||
var isSegmentVariant = !segment.IsNullOrWhiteSpace();
|
||||
|
||||
if(!isCultureVariant && !isSegmentVariant)
|
||||
{
|
||||
return _localizedTextService.Localize("general/default");
|
||||
}
|
||||
|
||||
var parts = new List<string>();
|
||||
|
||||
if (isSegmentVariant)
|
||||
parts.Add(segment);
|
||||
|
||||
if (isCultureVariant)
|
||||
parts.Add(language.Name);
|
||||
|
||||
return string.Join(" — ", parts);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class DataTypeMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly ILogger<DataTypeMapDefinition> _logger;
|
||||
private readonly ContentSettings _contentSettings;
|
||||
|
||||
public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger<DataTypeMapDefinition> logger, IOptions<ContentSettings> contentSettings)
|
||||
{
|
||||
_propertyEditors = propertyEditors;
|
||||
_logger = logger;
|
||||
_contentSettings = contentSettings.Value ?? throw new ArgumentNullException(nameof(contentSettings));
|
||||
}
|
||||
|
||||
private static readonly int[] SystemIds =
|
||||
{
|
||||
Constants.DataTypes.DefaultContentListView,
|
||||
Constants.DataTypes.DefaultMediaListView,
|
||||
Constants.DataTypes.DefaultMembersListView
|
||||
};
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IDataEditor, PropertyEditorBasic>((source, context) => new PropertyEditorBasic(), Map);
|
||||
mapper.Define<ConfigurationField, DataTypeConfigurationFieldDisplay>((source, context) => new DataTypeConfigurationFieldDisplay(), Map);
|
||||
mapper.Define<IDataEditor, DataTypeBasic>((source, context) => new DataTypeBasic(), Map);
|
||||
mapper.Define<IDataType, DataTypeBasic>((source, context) => new DataTypeBasic(), Map);
|
||||
mapper.Define<IDataType, DataTypeDisplay>((source, context) => new DataTypeDisplay(), Map);
|
||||
mapper.Define<IDataType, IEnumerable<DataTypeConfigurationFieldDisplay>>(MapPreValues);
|
||||
mapper.Define<DataTypeSave, IDataType>((source, context) => new DataType(_propertyEditors[source.EditorAlias]) { CreateDate = DateTime.Now },Map);
|
||||
mapper.Define<IDataEditor, IEnumerable<DataTypeConfigurationFieldDisplay>>(MapPreValues);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(IDataEditor source, PropertyEditorBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = source.Icon;
|
||||
target.Name = source.Name;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Value
|
||||
private static void Map(ConfigurationField source, DataTypeConfigurationFieldDisplay target, MapperContext context)
|
||||
{
|
||||
target.Config = source.Config;
|
||||
target.Description = source.Description;
|
||||
target.HideLabel = source.HideLabel;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.View = source.View;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -HasPrevalues -IsSystemDataType -Id -Trashed -Key
|
||||
// Umbraco.Code.MapAll -ParentId -Path
|
||||
private static void Map(IDataEditor source, DataTypeBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Group = source.Group;
|
||||
target.Icon = source.Icon;
|
||||
target.Name = source.Name;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -HasPrevalues
|
||||
private void Map(IDataType source, DataTypeBasic target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.IsSystemDataType = SystemIds.Contains(source.Id);
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key);
|
||||
|
||||
if (!_propertyEditors.TryGet(source.EditorAlias, out var editor))
|
||||
return;
|
||||
|
||||
target.Alias = editor.Alias;
|
||||
target.Group = editor.Group;
|
||||
target.Icon = editor.Icon;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -HasPrevalues
|
||||
private void Map(IDataType source, DataTypeDisplay target, MapperContext context)
|
||||
{
|
||||
target.AvailableEditors = MapAvailableEditors(source, context);
|
||||
target.Id = source.Id;
|
||||
target.IsSystemDataType = SystemIds.Contains(source.Id);
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.PreValues = MapPreValues(source, context);
|
||||
target.SelectedEditor = source.EditorAlias.IsNullOrWhiteSpace() ? null : source.EditorAlias;
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key);
|
||||
|
||||
if (!_propertyEditors.TryGet(source.EditorAlias, out var editor))
|
||||
return;
|
||||
|
||||
target.Alias = editor.Alias;
|
||||
target.Group = editor.Group;
|
||||
target.Icon = editor.Icon;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
|
||||
// Umbraco.Code.MapAll -Key -Path -CreatorId -Level -SortOrder -Configuration
|
||||
private void Map(DataTypeSave source, IDataType target, MapperContext context)
|
||||
{
|
||||
target.DatabaseType = MapDatabaseType(source);
|
||||
target.Editor = _propertyEditors[source.EditorAlias];
|
||||
target.Id = Convert.ToInt32(source.Id);
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
}
|
||||
|
||||
private IEnumerable<PropertyEditorBasic> MapAvailableEditors(IDataType source, MapperContext context)
|
||||
{
|
||||
var properties = _propertyEditors
|
||||
.Where(x => !x.IsDeprecated || _contentSettings.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias)
|
||||
.OrderBy(x => x.Name);
|
||||
return context.MapEnumerable<IDataEditor, PropertyEditorBasic>(properties);
|
||||
}
|
||||
|
||||
private IEnumerable<DataTypeConfigurationFieldDisplay> MapPreValues(IDataType dataType, MapperContext context)
|
||||
{
|
||||
// in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto
|
||||
// an empty fields list, which made no sense since there would be nothing to map to - and besides,
|
||||
// a datatype without an editor alias is a serious issue - v8 wants an editor here
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !_propertyEditors.TryGet(dataType.EditorAlias, out var editor))
|
||||
throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\".");
|
||||
|
||||
var configurationEditor = editor.GetConfigurationEditor();
|
||||
var fields = context.MapEnumerable<ConfigurationField,DataTypeConfigurationFieldDisplay>(configurationEditor.Fields);
|
||||
var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration);
|
||||
|
||||
MapConfigurationFields(dataType, fields, configurationDictionary);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private void MapConfigurationFields(IDataType dataType, List<DataTypeConfigurationFieldDisplay> fields, IDictionary<string, object> configuration)
|
||||
{
|
||||
if (fields == null) throw new ArgumentNullException(nameof(fields));
|
||||
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
// now we need to wire up the pre-values values with the actual fields defined
|
||||
foreach (var field in fields.ToList())
|
||||
{
|
||||
//filter out the not-supported pre-values for built-in data types
|
||||
if (dataType != null && dataType.IsBuildInDataType() && field.Key.InvariantEquals(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes))
|
||||
{
|
||||
fields.Remove(field);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (configuration.TryGetValue(field.Key, out var value))
|
||||
{
|
||||
field.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// weird - just leave the field without a value - but warn
|
||||
_logger.LogWarning("Could not find a value for configuration field '{ConfigField}'", field.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValueStorageType MapDatabaseType(DataTypeSave source)
|
||||
{
|
||||
if (!_propertyEditors.TryGet(source.EditorAlias, out var editor))
|
||||
throw new InvalidOperationException($"Could not find property editor \"{source.EditorAlias}\".");
|
||||
|
||||
// TODO: what about source.PropertyEditor? can we get the configuration here? 'cos it may change the storage type?!
|
||||
var valueType = editor.GetValueEditor().ValueType;
|
||||
return ValueTypes.ToStorageType(valueType);
|
||||
}
|
||||
|
||||
private IEnumerable<DataTypeConfigurationFieldDisplay> MapPreValues(IDataEditor source, MapperContext context)
|
||||
{
|
||||
// this is a new data type, initialize default configuration
|
||||
// get the configuration editor,
|
||||
// get the configuration fields and map to UI,
|
||||
// get the configuration default values and map to UI
|
||||
|
||||
var configurationEditor = source.GetConfigurationEditor();
|
||||
|
||||
var fields = context.MapEnumerable<ConfigurationField, DataTypeConfigurationFieldDisplay>(configurationEditor.Fields);
|
||||
|
||||
var defaultConfiguration = configurationEditor.DefaultConfiguration;
|
||||
if (defaultConfiguration != null)
|
||||
MapConfigurationFields(null, fields, defaultConfiguration);
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The dictionary model mapper.
|
||||
/// </summary>
|
||||
public class DictionaryMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
public DictionaryMapDefinition(ILocalizationService localizationService)
|
||||
{
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IDictionaryItem, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<IDictionaryItem, DictionaryDisplay>((source, context) => new DictionaryDisplay(), Map);
|
||||
mapper.Define<IDictionaryItem, DictionaryOverviewDisplay>((source, context) => new DictionaryOverviewDisplay(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ParentId -Path -Trashed -Udi -Icon
|
||||
private static void Map(IDictionaryItem source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.ItemKey;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.ItemKey;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Icon -Trashed -Alias
|
||||
private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.ItemKey;
|
||||
target.ParentId = source.ParentId ?? Guid.Empty;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.DictionaryItem, source.Key);
|
||||
|
||||
// build up the path to make it possible to set active item in tree
|
||||
// TODO: check if there is a better way
|
||||
if (source.ParentId.HasValue)
|
||||
{
|
||||
var ids = new List<int> { -1 };
|
||||
var parentIds = new List<int>();
|
||||
GetParentId(source.ParentId.Value, _localizationService, parentIds);
|
||||
parentIds.Reverse();
|
||||
ids.AddRange(parentIds);
|
||||
ids.Add(source.Id);
|
||||
target.Path = string.Join(",", ids);
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Path = "-1," + source.Id;
|
||||
}
|
||||
|
||||
// add all languages and the translations
|
||||
foreach (var lang in _localizationService.GetAllLanguages())
|
||||
{
|
||||
var langId = lang.Id;
|
||||
var translation = source.Translations.FirstOrDefault(x => x.LanguageId == langId);
|
||||
|
||||
target.Translations.Add(new DictionaryTranslationDisplay
|
||||
{
|
||||
IsoCode = lang.IsoCode,
|
||||
DisplayName = lang.CultureInfo.DisplayName,
|
||||
Translation = (translation != null) ? translation.Value : string.Empty,
|
||||
LanguageId = lang.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Level -Translations
|
||||
private void Map(IDictionaryItem source, DictionaryOverviewDisplay target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Name = source.ItemKey;
|
||||
|
||||
// add all languages and the translations
|
||||
foreach (var lang in _localizationService.GetAllLanguages())
|
||||
{
|
||||
var langId = lang.Id;
|
||||
var translation = source.Translations.FirstOrDefault(x => x.LanguageId == langId);
|
||||
|
||||
target.Translations.Add(
|
||||
new DictionaryOverviewTranslationDisplay
|
||||
{
|
||||
DisplayName = lang.CultureInfo.DisplayName,
|
||||
HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetParentId(Guid parentId, ILocalizationService localizationService, List<int> ids)
|
||||
{
|
||||
var dictionary = localizationService.GetDictionaryItemById(parentId);
|
||||
if (dictionary == null)
|
||||
return;
|
||||
|
||||
ids.Add(dictionary.Id);
|
||||
|
||||
if (dictionary.ParentId.HasValue)
|
||||
GetParentId(dictionary.ParentId.Value, localizationService, ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Language = Umbraco.Web.Models.ContentEditing.Language;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class LanguageMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<ILanguage, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<ILanguage, Language>((source, context) => new Language(), Map);
|
||||
mapper.Define<IEnumerable<ILanguage>, IEnumerable<Language>>((source, context) => new List<Language>(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -Path -Trashed -AdditionalData -Icon
|
||||
private static void Map(ILanguage source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Name = source.CultureName;
|
||||
target.Key = source.Key;
|
||||
target.ParentId = -1;
|
||||
target.Alias = source.IsoCode;
|
||||
target.Id = source.Id;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(ILanguage source, Language target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.IsoCode = source.IsoCode;
|
||||
target.Name = source.CultureInfo.DisplayName;
|
||||
target.IsDefault = source.IsDefault;
|
||||
target.IsMandatory = source.IsMandatory;
|
||||
target.FallbackLanguageId = source.FallbackLanguageId;
|
||||
}
|
||||
|
||||
private static void Map(IEnumerable<ILanguage> source, IEnumerable<Language> target, MapperContext context)
|
||||
{
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
if (!(target is List<Language> list))
|
||||
throw new NotSupportedException($"{nameof(target)} must be a List<Language>.");
|
||||
|
||||
var temp = context.MapEnumerable<ILanguage, Language>(source);
|
||||
|
||||
//Put the default language first in the list & then sort rest by a-z
|
||||
var defaultLang = temp.SingleOrDefault(x => x.IsDefault);
|
||||
|
||||
// insert default lang first, then remaining language a-z
|
||||
list.Add(defaultLang);
|
||||
list.AddRange(temp.Where(x => x != defaultLang).OrderBy(x => x.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class MacroMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly ParameterEditorCollection _parameterEditors;
|
||||
private readonly ILogger<MacroMapDefinition> _logger;
|
||||
|
||||
public MacroMapDefinition(ParameterEditorCollection parameterEditors, ILogger<MacroMapDefinition> logger)
|
||||
{
|
||||
_parameterEditors = parameterEditors;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IMacro, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<IMacro, MacroDisplay>((source, context) => new MacroDisplay(), Map);
|
||||
mapper.Define<IMacro, IEnumerable<MacroParameter>>((source, context) => context.MapEnumerable<IMacroProperty, MacroParameter>(source.Properties.Values));
|
||||
mapper.Define<IMacroProperty, MacroParameter>((source, context) => new MacroParameter(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed -AdditionalData
|
||||
private static void Map(IMacro source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = Constants.Icons.Macro;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key);
|
||||
}
|
||||
|
||||
private void Map(IMacro source, MacroDisplay target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = Constants.Icons.Macro;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key);
|
||||
target.CacheByPage = source.CacheByPage;
|
||||
target.CacheByUser = source.CacheByMember;
|
||||
target.CachePeriod = source.CacheDuration;
|
||||
target.UseInEditor = source.UseInEditor;
|
||||
target.RenderInEditor = !source.DontRender;
|
||||
target.View = source.MacroSource;
|
||||
}
|
||||
// Umbraco.Code.MapAll -Value
|
||||
private void Map(IMacroProperty source, MacroParameter target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
|
||||
//map the view and the config
|
||||
// we need to show the deprecated ones for backwards compatibility
|
||||
var paramEditor = _parameterEditors[source.EditorAlias]; // TODO: include/filter deprecated?!
|
||||
if (paramEditor == null)
|
||||
{
|
||||
//we'll just map this to a text box
|
||||
paramEditor = _parameterEditors[Constants.PropertyEditors.Aliases.TextBox];
|
||||
_logger.LogWarning("Could not resolve a parameter editor with alias {PropertyEditorAlias}, a textbox will be rendered in it's place", source.EditorAlias);
|
||||
}
|
||||
|
||||
target.View = paramEditor.GetValueEditor().View;
|
||||
|
||||
// sets the parameter configuration to be the default configuration editor's configuration,
|
||||
// ie configurationEditor.DefaultConfigurationObject, prepared for the value editor, ie
|
||||
// after ToValueEditor - important to use DefaultConfigurationObject here, because depending
|
||||
// on editors, ToValueEditor expects the actual strongly typed configuration - not the
|
||||
// dictionary thing returned by DefaultConfiguration
|
||||
|
||||
var configurationEditor = paramEditor.GetConfigurationEditor();
|
||||
target.Configuration = configurationEditor.ToValueEditor(configurationEditor.DefaultConfigurationObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom tab/property resolver for members which will ensure that the built-in membership properties are or aren't displayed
|
||||
/// depending on if the member type has these properties
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because
|
||||
/// an admin cannot actually set isLockedOut = true, they can only unlock.
|
||||
/// </remarks>
|
||||
public class MemberTabsAndPropertiesMapper : TabsAndPropertiesMapper<IMember>
|
||||
{
|
||||
private readonly IBackofficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly IMemberService _memberService;
|
||||
private readonly IMemberGroupService _memberGroupService;
|
||||
private readonly MemberPasswordConfigurationSettings _memberPasswordConfiguration;
|
||||
private readonly PropertyEditorCollection _propertyEditorCollection;
|
||||
|
||||
public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary,
|
||||
IBackofficeSecurityAccessor backofficeSecurityAccessor,
|
||||
ILocalizedTextService localizedTextService,
|
||||
IMemberTypeService memberTypeService,
|
||||
IMemberService memberService,
|
||||
IMemberGroupService memberGroupService,
|
||||
IOptions<MemberPasswordConfigurationSettings> memberPasswordConfiguration,
|
||||
IContentTypeBaseServiceProvider contentTypeBaseServiceProvider,
|
||||
PropertyEditorCollection propertyEditorCollection)
|
||||
: base(cultureDictionary, localizedTextService, contentTypeBaseServiceProvider)
|
||||
{
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
|
||||
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
_memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
|
||||
_memberService = memberService ?? throw new ArgumentNullException(nameof(memberService));
|
||||
_memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
|
||||
_memberPasswordConfiguration = memberPasswordConfiguration.Value;
|
||||
_propertyEditorCollection = propertyEditorCollection;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Overridden to deal with custom member properties and permissions.</remarks>
|
||||
public override IEnumerable<Tab<ContentPropertyDisplay>> Map(IMember source, MapperContext context)
|
||||
{
|
||||
|
||||
var memberType = _memberTypeService.Get(source.ContentTypeId);
|
||||
|
||||
IgnoreProperties = memberType.CompositionPropertyTypes
|
||||
.Where(x => x.HasIdentity == false)
|
||||
.Select(x => x.Alias)
|
||||
.ToArray();
|
||||
|
||||
var resolved = base.Map(source, context);
|
||||
|
||||
// This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier
|
||||
// if we just had all of the membership provider fields on the member table :(
|
||||
// TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno.
|
||||
var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut);
|
||||
if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1")
|
||||
{
|
||||
isLockedOutProperty.View = "readonlyvalue";
|
||||
isLockedOutProperty.Value = _localizedTextService.Localize("general/no");
|
||||
}
|
||||
|
||||
if (_backofficeSecurityAccessor.BackofficeSecurity.CurrentUser != null
|
||||
&& _backofficeSecurityAccessor.BackofficeSecurity.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
|
||||
{
|
||||
var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", source.ContentTypeId);
|
||||
|
||||
// Replace the doctype property
|
||||
var docTypeProperty = resolved.SelectMany(x => x.Properties)
|
||||
.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
|
||||
docTypeProperty.Value = new List<object>
|
||||
{
|
||||
new
|
||||
{
|
||||
linkText = source.ContentType.Name,
|
||||
url = memberTypeLink,
|
||||
target = "_self",
|
||||
icon = Constants.Icons.ContentType
|
||||
}
|
||||
};
|
||||
docTypeProperty.View = "urllist";
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
protected override IEnumerable<ContentPropertyDisplay> GetCustomGenericProperties(IContentBase content)
|
||||
{
|
||||
var member = (IMember)content;
|
||||
|
||||
var genericProperties = new List<ContentPropertyDisplay>
|
||||
{
|
||||
new ContentPropertyDisplay
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id",
|
||||
Label = _localizedTextService.Localize("general/id"),
|
||||
Value = new List<string> {member.Id.ToString(), member.Key.ToString()},
|
||||
View = "idwithguid"
|
||||
},
|
||||
new ContentPropertyDisplay
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype",
|
||||
Label = _localizedTextService.Localize("content/membertype"),
|
||||
Value = _localizedTextService.UmbracoDictionaryTranslate(CultureDictionary, member.ContentType.Name),
|
||||
View = _propertyEditorCollection[Constants.PropertyEditors.Aliases.Label].GetValueEditor().View
|
||||
},
|
||||
GetLoginProperty(_memberTypeService, member, _localizedTextService),
|
||||
new ContentPropertyDisplay
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email",
|
||||
Label = _localizedTextService.Localize("general/email"),
|
||||
Value = member.Email,
|
||||
View = "email",
|
||||
Validation = {Mandatory = true}
|
||||
},
|
||||
new ContentPropertyDisplay
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password",
|
||||
Label = _localizedTextService.Localize("password"),
|
||||
|
||||
Value = new Dictionary<string, object>
|
||||
{
|
||||
// TODO: why ignoreCase, what are we doing here?!
|
||||
{"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)},
|
||||
},
|
||||
// TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor
|
||||
View = "changepassword",
|
||||
// initialize the dictionary with the configuration from the default membership provider
|
||||
Config = GetPasswordConfig(member)
|
||||
},
|
||||
new ContentPropertyDisplay
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup",
|
||||
Label = _localizedTextService.Localize("content/membergroup"),
|
||||
Value = GetMemberGroupValue(member.Username),
|
||||
View = "membergroups",
|
||||
Config = new Dictionary<string, object> {{"IsRequired", true}}
|
||||
}
|
||||
};
|
||||
|
||||
return genericProperties;
|
||||
}
|
||||
|
||||
private Dictionary<string, object> GetPasswordConfig(IMember member)
|
||||
{
|
||||
var result = new Dictionary<string, object>(_memberPasswordConfiguration.GetConfiguration(true))
|
||||
{
|
||||
// the password change toggle will only be displayed if there is already a password assigned.
|
||||
{"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false}
|
||||
};
|
||||
|
||||
// This will always be true for members since we always want to allow admins to change a password - so long as that
|
||||
// user has access to edit members (but that security is taken care of separately)
|
||||
result["allowManuallyChangingPassword"] = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to assign the IsSensitive property values
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="properties"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
protected override List<ContentPropertyDisplay> MapProperties(IContentBase content, List<IProperty> properties, MapperContext context)
|
||||
{
|
||||
var result = base.MapProperties(content, properties, context);
|
||||
var member = (IMember)content;
|
||||
var memberType = _memberTypeService.Get(member.ContentTypeId);
|
||||
|
||||
// now update the IsSensitive value
|
||||
foreach (var prop in result)
|
||||
{
|
||||
// check if this property is flagged as sensitive
|
||||
var isSensitiveProperty = memberType.IsSensitiveProperty(prop.Alias);
|
||||
// check permissions for viewing sensitive data
|
||||
if (isSensitiveProperty && (_backofficeSecurityAccessor.BackofficeSecurity.CurrentUser.HasAccessToSensitiveData() == false))
|
||||
{
|
||||
// mark this property as sensitive
|
||||
prop.IsSensitive = true;
|
||||
// mark this property as readonly so that it does not post any data
|
||||
prop.Readonly = true;
|
||||
// replace this editor with a sensitive value
|
||||
prop.View = "sensitivevalue";
|
||||
// clear the value
|
||||
prop.Value = null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the login property display field
|
||||
/// </summary>
|
||||
/// <param name="memberTypeService"></param>
|
||||
/// <param name="member"></param>
|
||||
/// <param name="display"></param>
|
||||
/// <param name="localizedText"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if
|
||||
/// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not actually natively
|
||||
/// allow that.
|
||||
/// </remarks>
|
||||
internal static ContentPropertyDisplay GetLoginProperty(IMemberTypeService memberTypeService, IMember member, ILocalizedTextService localizedText)
|
||||
{
|
||||
var prop = new ContentPropertyDisplay
|
||||
{
|
||||
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login",
|
||||
Label = localizedText.Localize("login"),
|
||||
Value = member.Username
|
||||
};
|
||||
|
||||
prop.View = "textbox";
|
||||
prop.Validation.Mandatory = true;
|
||||
return prop;
|
||||
}
|
||||
|
||||
internal IDictionary<string, bool> GetMemberGroupValue(string username)
|
||||
{
|
||||
var userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username);
|
||||
|
||||
// create a dictionary of all roles (except internal roles) + "false"
|
||||
var result = _memberGroupService.GetAll()
|
||||
.Select(x => x.Name)
|
||||
// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access
|
||||
.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)
|
||||
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(x => x, x => false);
|
||||
|
||||
// if user has no roles, just return the dictionary
|
||||
if (userRoles == null) return result;
|
||||
|
||||
// else update the dictionary to "true" for the user roles (except internal roles)
|
||||
foreach (var userRole in userRoles.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false))
|
||||
result[userRole] = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class PropertyTypeGroupMapper<TPropertyType>
|
||||
where TPropertyType : PropertyTypeDisplay, new()
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly ILogger<PropertyTypeGroupMapper<TPropertyType>> _logger;
|
||||
|
||||
public PropertyTypeGroupMapper(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IShortStringHelper shortStringHelper, ILogger<PropertyTypeGroupMapper<TPropertyType>> logger)
|
||||
{
|
||||
_propertyEditors = propertyEditors;
|
||||
_dataTypeService = dataTypeService;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content type that defines a property group, within a composition.
|
||||
/// </summary>
|
||||
/// <param name="contentType">The composition.</param>
|
||||
/// <param name="propertyGroupId">The identifier of the property group.</param>
|
||||
/// <returns>The composition content type that defines the specified property group.</returns>
|
||||
private static IContentTypeComposition GetContentTypeForPropertyGroup(IContentTypeComposition contentType, int propertyGroupId)
|
||||
{
|
||||
// test local groups
|
||||
if (contentType.PropertyGroups.Any(x => x.Id == propertyGroupId))
|
||||
return contentType;
|
||||
|
||||
// test composition types groups
|
||||
// .ContentTypeComposition is just the local ones, not recursive,
|
||||
// so we have to recurse here
|
||||
return contentType.ContentTypeComposition
|
||||
.Select(x => GetContentTypeForPropertyGroup(x, propertyGroupId))
|
||||
.FirstOrDefault(x => x != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content type that defines a property group, within a composition.
|
||||
/// </summary>
|
||||
/// <param name="contentType">The composition.</param>
|
||||
/// <param name="propertyTypeId">The identifier of the property type.</param>
|
||||
/// <returns>The composition content type that defines the specified property group.</returns>
|
||||
private static IContentTypeComposition GetContentTypeForPropertyType(IContentTypeComposition contentType, int propertyTypeId)
|
||||
{
|
||||
// test local property types
|
||||
if (contentType.PropertyTypes.Any(x => x.Id == propertyTypeId))
|
||||
return contentType;
|
||||
|
||||
// test composition property types
|
||||
// .ContentTypeComposition is just the local ones, not recursive,
|
||||
// so we have to recurse here
|
||||
return contentType.ContentTypeComposition
|
||||
.Select(x => GetContentTypeForPropertyType(x, propertyTypeId))
|
||||
.FirstOrDefault(x => x != null);
|
||||
}
|
||||
|
||||
public IEnumerable<PropertyGroupDisplay<TPropertyType>> Map(IContentTypeComposition source)
|
||||
{
|
||||
// deal with groups
|
||||
var groups = new List<PropertyGroupDisplay<TPropertyType>>();
|
||||
|
||||
// add groups local to this content type
|
||||
foreach (var tab in source.PropertyGroups)
|
||||
{
|
||||
var group = new PropertyGroupDisplay<TPropertyType>
|
||||
{
|
||||
Id = tab.Id,
|
||||
Inherited = false,
|
||||
Name = tab.Name,
|
||||
SortOrder = tab.SortOrder,
|
||||
ContentTypeId = source.Id
|
||||
};
|
||||
|
||||
group.Properties = MapProperties(tab.PropertyTypes, source, tab.Id, false);
|
||||
groups.Add(group);
|
||||
}
|
||||
|
||||
// add groups inherited through composition
|
||||
var localGroupIds = groups.Select(x => x.Id).ToArray();
|
||||
foreach (var tab in source.CompositionPropertyGroups)
|
||||
{
|
||||
// skip those that are local to this content type
|
||||
if (localGroupIds.Contains(tab.Id)) continue;
|
||||
|
||||
// get the content type that defines this group
|
||||
var definingContentType = GetContentTypeForPropertyGroup(source, tab.Id);
|
||||
if (definingContentType == null)
|
||||
throw new Exception("PropertyGroup with id=" + tab.Id + " was not found on any of the content type's compositions.");
|
||||
|
||||
var group = new PropertyGroupDisplay<TPropertyType>
|
||||
{
|
||||
Id = tab.Id,
|
||||
Inherited = true,
|
||||
Name = tab.Name,
|
||||
SortOrder = tab.SortOrder,
|
||||
ContentTypeId = definingContentType.Id,
|
||||
ParentTabContentTypes = new[] { definingContentType.Id },
|
||||
ParentTabContentTypeNames = new[] { definingContentType.Name }
|
||||
};
|
||||
|
||||
group.Properties = MapProperties(tab.PropertyTypes, definingContentType, tab.Id, true);
|
||||
groups.Add(group);
|
||||
}
|
||||
|
||||
// deal with generic properties
|
||||
var genericProperties = new List<TPropertyType>();
|
||||
|
||||
// add generic properties local to this content type
|
||||
var entityGenericProperties = source.PropertyTypes.Where(x => x.PropertyGroupId == null);
|
||||
genericProperties.AddRange(MapProperties(entityGenericProperties, source, PropertyGroupBasic.GenericPropertiesGroupId, false));
|
||||
|
||||
// add generic properties inherited through compositions
|
||||
var localGenericPropertyIds = genericProperties.Select(x => x.Id).ToArray();
|
||||
var compositionGenericProperties = source.CompositionPropertyTypes
|
||||
.Where(x => x.PropertyGroupId == null // generic
|
||||
&& localGenericPropertyIds.Contains(x.Id) == false); // skip those that are local
|
||||
foreach (var compositionGenericProperty in compositionGenericProperties)
|
||||
{
|
||||
var definingContentType = GetContentTypeForPropertyType(source, compositionGenericProperty.Id);
|
||||
if (definingContentType == null)
|
||||
throw new Exception("PropertyType with id=" + compositionGenericProperty.Id + " was not found on any of the content type's compositions.");
|
||||
genericProperties.AddRange(MapProperties(new [] { compositionGenericProperty }, definingContentType, PropertyGroupBasic.GenericPropertiesGroupId, true));
|
||||
}
|
||||
|
||||
// if there are any generic properties, add the corresponding tab
|
||||
if (genericProperties.Any())
|
||||
{
|
||||
var genericTab = new PropertyGroupDisplay<TPropertyType>
|
||||
{
|
||||
Id = PropertyGroupBasic.GenericPropertiesGroupId,
|
||||
Name = "Generic properties",
|
||||
ContentTypeId = source.Id,
|
||||
SortOrder = 999,
|
||||
Inherited = false,
|
||||
Properties = genericProperties
|
||||
};
|
||||
groups.Add(genericTab);
|
||||
}
|
||||
|
||||
// handle locked properties
|
||||
var lockedPropertyAliases = new List<string>();
|
||||
// add built-in member property aliases to list of aliases to be locked
|
||||
foreach (var propertyAlias in ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Keys)
|
||||
{
|
||||
lockedPropertyAliases.Add(propertyAlias);
|
||||
}
|
||||
// lock properties by aliases
|
||||
foreach (var property in groups.SelectMany(x => x.Properties))
|
||||
{
|
||||
property.Locked = lockedPropertyAliases.Contains(property.Alias);
|
||||
}
|
||||
|
||||
// now merge tabs based on names
|
||||
// as for one name, we might have one local tab, plus some inherited tabs
|
||||
var groupsGroupsByName = groups.GroupBy(x => x.Name).ToArray();
|
||||
groups = new List<PropertyGroupDisplay<TPropertyType>>(); // start with a fresh list
|
||||
foreach (var groupsByName in groupsGroupsByName)
|
||||
{
|
||||
// single group, just use it
|
||||
if (groupsByName.Count() == 1)
|
||||
{
|
||||
groups.Add(groupsByName.First());
|
||||
continue;
|
||||
}
|
||||
|
||||
// multiple groups, merge
|
||||
var group = groupsByName.FirstOrDefault(x => x.Inherited == false) // try local
|
||||
?? groupsByName.First(); // else pick one randomly
|
||||
groups.Add(group);
|
||||
|
||||
// in case we use the local one, flag as inherited
|
||||
group.Inherited = true;
|
||||
|
||||
// merge (and sort) properties
|
||||
var properties = groupsByName.SelectMany(x => x.Properties).OrderBy(x => x.SortOrder).ToArray();
|
||||
group.Properties = properties;
|
||||
|
||||
// collect parent group info
|
||||
var parentGroups = groupsByName.Where(x => x.ContentTypeId != source.Id).ToArray();
|
||||
group.ParentTabContentTypes = parentGroups.SelectMany(x => x.ParentTabContentTypes).ToArray();
|
||||
group.ParentTabContentTypeNames = parentGroups.SelectMany(x => x.ParentTabContentTypeNames).ToArray();
|
||||
}
|
||||
|
||||
return groups.OrderBy(x => x.SortOrder);
|
||||
}
|
||||
|
||||
private IEnumerable<TPropertyType> MapProperties(IEnumerable<IPropertyType> properties, IContentTypeBase contentType, int groupId, bool inherited)
|
||||
{
|
||||
var mappedProperties = new List<TPropertyType>();
|
||||
|
||||
foreach (var p in properties.Where(x => x.DataTypeId != 0).OrderBy(x => x.SortOrder))
|
||||
{
|
||||
var propertyEditorAlias = p.PropertyEditorAlias;
|
||||
var propertyEditor = _propertyEditors[propertyEditorAlias];
|
||||
var dataType = _dataTypeService.GetDataType(p.DataTypeId);
|
||||
|
||||
//fixme: Don't explode if we can't find this, log an error and change this to a label
|
||||
if (propertyEditor == null)
|
||||
{
|
||||
_logger.LogError("No property editor could be resolved with the alias: {PropertyEditorAlias}, defaulting to label", p.PropertyEditorAlias);
|
||||
propertyEditorAlias = Constants.PropertyEditors.Aliases.Label;
|
||||
propertyEditor = _propertyEditors[propertyEditorAlias];
|
||||
}
|
||||
|
||||
var config = propertyEditor == null
|
||||
? new Dictionary<string, object>()
|
||||
: dataType.Editor.GetConfigurationEditor().ToConfigurationEditor(dataType.Configuration);
|
||||
|
||||
mappedProperties.Add(new TPropertyType
|
||||
{
|
||||
Id = p.Id,
|
||||
Alias = p.Alias,
|
||||
Description = p.Description,
|
||||
Editor = p.PropertyEditorAlias,
|
||||
Validation = new PropertyTypeValidation
|
||||
{
|
||||
Mandatory = p.Mandatory,
|
||||
MandatoryMessage = p.MandatoryMessage,
|
||||
Pattern = p.ValidationRegExp,
|
||||
PatternMessage = p.ValidationRegExpMessage,
|
||||
},
|
||||
Label = p.Name,
|
||||
View = propertyEditor.GetValueEditor().View,
|
||||
Config = config,
|
||||
//Value = "",
|
||||
GroupId = groupId,
|
||||
Inherited = inherited,
|
||||
DataTypeId = p.DataTypeId,
|
||||
DataTypeKey = p.DataTypeKey,
|
||||
DataTypeName = dataType.Name,
|
||||
DataTypeIcon = propertyEditor.Icon,
|
||||
SortOrder = p.SortOrder,
|
||||
ContentTypeId = contentType.Id,
|
||||
ContentTypeName = contentType.Name,
|
||||
AllowCultureVariant = p.VariesByCulture(),
|
||||
AllowSegmentVariant = p.VariesBySegment()
|
||||
});
|
||||
}
|
||||
|
||||
return mappedProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class RedirectUrlMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
|
||||
public RedirectUrlMapDefinition(IPublishedUrlProvider publishedUrlProvider)
|
||||
{
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IRedirectUrl, ContentRedirectUrl>((source, context) => new ContentRedirectUrl(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(IRedirectUrl source, ContentRedirectUrl target, MapperContext context)
|
||||
{
|
||||
target.ContentId = source.ContentId;
|
||||
target.CreateDateUtc = source.CreateDateUtc;
|
||||
target.Culture = source.Culture;
|
||||
target.DestinationUrl = source.ContentId > 0 ? _publishedUrlProvider?.GetUrl(source.ContentId, culture: source.Culture) : "#";
|
||||
target.OriginalUrl = _publishedUrlProvider?.GetUrlFromRoute(source.ContentId, source.Url, source.Culture);
|
||||
target.RedirectId = source.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class RelationMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public RelationMapDefinition(IEntityService entityService, IRelationService relationService)
|
||||
{
|
||||
_entityService = entityService;
|
||||
_relationService = relationService;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IRelationType, RelationTypeDisplay>((source, context) => new RelationTypeDisplay(), Map);
|
||||
mapper.Define<IRelation, RelationDisplay>((source, context) => new RelationDisplay(), Map);
|
||||
mapper.Define<RelationTypeSave, IRelationType>(Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Icon -Trashed -AdditionalData
|
||||
// Umbraco.Code.MapAll -ParentId -Notifications
|
||||
private void Map(IRelationType source, RelationTypeDisplay target, MapperContext context)
|
||||
{
|
||||
target.ChildObjectType = source.ChildObjectType;
|
||||
target.Id = source.Id;
|
||||
target.IsBidirectional = source.IsBidirectional;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.Alias = source.Alias;
|
||||
target.ParentObjectType = source.ParentObjectType;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key);
|
||||
target.Path = "-1," + source.Id;
|
||||
|
||||
target.IsSystemRelationType = source.IsSystemRelationType();
|
||||
|
||||
// Set the "friendly" and entity names for the parent and child object types
|
||||
if (source.ParentObjectType.HasValue)
|
||||
{
|
||||
var objType = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value);
|
||||
target.ParentObjectTypeName = objType.GetFriendlyName();
|
||||
}
|
||||
|
||||
if (source.ChildObjectType.HasValue)
|
||||
{
|
||||
var objType = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value);
|
||||
target.ChildObjectTypeName = objType.GetFriendlyName();
|
||||
}
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ParentName -ChildName
|
||||
private void Map(IRelation source, RelationDisplay target, MapperContext context)
|
||||
{
|
||||
target.ChildId = source.ChildId;
|
||||
target.Comment = source.Comment;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.ParentId = source.ParentId;
|
||||
|
||||
var entities = _relationService.GetEntitiesFromRelation(source);
|
||||
|
||||
target.ParentName = entities.Item1.Name;
|
||||
target.ChildName = entities.Item2.Name;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
|
||||
private static void Map(RelationTypeSave source, IRelationType target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.ChildObjectType = source.ChildObjectType;
|
||||
target.Id = source.Id.TryConvertTo<int>().Result;
|
||||
target.IsBidirectional = source.IsBidirectional;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentObjectType = source.ParentObjectType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models.Sections;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Sections;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class SectionMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
public SectionMapDefinition(ILocalizedTextService textService)
|
||||
{
|
||||
_textService = textService;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<ISection, Section>((source, context) => new Section(), Map);
|
||||
|
||||
// this is for AutoMapper ReverseMap - but really?
|
||||
mapper.Define<Section, ContentSection>();
|
||||
mapper.Define<Section, ContentSection>();
|
||||
mapper.Define<Section, ManifestSection>(Map);
|
||||
mapper.Define<Section, MediaSection>();
|
||||
mapper.Define<Section, MembersSection>();
|
||||
mapper.Define<Section, PackagesSection>();
|
||||
mapper.Define<Section, SettingsSection>();
|
||||
mapper.Define<Section, TranslationSection>();
|
||||
mapper.Define<Section, UsersSection>();
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -RoutePath
|
||||
private void Map(ISection source, Section target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Name = _textService.Localize("sections/" + source.Alias);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(Section source, ManifestSection target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Name = source.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Dictionary;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public abstract class TabsAndPropertiesMapper
|
||||
{
|
||||
protected ICultureDictionary CultureDictionary { get; }
|
||||
protected ILocalizedTextService LocalizedTextService { get; }
|
||||
protected IEnumerable<string> IgnoreProperties { get; set; }
|
||||
|
||||
protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary));
|
||||
LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
IgnoreProperties = new List<string>();
|
||||
}
|
||||
|
||||
protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IEnumerable<string> ignoreProperties)
|
||||
: this(cultureDictionary, localizedTextService)
|
||||
{
|
||||
IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of custom generic properties that exist on the generic properties tab
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual IEnumerable<ContentPropertyDisplay> GetCustomGenericProperties(IContentBase content)
|
||||
{
|
||||
return Enumerable.Empty<ContentPropertyDisplay>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps properties on to the generic properties tab
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="tabs"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <remarks>
|
||||
/// The generic properties tab is responsible for
|
||||
/// setting up the properties such as Created date, updated date, template selected, etc...
|
||||
/// </remarks>
|
||||
protected virtual void MapGenericProperties(IContentBase content, List<Tab<ContentPropertyDisplay>> tabs, MapperContext context)
|
||||
{
|
||||
// add the generic properties tab, for properties that don't belong to a tab
|
||||
// get the properties, map and translate them, then add the tab
|
||||
var noGroupProperties = content.GetNonGroupedProperties()
|
||||
.Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored
|
||||
.ToList();
|
||||
var genericproperties = MapProperties(content, noGroupProperties, context);
|
||||
|
||||
tabs.Add(new Tab<ContentPropertyDisplay>
|
||||
{
|
||||
Id = 0,
|
||||
Label = LocalizedTextService.Localize("general/properties"),
|
||||
Alias = "Generic properties",
|
||||
Properties = genericproperties
|
||||
});
|
||||
|
||||
var genericProps = tabs.Single(x => x.Id == 0);
|
||||
|
||||
//store the current props to append to the newly inserted ones
|
||||
var currProps = genericProps.Properties.ToArray();
|
||||
|
||||
var contentProps = new List<ContentPropertyDisplay>();
|
||||
|
||||
var customProperties = GetCustomGenericProperties(content);
|
||||
if (customProperties != null)
|
||||
{
|
||||
//add the custom ones
|
||||
contentProps.AddRange(customProperties);
|
||||
}
|
||||
|
||||
//now add the user props
|
||||
contentProps.AddRange(currProps);
|
||||
|
||||
//re-assign
|
||||
genericProps.Properties = contentProps;
|
||||
|
||||
//Show or hide properties tab based on whether it has or not any properties
|
||||
if (genericProps.Properties.Any() == false)
|
||||
{
|
||||
//loop through the tabs, remove the one with the id of zero and exit the loop
|
||||
for (var i = 0; i < tabs.Count; i++)
|
||||
{
|
||||
if (tabs[i].Id != 0) continue;
|
||||
tabs.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a list of <see cref="Property"/> to a list of <see cref="ContentPropertyDisplay"/>
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="properties"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual List<ContentPropertyDisplay> MapProperties(IContentBase content, List<IProperty> properties, MapperContext context)
|
||||
{
|
||||
return context.MapEnumerable<IProperty, ContentPropertyDisplay>(properties.OrderBy(x => x.PropertyType.SortOrder));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the tabs collection with properties assigned for display models
|
||||
/// </summary>
|
||||
public class TabsAndPropertiesMapper<TSource> : TabsAndPropertiesMapper
|
||||
where TSource : IContentBase
|
||||
{
|
||||
private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
|
||||
|
||||
public TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider)
|
||||
: base(cultureDictionary, localizedTextService)
|
||||
{
|
||||
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider ?? throw new ArgumentNullException(nameof(contentTypeBaseServiceProvider));
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Tab<ContentPropertyDisplay>> Map(TSource source, MapperContext context)
|
||||
{
|
||||
var tabs = new List<Tab<ContentPropertyDisplay>>();
|
||||
|
||||
var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source);
|
||||
|
||||
// add the tabs, for properties that belong to a tab
|
||||
// need to aggregate the tabs, as content.PropertyGroups contains all the composition tabs,
|
||||
// and there might be duplicates (content does not work like contentType and there is no
|
||||
// content.CompositionPropertyGroups).
|
||||
var groupsGroupsByName = contentType.CompositionPropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name);
|
||||
foreach (var groupsByName in groupsGroupsByName)
|
||||
{
|
||||
var properties = new List<IProperty>();
|
||||
|
||||
// merge properties for groups with the same name
|
||||
foreach (var group in groupsByName)
|
||||
{
|
||||
var groupProperties = source.GetPropertiesForGroup(group)
|
||||
.Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored
|
||||
|
||||
properties.AddRange(groupProperties);
|
||||
}
|
||||
|
||||
if (properties.Count == 0)
|
||||
continue;
|
||||
|
||||
//map the properties
|
||||
var mappedProperties = MapProperties(source, properties, context);
|
||||
|
||||
// add the tab
|
||||
// we need to pick an identifier... there is no "right" way...
|
||||
var g = groupsByName.FirstOrDefault(x => x.Id == source.ContentTypeId) // try local
|
||||
?? groupsByName.First(); // else pick one randomly
|
||||
var groupId = g.Id;
|
||||
var groupName = groupsByName.Key;
|
||||
tabs.Add(new Tab<ContentPropertyDisplay>
|
||||
{
|
||||
Id = groupId,
|
||||
Alias = groupName,
|
||||
Label = LocalizedTextService.UmbracoDictionaryTranslate(CultureDictionary, groupName),
|
||||
Properties = mappedProperties,
|
||||
IsActive = false
|
||||
});
|
||||
}
|
||||
|
||||
MapGenericProperties(source, tabs, context);
|
||||
|
||||
// activate the first tab, if any
|
||||
if (tabs.Count > 0)
|
||||
tabs[0].IsActive = true;
|
||||
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class TagMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<ITag, TagModel>((source, context) => new TagModel(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(ITag source, TagModel target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Text = source.Text;
|
||||
target.Group = source.Group;
|
||||
target.NodeCount = source.NodeCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class TemplateMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
public TemplateMapDefinition(IShortStringHelper shortStringHelper)
|
||||
{
|
||||
_shortStringHelper = shortStringHelper;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<ITemplate, TemplateDisplay>((source, context) => new TemplateDisplay(), Map);
|
||||
mapper.Define<TemplateDisplay, ITemplate>((source, context) => new Template(_shortStringHelper, source.Name, source.Alias), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(ITemplate source, TemplateDisplay target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Name = source.Name;
|
||||
target.Alias = source.Alias;
|
||||
target.Key = source.Key;
|
||||
target.Content = source.Content;
|
||||
target.Path = source.Path;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
target.MasterTemplateAlias = source.MasterTemplateAlias;
|
||||
target.IsMasterTemplate = source.IsMasterTemplate;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
|
||||
// Umbraco.Code.MapAll -Path -VirtualPath -MasterTemplateId -IsMasterTemplate
|
||||
// Umbraco.Code.MapAll -GetFileContent
|
||||
private static void Map(TemplateDisplay source, ITemplate target, MapperContext context)
|
||||
{
|
||||
// don't need to worry about mapping MasterTemplateAlias here;
|
||||
// the template controller handles any changes made to the master template
|
||||
target.Name = source.Name;
|
||||
target.Alias = source.Alias;
|
||||
target.Content = source.Content;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,452 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Sections;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Actions;
|
||||
using Umbraco.Web.Services;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class UserMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly ISectionService _sectionService;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly ActionCollection _actions;
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IMediaFileSystem _mediaFileSystem;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
|
||||
public UserMapDefinition(ILocalizedTextService textService, IUserService userService, IEntityService entityService, ISectionService sectionService,
|
||||
AppCaches appCaches, ActionCollection actions, IOptions<GlobalSettings> globalSettings, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper,
|
||||
IImageUrlGenerator imageUrlGenerator)
|
||||
{
|
||||
_sectionService = sectionService;
|
||||
_entityService = entityService;
|
||||
_userService = userService;
|
||||
_textService = textService;
|
||||
_actions = actions;
|
||||
_appCaches = appCaches;
|
||||
_globalSettings = globalSettings.Value;
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_imageUrlGenerator = imageUrlGenerator;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<UserGroupSave, IUserGroup>((source, context) => new UserGroup(_shortStringHelper) { CreateDate = DateTime.UtcNow }, Map);
|
||||
mapper.Define<UserInvite, IUser>(Map);
|
||||
mapper.Define<IProfile, ContentEditing.UserProfile>((source, context) => new ContentEditing.UserProfile(), Map);
|
||||
mapper.Define<IReadOnlyUserGroup, UserGroupBasic>((source, context) => new UserGroupBasic(), Map);
|
||||
mapper.Define<IUserGroup, UserGroupBasic>((source, context) => new UserGroupBasic(), Map);
|
||||
mapper.Define<IUserGroup, AssignedUserGroupPermissions>((source, context) => new AssignedUserGroupPermissions(), Map);
|
||||
mapper.Define<EntitySlim, AssignedContentPermissions>((source, context) => new AssignedContentPermissions(), Map);
|
||||
mapper.Define<IUserGroup, UserGroupDisplay>((source, context) => new UserGroupDisplay(), Map);
|
||||
mapper.Define<IUser, UserBasic>((source, context) => new UserBasic(), Map);
|
||||
mapper.Define<IUser, UserDetail>((source, context) => new UserDetail(), Map);
|
||||
|
||||
// used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance!
|
||||
mapper.Define<UserSave, IUser>(Map);
|
||||
|
||||
// important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that
|
||||
// this will cause an N+1 and we'll need to change how this works.
|
||||
mapper.Define<IUser, UserDisplay>((source, context) => new UserDisplay(), Map);
|
||||
}
|
||||
|
||||
// mappers
|
||||
|
||||
private static void Map(UserGroupSave source, IUserGroup target, MapperContext context)
|
||||
{
|
||||
if (!(target is UserGroup ttarget))
|
||||
throw new NotSupportedException($"{nameof(target)} must be a UserGroup.");
|
||||
Map(source, ttarget);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
|
||||
private static void Map(UserGroupSave source, UserGroup target)
|
||||
{
|
||||
target.StartMediaId = source.StartMediaId;
|
||||
target.StartContentId = source.StartContentId;
|
||||
target.Icon = source.Icon;
|
||||
target.Alias = source.Alias;
|
||||
target.Name = source.Name;
|
||||
target.Permissions = source.DefaultPermissions;
|
||||
target.Key = source.Key;
|
||||
|
||||
var id = GetIntId(source.Id);
|
||||
if (id > 0)
|
||||
target.Id = id;
|
||||
|
||||
target.ClearAllowedSections();
|
||||
foreach (var section in source.Sections)
|
||||
target.AddAllowedSection(section);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
|
||||
// Umbraco.Code.MapAll -Id -TourData -StartContentIds -StartMediaIds -Language -Username
|
||||
// Umbraco.Code.MapAll -PasswordQuestion -SessionTimeout -EmailConfirmedDate -InvitedDate
|
||||
// Umbraco.Code.MapAll -SecurityStamp -Avatar -ProviderUserKey -RawPasswordValue
|
||||
// Umbraco.Code.MapAll -RawPasswordAnswerValue -Comments -IsApproved -IsLockedOut -LastLoginDate
|
||||
// Umbraco.Code.MapAll -LastPasswordChangeDate -LastLockoutDate -FailedPasswordAttempts
|
||||
private void Map(UserInvite source, IUser target, MapperContext context)
|
||||
{
|
||||
target.Email = source.Email;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.IsApproved = false;
|
||||
|
||||
target.ClearGroups();
|
||||
var groups = _userService.GetUserGroupsByAlias(source.UserGroups.ToArray());
|
||||
foreach (var group in groups)
|
||||
target.AddGroup(group.ToReadOnlyGroup());
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
|
||||
// Umbraco.Code.MapAll -TourData -SessionTimeout -EmailConfirmedDate -InvitedDate -SecurityStamp -Avatar
|
||||
// Umbraco.Code.MapAll -ProviderUserKey -RawPasswordValue -RawPasswordAnswerValue -PasswordQuestion -Comments
|
||||
// Umbraco.Code.MapAll -IsApproved -IsLockedOut -LastLoginDate -LastPasswordChangeDate -LastLockoutDate
|
||||
// Umbraco.Code.MapAll -FailedPasswordAttempts
|
||||
private void Map(UserSave source, IUser target, MapperContext context)
|
||||
{
|
||||
target.Name = source.Name;
|
||||
target.StartContentIds = source.StartContentIds ?? Array.Empty<int>();
|
||||
target.StartMediaIds = source.StartMediaIds ?? Array.Empty<int>();
|
||||
target.Language = source.Culture;
|
||||
target.Email = source.Email;
|
||||
target.Key = source.Key;
|
||||
target.Username = source.Username;
|
||||
target.Id = source.Id;
|
||||
|
||||
target.ClearGroups();
|
||||
var groups = _userService.GetUserGroupsByAlias(source.UserGroups.ToArray());
|
||||
foreach (var group in groups)
|
||||
target.AddGroup(group.ToReadOnlyGroup());
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(IProfile source, ContentEditing.UserProfile target, MapperContext context)
|
||||
{
|
||||
target.Name = source.Name;
|
||||
target.UserId = source.Id;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ContentStartNode -UserCount -MediaStartNode -Key -Sections
|
||||
// Umbraco.Code.MapAll -Notifications -Udi -Trashed -AdditionalData -IsSystemUserGroup
|
||||
private void Map(IReadOnlyUserGroup source, UserGroupBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = source.Icon;
|
||||
target.Id = source.Id;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.IsSystemUserGroup = source.IsSystemUserGroup();
|
||||
|
||||
MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications
|
||||
// Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -IsSystemUserGroup
|
||||
private void Map(IUserGroup source, UserGroupBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = source.Icon;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.UserCount = source.UserCount;
|
||||
target.IsSystemUserGroup = source.IsSystemUserGroup();
|
||||
|
||||
MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -AssignedPermissions
|
||||
private void Map(IUserGroup source, AssignedUserGroupPermissions target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = source.Icon;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
|
||||
target.DefaultPermissions = MapUserGroupDefaultPermissions(source);
|
||||
|
||||
if (target.Icon.IsNullOrWhiteSpace())
|
||||
target.Icon = Constants.Icons.UserGroup;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed -Alias -AssignedPermissions
|
||||
private static void Map(EntitySlim source, AssignedContentPermissions target, MapperContext context)
|
||||
{
|
||||
target.Icon = MapContentTypeIcon(source);
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key);
|
||||
|
||||
if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace())
|
||||
target.Icon = Constants.Icons.Member;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications -Udi
|
||||
// Umbraco.Code.MapAll -Trashed -AdditionalData -Users -AssignedPermissions
|
||||
private void Map(IUserGroup source, UserGroupDisplay target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.DefaultPermissions = MapUserGroupDefaultPermissions(source);
|
||||
target.Icon = source.Icon;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.UserCount = source.UserCount;
|
||||
target.IsSystemUserGroup = source.IsSystemUserGroup();
|
||||
|
||||
MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context);
|
||||
|
||||
//Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that
|
||||
// this will cause an N+1 and we'll need to change how this works.
|
||||
var users = _userService.GetAllInGroup(source.Id);
|
||||
target.Users = context.MapEnumerable<IUser, UserBasic>(users);
|
||||
|
||||
//Deal with assigned permissions:
|
||||
|
||||
var allContentPermissions = _userService.GetPermissions(source, true)
|
||||
.ToDictionary(x => x.EntityId, x => x);
|
||||
|
||||
IEntitySlim[] contentEntities;
|
||||
if (allContentPermissions.Keys.Count == 0)
|
||||
{
|
||||
contentEntities = Array.Empty<IEntitySlim>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// a group can end up with way more than 2000 assigned permissions,
|
||||
// so we need to break them into groups in order to avoid breaking
|
||||
// the entity service due to too many Sql parameters.
|
||||
|
||||
var list = new List<IEntitySlim>();
|
||||
foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000))
|
||||
list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray()));
|
||||
contentEntities = list.ToArray();
|
||||
}
|
||||
|
||||
var allAssignedPermissions = new List<AssignedContentPermissions>();
|
||||
foreach (var entity in contentEntities)
|
||||
{
|
||||
var contentPermissions = allContentPermissions[entity.Id];
|
||||
|
||||
var assignedContentPermissions = context.Map<AssignedContentPermissions>(entity);
|
||||
assignedContentPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(target.DefaultPermissions);
|
||||
|
||||
//since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions
|
||||
//and we'll re-check it if it's one of the explicitly assigned ones
|
||||
foreach (var permission in assignedContentPermissions.AssignedPermissions.SelectMany(x => x.Value))
|
||||
{
|
||||
permission.Checked = false;
|
||||
permission.Checked = contentPermissions.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture);
|
||||
}
|
||||
|
||||
allAssignedPermissions.Add(assignedContentPermissions);
|
||||
}
|
||||
|
||||
target.AssignedPermissions = allAssignedPermissions;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Notifications -Udi -Icon -IsCurrentUser -Trashed -ResetPasswordValue
|
||||
// Umbraco.Code.MapAll -Alias -AdditionalData
|
||||
private void Map(IUser source, UserDisplay target, MapperContext context)
|
||||
{
|
||||
target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName);
|
||||
target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
||||
target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService), UmbracoObjectTypes.Document, "content/contentRoot", context);
|
||||
target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService), UmbracoObjectTypes.Media, "media/mediaRoot", context);
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString();
|
||||
target.Email = source.Email;
|
||||
target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash();
|
||||
target.FailedPasswordAttempts = source.FailedPasswordAttempts;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.LastLockoutDate = source.LastLockoutDate;
|
||||
target.LastLoginDate = source.LastLoginDate == default ? null : (DateTime?) source.LastLoginDate;
|
||||
target.LastPasswordChangeDate = source.LastPasswordChangeDate;
|
||||
target.Name = source.Name;
|
||||
target.Navigation = CreateUserEditorNavigation();
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.StartContentIds = GetStartNodes(source.StartContentIds.ToArray(), UmbracoObjectTypes.Document, "content/contentRoot", context);
|
||||
target.StartMediaIds = GetStartNodes(source.StartMediaIds.ToArray(), UmbracoObjectTypes.Media, "media/mediaRoot", context);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
target.UserGroups = context.MapEnumerable<IReadOnlyUserGroup, UserGroupBasic>(source.Groups);
|
||||
target.Username = source.Username;
|
||||
target.UserState = source.UserState;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Notifications -IsCurrentUser -Udi -Icon -Trashed -Alias -AdditionalData
|
||||
private void Map(IUser source, UserBasic target, MapperContext context)
|
||||
{
|
||||
//Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost
|
||||
//Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look
|
||||
//like the load time is waiting.
|
||||
target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
||||
target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString();
|
||||
target.Email = source.Email;
|
||||
target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash();
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.LastLoginDate = source.LastLoginDate == default ? null : (DateTime?) source.LastLoginDate;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.UserGroups = context.MapEnumerable<IReadOnlyUserGroup, UserGroupBasic>(source.Groups);
|
||||
target.Username = source.Username;
|
||||
target.UserState = source.UserState;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -SecondsUntilTimeout
|
||||
private void Map(IUser source, UserDetail target, MapperContext context)
|
||||
{
|
||||
target.AllowedSections = source.AllowedSections;
|
||||
target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator);
|
||||
target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString();
|
||||
target.Email = source.Email;
|
||||
target.EmailHash = source.Email.ToLowerInvariant().Trim().GenerateHash();
|
||||
target.Name = source.Name;
|
||||
target.StartContentIds = source.CalculateContentStartNodeIds(_entityService);
|
||||
target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService);
|
||||
target.UserId = source.Id;
|
||||
|
||||
//we need to map the legacy UserType
|
||||
//the best we can do here is to return the user's first user group as a IUserType object
|
||||
//but we should attempt to return any group that is the built in ones first
|
||||
target.UserGroups = source.Groups.Select(x => x.Alias).ToArray();
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
private void MapUserGroupBasic(UserGroupBasic target, IEnumerable<string> sourceAllowedSections, int? sourceStartContentId, int? sourceStartMediaId, MapperContext context)
|
||||
{
|
||||
var allSections = _sectionService.GetSections();
|
||||
target.Sections = context.MapEnumerable<ISection, Section>(allSections.Where(x => sourceAllowedSections.Contains(x.Alias)));
|
||||
|
||||
if (sourceStartMediaId > 0)
|
||||
target.MediaStartNode = context.Map<EntityBasic>(_entityService.Get(sourceStartMediaId.Value, UmbracoObjectTypes.Media));
|
||||
else if (sourceStartMediaId == -1)
|
||||
target.MediaStartNode = CreateRootNode(_textService.Localize("media/mediaRoot"));
|
||||
|
||||
if (sourceStartContentId > 0)
|
||||
target.ContentStartNode = context.Map<EntityBasic>(_entityService.Get(sourceStartContentId.Value, UmbracoObjectTypes.Document));
|
||||
else if (sourceStartContentId == -1)
|
||||
target.ContentStartNode = CreateRootNode(_textService.Localize("content/contentRoot"));
|
||||
|
||||
if (target.Icon.IsNullOrWhiteSpace())
|
||||
target.Icon = Constants.Icons.UserGroup;
|
||||
}
|
||||
|
||||
private IDictionary<string, IEnumerable<Permission>> MapUserGroupDefaultPermissions(IUserGroup source)
|
||||
{
|
||||
Permission GetPermission(IAction action)
|
||||
=> new Permission
|
||||
{
|
||||
Category = action.Category.IsNullOrWhiteSpace()
|
||||
? _textService.Localize($"actionCategories/{Constants.Conventions.PermissionCategories.OtherCategory}")
|
||||
: _textService.Localize($"actionCategories/{action.Category}"),
|
||||
Name = _textService.Localize($"actions/{action.Alias}"),
|
||||
Description = _textService.Localize($"actionDescriptions/{action.Alias}"),
|
||||
Icon = action.Icon,
|
||||
Checked = source.Permissions != null && source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture)),
|
||||
PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
return _actions
|
||||
.Where(x => x.CanBePermissionAssigned)
|
||||
.Select(GetPermission)
|
||||
.GroupBy(x => x.Category)
|
||||
.ToDictionary(x => x.Key, x => (IEnumerable<Permission>)x.ToArray());
|
||||
}
|
||||
|
||||
private static string MapContentTypeIcon(IEntitySlim entity)
|
||||
=> entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null;
|
||||
|
||||
private IEnumerable<EntityBasic> GetStartNodes(int[] startNodeIds, UmbracoObjectTypes objectType, string localizedKey, MapperContext context)
|
||||
{
|
||||
if (startNodeIds.Length <= 0)
|
||||
return Enumerable.Empty<EntityBasic>();
|
||||
|
||||
var startNodes = new List<EntityBasic>();
|
||||
if (startNodeIds.Contains(-1))
|
||||
startNodes.Add(CreateRootNode(_textService.Localize(localizedKey)));
|
||||
|
||||
var mediaItems = _entityService.GetAll(objectType, startNodeIds);
|
||||
startNodes.AddRange(context.MapEnumerable<IEntitySlim, EntityBasic>(mediaItems));
|
||||
return startNodes;
|
||||
}
|
||||
|
||||
private IEnumerable<EditorNavigation> CreateUserEditorNavigation()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new EditorNavigation
|
||||
{
|
||||
Active = true,
|
||||
Alias = "details",
|
||||
Icon = "icon-umb-users",
|
||||
Name = _textService.Localize("general/user"),
|
||||
View = "views/users/views/user/details.html"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetIntId(object id)
|
||||
{
|
||||
var result = id.TryConvertTo<int>();
|
||||
if (result.Success == false)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Cannot convert the profile to a " + typeof(UserDetail).Name + " object since the id is not an integer");
|
||||
}
|
||||
return result.Result;
|
||||
}
|
||||
|
||||
private EntityBasic CreateRootNode(string name)
|
||||
{
|
||||
return new EntityBasic
|
||||
{
|
||||
Name = name,
|
||||
Path = "-1",
|
||||
Icon = "icon-folder",
|
||||
Id = -1,
|
||||
Trashed = false,
|
||||
ParentId = -1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="Property"/> class to manage tags.
|
||||
/// </summary>
|
||||
public static class PropertyTagsExtensions
|
||||
{
|
||||
// gets the tag configuration for a property
|
||||
// from the datatype configuration, and the editor tag configuration attribute
|
||||
internal static TagConfiguration GetTagConfiguration(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
var editor = propertyEditors[property.PropertyType.PropertyEditorAlias];
|
||||
var tagAttribute = editor.GetTagAttribute();
|
||||
if (tagAttribute == null) return null;
|
||||
|
||||
var configurationObject = dataTypeService.GetDataType(property.PropertyType.DataTypeId).Configuration;
|
||||
var configuration = ConfigurationEditor.ConfigurationAs<TagConfiguration>(configurationObject);
|
||||
|
||||
if (configuration.Delimiter == default)
|
||||
configuration.Delimiter = tagAttribute.Delimiter;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign tags.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <param name="merge">A value indicating whether to merge the tags with existing tags instead of replacing them.</param>
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void AssignTags(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IEnumerable<string> tags, bool merge = false, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
var configuration = property.GetTagConfiguration(propertyEditors, dataTypeService);
|
||||
if (configuration == null)
|
||||
throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags.");
|
||||
|
||||
property.AssignTags(tags, merge, configuration.StorageType, configuration.Delimiter, culture);
|
||||
}
|
||||
|
||||
// assumes that parameters are consistent with the datatype configuration
|
||||
private static void AssignTags(this IProperty property, IEnumerable<string> tags, bool merge, TagsStorageType storageType, char delimiter, string culture)
|
||||
{
|
||||
// set the property value
|
||||
var trimmedTags = tags.Select(x => x.Trim()).ToArray();
|
||||
|
||||
if (merge)
|
||||
{
|
||||
var currentTags = property.GetTagsValue(storageType, delimiter);
|
||||
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)), culture); // csv string
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
property.SetValue(JsonConvert.SerializeObject(currentTags.Union(trimmedTags).ToArray()), culture); // json array
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
property.SetValue(string.Join(delimiter.ToString(), trimmedTags), culture); // csv string
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
property.SetValue(JsonConvert.SerializeObject(trimmedTags), culture); // json array
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes tags.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="tags">The tags.</param>
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
public static void RemoveTags(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IEnumerable<string> tags, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
var configuration = property.GetTagConfiguration(propertyEditors, dataTypeService);
|
||||
if (configuration == null)
|
||||
throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags.");
|
||||
|
||||
property.RemoveTags(tags, configuration.StorageType, configuration.Delimiter, culture);
|
||||
}
|
||||
|
||||
// assumes that parameters are consistent with the datatype configuration
|
||||
private static void RemoveTags(this IProperty property, IEnumerable<string> tags, TagsStorageType storageType, char delimiter, string culture)
|
||||
{
|
||||
// already empty = nothing to do
|
||||
var value = property.GetValue(culture)?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value)) return;
|
||||
|
||||
// set the property value
|
||||
var trimmedTags = tags.Select(x => x.Trim()).ToArray();
|
||||
var currentTags = property.GetTagsValue(storageType, delimiter, culture);
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)), culture); // csv string
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
property.SetValue(JsonConvert.SerializeObject(currentTags.Except(trimmedTags).ToArray()), culture); // json array
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// used by ContentRepositoryBase
|
||||
internal static IEnumerable<string> GetTagsValue(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
var configuration = property.GetTagConfiguration(propertyEditors, dataTypeService);
|
||||
if (configuration == null)
|
||||
throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags.");
|
||||
|
||||
return property.GetTagsValue(configuration.StorageType, configuration.Delimiter, culture);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetTagsValue(this IProperty property, TagsStorageType storageType, char delimiter, string culture = null)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
|
||||
var value = property.GetValue(culture)?.ToString();
|
||||
if (string.IsNullOrWhiteSpace(value)) return Enumerable.Empty<string>();
|
||||
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
return value.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim());
|
||||
|
||||
case TagsStorageType.Json:
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<JArray>(value).Select(x => x.ToString().Trim());
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
//cannot parse, malformed
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Value \"{storageType}\" is not a valid TagsStorageType.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets tags on a content property, based on the property editor tags configuration.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <param name="tagConfiguration">The datatype configuration.</param>
|
||||
/// <param name="culture">A culture, for multi-lingual properties.</param>
|
||||
/// <remarks>
|
||||
/// <para>The value is either a string (delimited string) or an enumeration of strings (tag list).</para>
|
||||
/// <para>This is used both by the content repositories to initialize a property with some tag values, and by the
|
||||
/// content controllers to update a property with values received from the property editor.</para>
|
||||
/// </remarks>
|
||||
public static void SetTagsValue(this IProperty property, object value, TagConfiguration tagConfiguration, string culture)
|
||||
{
|
||||
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||
if (tagConfiguration == null) throw new ArgumentNullException(nameof(tagConfiguration));
|
||||
|
||||
var storageType = tagConfiguration.StorageType;
|
||||
var delimiter = tagConfiguration.Delimiter;
|
||||
|
||||
SetTagsValue(property, value, storageType, delimiter, culture);
|
||||
}
|
||||
|
||||
// assumes that parameters are consistent with the datatype configuration
|
||||
// value can be an enumeration of string, or a serialized value using storageType format
|
||||
private static void SetTagsValue(IProperty property, object value, TagsStorageType storageType, char delimiter, string culture)
|
||||
{
|
||||
if (value == null) value = Enumerable.Empty<string>();
|
||||
|
||||
// if value is already an enumeration of strings, just use it
|
||||
if (value is IEnumerable<string> tags1)
|
||||
{
|
||||
property.AssignTags(tags1, false, storageType, delimiter, culture);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, deserialize value based upon storage type
|
||||
switch (storageType)
|
||||
{
|
||||
case TagsStorageType.Csv:
|
||||
var tags2 = value.ToString().Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
|
||||
property.AssignTags(tags2, false, storageType, delimiter, culture);
|
||||
break;
|
||||
|
||||
case TagsStorageType.Json:
|
||||
try
|
||||
{
|
||||
var tags3 = JsonConvert.DeserializeObject<IEnumerable<string>>(value.ToString());
|
||||
property.AssignTags(tags3 ?? Enumerable.Empty<string>(), false, storageType, delimiter, culture);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StaticApplicationLogging.Logger.LogWarning(ex, "Could not automatically convert stored json value to an enumerable string '{Json}'", value.ToString());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(storageType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides strongly typed published content models services.
|
||||
/// </summary>
|
||||
public static class PublishedContentExtensionsForModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a strongly typed published content model for an internal published content.
|
||||
/// </summary>
|
||||
/// <param name="content">The internal published content.</param>
|
||||
/// <returns>The strongly typed published content model.</returns>
|
||||
public static IPublishedContent CreateModel(this IPublishedContent content, IPublishedModelFactory publishedModelFactory)
|
||||
{
|
||||
if (publishedModelFactory == null) throw new ArgumentNullException(nameof(publishedModelFactory));
|
||||
if (content == null)
|
||||
return null;
|
||||
|
||||
// get model
|
||||
// if factory returns nothing, throw
|
||||
var model = publishedModelFactory.CreateModel(content);
|
||||
if (model == null)
|
||||
throw new InvalidOperationException("Factory returned null.");
|
||||
|
||||
// if factory returns a different type, throw
|
||||
if (!(model is IPublishedContent publishedContent))
|
||||
throw new InvalidOperationException($"Factory returned model of type {model.GetType().FullName} which does not implement IPublishedContent.");
|
||||
|
||||
return publishedContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a strongly-typed published content.
|
||||
/// </summary>
|
||||
/// <remarks>Every strongly-typed published content class should inherit from <c>PublishedContentModel</c>
|
||||
/// (or inherit from a class that inherits from... etc.) so they are picked by the factory.</remarks>
|
||||
public abstract class PublishedContentModel : PublishedContentWrapped
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedContentModel"/> class with
|
||||
/// an original <see cref="IPublishedContent"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="content">The original content.</param>
|
||||
protected PublishedContentModel(IPublishedContent content)
|
||||
: base(content)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a default implementation for <see cref="IPublishedContentTypeFactory"/>.
|
||||
/// </summary>
|
||||
internal class PublishedContentTypeFactory : IPublishedContentTypeFactory
|
||||
{
|
||||
private readonly IPublishedModelFactory _publishedModelFactory;
|
||||
private readonly PropertyValueConverterCollection _propertyValueConverters;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly object _publishedDataTypesLocker = new object();
|
||||
private Dictionary<int, PublishedDataType> _publishedDataTypes;
|
||||
|
||||
public PublishedContentTypeFactory(IPublishedModelFactory publishedModelFactory, PropertyValueConverterCollection propertyValueConverters, IDataTypeService dataTypeService)
|
||||
{
|
||||
_publishedModelFactory = publishedModelFactory;
|
||||
_propertyValueConverters = propertyValueConverters;
|
||||
_dataTypeService = dataTypeService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedContentType CreateContentType(IContentTypeComposition contentType)
|
||||
{
|
||||
return new PublishedContentType(contentType, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is for tests and is not intended to be used directly from application code.
|
||||
/// </summary>
|
||||
/// <remarks>Values are assumed to be consisted and are not checked.</remarks>
|
||||
internal IPublishedContentType CreateContentType(Guid key, int id, string alias, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false)
|
||||
{
|
||||
return new PublishedContentType(key, id, alias, PublishedItemType.Content, Enumerable.Empty<string>(), propertyTypes, variations, isElement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is for tests and is not intended to be used directly from application code.
|
||||
/// </summary>
|
||||
/// <remarks>Values are assumed to be consisted and are not checked.</remarks>
|
||||
internal IPublishedContentType CreateContentType(Guid key, int id, string alias, IEnumerable<string> compositionAliases, Func<IPublishedContentType, IEnumerable<IPublishedPropertyType>> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false)
|
||||
{
|
||||
return new PublishedContentType(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, IPropertyType propertyType)
|
||||
{
|
||||
return new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing)
|
||||
{
|
||||
return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing)
|
||||
{
|
||||
return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is for tests and is not intended to be used directly from application code.
|
||||
/// </summary>
|
||||
/// <remarks>Values are assumed to be consisted and are not checked.</remarks>
|
||||
internal IPublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing)
|
||||
{
|
||||
return new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PublishedDataType GetDataType(int id)
|
||||
{
|
||||
Dictionary<int, PublishedDataType> publishedDataTypes;
|
||||
lock (_publishedDataTypesLocker)
|
||||
{
|
||||
if (_publishedDataTypes == null)
|
||||
{
|
||||
var dataTypes = _dataTypeService.GetAll();
|
||||
_publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType);
|
||||
}
|
||||
|
||||
publishedDataTypes = _publishedDataTypes;
|
||||
}
|
||||
|
||||
if (!publishedDataTypes.TryGetValue(id, out var dataType))
|
||||
throw new ArgumentException($"Could not find a datatype with identifier {id}.", nameof(id));
|
||||
|
||||
return dataType;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void NotifyDataTypeChanges(int[] ids)
|
||||
{
|
||||
lock (_publishedDataTypesLocker)
|
||||
{
|
||||
if (_publishedDataTypes == null)
|
||||
{
|
||||
var dataTypes = _dataTypeService.GetAll();
|
||||
_publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var id in ids)
|
||||
_publishedDataTypes.Remove(id);
|
||||
|
||||
var dataTypes = _dataTypeService.GetAll(ids);
|
||||
foreach (var dataType in dataTypes)
|
||||
_publishedDataTypes[dataType.Id] = CreatePublishedDataType(dataType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PublishedDataType CreatePublishedDataType(IDataType dataType)
|
||||
=> new PublishedDataType(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy<object>(() => dataType.Configuration));
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a strongly typed content model factory
|
||||
/// </summary>
|
||||
public class PublishedModelFactory : IPublishedModelFactory
|
||||
{
|
||||
private readonly Dictionary<string, ModelInfo> _modelInfos;
|
||||
private readonly Dictionary<string, Type> _modelTypeMap;
|
||||
private readonly IPublishedValueFallback _publishedValueFallback;
|
||||
|
||||
private class ModelInfo
|
||||
{
|
||||
public Type ParameterType { get; set; }
|
||||
public Func<object, IPublishedValueFallback, object> Ctor { get; set; }
|
||||
public Type ModelType { get; set; }
|
||||
public Func<IList> ListCtor { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedModelFactory"/> class with types.
|
||||
/// </summary>
|
||||
/// <param name="types">The model types.</param>
|
||||
/// <remarks>
|
||||
/// <para>Types must implement <c>IPublishedContent</c> and have a unique constructor that
|
||||
/// accepts one IPublishedContent as a parameter.</para>
|
||||
/// <para>To activate,</para>
|
||||
/// <code>
|
||||
/// var types = TypeLoader.Current.GetTypes{PublishedContentModel}();
|
||||
/// var factory = new PublishedContentModelFactoryImpl(types);
|
||||
/// PublishedContentModelFactoryResolver.Current.SetFactory(factory);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public PublishedModelFactory(IEnumerable<Type> types,
|
||||
IPublishedValueFallback publishedValueFallback)
|
||||
{
|
||||
var modelInfos = new Dictionary<string, ModelInfo>(StringComparer.InvariantCultureIgnoreCase);
|
||||
var modelTypeMap = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
// so... the model type has to implement a ctor with one parameter being, or inheriting from,
|
||||
// IPublishedElement - but it can be IPublishedContent - so we cannot get one precise ctor,
|
||||
// we have to iterate over all ctors and try to find the right one
|
||||
|
||||
ConstructorInfo constructor = null;
|
||||
Type parameterType = null;
|
||||
|
||||
foreach (var ctor in type.GetConstructors())
|
||||
{
|
||||
var parms = ctor.GetParameters();
|
||||
if (parms.Length == 2 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType) && typeof(IPublishedValueFallback).IsAssignableFrom(parms[1].ParameterType))
|
||||
{
|
||||
if (constructor != null)
|
||||
throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPublishedElement.");
|
||||
constructor = ctor;
|
||||
parameterType = parms[0].ParameterType;
|
||||
}
|
||||
}
|
||||
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement.");
|
||||
|
||||
var attribute = type.GetCustomAttribute<PublishedModelAttribute>(false);
|
||||
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
|
||||
|
||||
if (modelInfos.TryGetValue(typeName, out var modelInfo))
|
||||
throw new InvalidOperationException($"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\".");
|
||||
|
||||
// have to use an unsafe ctor because we don't know the types, really
|
||||
var modelCtor = ReflectionUtilities.EmitConstructorUnsafe<Func<object,IPublishedValueFallback, object>>(constructor);
|
||||
modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor };
|
||||
modelTypeMap[typeName] = type;
|
||||
}
|
||||
|
||||
_modelInfos = modelInfos.Count > 0 ? modelInfos : null;
|
||||
_modelTypeMap = modelTypeMap;
|
||||
_publishedValueFallback = publishedValueFallback;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedElement CreateModel(IPublishedElement element)
|
||||
{
|
||||
// fail fast
|
||||
if (_modelInfos == null)
|
||||
return element;
|
||||
|
||||
if (!_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo))
|
||||
return element;
|
||||
|
||||
// ReSharper disable once UseMethodIsInstanceOfType
|
||||
if (modelInfo.ParameterType.IsAssignableFrom(element.GetType()) == false)
|
||||
throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}.");
|
||||
|
||||
// can cast, because we checked when creating the ctor
|
||||
return (IPublishedElement) modelInfo.Ctor(element, _publishedValueFallback);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IList CreateModelList(string alias)
|
||||
{
|
||||
// fail fast
|
||||
if (_modelInfos == null)
|
||||
return new List<IPublishedElement>();
|
||||
|
||||
if (!_modelInfos.TryGetValue(alias, out var modelInfo))
|
||||
return new List<IPublishedElement>();
|
||||
|
||||
var ctor = modelInfo.ListCtor;
|
||||
if (ctor != null) return ctor();
|
||||
|
||||
var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType);
|
||||
ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor<Func<IList>>(declaring: listType);
|
||||
return ctor();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type MapModelType(Type type)
|
||||
=> ModelType.Map(type, _modelTypeMap);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user