Merge pull request #7120 from umbraco/netcore/feature/AB3649-move-manifest-stuff

Netcore: Move manifest code
This commit is contained in:
Shannon Deminick
2019-11-14 12:10:25 +11:00
committed by GitHub
129 changed files with 935 additions and 705 deletions

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Core.Models
{
private int _contentTypeId;
private int _writerId;
private PropertyCollection _properties;
private IPropertyCollection _properties;
private ContentCultureInfosCollection _cultureInfos;
internal IReadOnlyList<PropertyType> AllPropertyTypes { get; }
@@ -135,7 +135,7 @@ namespace Umbraco.Core.Models
/// </remarks>
[DataMember]
[DoNotClone]
public PropertyCollection Properties
public IPropertyCollection Properties
{
get => _properties;
set
@@ -490,7 +490,7 @@ namespace Umbraco.Core.Models
if (clonedContent._properties != null)
{
clonedContent._properties.CollectionChanged -= PropertiesChanged; //clear this event handler if any
clonedContent._properties = (PropertyCollection)_properties.DeepClone(); //manually deep clone
clonedContent._properties = (IPropertyCollection)_properties.DeepClone(); //manually deep clone
clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler
}

View File

@@ -1,77 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a scheduled action for a document.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class ContentSchedule : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentSchedule"/> class.
/// </summary>
public ContentSchedule(string culture, DateTime date, ContentScheduleAction action)
{
Id = Guid.Empty; // will be assigned by document repository
Culture = culture;
Date = date;
Action = action;
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentSchedule"/> class.
/// </summary>
public ContentSchedule(Guid id, string culture, DateTime date, ContentScheduleAction action)
{
Id = id;
Culture = culture;
Date = date;
Action = action;
}
/// <summary>
/// Gets the unique identifier of the document targeted by the scheduled action.
/// </summary>
[DataMember]
public Guid Id { get; internal set; }
/// <summary>
/// Gets the culture of the scheduled action.
/// </summary>
/// <remarks>
/// string.Empty represents the invariant culture.
/// </remarks>
[DataMember]
public string Culture { get; }
/// <summary>
/// Gets the date of the scheduled action.
/// </summary>
[DataMember]
public DateTime Date { get; }
/// <summary>
/// Gets the action to take.
/// </summary>
[DataMember]
public ContentScheduleAction Action { get; }
public override bool Equals(object obj)
=> obj is ContentSchedule other && Equals(other);
public bool Equals(ContentSchedule other)
{
// don't compare Ids, two ContentSchedule are equal if they are for the same change
// for the same culture, on the same date - and the collection deals w/duplicates
return Culture.InvariantEquals(other.Culture) && Date == other.Date && Action == other.Action;
}
public object DeepClone()
{
return new ContentSchedule(Id, Culture, Date, Action);
}
}
}

View File

@@ -1,18 +0,0 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines scheduled actions for documents.
/// </summary>
public enum ContentScheduleAction
{
/// <summary>
/// Release the document.
/// </summary>
Release,
/// <summary>
/// Expire the document.
/// </summary>
Expire
}
}

View File

