Moves ContentTypeBase, Member, ContentTypeComposition, etc... removes the casting
This commit is contained in:
@@ -1,521 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an abstract class for base ContentType properties and methods
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
[DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")]
|
||||
public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase
|
||||
{
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
private string _alias;
|
||||
private string _description;
|
||||
private string _icon = "icon-folder";
|
||||
private string _thumbnail = "folder.png";
|
||||
private bool _allowedAsRoot; // note: only one that's not 'pure element type'
|
||||
private bool _isContainer;
|
||||
private bool _isElement;
|
||||
private PropertyGroupCollection _propertyGroups;
|
||||
private PropertyTypeCollection _noGroupPropertyTypes;
|
||||
private IEnumerable<ContentTypeSort> _allowedContentTypes;
|
||||
private bool _hasPropertyTypeBeenRemoved;
|
||||
private ContentVariation _variations;
|
||||
|
||||
protected ContentTypeBase(IShortStringHelper shortStringHelper, int parentId)
|
||||
{
|
||||
_shortStringHelper = shortStringHelper;
|
||||
if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId));
|
||||
ParentId = parentId;
|
||||
|
||||
_allowedContentTypes = new List<ContentTypeSort>();
|
||||
_propertyGroups = new PropertyGroupCollection();
|
||||
|
||||
// actually OK as IsPublishing is constant
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing);
|
||||
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
|
||||
|
||||
_variations = ContentVariation.Nothing;
|
||||
}
|
||||
|
||||
protected ContentTypeBase(IShortStringHelper shortStringHelper, IContentTypeBase parent)
|
||||
: this(shortStringHelper, parent, null)
|
||||
{ }
|
||||
|
||||
protected ContentTypeBase(IShortStringHelper shortStringHelper, IContentTypeBase parent, string alias)
|
||||
{
|
||||
if (parent == null) throw new ArgumentNullException(nameof(parent));
|
||||
SetParent(parent);
|
||||
|
||||
_shortStringHelper = shortStringHelper;
|
||||
_alias = alias;
|
||||
_allowedContentTypes = new List<ContentTypeSort>();
|
||||
_propertyGroups = new PropertyGroupCollection();
|
||||
|
||||
// actually OK as IsPublishing is constant
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing);
|
||||
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
|
||||
|
||||
_variations = ContentVariation.Nothing;
|
||||
}
|
||||
|
||||
public abstract ISimpleContentType ToSimple();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content type supports publishing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>A publishing content type supports draft and published values for properties.
|
||||
/// It is possible to retrieve either the draft (default) or published value of a property.
|
||||
/// Setting the value always sets the draft value, which then needs to be published.</para>
|
||||
/// <para>A non-publishing content type only supports one value for properties. Getting
|
||||
/// the draft or published value of a property returns the same thing, and publishing
|
||||
/// a value property has no effect.</para>
|
||||
/// </remarks>
|
||||
public abstract bool SupportsPublishing { get; }
|
||||
|
||||
//Custom comparer for enumerable
|
||||
private static readonly DelegateEqualityComparer<IEnumerable<ContentTypeSort>> ContentTypeSortComparer =
|
||||
new DelegateEqualityComparer<IEnumerable<ContentTypeSort>>(
|
||||
(sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable),
|
||||
sorts => sorts.GetHashCode());
|
||||
|
||||
protected void PropertyGroupsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(PropertyGroups));
|
||||
}
|
||||
|
||||
protected void PropertyTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
//enable this to detect duplicate property aliases. We do want this, however making this change in a
|
||||
//patch release might be a little dangerous
|
||||
|
||||
////detect if there are any duplicate aliases - this cannot be allowed
|
||||
//if (e.Action == NotifyCollectionChangedAction.Add
|
||||
// || e.Action == NotifyCollectionChangedAction.Replace)
|
||||
//{
|
||||
// var allAliases = _noGroupPropertyTypes.Concat(PropertyGroups.SelectMany(x => x.PropertyTypes)).Select(x => x.Alias);
|
||||
// if (allAliases.HasDuplicates(false))
|
||||
// {
|
||||
// var newAliases = string.Join(", ", e.NewItems.Cast<PropertyType>().Select(x => x.Alias));
|
||||
// throw new InvalidOperationException($"Other property types already exist with the aliases: {newAliases}");
|
||||
// }
|
||||
//}
|
||||
|
||||
OnPropertyChanged(nameof(PropertyTypes));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Alias of the ContentType
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public virtual string Alias
|
||||
{
|
||||
get => _alias;
|
||||
set => SetPropertyValueAndDetectChanges(
|
||||
value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase),
|
||||
ref _alias,
|
||||
nameof(Alias));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Description for the ContentType
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Description
|
||||
{
|
||||
get => _description;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the icon (sprite class) used to identify the ContentType
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Icon
|
||||
{
|
||||
get => _icon;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the thumbnail used to identify the ContentType
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Thumbnail
|
||||
{
|
||||
get => _thumbnail;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, nameof(Thumbnail));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets a boolean indicating whether this ContentType is allowed at the root
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public bool AllowedAsRoot
|
||||
{
|
||||
get => _allowedAsRoot;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, nameof(AllowedAsRoot));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Sets a boolean indicating whether this ContentType is a Container
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ContentType Containers doesn't show children in the tree, but rather in grid-type view.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public bool IsContainer
|
||||
{
|
||||
get => _isContainer;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isContainer, nameof(IsContainer));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public bool IsElement
|
||||
{
|
||||
get => _isElement;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isElement, nameof(IsElement));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of integer Ids for allowed ContentTypes
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public IEnumerable<ContentTypeSort> AllowedContentTypes
|
||||
{
|
||||
get => _allowedContentTypes;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, nameof(AllowedContentTypes),
|
||||
ContentTypeSortComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content variation of the content type.
|
||||
/// </summary>
|
||||
public virtual ContentVariation Variations
|
||||
{
|
||||
get => _variations;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsVariation(string culture, string segment, bool wildcards = false)
|
||||
{
|
||||
// exact validation: cannot accept a 'null' culture if the property type varies
|
||||
// by culture, and likewise for segment
|
||||
// wildcard validation: can accept a '*' culture or segment
|
||||
return Variations.ValidateVariation(culture, segment, true, wildcards, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false)
|
||||
{
|
||||
// non-exact validation: can accept a 'null' culture if the property type varies
|
||||
// by culture, and likewise for segment
|
||||
// wildcard validation: can accept a '*' culture or segment
|
||||
return Variations.ValidateVariation(culture, segment, false, true, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
/// <para>A PropertyGroup corresponds to a Tab in the UI</para>
|
||||
/// <para>Marked DoNotClone because we will manually deal with cloning and the event handlers</para>
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
[DoNotClone]
|
||||
public PropertyGroupCollection PropertyGroups
|
||||
{
|
||||
get => _propertyGroups;
|
||||
set
|
||||
{
|
||||
_propertyGroups = value;
|
||||
_propertyGroups.CollectionChanged += PropertyGroupsChanged;
|
||||
PropertyGroupsChanged(_propertyGroups, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
[DoNotClone]
|
||||
public IEnumerable<IPropertyType> PropertyTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return _noGroupPropertyTypes.Union(PropertyGroups.SelectMany(x => x.PropertyTypes));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DoNotClone]
|
||||
public IEnumerable<IPropertyType> NoGroupPropertyTypes
|
||||
{
|
||||
get => _noGroupPropertyTypes;
|
||||
set
|
||||
{
|
||||
if (_noGroupPropertyTypes != null)
|
||||
_noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged;
|
||||
_noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing, value);
|
||||
_noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged;
|
||||
PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A boolean flag indicating if a property type has been removed from this instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is currently (specifically) used in order to know that we need to refresh the content cache which
|
||||
/// needs to occur when a property has been removed from a content type
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
internal bool HasPropertyTypeBeenRemoved
|
||||
{
|
||||
get => _hasPropertyTypeBeenRemoved;
|
||||
private set
|
||||
{
|
||||
_hasPropertyTypeBeenRemoved = value;
|
||||
OnPropertyChanged(nameof(HasPropertyTypeBeenRemoved));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a PropertyType with a given alias already exists
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">Alias of the PropertyType</param>
|
||||
/// <returns>Returns <c>True</c> if a PropertyType with the passed in alias exists, otherwise <c>False</c></returns>
|
||||
public abstract bool PropertyTypeExists(string propertyTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PropertyGroup.
|
||||
/// This method will also check if a group already exists with the same name and link it to the parent.
|
||||
/// </summary>
|
||||
/// <param name="groupName">Name of the PropertyGroup to add</param>
|
||||
/// <returns>Returns <c>True</c> if a PropertyGroup with the passed in name was added, otherwise <c>False</c></returns>
|
||||
public abstract bool AddPropertyGroup(string groupName);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PropertyType to a specific PropertyGroup
|
||||
/// </summary>
|
||||
/// <param name="propertyType"><see cref="IPropertyType"/> to add</param>
|
||||
/// <param name="propertyGroupName">Name of the PropertyGroup to add the PropertyType to</param>
|
||||
/// <returns>Returns <c>True</c> if PropertyType was added, otherwise <c>False</c></returns>
|
||||
public abstract bool AddPropertyType(IPropertyType propertyType, string propertyGroupName);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PropertyType, which does not belong to a PropertyGroup.
|
||||
/// </summary>
|
||||
/// <param name="propertyType"><see cref="IPropertyType"/> to add</param>
|
||||
/// <returns>Returns <c>True</c> if PropertyType was added, otherwise <c>False</c></returns>
|
||||
public bool AddPropertyType(IPropertyType propertyType)
|
||||
{
|
||||
if (PropertyTypeExists(propertyType.Alias) == false)
|
||||
{
|
||||
_noGroupPropertyTypes.Add(propertyType);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a PropertyType to a specified PropertyGroup
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">Alias of the PropertyType to move</param>
|
||||
/// <param name="propertyGroupName">Name of the PropertyGroup to move the PropertyType to</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>If <paramref name="propertyGroupName"/> is null then the property is moved back to
|
||||
/// "generic properties" ie does not have a tab anymore.</remarks>
|
||||
public bool MovePropertyType(string propertyTypeAlias, string propertyGroupName)
|
||||
{
|
||||
// note: not dealing with alias casing at all here?
|
||||
|
||||
// get property, ensure it exists
|
||||
var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
if (propertyType == null) return false;
|
||||
|
||||
// get new group, if required, and ensure it exists
|
||||
var newPropertyGroup = propertyGroupName == null
|
||||
? null
|
||||
: PropertyGroups.FirstOrDefault(x => x.Name == propertyGroupName);
|
||||
if (propertyGroupName != null && newPropertyGroup == null) return false;
|
||||
|
||||
// get old group
|
||||
var oldPropertyGroup = PropertyGroups.FirstOrDefault(x =>
|
||||
x.PropertyTypes.Any(y => y.Alias == propertyTypeAlias));
|
||||
|
||||
// set new group
|
||||
propertyType.PropertyGroupId = newPropertyGroup == null ? null : new Lazy<int>(() => newPropertyGroup.Id, false);
|
||||
|
||||
// remove from old group, if any - add to new group, if any
|
||||
oldPropertyGroup?.PropertyTypes.RemoveItem(propertyTypeAlias);
|
||||
newPropertyGroup?.PropertyTypes.Add(propertyType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a PropertyType from the current ContentType
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">Alias of the <see cref="IPropertyType"/> to remove</param>
|
||||
public void RemovePropertyType(string propertyTypeAlias)
|
||||
{
|
||||
//check through each property group to see if we can remove the property type by alias from it
|
||||
foreach (var propertyGroup in PropertyGroups)
|
||||
{
|
||||
if (propertyGroup.PropertyTypes.RemoveItem(propertyTypeAlias))
|
||||
{
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(nameof(PropertyTypes));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//check through each local property type collection (not assigned to a tab)
|
||||
if (_noGroupPropertyTypes.RemoveItem(propertyTypeAlias))
|
||||
{
|
||||
if (!HasPropertyTypeBeenRemoved)
|
||||
{
|
||||
HasPropertyTypeBeenRemoved = true;
|
||||
OnPropertyChanged(nameof(PropertyTypes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a PropertyGroup from the current ContentType
|
||||
/// </summary>
|
||||
/// <param name="propertyGroupName">Name of the <see cref="PropertyGroup"/> to remove</param>
|
||||
public void RemovePropertyGroup(string propertyGroupName)
|
||||
{
|
||||
// if no group exists with that name, do nothing
|
||||
var group = PropertyGroups[propertyGroupName];
|
||||
if (group == null) return;
|
||||
|
||||
// first remove the group
|
||||
PropertyGroups.RemoveItem(propertyGroupName);
|
||||
|
||||
// Then re-assign the group's properties to no group
|
||||
foreach (var property in group.PropertyTypes)
|
||||
{
|
||||
property.PropertyGroupId = null;
|
||||
_noGroupPropertyTypes.Add(property);
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(PropertyGroups));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyTypes that are not part of a PropertyGroup
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
// TODO: should we mark this as EditorBrowsable hidden since it really isn't ever used?
|
||||
internal PropertyTypeCollection PropertyTypeCollection => _noGroupPropertyTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current entity is dirty.
|
||||
/// </summary>
|
||||
/// <returns>True if entity is dirty, otherwise False</returns>
|
||||
public override bool IsDirty()
|
||||
{
|
||||
bool dirtyEntity = base.IsDirty();
|
||||
|
||||
bool dirtyGroups = PropertyGroups.Any(x => x.IsDirty());
|
||||
bool dirtyTypes = PropertyTypes.Any(x => x.IsDirty());
|
||||
|
||||
return dirtyEntity || dirtyGroups || dirtyTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets dirty properties by clearing the dictionary used to track changes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Please note that resetting the dirty properties could potentially
|
||||
/// obstruct the saving of a new or updated entity.
|
||||
/// </remarks>
|
||||
public override void ResetDirtyProperties()
|
||||
{
|
||||
base.ResetDirtyProperties();
|
||||
|
||||
//loop through each property group to reset the property types
|
||||
var propertiesReset = new List<int>();
|
||||
|
||||
foreach (var propertyGroup in PropertyGroups)
|
||||
{
|
||||
propertyGroup.ResetDirtyProperties();
|
||||
foreach (var propertyType in propertyGroup.PropertyTypes)
|
||||
{
|
||||
propertyType.ResetDirtyProperties();
|
||||
propertiesReset.Add(propertyType.Id);
|
||||
}
|
||||
}
|
||||
//then loop through our property type collection since some might not exist on a property group
|
||||
//but don't re-reset ones we've already done.
|
||||
foreach (var propertyType in PropertyTypes.Where(x => propertiesReset.Contains(x.Id) == false))
|
||||
{
|
||||
propertyType.ResetDirtyProperties();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PerformDeepClone(object clone)
|
||||
{
|
||||
base.PerformDeepClone(clone);
|
||||
|
||||
var clonedEntity = (ContentTypeBase) clone;
|
||||
|
||||
if (clonedEntity._noGroupPropertyTypes != null)
|
||||
{
|
||||
//need to manually wire up the event handlers for the property type collections - we've ensured
|
||||
// its ignored from the auto-clone process because its return values are unions, not raw and
|
||||
// we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842
|
||||
|
||||
clonedEntity._noGroupPropertyTypes.CollectionChanged -= PropertyTypesChanged; //clear this event handler if any
|
||||
clonedEntity._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone
|
||||
clonedEntity._noGroupPropertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler
|
||||
}
|
||||
|
||||
if (clonedEntity._propertyGroups != null)
|
||||
{
|
||||
clonedEntity._propertyGroups.CollectionChanged -= PropertyGroupsChanged; //clear this event handler if any
|
||||
clonedEntity._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone
|
||||
clonedEntity._propertyGroups.CollectionChanged += clonedEntity.PropertyGroupsChanged; //re-assign correct event handler
|
||||
}
|
||||
}
|
||||
|
||||
public ContentTypeBase DeepCloneWithResetIdentities(string alias)
|
||||
{
|
||||
var clone = (ContentTypeBase)DeepClone();
|
||||
clone.Alias = alias;
|
||||
clone.Key = Guid.Empty;
|
||||
foreach (var propertyGroup in clone.PropertyGroups)
|
||||
{
|
||||
propertyGroup.ResetIdentity();
|
||||
propertyGroup.ResetDirtyProperties(false);
|
||||
}
|
||||
foreach (var propertyType in clone.PropertyTypes)
|
||||
{
|
||||
propertyType.ResetIdentity();
|
||||
propertyType.ResetDirtyProperties(false);
|
||||
}
|
||||
|
||||
clone.ResetIdentity();
|
||||
clone.ResetDirtyProperties(false);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an abstract class for composition specific ContentType properties and methods
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition
|
||||
{
|
||||
private List<IContentTypeComposition> _contentTypeComposition = new List<IContentTypeComposition>();
|
||||
internal List<int> RemovedContentTypeKeyTracker = new List<int>();
|
||||
|
||||
protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId)
|
||||
{ }
|
||||
|
||||
protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper,IContentTypeComposition parent)
|
||||
: this(shortStringHelper, parent, null)
|
||||
{ }
|
||||
|
||||
protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, IContentTypeComposition parent, string alias)
|
||||
: base(shortStringHelper, parent, alias)
|
||||
{
|
||||
AddContentType(parent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content types that compose this content type.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public IEnumerable<IContentTypeComposition> ContentTypeComposition
|
||||
{
|
||||
get => _contentTypeComposition;
|
||||
set
|
||||
{
|
||||
_contentTypeComposition = value.ToList();
|
||||
OnPropertyChanged(nameof(ContentTypeComposition));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<PropertyGroup> CompositionPropertyGroups
|
||||
{
|
||||
get
|
||||
{
|
||||
// we need to "acquire" composition groups and properties here, ie get our own clones,
|
||||
// so that we can change their variation according to this content type variations.
|
||||
//
|
||||
// it would be nice to cache the resulting enumerable, but alas we cannot, otherwise
|
||||
// any change to compositions are ignored and that breaks many things - and tracking
|
||||
// changes to refresh the cache would be expensive.
|
||||
|
||||
void AcquireProperty(IPropertyType propertyType)
|
||||
{
|
||||
propertyType.Variations = propertyType.Variations & Variations;
|
||||
propertyType.ResetDirtyProperties(false);
|
||||
}
|
||||
|
||||
return ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups)
|
||||
.Select(group =>
|
||||
{
|
||||
group = (PropertyGroup) group.DeepClone();
|
||||
foreach (var property in group.PropertyTypes)
|
||||
AcquireProperty(property);
|
||||
return group;
|
||||
})
|
||||
.Union(PropertyGroups);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<IPropertyType> CompositionPropertyTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
// we need to "acquire" composition properties here, ie get our own clones,
|
||||
// so that we can change their variation according to this content type variations.
|
||||
//
|
||||
// see note in CompositionPropertyGroups for comments on caching the resulting enumerable
|
||||
|
||||
IPropertyType AcquireProperty(IPropertyType propertyType)
|
||||
{
|
||||
propertyType = (IPropertyType) propertyType.DeepClone();
|
||||
propertyType.Variations = propertyType.Variations & Variations;
|
||||
propertyType.ResetDirtyProperties(false);
|
||||
return propertyType;
|
||||
}
|
||||
|
||||
return ContentTypeComposition
|
||||
.SelectMany(x => x.CompositionPropertyTypes)
|
||||
.Select(AcquireProperty)
|
||||
.Union(PropertyTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property types obtained via composition.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Gets them raw, ie with their original variation.</para>
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
internal IEnumerable<IPropertyType> RawComposedPropertyTypes => GetRawComposedPropertyTypes();
|
||||
|
||||
private IEnumerable<IPropertyType> GetRawComposedPropertyTypes(bool start = true)
|
||||
{
|
||||
var propertyTypes = ContentTypeComposition
|
||||
.Cast<ContentTypeCompositionBase>()
|
||||
.SelectMany(x => start ? x.GetRawComposedPropertyTypes(false) : x.CompositionPropertyTypes);
|
||||
|
||||
if (!start)
|
||||
propertyTypes = propertyTypes.Union(PropertyTypes);
|
||||
|
||||
return propertyTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a content type to the composition.
|
||||
/// </summary>
|
||||
/// <param name="contentType">The content type to add.</param>
|
||||
/// <returns>True if the content type was added, otherwise false.</returns>
|
||||
public bool AddContentType(IContentTypeComposition contentType)
|
||||
{
|
||||
if (contentType.ContentTypeComposition.Any(x => x.CompositionAliases().Any(ContentTypeCompositionExists)))
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(Alias) == false && Alias.Equals(contentType.Alias))
|
||||
return false;
|
||||
|
||||
if (ContentTypeCompositionExists(contentType.Alias) == false)
|
||||
{
|
||||
//Before we actually go ahead and add the ContentType as a Composition we ensure that we don't
|
||||
//end up with duplicate PropertyType aliases - in which case we throw an exception.
|
||||
var conflictingPropertyTypeAliases = CompositionPropertyTypes.SelectMany(
|
||||
x => contentType.CompositionPropertyTypes
|
||||
.Where(y => y.Alias.Equals(x.Alias, StringComparison.InvariantCultureIgnoreCase))
|
||||
.Select(p => p.Alias)).ToList();
|
||||
|
||||
if (conflictingPropertyTypeAliases.Any())
|
||||
throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray());
|
||||
|
||||
_contentTypeComposition.Add(contentType);
|
||||
OnPropertyChanged(nameof(ContentTypeComposition));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a content type with a specified alias from the composition.
|
||||
/// </summary>
|
||||
/// <param name="alias">The alias of the content type to remove.</param>
|
||||
/// <returns>True if the content type was removed, otherwise false.</returns>
|
||||
public bool RemoveContentType(string alias)
|
||||
{
|
||||
if (ContentTypeCompositionExists(alias))
|
||||
{
|
||||
var contentTypeComposition = ContentTypeComposition.FirstOrDefault(x => x.Alias == alias);
|
||||
if (contentTypeComposition == null)//You can't remove a composition from another composition
|
||||
return false;
|
||||
|
||||
RemovedContentTypeKeyTracker.Add(contentTypeComposition.Id);
|
||||
|
||||
//If the ContentType we are removing has Compositions of its own these needs to be removed as well
|
||||
var compositionIdsToRemove = contentTypeComposition.CompositionIds().ToList();
|
||||
if (compositionIdsToRemove.Any())
|
||||
RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove);
|
||||
|
||||
OnPropertyChanged(nameof(ContentTypeComposition));
|
||||
return _contentTypeComposition.Remove(contentTypeComposition);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes
|
||||
/// </summary>
|
||||
/// <param name="alias">Alias of a <see cref="ContentType"/></param>
|
||||
/// <returns>True if ContentType with alias exists, otherwise returns False</returns>
|
||||
public bool ContentTypeCompositionExists(string alias)
|
||||
{
|
||||
if (ContentTypeComposition.Any(x => x.Alias.Equals(alias)))
|
||||
return true;
|
||||
|
||||
if (ContentTypeComposition.Any(x => x.ContentTypeCompositionExists(alias)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a PropertyType with a given alias already exists
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">Alias of the PropertyType</param>
|
||||
/// <returns>Returns <c>True</c> if a PropertyType with the passed in alias exists, otherwise <c>False</c></returns>
|
||||
public override bool PropertyTypeExists(string propertyTypeAlias)
|
||||
{
|
||||
return CompositionPropertyTypes.Any(x => x.Alias == propertyTypeAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PropertyGroup.
|
||||
/// </summary>
|
||||
/// <param name="groupName">Name of the PropertyGroup to add</param>
|
||||
/// <returns>Returns <c>True</c> if a PropertyGroup with the passed in name was added, otherwise <c>False</c></returns>
|
||||
public override bool AddPropertyGroup(string groupName)
|
||||
{
|
||||
return AddAndReturnPropertyGroup(groupName) != null;
|
||||
}
|
||||
|
||||
private PropertyGroup AddAndReturnPropertyGroup(string name)
|
||||
{
|
||||
// ensure we don't have it already
|
||||
if (PropertyGroups.Any(x => x.Name == name))
|
||||
return null;
|
||||
|
||||
// create the new group
|
||||
var group = new PropertyGroup(SupportsPublishing) { Name = name, SortOrder = 0 };
|
||||
|
||||
// check if it is inherited - there might be more than 1 but we want the 1st, to
|
||||
// reuse its sort order - if there are more than 1 and they have different sort
|
||||
// orders... there isn't much we can do anyways
|
||||
var inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Name == name);
|
||||
if (inheritGroup == null)
|
||||
{
|
||||
// no, just local, set sort order
|
||||
var lastGroup = PropertyGroups.LastOrDefault();
|
||||
if (lastGroup != null)
|
||||
group.SortOrder = lastGroup.SortOrder + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// yes, inherited, re-use sort order
|
||||
group.SortOrder = inheritGroup.SortOrder;
|
||||
}
|
||||
|
||||
// add
|
||||
PropertyGroups.Add(group);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a PropertyType to a specific PropertyGroup
|
||||
/// </summary>
|
||||
/// <param name="propertyType"><see cref="IPropertyType"/> to add</param>
|
||||
/// <param name="propertyGroupName">Name of the PropertyGroup to add the PropertyType to</param>
|
||||
/// <returns>Returns <c>True</c> if PropertyType was added, otherwise <c>False</c></returns>
|
||||
public override bool AddPropertyType(IPropertyType propertyType, string propertyGroupName)
|
||||
{
|
||||
// ensure no duplicate alias - over all composition properties
|
||||
if (PropertyTypeExists(propertyType.Alias))
|
||||
return false;
|
||||
|
||||
// get and ensure a group local to this content type
|
||||
var group = PropertyGroups.Contains(propertyGroupName)
|
||||
? PropertyGroups[propertyGroupName]
|
||||
: AddAndReturnPropertyGroup(propertyGroupName);
|
||||
if (group == null)
|
||||
return false;
|
||||
|
||||
// add property to group
|
||||
propertyType.PropertyGroupId = new Lazy<int>(() => group.Id);
|
||||
group.PropertyTypes.Add(propertyType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of ContentType aliases from the current composition
|
||||
/// </summary>
|
||||
/// <returns>An enumerable list of string aliases</returns>
|
||||
/// <remarks>Does not contain the alias of the Current ContentType</remarks>
|
||||
public IEnumerable<string> CompositionAliases()
|
||||
{
|
||||
return ContentTypeComposition
|
||||
.Select(x => x.Alias)
|
||||
.Union(ContentTypeComposition.SelectMany(x => x.CompositionAliases()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of ContentType Ids from the current composition
|
||||
/// </summary>
|
||||
/// <returns>An enumerable list of integer ids</returns>
|
||||
/// <remarks>Does not contain the Id of the Current ContentType</remarks>
|
||||
public IEnumerable<int> CompositionIds()
|
||||
{
|
||||
return ContentTypeComposition
|
||||
.Select(x => x.Id)
|
||||
.Union(ContentTypeComposition.SelectMany(x => x.CompositionIds()));
|
||||
}
|
||||
|
||||
protected override void PerformDeepClone(object clone)
|
||||
{
|
||||
base.PerformDeepClone(clone);
|
||||
|
||||
var clonedEntity = (ContentTypeCompositionBase)clone;
|
||||
|
||||
//need to manually assign since this is an internal field and will not be automatically mapped
|
||||
clonedEntity.RemovedContentTypeKeyTracker = new List<int>();
|
||||
clonedEntity._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Member object
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
public class Member : ContentBase, IMember
|
||||
{
|
||||
private IDictionary<string, object> _additionalData;
|
||||
private string _username;
|
||||
private string _email;
|
||||
private string _rawPasswordValue;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating an empty Member object
|
||||
/// </summary>
|
||||
/// <param name="contentType">ContentType for the current Content object</param>
|
||||
public Member(IMemberType contentType)
|
||||
: base("", -1, contentType, new PropertyCollection())
|
||||
{
|
||||
IsApproved = true;
|
||||
|
||||
//this cannot be null but can be empty
|
||||
_rawPasswordValue = "";
|
||||
_email = "";
|
||||
_username = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating a Member object
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the content</param>
|
||||
/// <param name="contentType">ContentType for the current Content object</param>
|
||||
public Member(string name, IMemberType contentType)
|
||||
: base(name, -1, contentType, new PropertyCollection())
|
||||
{
|
||||
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));
|
||||
|
||||
IsApproved = true;
|
||||
|
||||
//this cannot be null but can be empty
|
||||
_rawPasswordValue = "";
|
||||
_email = "";
|
||||
_username = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating a Member object
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="contentType"></param>
|
||||
public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true)
|
||||
: base(name, -1, contentType, new PropertyCollection())
|
||||
{
|
||||
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 (email == null) throw new ArgumentNullException(nameof(email));
|
||||
if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(email));
|
||||
if (username == null) throw new ArgumentNullException(nameof(username));
|
||||
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username));
|
||||
|
||||
_email = email;
|
||||
_username = username;
|
||||
IsApproved = isApproved;
|
||||
|
||||
//this cannot be null but can be empty
|
||||
_rawPasswordValue = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating a Member object
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="rawPasswordValue">
|
||||
/// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password
|
||||
/// </param>
|
||||
/// <param name="contentType"></param>
|
||||
public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType)
|
||||
: base(name, -1, contentType, new PropertyCollection())
|
||||
{
|
||||
_email = email;
|
||||
_username = username;
|
||||
_rawPasswordValue = rawPasswordValue;
|
||||
IsApproved = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for creating a Member object
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="rawPasswordValue">
|
||||
/// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password
|
||||
/// </param>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="isApproved"></param>
|
||||
public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved)
|
||||
: base(name, -1, contentType, new PropertyCollection())
|
||||
{
|
||||
_email = email;
|
||||
_username = username;
|
||||
_rawPasswordValue = rawPasswordValue;
|
||||
IsApproved = isApproved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Username
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Username
|
||||
{
|
||||
get => _username;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _username, nameof(Username));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Email
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string Email
|
||||
{
|
||||
get => _email;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _email, nameof(Email));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw password value
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
public string RawPasswordValue
|
||||
{
|
||||
get => _rawPasswordValue;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
//special case, this is used to ensure that the password is not updated when persisting, in this case
|
||||
//we don't want to track changes either
|
||||
_rawPasswordValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, nameof(RawPasswordValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Groups that Member is part of
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public IEnumerable<string> Groups { get; set; }
|
||||
|
||||
// TODO: When get/setting all of these properties we MUST:
|
||||
// * Check if we are using the umbraco membership provider, if so then we need to use the configured fields - not the explicit fields below
|
||||
// * If any of the fields don't exist, what should we do? Currently it will throw an exception!
|
||||
|
||||
/// <summary>
|
||||
/// Gets or set the comments for the member
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberComments
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public string Comments
|
||||
{
|
||||
get
|
||||
{
|
||||
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, nameof(Comments), default(string));
|
||||
if (a.Success == false) return a.Result;
|
||||
|
||||
return Properties[Constants.Conventions.Member.Comments].GetValue() == null
|
||||
? string.Empty
|
||||
: Properties[Constants.Conventions.Member.Comments].GetValue().ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.Comments,
|
||||
nameof(Comments)) == false) return;
|
||||
|
||||
Properties[Constants.Conventions.Member.Comments].SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the Member is approved
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberApproved
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public bool IsApproved
|
||||
{
|
||||
get
|
||||
{
|
||||
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsApproved, nameof(IsApproved),
|
||||
//This is the default value if the prop is not found
|
||||
true);
|
||||
if (a.Success == false) return a.Result;
|
||||
if (Properties[Constants.Conventions.Member.IsApproved].GetValue() == null) return true;
|
||||
var tryConvert = Properties[Constants.Conventions.Member.IsApproved].GetValue().TryConvertTo<bool>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
//if the property exists but it cannot be converted, we will assume true
|
||||
return true;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.IsApproved,
|
||||
nameof(IsApproved)) == false) return;
|
||||
|
||||
Properties[Constants.Conventions.Member.IsApproved].SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the Member is locked out
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberLockedOut
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public bool IsLockedOut
|
||||
{
|
||||
get
|
||||
{
|
||||
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.IsLockedOut, nameof(IsLockedOut), false);
|
||||
if (a.Success == false) return a.Result;
|
||||
if (Properties[Constants.Conventions.Member.IsLockedOut].GetValue() == null) return false;
|
||||
var tryConvert = Properties[Constants.Conventions.Member.IsLockedOut].GetValue().TryConvertTo<bool>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return false;
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.IsLockedOut,
|
||||
nameof(IsLockedOut)) == false) return;
|
||||
|
||||
Properties[Constants.Conventions.Member.IsLockedOut].SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date for last login
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberLastLogin
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public DateTime LastLoginDate
|
||||
{
|
||||
get
|
||||
{
|
||||
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLoginDate, nameof(LastLoginDate), default(DateTime));
|
||||
if (a.Success == false) return a.Result;
|
||||
if (Properties[Constants.Conventions.Member.LastLoginDate].GetValue() == null) return default(DateTime);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.LastLoginDate].GetValue().TryConvertTo<DateTime>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(DateTime);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.LastLoginDate,
|
||||
nameof(LastLoginDate)) == false) return;
|
||||
|
||||
Properties[Constants.Conventions.Member.LastLoginDate].SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gest or sets the date for last password change
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberLastPasswordChangeDate
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public DateTime LastPasswordChangeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastPasswordChangeDate, nameof(LastPasswordChangeDate), default(DateTime));
|
||||
if (a.Success == false) return a.Result;
|
||||
if (Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue() == null) return default(DateTime);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.LastPasswordChangeDate].GetValue().TryConvertTo<DateTime>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(DateTime);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.LastPasswordChangeDate,
|
||||
nameof(LastPasswordChangeDate)) == false) return;
|
||||
|
||||
Properties[Constants.Conventions.Member.LastPasswordChangeDate].SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date for when Member was locked out
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberLastLockoutDate
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public DateTime LastLockoutDate
|
||||
{
|
||||
get
|
||||
{
|
||||
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.LastLockoutDate, nameof(LastLockoutDate), default(DateTime));
|
||||
if (a.Success == false) return a.Result;
|
||||
if (Properties[Constants.Conventions.Member.LastLockoutDate].GetValue() == null) return default(DateTime);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.LastLockoutDate].GetValue().TryConvertTo<DateTime>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(DateTime);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.LastLockoutDate,
|
||||
nameof(LastLockoutDate)) == false) return;
|
||||
|
||||
Properties[Constants.Conventions.Member.LastLockoutDate].SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of failed password attempts.
|
||||
/// This is the number of times the password was entered incorrectly upon login.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Alias: umbracoMemberFailedPasswordAttempts
|
||||
/// Part of the standard properties collection.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public int FailedPasswordAttempts
|
||||
{
|
||||
get
|
||||
{
|
||||
var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.FailedPasswordAttempts, nameof(FailedPasswordAttempts), 0);
|
||||
if (a.Success == false) return a.Result;
|
||||
if (Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue() == null) return default(int);
|
||||
var tryConvert = Properties[Constants.Conventions.Member.FailedPasswordAttempts].GetValue().TryConvertTo<int>();
|
||||
if (tryConvert.Success)
|
||||
{
|
||||
return tryConvert.Result;
|
||||
}
|
||||
return default(int);
|
||||
// TODO: Use TryConvertTo<T> instead
|
||||
}
|
||||
set
|
||||
{
|
||||
if (WarnIfPropertyTypeNotFoundOnSet(
|
||||
Constants.Conventions.Member.FailedPasswordAttempts,
|
||||
nameof(FailedPasswordAttempts)) == false) return;
|
||||
|
||||
Properties[Constants.Conventions.Member.FailedPasswordAttempts].SetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String alias of the default ContentType
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public virtual string ContentTypeAlias => ContentType.Alias;
|
||||
|
||||
/* Internal experiment - only used for mapping queries.
|
||||
* Adding these to have first level properties instead of the Properties collection.
|
||||
*/
|
||||
[IgnoreDataMember]
|
||||
internal string LongStringPropertyValue { get; set; }
|
||||
[IgnoreDataMember]
|
||||
internal string ShortStringPropertyValue { get; set; }
|
||||
[IgnoreDataMember]
|
||||
internal int IntegerPropertyValue { get; set; }
|
||||
[IgnoreDataMember]
|
||||
internal bool BoolPropertyValue { get; set; }
|
||||
[IgnoreDataMember]
|
||||
internal DateTime DateTimePropertyValue { get; set; }
|
||||
[IgnoreDataMember]
|
||||
internal string PropertyTypeAlias { get; set; }
|
||||
|
||||
private Attempt<T> WarnIfPropertyTypeNotFoundOnGet<T>(string propertyAlias, string propertyName, T defaultVal)
|
||||
{
|
||||
void DoLog(string logPropertyAlias, string logPropertyName)
|
||||
{
|
||||
Current.Logger.Warn<Member>("Trying to access the '{PropertyName}' property on '{MemberType}' " +
|
||||
"but the {PropertyAlias} property does not exist on the member type so a default value is returned. " +
|
||||
"Ensure that you have a property type with alias: {PropertyAlias} configured on your member type in order to use the '{PropertyName}' property on the model correctly.",
|
||||
logPropertyName,
|
||||
typeof(Member),
|
||||
logPropertyAlias);
|
||||
}
|
||||
|
||||
// if the property doesn't exist,
|
||||
if (Properties.Contains(propertyAlias) == false)
|
||||
{
|
||||
// put a warn in the log if this entity has been persisted
|
||||
// then return a failure
|
||||
if (HasIdentity)
|
||||
DoLog(propertyAlias, propertyName);
|
||||
return Attempt<T>.Fail(defaultVal);
|
||||
}
|
||||
|
||||
return Attempt<T>.Succeed();
|
||||
}
|
||||
|
||||
private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyName)
|
||||
{
|
||||
void DoLog(string logPropertyAlias, string logPropertyName)
|
||||
{
|
||||
Current.Logger.Warn<Member>("An attempt was made to set a value on the property '{PropertyName}' on type '{MemberType}' but the " +
|
||||
"property type {PropertyAlias} does not exist on the member type, ensure that this property type exists so that setting this property works correctly.",
|
||||
logPropertyName,
|
||||
typeof(Member),
|
||||
logPropertyAlias);
|
||||
}
|
||||
|
||||
// if the property doesn't exist,
|
||||
if (Properties.Contains(propertyAlias) == false)
|
||||
{
|
||||
// put a warn in the log if this entity has been persisted
|
||||
// then return a failure
|
||||
if (HasIdentity)
|
||||
DoLog(propertyAlias, propertyName);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
[DoNotClone]
|
||||
public IDictionary<string, object> AdditionalData => _additionalData ?? (_additionalData = new Dictionary<string, object>());
|
||||
|
||||
/// <inheritdoc />
|
||||
[IgnoreDataMember]
|
||||
public bool HasAdditionalData => _additionalData != null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user