Moves ContentTypeBase, Member, ContentTypeComposition, etc... removes the casting

This commit is contained in:
Shannon
2020-05-20 11:33:10 +10:00
parent 9e8bfd039e
commit d444dfd441
7 changed files with 127 additions and 29 deletions

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}