@@ -1,223 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Umbraco.Core.Models
{
public class ContentScheduleCollection : INotifyCollectionChanged, IDeepCloneable, IEquatable<ContentScheduleCollection>
{
//underlying storage for the collection backed by a sorted list so that the schedule is always in order of date and that duplicate dates per culture are not allowed
private readonly Dictionary<string, SortedList<DateTime, ContentSchedule>> _schedule
= new Dictionary<string, SortedList<DateTime, ContentSchedule>>(StringComparer.InvariantCultureIgnoreCase);
public event NotifyCollectionChangedEventHandler CollectionChanged;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
CollectionChanged?.Invoke(this, args);
}
/// <summary>
/// Add an existing schedule
/// </summary>
/// <param name="schedule"></param>
public void Add(ContentSchedule schedule)
{
if (!_schedule.TryGetValue(schedule.Culture, out var changes))
{
changes = new SortedList<DateTime, ContentSchedule>();
_schedule[schedule.Culture] = changes;
}
// TODO: Below will throw if there are duplicate dates added, validate/return bool?
changes.Add(schedule.Date, schedule);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, schedule));
}
/// <summary>
/// Adds a new schedule for invariant content
/// </summary>
/// <param name="releaseDate"></param>
/// <param name="expireDate"></param>
public bool Add(DateTime? releaseDate, DateTime? expireDate)
{
return Add(string.Empty, releaseDate, expireDate);
}
/// <summary>
/// Adds a new schedule for a culture
/// </summary>
/// <param name="culture"></param>
/// <param name="releaseDate"></param>
/// <param name="expireDate"></param>
/// <returns>true if successfully added, false if validation fails</returns>
public bool Add(string culture, DateTime? releaseDate, DateTime? expireDate)
{
if (culture == null) throw new ArgumentNullException(nameof(culture));
if (releaseDate.HasValue && expireDate.HasValue && releaseDate >= expireDate)
return false;
if (!releaseDate.HasValue && !expireDate.HasValue) return false;
// TODO: Do we allow passing in a release or expiry date that is before now?
if (!_schedule.TryGetValue(culture, out var changes))
{
changes = new SortedList<DateTime, ContentSchedule>();
_schedule[culture] = changes;
}
// TODO: Below will throw if there are duplicate dates added, should validate/return bool?
// but the bool won't indicate which date was in error, maybe have 2 diff methods to schedule start/end?
if (releaseDate.HasValue)
{
var entry = new ContentSchedule(culture, releaseDate.Value, ContentScheduleAction.Release);
changes.Add(releaseDate.Value, entry);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry));
}
if (expireDate.HasValue)
{
var entry = new ContentSchedule(culture, expireDate.Value, ContentScheduleAction.Expire);
changes.Add(expireDate.Value, entry);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry));
}
return true;
}
/// <summary>
/// Remove a scheduled change
/// </summary>
/// <param name="change"></param>
public void Remove(ContentSchedule change)
{
if (_schedule.TryGetValue(change.Culture, out var s))
{
var removed = s.Remove(change.Date);
if (removed)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, change));
if (s.Count == 0)
_schedule.Remove(change.Culture);
}
}
}
/// <summary>
/// Clear all of the scheduled change type for invariant content
/// </summary>
/// <param name="action"></param>
/// <param name="changeDate">If specified, will clear all entries with dates less than or equal to the value</param>
public void Clear(ContentScheduleAction action, DateTime? changeDate = null)
{
Clear(string.Empty, action, changeDate);
}
/// <summary>
/// Clear all of the scheduled change type for the culture
/// </summary>
/// <param name="culture"></param>
/// <param name="action"></param>
/// <param name="date">If specified, will clear all entries with dates less than or equal to the value</param>
public void Clear(string culture, ContentScheduleAction action, DateTime? date = null)
{
if (!_schedule.TryGetValue(culture, out var schedules))
return;
var removes = schedules.Where(x => x.Value.Action == action && (!date.HasValue || x.Value.Date <= date.Value)).ToList();
foreach (var remove in removes)
{
var removed = schedules.Remove(remove.Value.Date);
if (!removed)
continue;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, remove.Value));
}
if (schedules.Count == 0)
_schedule.Remove(culture);
}
/// <summary>
/// Returns all pending schedules based on the date and type provided
/// </summary>
/// <param name="action"></param>
/// <param name="date"></param>
/// <returns></returns>
public IReadOnlyList<ContentSchedule> GetPending(ContentScheduleAction action, DateTime date)
{
return _schedule.Values.SelectMany(x => x.Values).Where(x => x.Date <= date).ToList();
}
/// <summary>
/// Gets the schedule for invariant content
/// </summary>
/// <returns></returns>
public IEnumerable<ContentSchedule> GetSchedule(ContentScheduleAction? action = null)
{
return GetSchedule(string.Empty, action);
}
/// <summary>
/// Gets the schedule for a culture
/// </summary>
/// <param name="culture"></param>
/// <param name="action"></param>
/// <returns></returns>
public IEnumerable<ContentSchedule> GetSchedule(string culture, ContentScheduleAction? action = null)
{
if (_schedule.TryGetValue(culture, out var changes))
return action == null ? changes.Values : changes.Values.Where(x => x.Action == action.Value);
return Enumerable.Empty<ContentSchedule>();
}
/// <summary>
/// Returns all schedules registered
/// </summary>
/// <returns></returns>
public IReadOnlyList<ContentSchedule> FullSchedule => _schedule.SelectMany(x => x.Value.Values).ToList();
public object DeepClone()
{
var clone = new ContentScheduleCollection();
foreach(var cultureSched in _schedule)
{
var list = new SortedList<DateTime, ContentSchedule>();
foreach (var schedEntry in cultureSched.Value)
list.Add(schedEntry.Key, (ContentSchedule)schedEntry.Value.DeepClone());
clone._schedule[cultureSched.Key] = list;
}
return clone;
}
public override bool Equals(object obj)
=> obj is ContentScheduleCollection other && Equals(other);
public bool Equals(ContentScheduleCollection other)
{
if (other == null) return false;
var thisSched = _schedule;
var thatSched = other._schedule;
if (thisSched.Count != thatSched.Count)
return false;
foreach (var (culture, thisList) in thisSched)
{
// if culture is missing, or actions differ, false
if (!thatSched.TryGetValue(culture, out var thatList) || !thatList.SequenceEqual(thisList))
return false;
}
return true;
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Umbraco.Core.Models
}
// gets and validates the property
private static Property GetTagProperty(this IContentBase content, string propertyTypeAlias)
private static IProperty GetTagProperty(this IContentBase content, string propertyTypeAlias)
{
if (content == null) throw new ArgumentNullException(nameof(content));

View File

@@ -1,134 +0,0 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a document.
/// </summary>
/// <remarks>
/// <para>A document can be published, rendered by a template.</para>
/// </remarks>
public interface IContent : IContentBase
{
/// <summary>
/// Gets or sets the content schedule
/// </summary>
ContentScheduleCollection ContentSchedule { get; set; }
/// <summary>
/// Gets or sets the template id used to render the content.
/// </summary>
int? TemplateId { get; set; }
/// <summary>
/// Gets a value indicating whether the content is published.
/// </summary>
bool Published { get; set; }
PublishedState PublishedState { get; set; }
/// <summary>
/// Gets a value indicating whether the content has been edited.
/// </summary>
bool Edited { get; set; }
/// <summary>
/// Gets the published version identifier.
/// </summary>
int PublishedVersionId { get; set; }
/// <summary>
/// Gets a value indicating whether the content item is a blueprint.
/// </summary>
bool Blueprint { get; set; }
/// <summary>
/// Gets the template id used to render the published version of the content.
/// </summary>
/// <remarks>When editing the content, the template can change, but this will not until the content is published.</remarks>
int? PublishTemplateId { get; set; }
/// <summary>
/// Gets the name of the published version of the content.
/// </summary>
/// <remarks>When editing the content, the name can change, but this will not until the content is published.</remarks>
string PublishName { get; set; }
/// <summary>
/// Gets the identifier of the user who published the content.
/// </summary>
int? PublisherId { get; set; }
/// <summary>
/// Gets the date and time the content was published.
/// </summary>
DateTime? PublishDate { get; set; }
/// <summary>
/// Gets a value indicating whether a culture is published.
/// </summary>
/// <remarks>
/// <para>A culture becomes published whenever values for this culture are published,
/// and the content published name for this culture is non-null. It becomes non-published
/// whenever values for this culture are unpublished.</para>
/// <para>A culture becomes published as soon as PublishCulture has been invoked,
/// even though the document might not have been saved yet (and can have no identity).</para>
/// <para>Does not support the '*' wildcard (returns false).</para>
/// </remarks>
bool IsCulturePublished(string culture);
/// <summary>
/// Gets the date a culture was published.
/// </summary>
DateTime? GetPublishDate(string culture);
/// <summary>
/// Gets a value indicated whether a given culture is edited.
/// </summary>
/// <remarks>
/// <para>A culture is edited when it is available, and not published or published but
/// with changes.</para>
/// <para>A culture can be edited even though the document might now have been saved yet (and can have no identity).</para>
/// <para>Does not support the '*' wildcard (returns false).</para>
/// </remarks>
bool IsCultureEdited(string culture);
/// <summary>
/// Gets the name of the published version of the content for a given culture.
/// </summary>
/// <remarks>
/// <para>When editing the content, the name can change, but this will not until the content is published.</para>
/// <para>When <paramref name="culture"/> is <c>null</c>, gets the invariant
/// language, which is the value of the <see cref="PublishName"/> property.</para>
/// </remarks>
string GetPublishName(string culture);
/// <summary>
/// Gets the published culture infos of the content.
/// </summary>
/// <remarks>
/// <para>Because a dictionary key cannot be <c>null</c> this cannot get the invariant
/// name, which must be get via the <see cref="PublishName"/> property.</para>
/// </remarks>
ContentCultureInfosCollection PublishCultureInfos { get; set; }
/// <summary>
/// Gets the published cultures.
/// </summary>
IEnumerable<string> PublishedCultures { get; }
/// <summary>
/// Gets the edited cultures.
/// </summary>
IEnumerable<string> EditedCultures { get; set; }
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <returns></returns>
IContent DeepCloneWithResetIdentities();
}
}

View File

@@ -1,129 +0,0 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides a base class for content items.
/// </summary>
/// <remarks>
/// <para>Content items are documents, medias and members.</para>
/// <para>Content items have a content type, and properties.</para>
/// </remarks>
public interface IContentBase : IUmbracoEntity
{
/// <summary>
/// Integer Id of the default ContentType
/// </summary>
int ContentTypeId { get; }
/// <summary>
/// Gets the content type of this content.
/// </summary>
ISimpleContentType ContentType { get; }
/// <summary>
/// Gets the identifier of the writer.
/// </summary>
int WriterId { get; set; }
/// <summary>
/// Gets the version identifier.
/// </summary>
int VersionId { get; set; }
/// <summary>
/// Sets the name of the content item for a specified culture.
/// </summary>
/// <remarks>
/// <para>When <paramref name="culture"/> is null, sets the invariant
/// culture name, which sets the <see cref="TreeEntityBase.Name"/> property.</para>
/// <para>When <paramref name="culture"/> is not null, throws if the content
/// type does not vary by culture.</para>
/// </remarks>
void SetCultureName(string value, string culture);
/// <summary>
/// Gets the name of the content item for a specified language.
/// </summary>
/// <remarks>
/// <para>When <paramref name="culture"/> is null, gets the invariant
/// culture name, which is the value of the <see cref="TreeEntityBase.Name"/> property.</para>
/// <para>When <paramref name="culture"/> is not null, and the content type
/// does not vary by culture, returns null.</para>
/// </remarks>
string GetCultureName(string culture);
/// <summary>
/// Gets culture infos of the content item.
/// </summary>
/// <remarks>
/// <para>Because a dictionary key cannot be <c>null</c> this cannot contain the invariant
/// culture name, which must be get or set via the <see cref="TreeEntityBase.Name"/> property.</para>
/// </remarks>
ContentCultureInfosCollection CultureInfos { get; set; }
/// <summary>
/// Gets the available cultures.
/// </summary>
/// <remarks>
/// <para>Cannot contain the invariant culture, which is always available.</para>
/// </remarks>
IEnumerable<string> AvailableCultures { get; }
/// <summary>
/// Gets a value indicating whether a given culture is available.
/// </summary>
/// <remarks>
/// <para>A culture becomes available whenever the content name for this culture is
/// non-null, and it becomes unavailable whenever the content name is null.</para>
/// <para>Returns <c>false</c> for the invariant culture, in order to be consistent
/// with <seealso cref="AvailableCultures"/>, even though the invariant culture is
/// always available.</para>
/// <para>Does not support the '*' wildcard (returns false).</para>
/// </remarks>
bool IsCultureAvailable(string culture);
/// <summary>
/// Gets the date a culture was updated.
/// </summary>
/// <remarks>
/// <para>When <paramref name="culture" /> is <c>null</c>, returns <c>null</c>.</para>
/// <para>If the specified culture is not available, returns <c>null</c>.</para>
/// </remarks>
DateTime? GetUpdateDate(string culture);
/// <summary>
/// List of properties, which make up all the data available for this Content object
/// </summary>
/// <remarks>Properties are loaded as part of the Content object graph</remarks>
PropertyCollection Properties { get; set; }
/// <summary>
/// Gets a value indicating whether the content entity has a property with the supplied alias.
/// </summary>
/// <remarks>Indicates that the content entity has a property with the supplied alias, but
/// not necessarily that the content has a value for that property. Could be missing.</remarks>
bool HasProperty(string propertyTypeAlias);
/// <summary>
/// Gets the value of a Property
/// </summary>
/// <remarks>Values 'null' and 'empty' are equivalent for culture and segment.</remarks>
object GetValue(string propertyTypeAlias, string culture = null, string segment = null, bool published = false);
/// <summary>
/// Gets the typed value of a Property
/// </summary>
/// <remarks>Values 'null' and 'empty' are equivalent for culture and segment.</remarks>
TValue GetValue<TValue>(string propertyTypeAlias, string culture = null, string segment = null, bool published = false);
/// <summary>
/// Sets the (edited) value of a Property
/// </summary>
/// <remarks>Values 'null' and 'empty' are equivalent for culture and segment.</remarks>
void SetValue(string propertyTypeAlias, object value, string culture = null, string segment = null);
}
}

View File

@@ -1,70 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Services;
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Represents an editor for editing data values.
/// </summary>
/// <remarks>This is the base interface for parameter and property value editors.</remarks>
public interface IDataValueEditor
{
/// <summary>
/// Gets the editor view.
/// </summary>
string View { get; }
/// <summary>
/// Gets the type of the value.
/// </summary>
/// <remarks>The value has to be a valid <see cref="ValueTypes"/> value.</remarks>
string ValueType { get; set; }
/// <summary>
/// Gets a value indicating whether the edited value is read-only.
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Gets a value indicating whether to display the associated label.
/// </summary>
bool HideLabel { get; }
/// <summary>
/// Validates a property value.
/// </summary>
/// <param name="value">The property value.</param>
/// <param name="required">A value indicating whether the property value is required.</param>
/// <param name="format">A specific format (regex) that the property value must respect.</param>
IEnumerable<ValidationResult> Validate(object value, bool required, string format);
/// <summary>
/// Gets the validators to use to validate the edited value.
/// </summary>
/// <remarks>
/// <para>Use this property to add validators, not to validate. Use <see cref="Validate"/> instead.</para>
/// TODO: replace with AddValidator? WithValidator?
/// </remarks>
List<IValueValidator> Validators { get; }
/// <summary>
/// Converts a value posted by the editor to a property value.
/// </summary>
object FromEditor(ContentPropertyData editorValue, object currentValue);
/// <summary>
/// Converts a property value to a value for the editor.
/// </summary>
object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null);
// TODO: / deal with this when unplugging the xml cache
// why property vs propertyType? services should be injected! etc...
IEnumerable<XElement> ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published);
XNode ConvertDbToXml(PropertyType propertyType, object value, IDataTypeService dataTypeService);
string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService);
}
}

View File

@@ -1,5 +0,0 @@
namespace Umbraco.Core.Models
{
public interface IMedia : IContentBase
{ }
}

View File

@@ -13,16 +13,16 @@ namespace Umbraco.Core.Models
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class Property : EntityBase
public class Property : EntityBase, IProperty
{
// _values contains all property values, including the invariant-neutral value
private List<PropertyValue> _values = new List<PropertyValue>();
private List<IPropertyValue> _values = new List<IPropertyValue>();
// _pvalue contains the invariant-neutral property value
private PropertyValue _pvalue;
private IPropertyValue _pvalue;
// _vvalues contains the (indexed) variant property values
private Dictionary<CompositeNStringNStringKey, PropertyValue> _vvalues;
private Dictionary<CompositeNStringNStringKey, IPropertyValue> _vvalues;
/// <summary>
/// Initializes a new instance of the <see cref="Property"/> class.
@@ -33,7 +33,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Initializes a new instance of the <see cref="Property"/> class.
/// </summary>
public Property(PropertyType propertyType)
public Property(IPropertyType propertyType)
{
PropertyType = propertyType;
}
@@ -41,7 +41,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Initializes a new instance of the <see cref="Property"/> class.
/// </summary>
public Property(int id, PropertyType propertyType)
public Property(int id, IPropertyType propertyType)
{
Id = id;
PropertyType = propertyType;
@@ -50,7 +50,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Represents a property value.
/// </summary>
public class PropertyValue
public class PropertyValue : IPropertyValue
{
// TODO: Either we allow change tracking at this class level, or we add some special change tracking collections to the Property
// class to deal with change tracking which variants have changed
@@ -66,7 +66,7 @@ namespace Umbraco.Core.Models
public string Culture
{
get => _culture;
internal set => _culture = value.IsNullOrWhiteSpace() ? null : value.ToLowerInvariant();
set => _culture = value.IsNullOrWhiteSpace() ? null : value.ToLowerInvariant();
}
/// <summary>
@@ -77,23 +77,23 @@ namespace Umbraco.Core.Models
public string Segment
{
get => _segment;
internal set => _segment = value?.ToLowerInvariant();
set => _segment = value?.ToLowerInvariant();
}
/// <summary>
/// Gets or sets the edited value of the property.
/// </summary>
public object EditedValue { get; internal set; }
public object EditedValue { get; set; }
/// <summary>
/// Gets or sets the published value of the property.
/// </summary>
public object PublishedValue { get; internal set; }
public object PublishedValue { get; set; }
/// <summary>
/// Clones the property value.
/// </summary>
public PropertyValue Clone()
public IPropertyValue Clone()
=> new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue };
}
@@ -121,13 +121,13 @@ namespace Umbraco.Core.Models
/// Returns the PropertyType, which this Property is based on
/// </summary>
[IgnoreDataMember]
public PropertyType PropertyType { get; private set; }
public IPropertyType PropertyType { get; private set; }
/// <summary>
/// Gets the list of values.
/// </summary>
[DataMember]
public IReadOnlyCollection<PropertyValue> Values
public IReadOnlyCollection<IPropertyValue> Values
{
get => _values;
set
@@ -152,7 +152,7 @@ namespace Umbraco.Core.Models
/// Returns the Id of the PropertyType, which this Property is based on
/// </summary>
[IgnoreDataMember]
internal int PropertyTypeId => PropertyType.Id;
public int PropertyTypeId => PropertyType.Id;
/// <summary>
/// Returns the DatabaseType that the underlaying DataType is using to store its values
@@ -161,7 +161,7 @@ namespace Umbraco.Core.Models
/// Only used internally when saving the property value.
/// </remarks>
[IgnoreDataMember]
internal ValueStorageType ValueStorageType => PropertyType.ValueStorageType;
public ValueStorageType ValueStorageType => PropertyType.ValueStorageType;
/// <summary>
/// Gets the value.
@@ -180,7 +180,7 @@ namespace Umbraco.Core.Models
: null;
}
private object GetPropertyValue(PropertyValue pvalue, bool published)
private object GetPropertyValue(IPropertyValue pvalue, bool published)
{
if (pvalue == null) return null;
@@ -191,7 +191,7 @@ namespace Umbraco.Core.Models
// internal - must be invoked by the content item
// does *not* validate the value - content item must validate first
internal void PublishValues(string culture = "*", string segment = "*")
public void PublishValues(string culture = "*", string segment = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
segment = segment.NullOrWhiteSpaceAsNull();
@@ -216,7 +216,7 @@ namespace Umbraco.Core.Models
}
// internal - must be invoked by the content item
internal void UnpublishValues(string culture = "*", string segment = "*")
public void UnpublishValues(string culture = "*", string segment = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
segment = segment.NullOrWhiteSpaceAsNull();
@@ -240,7 +240,7 @@ namespace Umbraco.Core.Models
UnpublishValue(pvalue);
}
private void PublishValue(PropertyValue pvalue)
private void PublishValue(IPropertyValue pvalue)
{
if (pvalue == null) return;
@@ -251,7 +251,7 @@ namespace Umbraco.Core.Models
DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false);
}
private void UnpublishValue(PropertyValue pvalue)
private void UnpublishValue(IPropertyValue pvalue)
{
if (pvalue == null) return;
@@ -294,7 +294,7 @@ namespace Umbraco.Core.Models
pvalue.EditedValue = value;
}
private (PropertyValue, bool) GetPValue(bool create)
private (IPropertyValue, bool) GetPValue(bool create)
{
var change = false;
if (_pvalue == null)
@@ -307,7 +307,7 @@ namespace Umbraco.Core.Models
return (_pvalue, change);
}
private (PropertyValue, bool) GetPValue(string culture, string segment, bool create)
private (IPropertyValue, bool) GetPValue(string culture, string segment, bool create)
{
if (culture == null && segment == null)
return GetPValue(create);
@@ -316,7 +316,7 @@ namespace Umbraco.Core.Models
if (_vvalues == null)
{
if (!create) return (null, false);
_vvalues = new Dictionary<CompositeNStringNStringKey, PropertyValue>();
_vvalues = new Dictionary<CompositeNStringNStringKey, IPropertyValue>();
change = true;
}
var k = new CompositeNStringNStringKey(culture, segment);

View File

@@ -12,11 +12,9 @@ namespace Umbraco.Core.Models
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class PropertyCollection : KeyedCollection<string, Property>, INotifyCollectionChanged, IDeepCloneable
public class PropertyCollection : KeyedCollection<string, IProperty>, IPropertyCollection
{
private readonly object _addLocker = new object();
internal Func<Property, bool> AdditionValidator { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PropertyCollection"/> class.
@@ -25,16 +23,6 @@ namespace Umbraco.Core.Models
: base(StringComparer.InvariantCultureIgnoreCase)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="PropertyCollection"/> class.
/// </summary>
/// <param name="additionValidator">A function validating added properties.</param>
internal PropertyCollection(Func<Property, bool> additionValidator)
: this()
{
AdditionValidator = additionValidator;
}
/// <summary>
/// Initializes a new instance of the <see cref="PropertyCollection"/> class.
/// </summary>
@@ -47,7 +35,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Replaces all properties, whilst maintaining validation delegates.
/// </summary>
internal void Reset(IEnumerable<Property> properties)
private void Reset(IEnumerable<Property> properties)
{
//collection events will be raised in each of these calls
Clear();
@@ -60,7 +48,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Replaces the property at the specified index with the specified property.
/// </summary>
protected override void SetItem(int index, Property property)
protected override void SetItem(int index, IProperty property)
{
var oldItem = index >= 0 ? this[index] : property;
base.SetItem(index, property);
@@ -80,7 +68,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Inserts the specified property at the specified index.
/// </summary>
protected override void InsertItem(int index, Property property)
protected override void InsertItem(int index, IProperty property)
{
base.InsertItem(index, property);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property));
@@ -95,10 +83,8 @@ namespace Umbraco.Core.Models
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
/// Adds or updates a property.
/// </summary>
internal new void Add(Property property)
/// <inheritdoc />
public new void Add(IProperty property)
{
lock (_addLocker) // TODO: why are we locking here and not everywhere else?!
{
@@ -131,7 +117,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Gets the index for a specified property alias.
/// </summary>
public int IndexOfKey(string key)
private int IndexOfKey(string key)
{
for (var i = 0; i < Count; i++)
{
@@ -141,7 +127,7 @@ namespace Umbraco.Core.Models
return -1;
}
protected override string GetKeyForItem(Property item)
protected override string GetKeyForItem(IProperty item)
{
return item.Alias;
}
@@ -149,7 +135,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Gets the property with the specified PropertyType.
/// </summary>
internal Property this[PropertyType propertyType]
internal IProperty this[IPropertyType propertyType]
{
get
{
@@ -157,7 +143,7 @@ namespace Umbraco.Core.Models
}
}
public bool TryGetValue(string propertyTypeAlias, out Property property)
public bool TryGetValue(string propertyTypeAlias, out IProperty property)
{
property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
return property != null;
@@ -173,10 +159,9 @@ namespace Umbraco.Core.Models
CollectionChanged?.Invoke(this, args);
}
/// <summary>
/// Ensures that the collection contains properties for the specified property types.
/// </summary>
protected internal void EnsurePropertyTypes(IEnumerable<PropertyType> propertyTypes)
/// <inheritdoc />
public void EnsurePropertyTypes(IEnumerable<IPropertyType> propertyTypes)
{
if (propertyTypes == null)
return;
@@ -185,10 +170,9 @@ namespace Umbraco.Core.Models
Add(new Property(propertyType));
}
/// <summary>
/// Ensures that the collection does not contain properties not in the specified property types.
/// </summary>
protected internal void EnsureCleanPropertyTypes(IEnumerable<PropertyType> propertyTypes)
/// <inheritdoc />
public void EnsureCleanPropertyTypes(IEnumerable<IPropertyType> propertyTypes)
{
if (propertyTypes == null)
return;

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Core.Models
// gets the tag configuration for a property
// from the datatype configuration, and the editor tag configuration attribute
internal static TagConfiguration GetTagConfiguration(this Property property)
internal static TagConfiguration GetTagConfiguration(this IProperty property)
{
if (property == null) throw new ArgumentNullException(nameof(property));
@@ -44,7 +44,7 @@ namespace Umbraco.Core.Models
/// <param name="tags">The tags.</param>
/// <param name="merge">A value indicating whether to merge the tags with existing tags instead of replacing them.</param>
/// <param name="culture">A culture, for multi-lingual properties.</param>
public static void AssignTags(this Property property, IEnumerable<string> tags, bool merge = false, string culture = null)
public static void AssignTags(this IProperty property, IEnumerable<string> tags, bool merge = false, string culture = null)
{
if (property == null) throw new ArgumentNullException(nameof(property));
@@ -56,7 +56,7 @@ namespace Umbraco.Core.Models
}
// assumes that parameters are consistent with the datatype configuration
private static void AssignTags(this Property property, IEnumerable<string> tags, bool merge, TagsStorageType storageType, char delimiter, string culture)
private static void AssignTags(this IProperty property, IEnumerable<string> tags, bool merge, TagsStorageType storageType, char delimiter, string culture)
{
// set the property value
var trimmedTags = tags.Select(x => x.Trim()).ToArray();
@@ -97,7 +97,7 @@ namespace Umbraco.Core.Models
/// <param name="property">The property.</param>
/// <param name="tags">The tags.</param>
/// <param name="culture">A culture, for multi-lingual properties.</param>
public static void RemoveTags(this Property property, IEnumerable<string> tags, string culture = null)
public static void RemoveTags(this IProperty property, IEnumerable<string> tags, string culture = null)
{
if (property == null) throw new ArgumentNullException(nameof(property));
@@ -109,7 +109,7 @@ namespace Umbraco.Core.Models
}
// assumes that parameters are consistent with the datatype configuration
private static void RemoveTags(this Property property, IEnumerable<string> tags, TagsStorageType storageType, char delimiter, string culture)
private static void RemoveTags(this IProperty property, IEnumerable<string> tags, TagsStorageType storageType, char delimiter, string culture)
{
// already empty = nothing to do
var value = property.GetValue(culture)?.ToString();
@@ -131,7 +131,7 @@ namespace Umbraco.Core.Models
}
// used by ContentRepositoryBase
internal static IEnumerable<string> GetTagsValue(this Property property, string culture = null)
internal static IEnumerable<string> GetTagsValue(this IProperty property, string culture = null)
{
if (property == null) throw new ArgumentNullException(nameof(property));
@@ -142,7 +142,7 @@ namespace Umbraco.Core.Models
return property.GetTagsValue(configuration.StorageType, configuration.Delimiter, culture);
}
private static IEnumerable<string> GetTagsValue(this Property property, TagsStorageType storageType, char delimiter, string culture = null)
private static IEnumerable<string> GetTagsValue(this IProperty property, TagsStorageType storageType, char delimiter, string culture = null)
{
if (property == null) throw new ArgumentNullException(nameof(property));
@@ -182,7 +182,7 @@ namespace Umbraco.Core.Models
/// <para>This is used both by the content repositories to initialize a property with some tag values, and by the
/// content controllers to update a property with values received from the property editor.</para>
/// </remarks>
internal static void SetTagsValue(this Property property, object value, TagConfiguration tagConfiguration, string culture)
internal static void SetTagsValue(this IProperty property, object value, TagConfiguration tagConfiguration, string culture)
{
if (property == null) throw new ArgumentNullException(nameof(property));
if (tagConfiguration == null) throw new ArgumentNullException(nameof(tagConfiguration));
@@ -195,7 +195,7 @@ namespace Umbraco.Core.Models
// assumes that parameters are consistent with the datatype configuration
// value can be an enumeration of string, or a serialized value using storageType format
private static void SetTagsValue(Property property, object value, TagsStorageType storageType, char delimiter, string culture)
private static void SetTagsValue(IProperty property, object value, TagsStorageType storageType, char delimiter, string culture)
{
if (value == null) value = Enumerable.Empty<string>();

View File

@@ -13,7 +13,7 @@ namespace Umbraco.Core.Models
[Serializable]
[DataContract(IsReference = true)]
[DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")]
public class PropertyType : EntityBase, IEquatable<PropertyType>
public class PropertyType : EntityBase, IPropertyType, IEquatable<PropertyType>
{
private readonly bool _forceValueStorageType;
private string _name;
@@ -36,7 +36,7 @@ namespace Umbraco.Core.Models
{
if (dataType == null) throw new ArgumentNullException(nameof(dataType));
if(dataType.HasIdentity)
if (dataType.HasIdentity)
_dataTypeId = dataType.Id;
_propertyEditorAlias = dataType.EditorAlias;
@@ -58,14 +58,16 @@ namespace Umbraco.Core.Models
/// </summary>
public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType)
: this(propertyEditorAlias, valueStorageType, false)
{ }
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PropertyType"/> class.
/// </summary>
public PropertyType(string propertyEditorAlias, ValueStorageType valueStorageType, string propertyTypeAlias)
: this(propertyEditorAlias, valueStorageType, false, propertyTypeAlias)
{ }
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PropertyType"/> class.
@@ -99,9 +101,7 @@ namespace Umbraco.Core.Models
/// </remarks>
public bool SupportsPublishing { get; internal set; }
/// <summary>
/// Gets of sets the name of the property type.
/// </summary>
/// <inheritdoc />
[DataMember]
public string Name
{
@@ -109,9 +109,7 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
}
/// <summary>
/// Gets of sets the alias of the property type.
/// </summary>
/// <inheritdoc />
[DataMember]
public virtual string Alias
{
@@ -119,9 +117,7 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias, nameof(Alias));
}
/// <summary>
/// Gets of sets the description of the property type.
/// </summary>
/// <inheritdoc />
[DataMember]
public string Description
{
@@ -129,9 +125,7 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description));
}
/// <summary>
/// Gets or sets the identifier of the datatype for this property type.
/// </summary>
/// <inheritdoc />
[DataMember]
public int DataTypeId
{
@@ -146,9 +140,7 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey));
}
/// <summary>
/// Gets or sets the alias of the property editor for this property type.
/// </summary>
/// <inheritdoc />
[DataMember]
public string PropertyEditorAlias
{
@@ -156,11 +148,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, nameof(PropertyEditorAlias));
}
/// <summary>
/// Gets or sets the database type for storing value for this property type.
/// </summary>
/// <inheritdoc />
[DataMember]
internal ValueStorageType ValueStorageType
public ValueStorageType ValueStorageType
{
get => _valueStorageType;
set
@@ -170,20 +160,16 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Gets or sets the identifier of the property group this property type belongs to.
/// </summary>
/// <remarks>For generic properties, the value is <c>null</c>.</remarks>
/// <inheritdoc />
[DataMember]
internal Lazy<int> PropertyGroupId
public Lazy<int> PropertyGroupId
{
get => _propertyGroupId;
set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, nameof(PropertyGroupId));
}
/// <summary>
/// Gets of sets a value indicating whether a value for this property type is required.
/// </summary>
/// <inheritdoc />
[DataMember]
public bool Mandatory
{
@@ -191,9 +177,8 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(Mandatory));
}
/// <summary>
/// Gets of sets the sort order of the property type.
/// </summary>
/// <inheritdoc />
[DataMember]
public int SortOrder
{
@@ -201,9 +186,7 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder));
}
/// <summary>
/// Gets or sets the regular expression validating the property values.
/// </summary>
/// <inheritdoc />
[DataMember]
public string ValidationRegExp
{
@@ -211,21 +194,14 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, nameof(ValidationRegExp));
}
/// <summary>
/// Gets or sets the content variation of the property type.
/// </summary>
/// <inheritdoc />
public ContentVariation Variations
{
get => _variations;
set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations));
}
/// <summary>
/// Determines whether the property type supports a combination of culture and segment.
/// </summary>
/// <param name="culture">The culture.</param>
/// <param name="segment">The segment.</param>
/// <param name="wildcards">A value indicating whether wildcards are valid.</param>
/// <inheritdoc />
public bool SupportsVariation(string culture, string segment, bool wildcards = false)
{
// exact validation: cannot accept a 'null' culture if the property type varies
@@ -249,7 +225,7 @@ namespace Umbraco.Core.Models
/// <para>If the value is of the expected type, it can be directly assigned to the property.
/// Otherwise, some conversion is required.</para>
/// </remarks>
public bool IsOfExpectedPropertyType(object value)
private bool IsOfExpectedPropertyType(object value)
{
// null values are assumed to be ok
if (value == null)
@@ -275,19 +251,7 @@ namespace Umbraco.Core.Models
}
}
/// <summary>
/// Determines whether a value can be assigned to a property.
/// </summary>
public bool IsValueAssignable(object value) => TryConvertAssignedValue(value, false, out _);
/// <summary>
/// Converts a value assigned to a property.
/// </summary>
/// <remarks>
/// <para>The input value can be pretty much anything, and is converted to the actual CLR type
/// expected by the property (eg an integer if the property values are integers).</para>
/// <para>Throws if the value cannot be converted.</para>
/// </remarks>
/// <inheritdoc />
public object ConvertAssignedValue(object value) => TryConvertAssignedValue(value, true, out var converted) ? converted : null;
/// <summary>
@@ -296,8 +260,6 @@ namespace Umbraco.Core.Models
/// <remarks>
/// <para></para>
/// </remarks>
public bool TryConvertAssignedValue(object value, out object converted) => TryConvertAssignedValue(value, false, out converted);
private bool TryConvertAssignedValue(object value, bool throwOnError, out object converted)
{
var isOfExpectedType = IsOfExpectedPropertyType(value);
@@ -332,6 +294,7 @@ namespace Umbraco.Core.Models
converted = convInt.Result;
return true;
}
if (throwOnError)
ThrowTypeException(value, typeof(int), Alias);
return false;
@@ -347,6 +310,7 @@ namespace Umbraco.Core.Models
converted = convDecimal.Result.Normalize();
return true;
}
if (throwOnError)
ThrowTypeException(value, typeof(decimal), Alias);
return false;
@@ -360,6 +324,7 @@ namespace Umbraco.Core.Models
converted = convDateTime.Result;
return true;
}
if (throwOnError)
ThrowTypeException(value, typeof(DateTime), Alias);
return false;
@@ -413,7 +378,7 @@ namespace Umbraco.Core.Models
{
base.PerformDeepClone(clone);
var clonedEntity = (PropertyType)clone;
var clonedEntity = (PropertyType) clone;
//need to manually assign the Lazy value as it will not be automatically mapped
if (PropertyGroupId != null)

View File

@@ -105,6 +105,7 @@ namespace Umbraco.Core.Models
// we have to have all this, because we're an IUmbracoEntity, because that is
// required by the query expression visitor / SimpleContentTypeMapper
// TODO: Make the query expression visitor use a different common interface, or investigate removing IRememberBeingDirty from being a requirement on that interface and expliclty checking for that throughout the code?
string ITreeEntity.Name { get => this.Name; set => throw new NotImplementedException(); }
int IEntity.Id { get => this.Id; set => throw new NotImplementedException(); }
@@ -130,5 +131,7 @@ namespace Umbraco.Core.Models
bool ICanBeDirty.IsPropertyDirty(string propName) => throw new NotImplementedException();
IEnumerable<string> ICanBeDirty.GetDirtyProperties() => throw new NotImplementedException();
void ICanBeDirty.ResetDirtyProperties() => throw new NotImplementedException();
public void DisableChangeTracking() => throw new NotImplementedException();
public void EnableChangeTracking() => throw new NotImplementedException();
}
}