Revert "Temp8 tinymce"

This commit is contained in:
Warren Buckley
2018-11-22 14:05:51 +00:00
committed by GitHub
parent 2a0748fc1e
commit 54a2aa00a7
6677 changed files with 646351 additions and 410535 deletions
+148 -170
View File
@@ -1,170 +1,148 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
{
[DebuggerDisplay("Tree - {Title} ({ApplicationAlias})")]
public class ApplicationTree
{
private static readonly ConcurrentDictionary<string, Type> ResolvedTypes = new ConcurrentDictionary<string, Type>();
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationTree"/> class.
/// </summary>
public ApplicationTree() { }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationTree"/> class.
/// </summary>
/// <param name="initialize">if set to <c>true</c> [initialize].</param>
/// <param name="sortOrder">The sort order.</param>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="alias">The tree alias.</param>
/// <param name="title">The tree title.</param>
/// <param name="iconClosed">The icon closed.</param>
/// <param name="iconOpened">The icon opened.</param>
/// <param name="type">The tree type.</param>
public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type)
{
Initialize = initialize;
SortOrder = sortOrder;
ApplicationAlias = applicationAlias;
Alias = alias;
Title = title;
IconClosed = iconClosed;
IconOpened = iconOpened;
Type = type;
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="ApplicationTree"/> should initialize.
/// </summary>
/// <value><c>true</c> if initialize; otherwise, <c>false</c>.</value>
public bool Initialize { get; set; }
/// <summary>
/// Gets or sets the sort order.
/// </summary>
/// <value>The sort order.</value>
public int SortOrder { get; set; }
/// <summary>
/// Gets the application alias.
/// </summary>
/// <value>The application alias.</value>
public string ApplicationAlias { get; }
/// <summary>
/// Gets the tree alias.
/// </summary>
/// <value>The alias.</value>
public string Alias { get; }
/// <summary>
/// Gets or sets the tree title.
/// </summary>
/// <value>The title.</value>
public string Title { get; set; }
/// <summary>
/// Gets or sets the icon closed.
/// </summary>
/// <value>The icon closed.</value>
public string IconClosed { get; set; }
/// <summary>
/// Gets or sets the icon opened.
/// </summary>
/// <value>The icon opened.</value>
public string IconOpened { get; set; }
/// <summary>
/// Gets or sets the tree type assembly name.
/// </summary>
/// <value>The type.</value>
public string Type { get; set; }
/// <summary>
/// Returns the localized root node display name
/// </summary>
/// <param name="textService"></param>
/// <returns></returns>
public string GetRootNodeDisplayName(ILocalizedTextService textService)
{
var label = $"[{Alias}]";
// try to look up a the localized tree header matching the tree alias
var localizedLabel = textService.Localize("treeHeaders/" + Alias);
// if the localizedLabel returns [alias] then return the title attribute from the trees.config file, if it's defined
if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(Title) == false)
label = Title;
}
else
{
// the localizedLabel translated into something that's not just [alias], so use the translation
label = localizedLabel;
}
return label;
}
private Type _runtimeType;
/// <summary>
/// Returns the CLR type based on it's assembly name stored in the config
/// </summary>
/// <returns></returns>
public Type GetRuntimeType()
{
return _runtimeType ?? (_runtimeType = System.Type.GetType(Type));
}
/// <summary>
/// Used to try to get and cache the tree type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
internal static Type TryGetType(string type)
{
try
{
return ResolvedTypes.GetOrAdd(type, s =>
{
var result = System.Type.GetType(type);
if (result != null)
{
return result;
}
//we need to implement a bit of a hack here due to some trees being renamed and backwards compat
var parts = type.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
throw new InvalidOperationException("Could not resolve type");
if (parts[1].Trim() != "Umbraco.Web" || parts[0].StartsWith("Umbraco.Web.Trees") == false || parts[0].EndsWith("Controller"))
throw new InvalidOperationException("Could not resolve type");
//if it's one of our controllers but it's not suffixed with "Controller" then add it and try again
var tempType = parts[0] + "Controller, Umbraco.Web";
result = System.Type.GetType(tempType);
if (result != null)
return result;
throw new InvalidOperationException("Could not resolve type");
});
}
catch (InvalidOperationException)
{
//swallow, this is our own exception, couldn't find the type
// fixme bad use of exceptions here!
return null;
}
}
}
}
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
namespace Umbraco.Core.Models
{
[DebuggerDisplay("Tree - {Title} ({ApplicationAlias})")]
public class ApplicationTree
{
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationTree"/> class.
/// </summary>
public ApplicationTree() { }
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationTree"/> class.
/// </summary>
/// <param name="initialize">if set to <c>true</c> [initialize].</param>
/// <param name="sortOrder">The sort order.</param>
/// <param name="applicationAlias">The application alias.</param>
/// <param name="alias">The tree alias.</param>
/// <param name="title">The tree title.</param>
/// <param name="iconClosed">The icon closed.</param>
/// <param name="iconOpened">The icon opened.</param>
/// <param name="type">The tree type.</param>
public ApplicationTree(bool initialize, int sortOrder, string applicationAlias, string alias, string title, string iconClosed, string iconOpened, string type)
{
this.Initialize = initialize;
this.SortOrder = sortOrder;
this.ApplicationAlias = applicationAlias;
this.Alias = alias;
this.Title = title;
this.IconClosed = iconClosed;
this.IconOpened = iconOpened;
this.Type = type;
}
/// <summary>
/// Gets or sets a value indicating whether this <see cref="ApplicationTree"/> should initialize.
/// </summary>
/// <value><c>true</c> if initialize; otherwise, <c>false</c>.</value>
public bool Initialize { get; set; }
/// <summary>
/// Gets or sets the sort order.
/// </summary>
/// <value>The sort order.</value>
public int SortOrder { get; set; }
/// <summary>
/// Gets the application alias.
/// </summary>
/// <value>The application alias.</value>
public string ApplicationAlias { get; private set; }
/// <summary>
/// Gets the tree alias.
/// </summary>
/// <value>The alias.</value>
public string Alias { get; private set; }
/// <summary>
/// Gets or sets the tree title.
/// </summary>
/// <value>The title.</value>
public string Title { get; set; }
/// <summary>
/// Gets or sets the icon closed.
/// </summary>
/// <value>The icon closed.</value>
public string IconClosed { get; set; }
/// <summary>
/// Gets or sets the icon opened.
/// </summary>
/// <value>The icon opened.</value>
public string IconOpened { get; set; }
/// <summary>
/// Gets or sets the tree type assembly name.
/// </summary>
/// <value>The type.</value>
public string Type { get; set; }
private Type _runtimeType;
/// <summary>
/// Returns the CLR type based on it's assembly name stored in the config
/// </summary>
/// <returns></returns>
public Type GetRuntimeType()
{
if (_runtimeType != null)
return _runtimeType;
_runtimeType = TryGetType(Type);
return _runtimeType;
}
/// <summary>
/// Used to try to get and cache the tree type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
internal static Type TryGetType(string type)
{
try
{
return ResolvedTypes.GetOrAdd(type, s =>
{
var result = System.Type.GetType(type);
if (result != null)
{
return result;
}
//we need to implement a bit of a hack here due to some trees being renamed and backwards compat
var parts = type.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
if (parts[1].Trim() == "umbraco" && parts[0].StartsWith("Umbraco.Web.Trees") && parts[0].EndsWith("Controller") == false)
{
//if it's one of our controllers but it's not suffixed with "Controller" then add it and try again
var tempType = parts[0] + "Controller, umbraco";
result = System.Type.GetType(tempType);
if (result != null)
{
return result;
}
}
}
throw new InvalidOperationException("Could not resolve type");
});
}
catch (InvalidOperationException)
{
//swallow, this is our own exception, couldn't find the type
return null;
}
}
private static readonly ConcurrentDictionary<string, Type> ResolvedTypes = new ConcurrentDictionary<string, Type>();
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
using System;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Models
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class AuditEntry : EntityBase, IAuditEntry
internal class AuditEntry : Entity, IAuditEntry
{
private static PropertySelectors _selectors;
+13 -21
View File
@@ -1,13 +1,17 @@
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
public sealed class AuditItem : EntityBase, IAuditItem
public sealed class AuditItem : Entity, IAuditItem
{
/// <summary>
/// Initializes a new instance of the <see cref="AuditItem"/> class.
/// Constructor for creating an item to be created
/// </summary>
public AuditItem(int objectId, AuditType type, int userId, string entityType, string comment = null, string parameters = null)
/// <param name="objectId"></param>
/// <param name="comment"></param>
/// <param name="type"></param>
/// <param name="userId"></param>
public AuditItem(int objectId, string comment, AuditType type, int userId)
{
DisableChangeTracking();
@@ -15,25 +19,13 @@ namespace Umbraco.Core.Models
Comment = comment;
AuditType = type;
UserId = userId;
EntityType = entityType;
Parameters = parameters;
EnableChangeTracking();
}
/// <inheritdoc/>
public AuditType AuditType { get; }
/// <inheritdoc/>
public string EntityType { get; }
/// <inheritdoc/>
public int UserId { get; }
/// <inheritdoc/>
public string Comment { get; }
/// <inheritdoc/>
public string Parameters { get; }
public string Comment { get; private set; }
public AuditType AuditType { get; private set; }
public int UserId { get; private set; }
}
}
}
+25 -58
View File
@@ -1,118 +1,85 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines audit types.
/// Enums for vailable types of auditing
/// </summary>
public enum AuditType
{
/// <summary>
/// New node(s) being added.
/// Used when new nodes are added
/// </summary>
New,
/// <summary>
/// Node(s) being saved.
/// Used when nodes are saved
/// </summary>
Save,
/// <summary>
/// Variant(s) being saved.
/// </summary>
SaveVariant,
/// <summary>
/// Node(s) being opened.
/// Used when nodes are opened
/// </summary>
Open,
/// <summary>
/// Node(s) being deleted.
/// Used when nodes are deleted
/// </summary>
Delete,
/// <summary>
/// Node(s) being published.
/// Used when nodes are published
/// </summary>
Publish,
/// <summary>
/// Variant(s) being published.
/// </summary>
PublishVariant,
/// <summary>
/// Node(s) being sent to publishing.
/// Used when nodes are send to publishing
/// </summary>
SendToPublish,
/// <summary>
/// Variant(s) being sent to publishing.
/// </summary>
SendToPublishVariant,
/// <summary>
/// Node(s) being unpublished.
/// Used when nodes are unpublished
/// </summary>
Unpublish,
UnPublish,
/// <summary>
/// Variant(s) being unpublished.
/// </summary>
UnpublishVariant,
/// <summary>
/// Node(s) being moved.
/// Used when nodes are moved
/// </summary>
Move,
/// <summary>
/// Node(s) being copied.
/// Used when nodes are copied
/// </summary>
Copy,
/// <summary>
/// Node(s) being assigned domains.
/// Used when nodes are assígned a domain
/// </summary>
AssignDomain,
/// <summary>
/// Node(s) public access changing.
/// Used when public access are changed for a node
/// </summary>
PublicAccess,
/// <summary>
/// Node(s) being sorted.
/// Used when nodes are sorted
/// </summary>
Sort,
/// <summary>
/// Notification(s) being sent to user.
/// Used when a notification are send to a user
/// </summary>
Notify,
/// <summary>
/// General system audit message.
/// General system notification
/// </summary>
System,
/// <summary>
/// Node's content being rolled back to a previous version.
/// Used when a node's content is rolled back to a previous version
/// </summary>
RollBack,
/// <summary>
/// Package being installed.
/// Used when a package is installed
/// </summary>
PackagerInstall,
/// <summary>
/// Package being uninstalled.
/// Used when a package is uninstalled
/// </summary>
PackagerUninstall,
/// <summary>
/// Custom audit message.
/// Used when a node is send to translation
/// </summary>
SendToTranslate,
/// <summary>
/// Use this log action for custom log messages that should be shown in the audit trail
/// </summary>
Custom
}
}
}
+2 -2
View File
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Models
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
internal class Consent : EntityBase, IConsent
internal class Consent : Entity, IConsent
{
private static PropertySelectors _selector;
+343 -524
View File
@@ -1,524 +1,343 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a Content object
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class Content : ContentBase, IContent
{
private IContentType _contentType;
private ITemplate _template;
private ContentScheduleCollection _schedule;
private bool _published;
private PublishedState _publishedState;
private ContentCultureInfosCollection _publishInfos;
private ContentCultureInfosCollection _publishInfosOrig;
private HashSet<string> _editedCultures;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parent">Parent <see cref="IContent"/> object</param>
/// <param name="contentType">ContentType for the current Content object</param>
/// <param name="culture">An optional culture.</param>
public Content(string name, IContent parent, IContentType contentType, string culture = null)
: this(name, parent, contentType, new PropertyCollection(), culture)
{ }
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parent">Parent <see cref="IContent"/> object</param>
/// <param name="contentType">ContentType for the current Content object</param>
/// <param name="properties">Collection of properties</param>
/// <param name="culture">An optional culture.</param>
public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string culture = null)
: base(name, parent, contentType, properties, culture)
{
_contentType = contentType ?? throw new ArgumentNullException(nameof(contentType));
_publishedState = PublishedState.Unpublished;
PublishedVersionId = 0;
}
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parentId">Id of the Parent content</param>
/// <param name="contentType">ContentType for the current Content object</param>
/// <param name="culture">An optional culture.</param>
public Content(string name, int parentId, IContentType contentType, string culture = null)
: this(name, parentId, contentType, new PropertyCollection(), culture)
{ }
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parentId">Id of the Parent content</param>
/// <param name="contentType">ContentType for the current Content object</param>
/// <param name="properties">Collection of properties</param>
/// <param name="culture">An optional culture.</param>
public Content(string name, int parentId, IContentType contentType, PropertyCollection properties, string culture = null)
: base(name, parentId, contentType, properties, culture)
{
_contentType = contentType ?? throw new ArgumentNullException(nameof(contentType));
_publishedState = PublishedState.Unpublished;
PublishedVersionId = 0;
}
// ReSharper disable once ClassNeverInstantiated.Local
private class PropertySelectors
{
public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo<Content, ITemplate>(x => x.Template);
public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.Published);
public readonly PropertyInfo ContentScheduleSelector = ExpressionHelper.GetPropertyInfo<Content, ContentScheduleCollection>(x => x.ContentSchedule);
public readonly PropertyInfo PublishCultureInfosSelector = ExpressionHelper.GetPropertyInfo<Content, IReadOnlyDictionary<string, ContentCultureInfos>>(x => x.PublishCultureInfos);
}
/// <inheritdoc />
[DoNotClone]
public ContentScheduleCollection ContentSchedule
{
get
{
if (_schedule == null)
{
_schedule = new ContentScheduleCollection();
_schedule.CollectionChanged += ScheduleCollectionChanged;
}
return _schedule;
}
set
{
if(_schedule != null)
_schedule.CollectionChanged -= ScheduleCollectionChanged;
SetPropertyValueAndDetectChanges(value, ref _schedule, Ps.Value.ContentScheduleSelector);
if (_schedule != null)
_schedule.CollectionChanged += ScheduleCollectionChanged;
}
}
/// <summary>
/// Collection changed event handler to ensure the schedule field is set to dirty when the schedule changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ScheduleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.ContentScheduleSelector);
}
/// <summary>
/// Gets or sets the template used by the Content.
/// This is used to override the default one from the ContentType.
/// </summary>
/// <remarks>
/// If no template is explicitly set on the Content object,
/// the Default template from the ContentType will be returned.
/// </remarks>
[DataMember]
public ITemplate Template
{
get => _template ?? _contentType.DefaultTemplate;
set => SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector);
}
/// <summary>
/// Gets or sets a value indicating whether this content item is published or not.
/// </summary>
[DataMember]
public bool Published
{
get => _published;
// the setter is internal and should only be invoked from
// - the ContentFactory when creating a content entity from a dto
// - the ContentRepository when updating a content entity
internal set
{
SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector);
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
}
}
/// <summary>
/// Gets the published state of the content item.
/// </summary>
/// <remarks>The state should be Published or Unpublished, depending on whether Published
/// is true or false, but can also temporarily be Publishing or Unpublishing when the
/// content item is about to be saved.</remarks>
[DataMember]
public PublishedState PublishedState
{
get => _publishedState;
set
{
if (value != PublishedState.Publishing && value != PublishedState.Unpublishing)
throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted.");
_publishedState = value;
}
}
[IgnoreDataMember]
public bool Edited { get; internal set; }
/// <summary>
/// Gets the ContentType used by this content object
/// </summary>
[IgnoreDataMember]
public IContentType ContentType => _contentType;
/// <inheritdoc />
[IgnoreDataMember]
public DateTime? PublishDate { get; internal set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public int? PublisherId { get; internal set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public ITemplate PublishTemplate { get; internal set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public string PublishName { get; internal set; } // set by persistence
/// <inheritdoc />
[IgnoreDataMember]
public IEnumerable<string> EditedCultures => CultureInfos.Keys.Where(IsCultureEdited);
/// <inheritdoc />
[IgnoreDataMember]
public IEnumerable<string> PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty<string>();
/// <inheritdoc />
public bool IsCulturePublished(string culture)
// just check _publishInfos
// a non-available culture could not become published anyways
=> _publishInfos != null && _publishInfos.ContainsKey(culture);
/// <inheritdoc />
public bool WasCulturePublished(string culture)
// just check _publishInfosOrig - a copy of _publishInfos
// a non-available culture could not become published anyways
=> _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
// adjust dates to sync between version, cultures etc
// used by the repo when persisting
internal void AdjustDates(DateTime date)
{
foreach (var culture in PublishedCultures.ToList())
{
if (_publishInfos == null || !_publishInfos.TryGetValue(culture, out var publishInfos))
continue;
if (_publishInfosOrig != null && _publishInfosOrig.TryGetValue(culture, out var publishInfosOrig)
&& publishInfosOrig.Date == publishInfos.Date)
continue;
_publishInfos.AddOrUpdate(culture, publishInfos.Name, date);
if (CultureInfos.TryGetValue(culture, out var infos))
SetCultureInfo(culture, infos.Name, date);
}
}
/// <inheritdoc />
public bool IsCultureEdited(string culture)
=> IsCultureAvailable(culture) && // is available, and
(!IsCulturePublished(culture) || // is not published, or
(_editedCultures != null && _editedCultures.Contains(culture))); // is edited
/// <inheritdoc/>
[IgnoreDataMember]
public IReadOnlyDictionary<string, ContentCultureInfos> PublishCultureInfos => _publishInfos ?? NoInfos;
/// <inheritdoc/>
public string GetPublishName(string culture)
{
if (culture.IsNullOrWhiteSpace()) return PublishName;
if (!ContentTypeBase.VariesByCulture()) return null;
if (_publishInfos == null) return null;
return _publishInfos.TryGetValue(culture, out var infos) ? infos.Name : null;
}
/// <inheritdoc />
public DateTime? GetPublishDate(string culture)
{
if (culture.IsNullOrWhiteSpace()) return PublishDate;
if (!ContentTypeBase.VariesByCulture()) return null;
if (_publishInfos == null) return null;
return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?) null;
}
// internal for repository
internal void SetPublishInfo(string culture, string name, DateTime date)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullOrEmptyException(nameof(name));
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_publishInfos == null)
{
_publishInfos = new ContentCultureInfosCollection();
_publishInfos.CollectionChanged += PublishNamesCollectionChanged;
}
_publishInfos.AddOrUpdate(culture, name, date);
}
private void ClearPublishInfos()
{
_publishInfos = null;
}
private void ClearPublishInfo(string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_publishInfos == null) return;
_publishInfos.Remove(culture);
if (_publishInfos.Count == 0) _publishInfos = null;
// set the culture to be dirty - it's been modified
TouchCultureInfo(culture);
}
// sets a publish edited
internal void SetCultureEdited(string culture)
{
if (culture.IsNullOrWhiteSpace())
throw new ArgumentNullOrEmptyException(nameof(culture));
if (_editedCultures == null)
_editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_editedCultures.Add(culture.ToLowerInvariant());
}
// sets all publish edited
internal void SetCultureEdited(IEnumerable<string> cultures)
{
if (cultures == null)
{
_editedCultures = null;
}
else
{
var editedCultures = new HashSet<string>(cultures.Where(x => !x.IsNullOrWhiteSpace()), StringComparer.OrdinalIgnoreCase);
_editedCultures = editedCultures.Count > 0 ? editedCultures : null;
}
}
/// <summary>
/// Handles culture infos collection changes.
/// </summary>
private void PublishNamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(Ps.Value.PublishCultureInfosSelector);
}
[IgnoreDataMember]
public int PublishedVersionId { get; internal set; }
[DataMember]
public bool Blueprint { get; internal set; }
/// <inheritdoc />
public bool PublishCulture(string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
// the variation should be supported by the content type properties
// if the content type is invariant, only '*' and 'null' is ok
// if the content type varies, everything is ok because some properties may be invariant
if (!ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\".");
// the values we want to publish should be valid
if (ValidateProperties(culture).Any())
return false;
var alsoInvariant = false;
if (culture == "*") // all cultures
{
foreach (var c in AvailableCultures)
{
var name = GetCultureName(c);
if (string.IsNullOrWhiteSpace(name))
return false;
SetPublishInfo(c, name, DateTime.Now);
}
}
else if (culture == null) // invariant culture
{
if (string.IsNullOrWhiteSpace(Name))
return false;
// PublishName set by repository - nothing to do here
}
else // one single culture
{
var name = GetCultureName(culture);
if (string.IsNullOrWhiteSpace(name))
return false;
SetPublishInfo(culture, name, DateTime.Now);
alsoInvariant = true; // we also want to publish invariant values
}
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in Properties)
{
property.PublishValues(culture);
if (alsoInvariant)
property.PublishValues(null);
}
_publishedState = PublishedState.Publishing;
return true;
}
/// <inheritdoc />
public void UnpublishCulture(string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
// the variation should be supported by the content type properties
if (!ContentType.SupportsPropertyVariation(culture, "*", true))
throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{ContentType.Alias}\" with variation \"{ContentType.Variations}\".");
if (culture == "*") // all cultures
ClearPublishInfos();
else // one single culture
ClearPublishInfo(culture);
// property.PublishValues only publishes what is valid, variation-wise
foreach (var property in Properties)
property.UnpublishValues(culture);
_publishedState = PublishedState.Publishing;
}
/// <summary>
/// Changes the <see cref="ContentType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
public void ChangeContentType(IContentType contentType)
{
ContentTypeId = contentType.Id;
_contentType = contentType;
ContentTypeBase = contentType;
Properties.EnsurePropertyTypes(PropertyTypes);
Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add
Properties.CollectionChanged += PropertiesChanged;
}
/// <summary>
/// Changes the <see cref="ContentType"/> for the current content object and removes PropertyTypes,
/// which are not part of the new ContentType.
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
public void ChangeContentType(IContentType contentType, bool clearProperties)
{
if(clearProperties)
{
ContentTypeId = contentType.Id;
_contentType = contentType;
ContentTypeBase = contentType;
Properties.EnsureCleanPropertyTypes(PropertyTypes);
Properties.CollectionChanged -= PropertiesChanged; // be sure not to double add
Properties.CollectionChanged += PropertiesChanged;
return;
}
ChangeContentType(contentType);
}
public override void ResetDirtyProperties(bool rememberDirty)
{
base.ResetDirtyProperties(rememberDirty);
if (Template != null)
Template.ResetDirtyProperties(rememberDirty);
if (ContentType != null)
ContentType.ResetDirtyProperties(rememberDirty);
// take care of the published state
_publishedState = _published ? PublishedState.Published : PublishedState.Unpublished;
// Make a copy of the _publishInfos, this is purely so that we can detect
// if this entity's previous culture publish state (regardless of the rememberDirty flag)
_publishInfosOrig = _publishInfos == null
? null
: new ContentCultureInfosCollection(_publishInfos);
if (_publishInfos == null) return;
foreach (var infos in _publishInfos)
infos.ResetDirtyProperties(rememberDirty);
}
/// <summary>
/// Creates a deep clone of the current entity with its identity and it's property identities reset
/// </summary>
/// <returns></returns>
public IContent DeepCloneWithResetIdentities()
{
var clone = (Content)DeepClone();
clone.Key = Guid.Empty;
clone.VersionId = clone.PublishedVersionId = 0;
clone.ResetIdentity();
foreach (var property in clone.Properties)
property.ResetIdentity();
return clone;
}
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
var clonedContent = (Content)clone;
//need to manually clone this since it's not settable
clonedContent._contentType = (IContentType) ContentType.DeepClone();
//if culture infos exist then deal with event bindings
if (clonedContent._publishInfos != null)
{
clonedContent._publishInfos.CollectionChanged -= PublishNamesCollectionChanged; //clear this event handler if any
clonedContent._publishInfos = (ContentCultureInfosCollection) _publishInfos.DeepClone(); //manually deep clone
clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler
}
//if properties exist then deal with event bindings
if (clonedContent._schedule != null)
{
clonedContent._schedule.CollectionChanged -= ScheduleCollectionChanged; //clear this event handler if any
clonedContent._schedule = (ContentScheduleCollection)_schedule.DeepClone(); //manually deep clone
clonedContent._schedule.CollectionChanged += clonedContent.ScheduleCollectionChanged; //re-assign correct event handler
}
}
}
}
using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a Content object
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class Content : ContentBase, IContent
{
private IContentType _contentType;
private ITemplate _template;
private bool _published;
private string _language;
private DateTime? _releaseDate;
private DateTime? _expireDate;
private int _writer;
private string _nodeName;//NOTE Once localization is introduced this will be the non-localized Node Name.
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parent">Parent <see cref="IContent"/> object</param>
/// <param name="contentType">ContentType for the current Content object</param>
public Content(string name, IContent parent, IContentType contentType)
: this(name, parent, contentType, new PropertyCollection())
{
}
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parent">Parent <see cref="IContent"/> object</param>
/// <param name="contentType">ContentType for the current Content object</param>
/// <param name="properties">Collection of properties</param>
public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties)
: base(name, parent, contentType, properties)
{
Mandate.ParameterNotNull(contentType, "contentType");
_contentType = contentType;
}
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parentId">Id of the Parent content</param>
/// <param name="contentType">ContentType for the current Content object</param>
public Content(string name, int parentId, IContentType contentType)
: this(name, parentId, contentType, new PropertyCollection())
{
}
/// <summary>
/// Constructor for creating a Content object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="parentId">Id of the Parent content</param>
/// <param name="contentType">ContentType for the current Content object</param>
/// <param name="properties">Collection of properties</param>
public Content(string name, int parentId, IContentType contentType, PropertyCollection properties)
: base(name, parentId, contentType, properties)
{
Mandate.ParameterNotNull(contentType, "contentType");
_contentType = contentType;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo TemplateSelector = ExpressionHelper.GetPropertyInfo<Content, ITemplate>(x => x.Template);
public readonly PropertyInfo PublishedSelector = ExpressionHelper.GetPropertyInfo<Content, bool>(x => x.Published);
public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.Language);
public readonly PropertyInfo ReleaseDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ReleaseDate);
public readonly PropertyInfo ExpireDateSelector = ExpressionHelper.GetPropertyInfo<Content, DateTime?>(x => x.ExpireDate);
public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo<Content, int>(x => x.WriterId);
public readonly PropertyInfo NodeNameSelector = ExpressionHelper.GetPropertyInfo<Content, string>(x => x.NodeName);
}
/// <summary>
/// Gets or sets the template used by the Content.
/// This is used to override the default one from the ContentType.
/// </summary>
/// <remarks>
/// If no template is explicitly set on the Content object,
/// the Default template from the ContentType will be returned.
/// </remarks>
[DataMember]
public virtual ITemplate Template
{
get { return _template; }
set { SetPropertyValueAndDetectChanges(value, ref _template, Ps.Value.TemplateSelector); }
}
/// <summary>
/// Gets the current status of the Content
/// </summary>
[IgnoreDataMember]
public ContentStatus Status
{
get
{
if(Trashed)
return ContentStatus.Trashed;
if(ExpireDate.HasValue && ExpireDate.Value > DateTime.MinValue && DateTime.Now > ExpireDate.Value)
return ContentStatus.Expired;
if(ReleaseDate.HasValue && ReleaseDate.Value > DateTime.MinValue && ReleaseDate.Value > DateTime.Now)
return ContentStatus.AwaitingRelease;
if(Published)
return ContentStatus.Published;
return ContentStatus.Unpublished;
}
}
/// <summary>
/// Boolean indicating whether this Content is Published or not
/// </summary>
/// <remarks>
/// Setting Published to true/false should be private or internal and should ONLY be used for wiring up the value
/// from the db or modifying it based on changing the published state.
/// </remarks>
[DataMember]
public bool Published
{
get { return _published; }
internal set { SetPropertyValueAndDetectChanges(value, ref _published, Ps.Value.PublishedSelector); }
}
/// <summary>
/// Language of the data contained within this Content object.
/// </summary>
[Obsolete("This is not used and will be removed from the codebase in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
public string Language
{
get { return _language; }
set { SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector); }
}
/// <summary>
/// The date this Content should be released and thus be published
/// </summary>
[DataMember]
public DateTime? ReleaseDate
{
get { return _releaseDate; }
set { SetPropertyValueAndDetectChanges(value, ref _releaseDate, Ps.Value.ReleaseDateSelector); }
}
/// <summary>
/// The date this Content should expire and thus be unpublished
/// </summary>
[DataMember]
public DateTime? ExpireDate
{
get { return _expireDate; }
set { SetPropertyValueAndDetectChanges(value, ref _expireDate, Ps.Value.ExpireDateSelector); }
}
/// <summary>
/// Id of the user who wrote/updated this Content
/// </summary>
[DataMember]
public virtual int WriterId
{
get { return _writer; }
set { SetPropertyValueAndDetectChanges(value, ref _writer, Ps.Value.WriterSelector); }
}
/// <summary>
/// Name of the Node (non-localized).
/// </summary>
/// <remarks>
/// This Property is kept internal until localization is introduced.
/// </remarks>
[DataMember]
internal string NodeName
{
get { return _nodeName; }
set { SetPropertyValueAndDetectChanges(value, ref _nodeName, Ps.Value.NodeNameSelector); }
}
/// <summary>
/// Gets the ContentType used by this content object
/// </summary>
[IgnoreDataMember]
public IContentType ContentType
{
get { return _contentType; }
}
/// <summary>
/// Changes the <see cref="ContentType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
public void ChangeContentType(IContentType contentType)
{
ContentTypeId = contentType.Id;
_contentType = contentType;
ContentTypeBase = contentType;
Properties.EnsurePropertyTypes(PropertyTypes);
Properties.CollectionChanged += PropertiesChanged;
}
/// <summary>
/// Changes the <see cref="ContentType"/> for the current content object and removes PropertyTypes,
/// which are not part of the new ContentType.
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
public void ChangeContentType(IContentType contentType, bool clearProperties)
{
if(clearProperties)
{
ContentTypeId = contentType.Id;
_contentType = contentType;
ContentTypeBase = contentType;
Properties.EnsureCleanPropertyTypes(PropertyTypes);
Properties.CollectionChanged += PropertiesChanged;
return;
}
ChangeContentType(contentType);
}
/// <summary>
/// Changes the Published state of the content object
/// </summary>
public void ChangePublishedState(PublishedState state)
{
Published = state == PublishedState.Published;
PublishedState = state;
}
[DataMember]
internal PublishedState PublishedState { get; set; }
/// <summary>
/// Gets or sets the unique identifier of the published version, if any.
/// </summary>
[IgnoreDataMember]
public Guid PublishedVersionGuid { get; internal set; }
/// <summary>
/// Gets a value indicating whether the content has a published version.
/// </summary>
public bool HasPublishedVersion { get { return PublishedVersionGuid != default(Guid); } }
[IgnoreDataMember]
internal DateTime PublishedDate { get; set; }
[DataMember]
public bool IsBlueprint { get; internal set; }
/// <summary>
/// Changes the Trashed state of the content object
/// </summary>
/// <param name="isTrashed">Boolean indicating whether content is trashed (true) or not trashed (false)</param>
/// <param name="parentId"> </param>
public override void ChangeTrashedState(bool isTrashed, int parentId = -20)
{
Trashed = isTrashed;
ParentId = parentId;
//If the content is trashed and is published it should be marked as unpublished
if (isTrashed && Published)
{
ChangePublishedState(PublishedState.Unpublished);
}
}
/// <summary>
/// Method to call when Entity is being updated
/// </summary>
/// <remarks>Modified Date is set and a new Version guid is set</remarks>
internal override void UpdatingEntity()
{
base.UpdatingEntity();
Version = Guid.NewGuid();
}
/// <summary>
/// Creates a deep clone of the current entity with its identity and it's property identities reset
/// </summary>
/// <returns></returns>
[Obsolete("Use DeepCloneWithResetIdentities instead")]
public IContent Clone()
{
return DeepCloneWithResetIdentities();
}
/// <summary>
/// Creates a deep clone of the current entity with its identity and it's property identities reset
/// </summary>
/// <returns></returns>
public IContent DeepCloneWithResetIdentities()
{
var clone = (Content)DeepClone();
clone.Key = Guid.Empty;
clone.Version = Guid.NewGuid();
clone.ResetIdentity();
foreach (var property in clone.Properties)
{
property.ResetIdentity();
property.Version = clone.Version;
}
return clone;
}
public override object DeepClone()
{
var clone = (Content)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually clone this since it's not settable
clone._contentType = (IContentType)ContentType.DeepClone();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// The name of a content variant for a given culture
/// </summary>
public class ContentCultureInfos : BeingDirtyBase, IDeepCloneable, IEquatable<ContentCultureInfos>
{
private DateTime _date;
private string _name;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
/// <summary>
/// Initializes a new instance of the <see cref="ContentCultureInfos"/> class.
/// </summary>
public ContentCultureInfos(string culture)
{
if (culture.IsNullOrWhiteSpace()) throw new ArgumentNullOrEmptyException(nameof(culture));
Culture = culture;
}
/// <summary>
/// Initializes a new instance of the <see cref="ContentCultureInfos"/> class.
/// </summary>
/// <remarks>Used for cloning, without change tracking.</remarks>
internal ContentCultureInfos(ContentCultureInfos other)
: this(other.Culture)
{
_name = other.Name;
_date = other.Date;
}
/// <summary>
/// Gets the culture.
/// </summary>
public string Culture { get; }
/// <summary>
/// Gets the name.
/// </summary>
public string Name
{
get => _name;
set => SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector);
}
/// <summary>
/// Gets the date.
/// </summary>
public DateTime Date
{
get => _date;
set => SetPropertyValueAndDetectChanges(value, ref _date, Ps.Value.DateSelector);
}
/// <inheritdoc />
public object DeepClone()
{
return new ContentCultureInfos(this);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is ContentCultureInfos other && Equals(other);
}
/// <inheritdoc />
public bool Equals(ContentCultureInfos other)
{
return other != null && Culture == other.Culture && Name == other.Name;
}
/// <inheritdoc />
public override int GetHashCode()
{
var hashCode = 479558943;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Culture);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name);
return hashCode;
}
/// <summary>
/// Deconstructs into culture and name.
/// </summary>
public void Deconstruct(out string culture, out string name)
{
culture = Culture;
name = Name;
}
/// <summary>
/// Deconstructs into culture, name and date.
/// </summary>
public void Deconstruct(out string culture, out string name, out DateTime date)
{
Deconstruct(out culture, out name);
date = Date;
}
// ReSharper disable once ClassNeverInstantiated.Local
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<ContentCultureInfos, string>(x => x.Name);
public readonly PropertyInfo DateSelector = ExpressionHelper.GetPropertyInfo<ContentCultureInfos, DateTime>(x => x.Date);
}
}
}
@@ -1,74 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Umbraco.Core.Collections;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Models
{
/// <summary>
/// The culture names of a content's variants
/// </summary>
public class ContentCultureInfosCollection : ObservableDictionary<string, ContentCultureInfos>, IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentCultureInfosCollection"/> class.
/// </summary>
public ContentCultureInfosCollection()
: base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ContentCultureInfosCollection"/> class with items.
/// </summary>
public ContentCultureInfosCollection(IEnumerable<ContentCultureInfos> items)
: base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase)
{
// make sure to add *copies* and not the original items,
// as items can be modified by AddOrUpdate, and therefore
// the new collection would be impacted by changes made
// to the old collection
foreach (var item in items)
Add(new ContentCultureInfos(item));
}
/// <summary>
/// Adds or updates a <see cref="ContentCultureInfos"/> instance.
/// </summary>
public void AddOrUpdate(string culture, string name, DateTime date)
{
if (culture.IsNullOrWhiteSpace()) throw new ArgumentNullOrEmptyException(nameof(culture));
culture = culture.ToLowerInvariant();
if (TryGetValue(culture, out var item))
{
item.Name = name;
item.Date = date;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item));
}
else
{
Add(new ContentCultureInfos(culture)
{
Name = name,
Date = date
});
}
}
/// <inheritdoc />
public object DeepClone()
{
var clone = new ContentCultureInfosCollection();
foreach (var item in this)
{
var itemClone = (ContentCultureInfos) item.DeepClone();
itemClone.ResetDirtyProperties(false);
clone.Add(itemClone);
}
return clone;
}
}
}
@@ -1,72 +0,0 @@
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.ContentEditing
{
/// <summary>
/// Represents a content app.
/// </summary>
/// <remarks>
/// <para>Content apps are editor extensions.</para>
/// </remarks>
[DataContract(Name = "app", Namespace = "")]
public class ContentApp
{
/// <summary>
/// Gets the name of the content app.
/// </summary>
[DataMember(Name = "name")]
public string Name { get; set; }
/// <summary>
/// Gets the unique alias of the content app.
/// </summary>
/// <remarks>
/// <para>Must be a valid javascript identifier, ie no spaces etc.</para>
/// </remarks>
[DataMember(Name = "alias")]
public string Alias { get; set; }
/// <summary>
/// Gets or sets the weight of the content app.
/// </summary>
/// <remarks>
/// <para>Content apps are ordered by weight, from left (lowest values) to right (highest values).</para>
/// <para>Some built-in apps have special weights: listview is -666, content is -100 and infos is +100.</para>
/// <para>The default weight is 0, meaning somewhere in-between content and infos, but weight could
/// be used for ordering between user-level apps, or anything really.</para>
/// </remarks>
[DataMember(Name = "weight")]
public int Weight { get; set; }
/// <summary>
/// Gets the icon of the content app.
/// </summary>
/// <remarks>
/// <para>Must be a valid helveticons class name (see http://hlvticons.ch/).</para>
/// </remarks>
[DataMember(Name = "icon")]
public string Icon { get; set; }
/// <summary>
/// Gets the view for rendering the content app.
/// </summary>
[DataMember(Name = "view")]
public string View { get; set; }
/// <summary>
/// The view model specific to this app
/// </summary>
[DataMember(Name = "viewModel")]
public object ViewModel { get; set; }
/// <summary>
/// Gets a value indicating whether the app is active.
/// </summary>
/// <remarks>
/// <para>Normally reserved for Angular to deal with but in some cases this can be set on the server side.</para>
/// </remarks>
[DataMember(Name = "active")]
public bool Active { get; set; }
}
}
@@ -1,23 +0,0 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Models.ContentEditing
{
/// <summary>
/// Represents a content app definition.
/// </summary>
public interface IContentAppDefinition
{
/// <summary>
/// Gets the content app for an object.
/// </summary>
/// <param name="source">The source object.</param>
/// <returns>The content app for the object, or null.</returns>
/// <remarks>
/// <para>The definition must determine, based on <paramref name="source"/>, whether
/// the content app should be displayed or not, and return either a <see cref="ContentApp"/>
/// instance, or null.</para>
/// </remarks>
ContentApp GetContentAppFor(object source, IEnumerable<IReadOnlyUserGroup> userGroups);
}
}
@@ -0,0 +1,863 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.IO;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
{
public static class ContentExtensions
{
#region IContent
/// <summary>
/// Returns true if this entity was just published as part of a recent save operation (i.e. it wasn't previously published)
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <remarks>
/// This is helpful for determining if the published event will execute during the saved event for a content item.
/// </remarks>
internal static bool JustPublished(this IContent entity)
{
var dirty = (IRememberBeingDirty)entity;
return dirty.WasPropertyDirty("Published") && entity.Published;
}
/// <summary>
/// Determines if the item should be persisted at all
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <remarks>
/// In one particular case, a content item shouldn't be persisted:
/// * The item exists and is published
/// * A call to ContentService.Save is made
/// * The item has not been modified whatsoever apart from changing it's published status from published to saved
///
/// In this case, there is no reason to make any database changes at all
/// </remarks>
internal static bool RequiresSaving(this IContent entity)
{
var publishedState = ((Content)entity).PublishedState;
return RequiresSaving(entity, publishedState);
}
/// <summary>
/// Determines if the item should be persisted at all
/// </summary>
/// <param name="entity"></param>
/// <param name="publishedState"></param>
/// <returns></returns>
/// <remarks>
/// In one particular case, a content item shouldn't be persisted:
/// * The item exists and is published
/// * A call to ContentService.Save is made
/// * The item has not been modified whatsoever apart from changing it's published status from published to saved
///
/// In this case, there is no reason to make any database changes at all
/// </remarks>
internal static bool RequiresSaving(this IContent entity, PublishedState publishedState)
{
var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished;
//check if any user prop has changed
var propertyValueChanged = entity.IsAnyUserPropertyDirty();
//We need to know if any other property apart from Published was changed here
//don't create a new version if the published state has changed to 'Save' but no data has actually been changed
if (publishedChanged && entity.Published == false && propertyValueChanged == false)
{
//at this point we need to check if any non property value has changed that wasn't the published state
var changedProps = ((TracksChangesEntityBase)entity).GetDirtyProperties();
if (changedProps.Any(x => x != "Published") == false)
{
return false;
}
}
return true;
}
/// <summary>
/// Determines if a new version should be created
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <remarks>
/// A new version needs to be created when:
/// * The publish status is changed
/// * The language is changed
/// * The item is already published and is being published again and any property value is changed (to enable a rollback)
/// </remarks>
internal static bool ShouldCreateNewVersion(this IContent entity)
{
var publishedState = ((Content)entity).PublishedState;
return ShouldCreateNewVersion(entity, publishedState);
}
/// <summary>
/// Returns a list of all dirty user defined properties
/// </summary>
/// <returns></returns>
public static IEnumerable<string> GetDirtyUserProperties(this IContentBase entity)
{
return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias);
}
public static bool IsAnyUserPropertyDirty(this IContentBase entity)
{
return entity.Properties.Any(x => x.IsDirty());
}
public static bool WasAnyUserPropertyDirty(this IContentBase entity)
{
return entity.Properties.Any(x => x.WasDirty());
}
/// <summary>
/// Determines if a new version should be created
/// </summary>
/// <param name="entity"></param>
/// <param name="publishedState"></param>
/// <returns></returns>
/// <remarks>
/// A new version needs to be created when:
/// * The publish status is changed
/// * The language is changed
/// * The item is already published and is being published again and any property value is changed (to enable a rollback)
/// </remarks>
internal static bool ShouldCreateNewVersion(this IContent entity, PublishedState publishedState)
{
//check if the published state has changed or the language
var publishedChanged = entity.IsPropertyDirty("Published") && publishedState != PublishedState.Unpublished;
var langChanged = entity.IsPropertyDirty("Language");
var contentChanged = publishedChanged || langChanged;
//check if any user prop has changed
var propertyValueChanged = entity.IsAnyUserPropertyDirty();
//return true if published or language has changed
if (contentChanged)
{
return true;
}
//check if any content prop has changed
var contentDataChanged = ((Content)entity).IsEntityDirty();
//return true if the item is published and a property has changed or if any content property has changed
return (propertyValueChanged && publishedState == PublishedState.Published) || contentDataChanged;
}
/// <summary>
/// Determines if the published db flag should be set to true for the current entity version and all other db
/// versions should have their flag set to false.
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
/// <remarks>
/// This is determined by:
/// * If a new version is being created and the entity is published
/// * If the published state has changed and the entity is published OR the entity has been un-published.
/// </remarks>
internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity)
{
var publishedState = ((Content)entity).PublishedState;
return entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, entity.ShouldCreateNewVersion(publishedState));
}
/// <summary>
/// Determines if the published db flag should be set to true for the current entity version and all other db
/// versions should have their flag set to false.
/// </summary>
/// <param name="entity"></param>
/// <param name="publishedState"></param>
/// <param name="isCreatingNewVersion"></param>
/// <returns></returns>
/// <remarks>
/// This is determined by:
/// * If a new version is being created and the entity is published
/// * If the published state has changed and the entity is published OR the entity has been un-published.
/// </remarks>
internal static bool ShouldClearPublishedFlagForPreviousVersions(this IContent entity, PublishedState publishedState, bool isCreatingNewVersion)
{
if (isCreatingNewVersion && entity.Published)
{
return true;
}
//If Published state has changed then previous versions should have their publish state reset.
//If state has been changed to unpublished the previous versions publish state should also be reset.
if (entity.IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished))
{
return true;
}
return false;
}
/// <summary>
/// Returns a list of the current contents ancestors, not including the content itself.
/// </summary>
/// <param name="content">Current content</param>
/// <returns>An enumerable list of <see cref="IContent"/> objects</returns>
public static IEnumerable<IContent> Ancestors(this IContent content)
{
return ApplicationContext.Current.Services.ContentService.GetAncestors(content);
}
/// <summary>
/// Returns a list of the current contents children.
/// </summary>
/// <param name="content">Current content</param>
/// <returns>An enumerable list of <see cref="IContent"/> objects</returns>
public static IEnumerable<IContent> Children(this IContent content)
{
return ApplicationContext.Current.Services.ContentService.GetChildren(content.Id);
}
/// <summary>
/// Returns a list of the current contents descendants, not including the content itself.
/// </summary>
/// <param name="content">Current content</param>
/// <returns>An enumerable list of <see cref="IContent"/> objects</returns>
public static IEnumerable<IContent> Descendants(this IContent content)
{
return ApplicationContext.Current.Services.ContentService.GetDescendants(content);
}
/// <summary>
/// Returns the parent of the current content.
/// </summary>
/// <param name="content">Current content</param>
/// <returns>An <see cref="IContent"/> object</returns>
public static IContent Parent(this IContent content)
{
return ApplicationContext.Current.Services.ContentService.GetById(content.ParentId);
}
#endregion
#region IMedia
/// <summary>
/// Returns a list of the current medias ancestors, not including the media itself.
/// </summary>
/// <param name="media">Current media</param>
/// <returns>An enumerable list of <see cref="IMedia"/> objects</returns>
public static IEnumerable<IMedia> Ancestors(this IMedia media)
{
return ApplicationContext.Current.Services.MediaService.GetAncestors(media);
}
/// <summary>
/// Returns a list of the current medias children.
/// </summary>
/// <param name="media">Current media</param>
/// <returns>An enumerable list of <see cref="IMedia"/> objects</returns>
public static IEnumerable<IMedia> Children(this IMedia media)
{
return ApplicationContext.Current.Services.MediaService.GetChildren(media.Id);
}
/// <summary>
/// Returns a list of the current medias descendants, not including the media itself.
/// </summary>
/// <param name="media">Current media</param>
/// <returns>An enumerable list of <see cref="IMedia"/> objects</returns>
public static IEnumerable<IMedia> Descendants(this IMedia media)
{
return ApplicationContext.Current.Services.MediaService.GetDescendants(media);
}
/// <summary>
/// Returns the parent of the current media.
/// </summary>
/// <param name="media">Current media</param>
/// <returns>An <see cref="IMedia"/> object</returns>
public static IMedia Parent(this IMedia media)
{
return ApplicationContext.Current.Services.MediaService.GetById(media.ParentId);
}
#endregion
internal static bool IsInRecycleBin(this IContent content)
{
return IsInRecycleBin(content, Constants.System.RecycleBinContent);
}
internal static bool IsInRecycleBin(this IMedia media)
{
return IsInRecycleBin(media, Constants.System.RecycleBinMedia);
}
internal static bool IsInRecycleBin(this IContentBase content, int recycleBinId)
{
return content.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Contains(recycleBinId.ToInvariantString());
}
/// <summary>
/// Removes characters that are not valide XML characters from all entity properties
/// of type string. See: http://stackoverflow.com/a/961504/5018
/// </summary>
/// <returns></returns>
/// <remarks>
/// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it.
/// </remarks>
/// <param name="entity"></param>
public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity)
{
entity.Name = entity.Name.ToValidXmlString();
foreach (var property in entity.Properties)
{
if (property.Value is string)
{
var value = (string) property.Value;
property.Value = value.ToValidXmlString();
}
}
}
/// <summary>
/// Checks if the IContentBase has children
/// </summary>
/// <param name="content"></param>
/// <param name="services"></param>
/// <returns></returns>
/// <remarks>
/// This is a bit of a hack because we need to type check!
/// </remarks>
internal static bool HasChildren(IContentBase content, ServiceContext services)
{
if (content is IContent)
{
return services.ContentService.HasChildren(content.Id);
}
if (content is IMedia)
{
return services.MediaService.HasChildren(content.Id);
}
return false;
}
/// <summary>
/// Returns the children for the content base item
/// </summary>
/// <param name="content"></param>
/// <param name="services"></param>
/// <returns></returns>
/// <remarks>
/// This is a bit of a hack because we need to type check!
/// </remarks>
internal static IEnumerable<IContentBase> Children(IContentBase content, ServiceContext services)
{
if (content is IContent)
{
return services.ContentService.GetChildren(content.Id);
}
if (content is IMedia)
{
return services.MediaService.GetChildren(content.Id);
}
return null;
}
/// <summary>
/// Returns properties that do not belong to a group
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
public static IEnumerable<Property> GetNonGroupedProperties(this IContentBase content)
{
var propertyIdsInTabs = content.PropertyGroups.SelectMany(pg => pg.PropertyTypes);
return content.Properties
.Where(property => propertyIdsInTabs.Contains(property.PropertyType) == false)
.OrderBy(x => x.PropertyType.SortOrder);
}
/// <summary>
/// Returns the Property object for the given property group
/// </summary>
/// <param name="content"></param>
/// <param name="propertyGroup"></param>
/// <returns></returns>
public static IEnumerable<Property> GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup)
{
//get the properties for the current tab
return content.Properties
.Where(property => propertyGroup.PropertyTypes
.Select(propertyType => propertyType.Id)
.Contains(property.PropertyTypeId));
}
/// <summary>
/// Set property values by alias with an annonymous object
/// </summary>
public static void PropertyValues(this IContentBase content, object value)
{
if (value == null)
throw new Exception("No properties has been passed in");
var propertyInfos = value.GetType().GetProperties();
foreach (var propertyInfo in propertyInfos)
{
//Check if a PropertyType with alias exists thus being a valid property
var propertyType = content.PropertyTypes.FirstOrDefault(x => x.Alias == propertyInfo.Name);
if (propertyType == null)
throw new Exception(
string.Format(
"The property alias {0} is not valid, because no PropertyType with this alias exists",
propertyInfo.Name));
//Check if a Property with the alias already exists in the collection thus being updated or inserted
var item = content.Properties.FirstOrDefault(x => x.Alias == propertyInfo.Name);
if (item != null)
{
item.Value = propertyInfo.GetValue(value, null);
//Update item with newly added value
content.Properties.Add(item);
}
else
{
//Create new Property to add to collection
var property = propertyType.CreatePropertyFromValue(propertyInfo.GetValue(value, null));
content.Properties.Add(property);
}
}
}
public static IContentTypeComposition GetContentType(this IContentBase contentBase)
{
if (contentBase == null) throw new ArgumentNullException("contentBase");
var content = contentBase as IContent;
if (content != null) return content.ContentType;
var media = contentBase as IMedia;
if (media != null) return media.ContentType;
var member = contentBase as IMember;
if (member != null) return member.ContentType;
throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + ".");
}
#region SetValue for setting file contents
/// <summary>
/// Stores and sets an uploaded HttpPostedFileBase as a property value.
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="value">The uploaded <see cref="HttpPostedFileBase"/>.</param>
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value)
{
// ensure we get the filename without the path in IE in intranet mode
// http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
var filename = value.FileName;
var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture);
if (pos > 0)
filename = filename.Substring(pos + 1);
// strip any directory info
pos = filename.LastIndexOf(IOHelper.DirSepChar);
if (pos > 0)
filename = filename.Substring(pos + 1);
// get a safe & clean filename
filename = IOHelper.SafeFileName(filename);
if (string.IsNullOrWhiteSpace(filename)) return;
filename = filename.ToLower();
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream);
}
/// <summary>
/// Stores and sets an uploaded HttpPostedFile as a property value.
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="value">The uploaded <see cref="HttpPostedFile"/>.</param>
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value)
{
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) new HttpPostedFileWrapper(value));
}
/// <summary>
/// Stores and sets an uploaded HttpPostedFileWrapper as a property value.
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="value">The uploaded <see cref="HttpPostedFileWrapper"/>.</param>
[Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")]
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value)
{
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) value);
}
/// <summary>
/// Stores and sets a file as a property value.
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="filename">The name of the file.</param>
/// <param name="filestream">A stream containing the file data.</param>
/// <remarks>This really is for FileUpload fields only, and should be obsoleted. For anything else,
/// you need to store the file by yourself using Store and then figure out
/// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself.</remarks>
public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream)
{
if (filename == null || filestream == null) return;
// get a safe & clean filename
filename = IOHelper.SafeFileName(filename);
if (string.IsNullOrWhiteSpace(filename)) return;
filename = filename.ToLower();
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream);
}
/// <summary>
/// Stores a file.
/// </summary>
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="filename">The name of the file.</param>
/// <param name="filestream">A stream containing the file data.</param>
/// <param name="filepath">The original file path, if any.</param>
/// <returns>The path to the file, relative to the media filesystem.</returns>
/// <remarks>
/// <para>Does NOT set the property value, so one should probably store the file and then do
/// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath).</para>
/// <para>The original file path is used, in the old media file path scheme, to try and reuse
/// the "folder number" that was assigned to the previous file referenced by the property,
/// if any.</para>
/// </remarks>
public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath)
{
var propertyType = content.GetContentType()
.CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + ".");
return FileSystemProviderManager.Current.MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath);
}
#endregion
#region User/Profile methods
/// <summary>
/// Gets the <see cref="IProfile"/> for the Creator of this media item.
/// </summary>
[Obsolete("Use the overload that declares the IUserService to use")]
public static IProfile GetCreatorProfile(this IMedia media)
{
return ApplicationContext.Current.Services.UserService.GetProfileById(media.CreatorId);
}
/// <summary>
/// Gets the <see cref="IProfile"/> for the Creator of this media item.
/// </summary>
public static IProfile GetCreatorProfile(this IMedia media, IUserService userService)
{
return userService.GetProfileById(media.CreatorId);
}
/// <summary>
/// Gets the <see cref="IProfile"/> for the Creator of this content item.
/// </summary>
[Obsolete("Use the overload that declares the IUserService to use")]
public static IProfile GetCreatorProfile(this IContentBase content)
{
return ApplicationContext.Current.Services.UserService.GetProfileById(content.CreatorId);
}
/// <summary>
/// Gets the <see cref="IProfile"/> for the Creator of this content item.
/// </summary>
public static IProfile GetCreatorProfile(this IContentBase content, IUserService userService)
{
return userService.GetProfileById(content.CreatorId);
}
/// <summary>
/// Gets the <see cref="IProfile"/> for the Writer of this content.
/// </summary>
[Obsolete("Use the overload that declares the IUserService to use")]
public static IProfile GetWriterProfile(this IContent content)
{
return ApplicationContext.Current.Services.UserService.GetProfileById(content.WriterId);
}
/// <summary>
/// Gets the <see cref="IProfile"/> for the Writer of this content.
/// </summary>
public static IProfile GetWriterProfile(this IContent content, IUserService userService)
{
return userService.GetProfileById(content.WriterId);
}
#endregion
/// <summary>
/// Checks whether an <see cref="IContent"/> item has any published versions
/// </summary>
/// <param name="content"></param>
/// <returns>True if the content has any published versiom otherwise False</returns>
[Obsolete("Use the HasPublishedVersion property.", false)]
public static bool HasPublishedVersion(this IContent content)
{
return content.HasPublishedVersion;
}
#region Tag methods
///// <summary>
///// Returns the tags for the given property
///// </summary>
///// <param name="content"></param>
///// <param name="propertyTypeAlias"></param>
///// <param name="tagGroup"></param>
///// <returns></returns>
///// <remarks>
///// The tags returned are only relavent for published content & saved media or members
///// </remarks>
//public static IEnumerable<ITag> GetTags(this IContentBase content, string propertyTypeAlias, string tagGroup = "default")
//{
//}
/// <summary>
/// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags.
/// </summary>
/// <param name="content">The content item to assign the tags to</param>
/// <param name="propertyTypeAlias">The property alias to assign the tags to</param>
/// <param name="tags">The tags to assign</param>
/// <param name="replaceTags">True to replace the tags on the current property with the tags specified or false to merge them with the currently assigned ones</param>
/// <param name="tagGroup">The group/category to assign the tags, the default value is "default"</param>
/// <returns></returns>
public static void SetTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
{
content.SetTags(TagCacheStorageType.Csv, propertyTypeAlias, tags, replaceTags, tagGroup);
}
/// <summary>
/// Sets tags for the property - will add tags to the tags table and set the property value to be the comma delimited value of the tags.
/// </summary>
/// <param name="content">The content item to assign the tags to</param>
/// <param name="storageType">The tag storage type in cache (default is csv)</param>
/// <param name="propertyTypeAlias">The property alias to assign the tags to</param>
/// <param name="tags">The tags to assign</param>
/// <param name="replaceTags">True to replace the tags on the current property with the tags specified or false to merge them with the currently assigned ones</param>
/// <param name="tagGroup">The group/category to assign the tags, the default value is "default"</param>
/// <returns></returns>
public static void SetTags(this IContentBase content, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
{
var property = content.Properties[propertyTypeAlias];
if (property == null)
{
throw new IndexOutOfRangeException("No property exists with name " + propertyTypeAlias);
}
property.SetTags(storageType, propertyTypeAlias, tags, replaceTags, tagGroup);
}
internal static void SetTags(this Property property, TagCacheStorageType storageType, string propertyTypeAlias, IEnumerable<string> tags, bool replaceTags, string tagGroup = "default")
{
if (property == null) throw new ArgumentNullException("property");
var trimmedTags = tags.Select(x => x.Trim()).ToArray();
property.TagSupport.Enable = true;
property.TagSupport.Tags = trimmedTags.Select(x => new Tuple<string, string>(x, tagGroup));
property.TagSupport.Behavior = replaceTags ? PropertyTagBehavior.Replace : PropertyTagBehavior.Merge;
//ensure the property value is set to the same thing
if (replaceTags)
{
switch (storageType)
{
case TagCacheStorageType.Csv:
property.Value = string.Join(",", trimmedTags);
break;
case TagCacheStorageType.Json:
//json array
property.Value = JsonConvert.SerializeObject(trimmedTags);
break;
}
}
else
{
switch (storageType)
{
case TagCacheStorageType.Csv:
var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim());
property.Value = string.Join(",", trimmedTags.Union(currTags));
break;
case TagCacheStorageType.Json:
var currJson = JsonConvert.DeserializeObject<JArray>(property.Value.ToString());
//need to append the new ones
foreach (var tag in trimmedTags)
{
currJson.Add(tag);
}
//json array
property.Value = JsonConvert.SerializeObject(currJson);
break;
}
}
}
/// <summary>
/// Remove any of the tags specified in the collection from the property if they are currently assigned.
/// </summary>
/// <param name="content"></param>
/// <param name="propertyTypeAlias"></param>
/// <param name="tags"></param>
/// <param name="tagGroup">The group/category that the tags are currently assigned to, the default value is "default"</param>
public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, string tagGroup = "default")
{
var property = content.Properties[propertyTypeAlias];
if (property == null)
{
throw new IndexOutOfRangeException("No property exists with name " + propertyTypeAlias);
}
var trimmedTags = tags.Select(x => x.Trim()).ToArray();
property.TagSupport.Behavior = PropertyTagBehavior.Remove;
property.TagSupport.Enable = true;
property.TagSupport.Tags = trimmedTags.Select(x => new Tuple<string, string>(x, tagGroup));
//set the property value
var currTags = property.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim());
property.Value = string.Join(",", currTags.Except(trimmedTags));
}
#endregion
#region XML methods
/// <summary>
/// Creates the full xml representation for the <see cref="IContent"/> object and all of it's descendants
/// </summary>
/// <param name="content"><see cref="IContent"/> to generate xml for</param>
/// <param name="packagingService"></param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
internal static XElement ToDeepXml(this IContent content, IPackagingService packagingService)
{
return packagingService.Export(content, true, raiseEvents: false);
}
/// <summary>
/// Creates the xml representation for the <see cref="IContent"/> object
/// </summary>
/// <param name="content"><see cref="IContent"/> to generate xml for</param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
[Obsolete("Use the overload that declares the IPackagingService to use")]
public static XElement ToXml(this IContent content)
{
return ApplicationContext.Current.Services.PackagingService.Export(content, raiseEvents: false);
}
/// <summary>
/// Creates the xml representation for the <see cref="IContent"/> object
/// </summary>
/// <param name="content"><see cref="IContent"/> to generate xml for</param>
/// <param name="packagingService"></param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
public static XElement ToXml(this IContent content, IPackagingService packagingService)
{
return packagingService.Export(content, raiseEvents: false);
}
/// <summary>
/// Creates the xml representation for the <see cref="IMedia"/> object
/// </summary>
/// <param name="media"><see cref="IContent"/> to generate xml for</param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
[Obsolete("Use the overload that declares the IPackagingService to use")]
public static XElement ToXml(this IMedia media)
{
return ApplicationContext.Current.Services.PackagingService.Export(media, raiseEvents: false);
}
/// <summary>
/// Creates the xml representation for the <see cref="IMedia"/> object
/// </summary>
/// <param name="media"><see cref="IContent"/> to generate xml for</param>
/// <param name="packagingService"></param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
public static XElement ToXml(this IMedia media, IPackagingService packagingService)
{
return packagingService.Export(media, raiseEvents: false);
}
/// <summary>
/// Creates the full xml representation for the <see cref="IMedia"/> object and all of it's descendants
/// </summary>
/// <param name="media"><see cref="IMedia"/> to generate xml for</param>
/// <param name="packagingService"></param>
/// <returns>Xml representation of the passed in <see cref="IMedia"/></returns>
internal static XElement ToDeepXml(this IMedia media, IPackagingService packagingService)
{
return packagingService.Export(media, true, raiseEvents: false);
}
/// <summary>
/// Creates the xml representation for the <see cref="IContent"/> object
/// </summary>
/// <param name="content"><see cref="IContent"/> to generate xml for</param>
/// <param name="isPreview">Boolean indicating whether the xml should be generated for preview</param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
[Obsolete("Use the overload that declares the IPackagingService to use")]
public static XElement ToXml(this IContent content, bool isPreview)
{
//TODO Do a proper implementation of this
//If current IContent is published we should get latest unpublished version
return content.ToXml();
}
/// <summary>
/// Creates the xml representation for the <see cref="IContent"/> object
/// </summary>
/// <param name="content"><see cref="IContent"/> to generate xml for</param>
/// <param name="packagingService"></param>
/// <param name="isPreview">Boolean indicating whether the xml should be generated for preview</param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
public static XElement ToXml(this IContent content, IPackagingService packagingService, bool isPreview)
{
//TODO Do a proper implementation of this
//If current IContent is published we should get latest unpublished version
return content.ToXml(packagingService);
}
/// <summary>
/// Creates the xml representation for the <see cref="IMember"/> object
/// </summary>
/// <param name="member"><see cref="IMember"/> to generate xml for</param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
[Obsolete("Use the overload that declares the IPackagingService to use")]
public static XElement ToXml(this IMember member)
{
return ((PackagingService)(ApplicationContext.Current.Services.PackagingService)).Export(member);
}
/// <summary>
/// Creates the xml representation for the <see cref="IMember"/> object
/// </summary>
/// <param name="member"><see cref="IMember"/> to generate xml for</param>
/// <param name="packagingService"></param>
/// <returns>Xml representation of the passed in <see cref="IContent"/></returns>
public static XElement ToXml(this IMember member, IPackagingService packagingService)
{
return ((PackagingService)(packagingService)).Export(member);
}
#endregion
}
}
@@ -0,0 +1,23 @@
using System;
using System.Xml.Linq;
namespace Umbraco.Core.Models
{
/// <summary>
/// Used content repository in order to add an entity to the persisted collection to be saved
/// in a single transaction during saving an entity
/// </summary>
internal class ContentPreviewEntity<TContent> : ContentXmlEntity<TContent>
where TContent : IContentBase
{
public ContentPreviewEntity(TContent content, Func<TContent, XElement> xml)
: base(content, xml)
{
}
public Guid Version
{
get { return Content.Version; }
}
}
}
@@ -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);
}
}
}
@@ -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
}
}
@@ -1,222 +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>
/// <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;
}
}
}
+24 -46
View File
@@ -1,46 +1,24 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Describes the states of a document, with regard to (schedule) publishing.
/// </summary>
[Serializable]
[DataContract]
public enum ContentStatus
{
// typical flow:
// Unpublished (add release date)-> AwaitingRelease (release)-> Published (expire)-> Expired
/// <summary>
/// The document is not trashed, and not published.
/// </summary>
[EnumMember]
Unpublished,
/// <summary>
/// The document is published.
/// </summary>
[EnumMember]
Published,
/// <summary>
/// The document is not trashed, not published, after being unpublished by a scheduled action.
/// </summary>
[EnumMember]
Expired,
/// <summary>
/// The document is trashed.
/// </summary>
[EnumMember]
Trashed,
/// <summary>
/// The document is not trashed, not published, and pending publication by a scheduled action.
/// </summary>
[EnumMember]
AwaitingRelease
}
}
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Enum for the various statuses a Content object can have
/// </summary>
[Serializable]
[DataContract]
public enum ContentStatus
{
[EnumMember]
Unpublished,
[EnumMember]
Published,
[EnumMember]
Expired,
[EnumMember]
Trashed,
[EnumMember]
AwaitingRelease
}
}
@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides extension methods for the <see cref="IContentBase"/> class, to manage tags.
/// </summary>
public static class ContentTagsExtensions
{
/// <summary>
/// Assign tags.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="tags">The tags.</param>
/// <param name="merge">A value indicating whether to merge the tags with existing tags instead of replacing them.</param>
/// <remarks>Tags do not support variants.</remarks>
public static void AssignTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags, bool merge = false)
{
content.GetTagProperty(propertyTypeAlias).AssignTags(tags, merge);
}
/// <summary>
/// Remove tags.
/// </summary>
/// <param name="content">The content item.</param>
/// <param name="propertyTypeAlias">The property alias.</param>
/// <param name="tags">The tags.</param>
/// <remarks>Tags do not support variants.</remarks>
public static void RemoveTags(this IContentBase content, string propertyTypeAlias, IEnumerable<string> tags)
{
content.GetTagProperty(propertyTypeAlias).RemoveTags(tags);
}
// gets and validates the property
private static Property GetTagProperty(this IContentBase content, string propertyTypeAlias)
{
if (content == null) throw new ArgumentNullException(nameof(content));
var property = content.Properties[propertyTypeAlias];
if (property != null) return property;
throw new IndexOutOfRangeException($"Could not find a property with alias \"{propertyTypeAlias}\".");
}
}
}
+201 -163
View File
@@ -1,164 +1,202 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents the content type that a <see cref="Content"/> object is based on
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class ContentType : ContentTypeCompositionBase, IContentType
{
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
public const bool IsPublishingConst = true;
private int _defaultTemplate;
private IEnumerable<ITemplate> _allowedTemplates;
/// <summary>
/// Constuctor for creating a ContentType with the parent's id.
/// </summary>
/// <remarks>Only use this for creating ContentTypes at the root (with ParentId -1).</remarks>
/// <param name="parentId"></param>
public ContentType(int parentId) : base(parentId)
{
_allowedTemplates = new List<ITemplate>();
}
/// <summary>
/// Constuctor for creating a ContentType with the parent as an inherited type.
/// </summary>
/// <remarks>Use this to ensure inheritance from parent.</remarks>
/// <param name="parent"></param>
/// <param name="alias"></param>
public ContentType(IContentType parent, string alias)
: base(parent, alias)
{
_allowedTemplates = new List<ITemplate>();
}
/// <inheritdoc />
public override bool IsPublishing => IsPublishingConst;
// ReSharper disable once ClassNeverInstantiated.Local
private class PropertySelectors
{
public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo<ContentType, int>(x => x.DefaultTemplateId);
public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo<ContentType, IEnumerable<ITemplate>>(x => x.AllowedTemplates);
//Custom comparer for enumerable
public readonly DelegateEqualityComparer<IEnumerable<ITemplate>> TemplateComparer = new DelegateEqualityComparer<IEnumerable<ITemplate>>(
(templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
templates => templates.GetHashCode());
}
/// <summary>
/// Gets or sets the alias of the default Template.
/// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
/// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
/// we should not store direct entity
/// </summary>
[IgnoreDataMember]
public ITemplate DefaultTemplate
{
get { return AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); }
}
/// <summary>
/// Internal property to store the Id of the default template
/// </summary>
[DataMember]
internal int DefaultTemplateId
{
get { return _defaultTemplate; }
set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); }
}
/// <summary>
/// Gets or Sets a list of Templates which are allowed for the ContentType
/// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
/// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
/// we should not store direct entity
/// </summary>
[DataMember]
public IEnumerable<ITemplate> AllowedTemplates
{
get => _allowedTemplates;
set
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents the content type that a <see cref="Content"/> object is based on
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class ContentType : ContentTypeCompositionBase, IContentType
{
private int _defaultTemplate;
private IEnumerable<ITemplate> _allowedTemplates;
/// <summary>
/// Constuctor for creating a ContentType with the parent's id.
/// </summary>
/// <remarks>Only use this for creating ContentTypes at the root (with ParentId -1).</remarks>
/// <param name="parentId"></param>
public ContentType(int parentId) : base(parentId)
{
_allowedTemplates = new List<ITemplate>();
}
/// <summary>
/// Constuctor for creating a ContentType with the parent as an inherited type.
/// </summary>
/// <remarks>Use this to ensure inheritance from parent.</remarks>
/// <param name="parent"></param>
[Obsolete("This method is obsolete, use ContentType(IContentType parent, string alias) instead.", false)]
public ContentType(IContentType parent) : this(parent, null)
{
}
/// <summary>
/// Constuctor for creating a ContentType with the parent as an inherited type.
/// </summary>
/// <remarks>Use this to ensure inheritance from parent.</remarks>
/// <param name="parent"></param>
/// <param name="alias"></param>
public ContentType(IContentType parent, string alias)
: base(parent, alias)
{
_allowedTemplates = new List<ITemplate>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo DefaultTemplateSelector = ExpressionHelper.GetPropertyInfo<ContentType, int>(x => x.DefaultTemplateId);
public readonly PropertyInfo AllowedTemplatesSelector = ExpressionHelper.GetPropertyInfo<ContentType, IEnumerable<ITemplate>>(x => x.AllowedTemplates);
//Custom comparer for enumerable
public readonly DelegateEqualityComparer<IEnumerable<ITemplate>> TemplateComparer = new DelegateEqualityComparer<IEnumerable<ITemplate>>(
(templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
templates => templates.GetHashCode());
}
/// <summary>
/// Gets or sets the alias of the default Template.
/// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
/// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
/// we should not store direct entity
/// </summary>
[IgnoreDataMember]
public ITemplate DefaultTemplate
{
get { return AllowedTemplates.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); }
}
/// <summary>
/// Internal property to store the Id of the default template
/// </summary>
[DataMember]
internal int DefaultTemplateId
{
get { return _defaultTemplate; }
set { SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, Ps.Value.DefaultTemplateSelector); }
}
/// <summary>
/// Gets or Sets a list of Templates which are allowed for the ContentType
/// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
/// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
/// we should not store direct entity
/// </summary>
[DataMember]
public IEnumerable<ITemplate> AllowedTemplates
{
get { return _allowedTemplates; }
set
{
SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, Ps.Value.AllowedTemplatesSelector, Ps.Value.TemplateComparer);
if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false)
DefaultTemplateId = 0;
}
}
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateId">The template id to check</param>
/// <returns>True if AllowedTemplates contains the templateId else False</returns>
public bool IsAllowedTemplate(int templateId)
{
return AllowedTemplates == null
? false
: AllowedTemplates.Any(t => t.Id == templateId);
}
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateAlias">The template alias to check</param>
/// <returns>True if AllowedTemplates contains the templateAlias else False</returns>
public bool IsAllowedTemplate(string templateAlias)
{
return AllowedTemplates == null
? false
: AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase));
}
/// <summary>
/// Sets the default template for the ContentType
/// </summary>
/// <param name="template">Default <see cref="ITemplate"/></param>
public void SetDefaultTemplate(ITemplate template)
{
if (template == null)
{
DefaultTemplateId = 0;
return;
}
DefaultTemplateId = template.Id;
if (_allowedTemplates.Any(x => x != null && x.Id == template.Id) == false)
{
var templates = AllowedTemplates.ToList();
templates.Add(template);
AllowedTemplates = templates;
}
}
/// <summary>
/// Removes a template from the list of allowed templates
/// </summary>
/// <param name="template"><see cref="ITemplate"/> to remove</param>
/// <returns>True if template was removed, otherwise False</returns>
public bool RemoveTemplate(ITemplate template)
{
if (DefaultTemplateId == template.Id)
DefaultTemplateId = default(int);
var templates = AllowedTemplates.ToList();
var remove = templates.FirstOrDefault(x => x.Id == template.Id);
var result = templates.Remove(remove);
AllowedTemplates = templates;
return result;
}
}
}
if (_allowedTemplates.Any(x => x.Id == _defaultTemplate) == false)
DefaultTemplateId = 0;
}
}
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateId">The template id to check</param>
/// <returns>True if AllowedTemplates contains the templateId else False</returns>
public bool IsAllowedTemplate(int templateId)
{
var allowedTemplates = AllowedTemplates ?? new ITemplate[0];
return allowedTemplates.Any(t => t.Id == templateId);
}
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateAlias">The template alias to check</param>
/// <returns>True if AllowedTemplates contains the templateAlias else False</returns>
public bool IsAllowedTemplate(string templateAlias)
{
var allowedTemplates = AllowedTemplates ?? new ITemplate[0];
return allowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase));
}
/// <summary>
/// Sets the default template for the ContentType
/// </summary>
/// <param name="template">Default <see cref="ITemplate"/></param>
public void SetDefaultTemplate(ITemplate template)
{
if (template == null)
{
DefaultTemplateId = 0;
return;
}
DefaultTemplateId = template.Id;
if (_allowedTemplates.Any(x => x != null && x.Id == template.Id) == false)
{
var templates = AllowedTemplates.ToList();
templates.Add(template);
AllowedTemplates = templates;
}
}
/// <summary>
/// Removes a template from the list of allowed templates
/// </summary>
/// <param name="template"><see cref="ITemplate"/> to remove</param>
/// <returns>True if template was removed, otherwise False</returns>
public bool RemoveTemplate(ITemplate template)
{
if (DefaultTemplateId == template.Id)
DefaultTemplateId = default(int);
var templates = AllowedTemplates.ToList();
var remove = templates.FirstOrDefault(x => x.Id == template.Id);
var result = templates.Remove(remove);
AllowedTemplates = templates;
return result;
}
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <returns></returns>
[Obsolete("Use DeepCloneWithResetIdentities instead")]
public IContentType Clone(string alias)
{
return DeepCloneWithResetIdentities(alias);
}
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <returns></returns>
public IContentType DeepCloneWithResetIdentities(string alias)
{
var clone = (ContentType)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,4 +1,4 @@
namespace Umbraco.Core.Models
namespace Umbraco.Core.Models
{
/// <summary>
/// Used when determining available compositions for a given content type
@@ -14,4 +14,4 @@
public IContentTypeComposition Composition { get; private set; }
public bool Allowed { get; private set; }
}
}
}
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Models
@@ -23,4 +23,4 @@ namespace Umbraco.Core.Models
public IEnumerable<IContentTypeComposition> Ancestors { get; private set; }
public IEnumerable<ContentTypeAvailableCompositionsResult> Results { get; private set; }
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides extensions methods for <see cref="IContentTypeBase"/>.
/// </summary>
public static class ContentTypeBaseExtensions
{
public static PublishedItemType GetItemType(this IContentTypeBase contentType)
{
var type = contentType.GetType();
var itemType = PublishedItemType.Unknown;
if (typeof(IContentType).IsAssignableFrom(type)) itemType = PublishedItemType.Content;
else if (typeof(IMediaType).IsAssignableFrom(type)) itemType = PublishedItemType.Media;
else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member;
return itemType;
}
/// <summary>
/// Used to check if any property type was changed between variant/invariant
/// </summary>
/// <param name="contentType"></param>
/// <returns></returns>
internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType)
{
return contentType.WasPropertyTypeVariationChanged(out var _);
}
/// <summary>
/// Used to check if any property type was changed between variant/invariant
/// </summary>
/// <param name="contentType"></param>
/// <returns></returns>
internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, out IReadOnlyCollection<string> aliases)
{
var a = new List<string>();
// property variation change?
var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType =>
{
if (!(propertyType is IRememberBeingDirty dirtyProperty))
throw new Exception("oops");
// skip new properties
//TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
var isNewProperty = dirtyProperty.WasPropertyDirty("Id");
if (isNewProperty) return false;
// variation change?
var dirty = dirtyProperty.WasPropertyDirty("Variations");
if (dirty)
a.Add(propertyType.Alias);
return dirty;
});
aliases = a;
return hasAnyPropertyVariationChanged;
}
}
}
@@ -1,325 +1,275 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Exceptions;
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 static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private List<IContentTypeComposition> _contentTypeComposition = new List<IContentTypeComposition>();
internal List<int> RemovedContentTypeKeyTracker = new List<int>();
protected ContentTypeCompositionBase(int parentId) : base(parentId)
{ }
protected ContentTypeCompositionBase(IContentTypeComposition parent)
: this(parent, null)
{ }
protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias)
: base(parent, alias)
{
AddContentType(parent);
}
// ReSharper disable once ClassNeverInstantiated.Local
private class PropertySelectors
{
public readonly PropertyInfo ContentTypeCompositionSelector =
ExpressionHelper.GetPropertyInfo<ContentTypeCompositionBase, IEnumerable<IContentTypeComposition>>(x => x.ContentTypeComposition);
}
/// <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(Ps.Value.ContentTypeCompositionSelector);
}
}
/// <summary>
/// Gets the property groups for the entire composition.
/// </summary>
[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(PropertyType 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);
}
}
/// <summary>
/// Gets the property types for the entire composition.
/// </summary>
[IgnoreDataMember]
public IEnumerable<PropertyType> 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
PropertyType AcquireProperty(PropertyType propertyType)
{
propertyType = (PropertyType) 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<PropertyType> RawComposedPropertyTypes => GetRawComposedPropertyTypes();
private IEnumerable<PropertyType> 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(Ps.Value.ContentTypeCompositionSelector);
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(Ps.Value.ContentTypeCompositionSelector);
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(IsPublishing) { 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="PropertyType"/> 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(PropertyType 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();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Exceptions;
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(int parentId) : base(parentId)
{
}
protected ContentTypeCompositionBase(IContentTypeComposition parent)
: this(parent, null)
{
}
protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias)
: base(parent, alias)
{
AddContentType(parent);
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ContentTypeCompositionSelector =
ExpressionHelper.GetPropertyInfo<ContentTypeCompositionBase, IEnumerable<IContentTypeComposition>>(x => x.ContentTypeComposition);
}
/// <summary>
/// Gets or sets the content types that compose this content type.
/// </summary>
[DataMember]
public IEnumerable<IContentTypeComposition> ContentTypeComposition
{
get { return _contentTypeComposition; }
set
{
_contentTypeComposition = value.ToList();
OnPropertyChanged(Ps.Value.ContentTypeCompositionSelector);
}
}
/// <summary>
/// Gets the property groups for the entire composition.
/// </summary>
[IgnoreDataMember]
public IEnumerable<PropertyGroup> CompositionPropertyGroups
{
get
{
var groups = ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups).Union(PropertyGroups);
return groups;
}
}
/// <summary>
/// Gets the property types for the entire composition.
/// </summary>
[IgnoreDataMember]
public IEnumerable<PropertyType> CompositionPropertyTypes
{
get
{
var propertyTypes = ContentTypeComposition.SelectMany(x => x.CompositionPropertyTypes).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(Ps.Value.ContentTypeCompositionSelector);
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(Ps.Value.ContentTypeCompositionSelector);
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 { 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="PropertyType"/> 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(PropertyType 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()));
}
public override object DeepClone()
{
var clone = (ContentTypeCompositionBase)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign since this is an internal field and will not be automatically mapped
clone.RemovedContentTypeKeyTracker = new List<int>();
clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
}
}
+80 -78
View File
@@ -1,78 +1,80 @@
using System;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a POCO for setting sort order on a ContentType reference
/// </summary>
public class ContentTypeSort : IValueObject, IDeepCloneable
{
// this parameterless ctor should never be used BUT is required by AutoMapper in EntityMapperProfile
internal ContentTypeSort() { }
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public ContentTypeSort(int id, int sortOrder)
{
Id = new Lazy<int>(() => id);
SortOrder = sortOrder;
}
public ContentTypeSort(Lazy<int> id, int sortOrder, string @alias)
{
Id = id;
SortOrder = sortOrder;
Alias = alias;
}
/// <summary>
/// Gets or sets the Id of the ContentType
/// </summary>
public Lazy<int> Id { get; set; }
/// <summary>
/// Gets or sets the Sort Order of the ContentType
/// </summary>
public int SortOrder { get; set; }
/// <summary>
/// Gets or sets the Alias of the ContentType
/// </summary>
public string Alias { get; set; }
public object DeepClone()
{
var clone = (ContentTypeSort)MemberwiseClone();
var id = Id.Value;
clone.Id = new Lazy<int>(() => id);
return clone;
}
protected bool Equals(ContentTypeSort other)
{
return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ContentTypeSort) obj);
}
public override int GetHashCode()
{
unchecked
{
//The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id.
//In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created.
return Alias != null ? Alias.GetHashCode() : (Id.Value.GetHashCode() * 397);
}
}
}
}
using System;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a POCO for setting sort order on a ContentType reference
/// </summary>
public class ContentTypeSort : IValueObject, IDeepCloneable
{
[Obsolete("This parameterless constructor should never be used")]
public ContentTypeSort()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public ContentTypeSort(int id, int sortOrder)
{
Id = new Lazy<int>(() => id);
SortOrder = sortOrder;
}
public ContentTypeSort(Lazy<int> id, int sortOrder, string @alias)
{
Id = id;
SortOrder = sortOrder;
Alias = alias;
}
/// <summary>
/// Gets or sets the Id of the ContentType
/// </summary>
public Lazy<int> Id { get; set; }
/// <summary>
/// Gets or sets the Sort Order of the ContentType
/// </summary>
public int SortOrder { get; set; }
/// <summary>
/// Gets or sets the Alias of the ContentType
/// </summary>
public string Alias { get; set; }
public object DeepClone()
{
var clone = (ContentTypeSort)MemberwiseClone();
var id = Id.Value;
clone.Id = new Lazy<int>(() => id);
return clone;
}
protected bool Equals(ContentTypeSort other)
{
return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ContentTypeSort) obj);
}
public override int GetHashCode()
{
unchecked
{
//The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id.
//In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created.
return Alias != null ? Alias.GetHashCode() : (Id.Value.GetHashCode() * 397);
}
}
}
}
@@ -1,37 +0,0 @@
using System;
namespace Umbraco.Core.Models
{
/// <summary>
/// Indicates how values can vary.
/// </summary>
/// <remarks>
/// <para>Values can vary by nothing, or culture, or segment, or both.</para>
/// <para>Varying by culture implies that each culture version of a document can
/// be available or not, and published or not, individually. Varying by segment
/// is a property-level thing.</para>
/// </remarks>
[Flags]
public enum ContentVariation : byte
{
/// <summary>
/// Values do not vary.
/// </summary>
Nothing = 0,
/// <summary>
/// Values vary by culture.
/// </summary>
Culture = 1,
/// <summary>
/// Values vary by segment.
/// </summary>
Segment = 2,
/// <summary>
/// Values vary by culture and segment.
/// </summary>
CultureAndSegment = Culture | Segment
}
}
@@ -0,0 +1,67 @@
using System;
using System.Xml.Linq;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Used in content/media/member repositories in order to add this type of entity to the persisted collection to be saved
/// in a single transaction during saving an entity
/// </summary>
internal class ContentXmlEntity<TContent> : IAggregateRoot
where TContent : IContentBase
{
private readonly Func<TContent, XElement> _xml;
public ContentXmlEntity(TContent content, Func<TContent, XElement> xml)
{
if (content == null) throw new ArgumentNullException("content");
_xml = xml;
Content = content;
}
public ContentXmlEntity(TContent content)
{
if (content == null) throw new ArgumentNullException("content");
Content = content;
}
public XElement Xml
{
get { return _xml(Content); }
}
public TContent Content { get; private set; }
public int Id
{
get { return Content.Id; }
set { throw new NotSupportedException(); }
}
public Guid Key { get; set; }
public DateTime CreateDate { get; set; }
public DateTime UpdateDate { get; set; }
public DateTime? DeletedDate { get; set; }
/// <summary>
/// Special case, always return false, this will cause the repositories managing
/// this object to always do an 'insert' but these are special repositories that
/// do an InsertOrUpdate on insert since the data for this needs to be managed this way
/// </summary>
public bool HasIdentity
{
get { return false; }
}
public object DeepClone()
{
var clone = (ContentXmlEntity<TContent>)MemberwiseClone();
//Automatically deep clone ref properties that are IDeepCloneable
DeepCloneHelper.DeepCloneRefProperties(this, clone);
return clone;
}
}
}
-177
View File
@@ -1,177 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Models
{
/// <summary>
/// Implements <see cref="IDataType"/>.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class DataType : TreeEntityBase, IDataType
{
private static PropertySelectors _selectors;
private IDataEditor _editor;
private ValueStorageType _databaseType;
private object _configuration;
private bool _hasConfiguration;
private string _configurationJson;
/// <summary>
/// Initializes a new instance of the <see cref="DataType"/> class.
/// </summary>
public DataType(IDataEditor editor, int parentId = -1)
{
_editor = editor ?? throw new ArgumentNullException(nameof(editor));
ParentId = parentId;
// set a default configuration
Configuration = _editor.GetConfigurationEditor().DefaultConfigurationObject;
}
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
private class PropertySelectors
{
public readonly PropertyInfo Editor = ExpressionHelper.GetPropertyInfo<DataType, IDataEditor>(x => x.Editor);
public readonly PropertyInfo DatabaseType = ExpressionHelper.GetPropertyInfo<DataType, ValueStorageType>(x => x.DatabaseType);
public readonly PropertyInfo Configuration = ExpressionHelper.GetPropertyInfo<DataType, object>(x => x.Configuration);
}
/// <inheritdoc />
[IgnoreDataMember]
public IDataEditor Editor
{
get => _editor;
set
{
// ignore if no change
if (_editor.Alias == value.Alias) return;
OnPropertyChanged(Selectors.Editor);
// try to map the existing configuration to the new configuration
// simulate saving to db and reloading (ie go via json)
var configuration = Configuration;
var json = JsonConvert.SerializeObject(configuration);
_editor = value;
Configuration = _editor.GetConfigurationEditor().FromDatabase(json);
}
}
/// <inheritdoc />
[DataMember]
public string EditorAlias => _editor.Alias;
/// <inheritdoc />
[DataMember]
public ValueStorageType DatabaseType
{
get => _databaseType;
set => SetPropertyValueAndDetectChanges(value, ref _databaseType, Selectors.DatabaseType);
}
/// <inheritdoc />
[DataMember]
public object Configuration
{
get
{
// if we know we have a configuration (which may be null), return it
// if we don't have an editor, then we have no configuration, return null
// else, use the editor to get the configuration object
if (_hasConfiguration) return _configuration;
_configuration = _editor.GetConfigurationEditor().FromDatabase(_configurationJson);
_hasConfiguration = true;
_configurationJson = null;
return _configuration;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
// we don't support re-assigning the same object
// configurations are kinda non-mutable, mainly because detecting changes would be a pain
if (_configuration == value) // reference comparison
throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", nameof(value));
// validate configuration type
if (!_editor.GetConfigurationEditor().IsConfiguration(value))
throw new ArgumentException($"Value of type {value.GetType().Name} cannot be a configuration for editor {_editor.Alias}, expecting.", nameof(value));
// extract database type from configuration object, if appropriate
if (value is IConfigureValueType valueTypeConfiguration)
DatabaseType = ValueTypes.ToStorageType(valueTypeConfiguration.ValueType);
// extract database type from dictionary, if appropriate
if (value is IDictionary<string, object> dictionaryConfiguration
&& dictionaryConfiguration.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObject)
&& valueTypeObject is string valueTypeString
&& ValueTypes.IsValue(valueTypeString))
DatabaseType = ValueTypes.ToStorageType(valueTypeString);
_configuration = value;
_hasConfiguration = true;
_configurationJson = null;
// it's always a change
OnPropertyChanged(Selectors.Configuration);
}
}
/// <summary>
/// Lazily set the configuration as a serialized json string.
/// </summary>
/// <remarks>
/// <para>Will be de-serialized on-demand.</para>
/// <para>This method is meant to be used when building entities from database, exclusively.
/// It does NOT register a property change to dirty. It ignores the fact that the configuration
/// may contain the database type, because the datatype DTO should also contain that database
/// type, and they should be the same.</para>
/// <para>Think before using!</para>
/// </remarks>
internal void SetLazyConfiguration(string configurationJson)
{
_hasConfiguration = false;
_configuration = null;
_configurationJson = configurationJson;
}
/// <summary>
/// Gets a lazy configuration.
/// </summary>
/// <remarks>
/// <para>The configuration object will be lazily de-serialized.</para>
/// <para>This method is meant to be used when creating published datatypes, exclusively.</para>
/// <para>Think before using!</para>
/// </remarks>
internal Lazy<object> GetLazyConfiguration()
{
// note: in both cases, make sure we capture what we need - we don't want
// to capture a reference to this full, potentially heavy, DataType instance.
if (_hasConfiguration)
{
// if configuration has already been de-serialized, return
var capturedConfiguration = _configuration;
return new Lazy<object>(() => capturedConfiguration);
}
else
{
// else, create a Lazy de-serializer
var capturedConfiguration = _configurationJson;
var capturedEditor = _editor;
return new Lazy<object>(() => capturedEditor.GetConfigurationEditor().FromDatabase(capturedConfiguration));
}
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
/// <summary>
/// Enum of the various DbTypes for which the Property values are stored
/// </summary>
[Serializable]
[DataContract]
public enum DataTypeDatabaseType
{
[EnumMember]
Ntext,
[EnumMember]
Nvarchar,
[EnumMember]
Integer,
[EnumMember]
Date,
[EnumMember]
Decimal
}
}
@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models
{
/// <summary>
/// Definition of a DataType/PropertyEditor
/// </summary>
/// <remarks>
/// The definition exists as a database reference between an actual DataType/PropertyEditor
/// (identified by its control id), its prevalues (configuration) and the named DataType in the backoffice UI.
/// </remarks>
[Serializable]
[DataContract(IsReference = true)]
public class DataTypeDefinition : Entity, IDataTypeDefinition
{
private int _parentId;
private string _name;
private int _sortOrder;
private int _level;
private string _path;
private int _creatorId;
private bool _trashed;
private string _propertyEditorAlias;
private DataTypeDatabaseType _databaseType;
[Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the alternative contructor that specifies an alias")]
[EditorBrowsable(EditorBrowsableState.Never)]
public DataTypeDefinition(int parentId, Guid controlId)
{
_parentId = parentId;
_propertyEditorAlias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(controlId, false);
if (_propertyEditorAlias == null)
{
//convert to Label!
LogHelper.Warn<DataTypeDefinition>("Could not find a GUID -> Alias mapping for the legacy property editor with id " + controlId + ". The DataType has been converted to a Label.");
_propertyEditorAlias = Constants.PropertyEditors.NoEditAlias;
}
_additionalData = new Dictionary<string, object>();
}
public DataTypeDefinition(int parentId, string propertyEditorAlias)
{
_parentId = parentId;
_propertyEditorAlias = propertyEditorAlias;
_additionalData = new Dictionary<string, object>();
}
public DataTypeDefinition(string propertyEditorAlias)
{
_parentId = -1;
_propertyEditorAlias = propertyEditorAlias;
_additionalData = new Dictionary<string, object>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.Name);
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.ParentId);
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.SortOrder);
public readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.Level);
public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.Path);
public readonly PropertyInfo UserIdSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, int>(x => x.CreatorId);
public readonly PropertyInfo TrashedSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, bool>(x => x.Trashed);
public readonly PropertyInfo PropertyEditorAliasSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, string>(x => x.PropertyEditorAlias);
public readonly PropertyInfo DatabaseTypeSelector = ExpressionHelper.GetPropertyInfo<DataTypeDefinition, DataTypeDatabaseType>(x => x.DatabaseType);
}
/// <summary>
/// Gets or sets the Id of the Parent entity
/// </summary>
/// <remarks>Might not be necessary if handled as a relation?</remarks>
[DataMember]
public int ParentId
{
get { return _parentId; }
set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); }
}
/// <summary>
/// Gets or sets the name of the current entity
/// </summary>
[DataMember]
public string Name
{
get { return _name; }
set { SetPropertyValueAndDetectChanges(value, ref _name, Ps.Value.NameSelector); }
}
/// <summary>
/// Gets or sets the sort order of the content entity
/// </summary>
[DataMember]
public int SortOrder
{
get { return _sortOrder; }
set { SetPropertyValueAndDetectChanges(value, ref _sortOrder, Ps.Value.SortOrderSelector); }
}
/// <summary>
/// Gets or sets the level of the content entity
/// </summary>
[DataMember]
public int Level
{
get { return _level; }
set { SetPropertyValueAndDetectChanges(value, ref _level, Ps.Value.LevelSelector); }
}
/// <summary>
/// Gets or sets the path
/// </summary>
[DataMember]
public string Path //Setting this value should be handled by the class not the user
{
get { return _path; }
set { SetPropertyValueAndDetectChanges(value, ref _path, Ps.Value.PathSelector); }
}
/// <summary>
/// Id of the user who created this entity
/// </summary>
[DataMember]
public int CreatorId
{
get { return _creatorId; }
set { SetPropertyValueAndDetectChanges(value, ref _creatorId, Ps.Value.UserIdSelector); }
}
//NOTE: SD: Why do we have this ??
/// <summary>
/// Boolean indicating whether this entity is Trashed or not.
/// </summary>
[DataMember]
public bool Trashed
{
get { return _trashed; }
internal set
{
SetPropertyValueAndDetectChanges(value, ref _trashed, Ps.Value.TrashedSelector);
//This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data
_additionalData["Trashed"] = value;
}
}
[DataMember]
public string PropertyEditorAlias
{
get { return _propertyEditorAlias; }
set
{
SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias, Ps.Value.PropertyEditorAliasSelector);
//This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data
_additionalData["DatabaseType"] = value;
}
}
/// <summary>
/// Id of the DataType control
/// </summary>
[DataMember]
[Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead. This method will return a generated GUID for any property editor alias not explicitly mapped to a legacy ID")]
public Guid ControlId
{
get
{
return LegacyPropertyEditorIdToAliasConverter.GetLegacyIdFromAlias(
_propertyEditorAlias, LegacyPropertyEditorIdToAliasConverter.NotFoundLegacyIdResponseBehavior.GenerateId).Value;
}
set
{
var alias = LegacyPropertyEditorIdToAliasConverter.GetAliasFromLegacyId(value, true);
PropertyEditorAlias = alias;
//This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data
_additionalData["ControlId"] = value;
}
}
/// <summary>
/// Gets or Sets the DatabaseType for which the DataType's value is saved as
/// </summary>
[DataMember]
public DataTypeDatabaseType DatabaseType
{
get { return _databaseType; }
set
{
SetPropertyValueAndDetectChanges(value, ref _databaseType, Ps.Value.DatabaseTypeSelector);
//This is a custom property that is not exposed in IUmbracoEntity so add it to the additional data
_additionalData["DatabaseType"] = value;
}
}
private readonly IDictionary<string, object> _additionalData;
/// <summary>
/// Some entities may expose additional data that other's might not, this custom data will be available in this collection
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
IDictionary<string, object> IUmbracoEntity.AdditionalData
{
get { return _additionalData; }
}
}
}
@@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides extensions methods for <see cref="IDataType"/>.
/// </summary>
public static class DataTypeExtensions
{
/// <summary>
/// Gets the configuration object.
/// </summary>
/// <typeparam name="T">The expected type of the configuration object.</typeparam>
/// <param name="dataType">This datatype.</param>
/// <exception cref="InvalidCastException">When the datatype configuration is not of the expected type.</exception>
public static T ConfigurationAs<T>(this IDataType dataType)
where T : class
{
if (dataType == null)
throw new ArgumentNullException(nameof(dataType));
var configuration = dataType.Configuration;
switch (configuration)
{
case null:
return null;
case T configurationAsT:
return configurationAsT;
}
throw new InvalidCastException($"Cannot cast dataType configuration, of type {configuration.GetType().Name}, to {typeof(T).Name}.");
}
}
}
+6 -7
View File
@@ -1,10 +1,9 @@
using System;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Umbraco.Core.Composing;
namespace Umbraco.Core.Models
{
@@ -23,7 +22,7 @@ namespace Umbraco.Core.Models
public PropertyInfo PropertyInfo { get; private set; }
public bool IsDeepCloneable { get; set; }
public Type GenericListType { get; set; }
public Type GenericListType { get; set; }
public bool IsList
{
get { return GenericListType != null; }
@@ -40,7 +39,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <param name="input"></param>
/// <param name="output"></param>
/// <returns></returns>
/// <returns></returns>
public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output)
{
var inputType = input.GetType();
@@ -68,7 +67,7 @@ namespace Umbraco.Core.Models
{
return null;
}
if (TypeHelper.IsTypeAssignableFrom<IDeepCloneable>(propertyInfo.PropertyType))
{
@@ -157,7 +156,7 @@ namespace Umbraco.Core.Models
}
else if (o is string || o.GetType().IsValueType)
{
//check if the item is a value type or a string, then we can just use it
//check if the item is a value type or a string, then we can just use it
newList.Add(o);
}
else
@@ -197,4 +196,4 @@ namespace Umbraco.Core.Models
}
}
}
}
+92 -92
View File
@@ -1,92 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a Dictionary Item
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class DictionaryItem : EntityBase, IDictionaryItem
{
public Func<int, ILanguage> GetLanguage { get; set; }
private Guid? _parentId;
private string _itemKey;
private IEnumerable<IDictionaryTranslation> _translations;
public DictionaryItem(string itemKey)
: this(null, itemKey)
{}
public DictionaryItem(Guid? parentId, string itemKey)
{
_parentId = parentId;
_itemKey = itemKey;
_translations = new List<IDictionaryTranslation>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, Guid?>(x => x.ParentId);
public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, string>(x => x.ItemKey);
public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, IEnumerable<IDictionaryTranslation>>(x => x.Translations);
//Custom comparer for enumerable
public readonly DelegateEqualityComparer<IEnumerable<IDictionaryTranslation>> DictionaryTranslationComparer =
new DelegateEqualityComparer<IEnumerable<IDictionaryTranslation>>(
(enumerable, translations) => enumerable.UnsortedSequenceEqual(translations),
enumerable => enumerable.GetHashCode());
}
/// <summary>
/// Gets or Sets the Parent Id of the Dictionary Item
/// </summary>
[DataMember]
public Guid? ParentId
{
get { return _parentId; }
set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); }
}
/// <summary>
/// Gets or sets the Key for the Dictionary Item
/// </summary>
[DataMember]
public string ItemKey
{
get { return _itemKey; }
set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); }
}
/// <summary>
/// Gets or sets a list of translations for the Dictionary Item
/// </summary>
[DataMember]
public IEnumerable<IDictionaryTranslation> Translations
{
get { return _translations; }
set
{
var asArray = value.ToArray();
//ensure the language callback is set on each translation
if (GetLanguage != null)
{
foreach (var translation in asArray.OfType<DictionaryTranslation>())
{
translation.GetLanguage = GetLanguage;
}
}
SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector,
Ps.Value.DictionaryTranslationComparer);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a Dictionary Item
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class DictionaryItem : Entity, IDictionaryItem
{
public Func<int, ILanguage> GetLanguage { get; set; }
private Guid? _parentId;
private string _itemKey;
private IEnumerable<IDictionaryTranslation> _translations;
public DictionaryItem(string itemKey)
: this(null, itemKey)
{}
public DictionaryItem(Guid? parentId, string itemKey)
{
_parentId = parentId;
_itemKey = itemKey;
_translations = new List<IDictionaryTranslation>();
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, Guid?>(x => x.ParentId);
public readonly PropertyInfo ItemKeySelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, string>(x => x.ItemKey);
public readonly PropertyInfo TranslationsSelector = ExpressionHelper.GetPropertyInfo<DictionaryItem, IEnumerable<IDictionaryTranslation>>(x => x.Translations);
//Custom comparer for enumerable
public readonly DelegateEqualityComparer<IEnumerable<IDictionaryTranslation>> DictionaryTranslationComparer =
new DelegateEqualityComparer<IEnumerable<IDictionaryTranslation>>(
(enumerable, translations) => enumerable.UnsortedSequenceEqual(translations),
enumerable => enumerable.GetHashCode());
}
/// <summary>
/// Gets or Sets the Parent Id of the Dictionary Item
/// </summary>
[DataMember]
public Guid? ParentId
{
get { return _parentId; }
set { SetPropertyValueAndDetectChanges(value, ref _parentId, Ps.Value.ParentIdSelector); }
}
/// <summary>
/// Gets or sets the Key for the Dictionary Item
/// </summary>
[DataMember]
public string ItemKey
{
get { return _itemKey; }
set { SetPropertyValueAndDetectChanges(value, ref _itemKey, Ps.Value.ItemKeySelector); }
}
/// <summary>
/// Gets or sets a list of translations for the Dictionary Item
/// </summary>
[DataMember]
public IEnumerable<IDictionaryTranslation> Translations
{
get { return _translations; }
set
{
var asArray = value.ToArray();
//ensure the language callback is set on each translation
if (GetLanguage != null)
{
foreach (var translation in asArray.OfType<DictionaryTranslation>())
{
translation.GetLanguage = GetLanguage;
}
}
SetPropertyValueAndDetectChanges(asArray, ref _translations, Ps.Value.TranslationsSelector,
Ps.Value.DictionaryTranslationComparer);
}
}
}
}
@@ -1,30 +0,0 @@
using System.Linq;
namespace Umbraco.Core.Models
{
public static class DictionaryItemExtensions
{
/// <summary>
/// Returns the translation value for the language id, if no translation is found it returns an empty string
/// </summary>
/// <param name="d"></param>
/// <param name="languageId"></param>
/// <returns></returns>
public static string GetTranslatedValue(this IDictionaryItem d, int languageId)
{
var trans = d.Translations.FirstOrDefault(x => x.LanguageId == languageId);
return trans == null ? string.Empty : trans.Value;
}
/// <summary>
/// Returns the default translated value based on the default language
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
public static string GetDefaultValue(this IDictionaryItem d)
{
var defaultTranslation = d.Translations.FirstOrDefault(x => x.Language.Id == 1);
return defaultTranslation == null ? string.Empty : defaultTranslation.Value;
}
}
}
+126 -117
View File
@@ -1,117 +1,126 @@
using System;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a translation for a <see cref="DictionaryItem"/>
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class DictionaryTranslation : EntityBase, IDictionaryTranslation
{
internal Func<int, ILanguage> GetLanguage { get; set; }
private ILanguage _language;
private string _value;
//note: this will be memberwise cloned
private int _languageId;
public DictionaryTranslation(ILanguage language, string value)
{
if (language == null) throw new ArgumentNullException("language");
_language = language;
_languageId = _language.Id;
_value = value;
}
public DictionaryTranslation(ILanguage language, string value, Guid uniqueId)
{
if (language == null) throw new ArgumentNullException("language");
_language = language;
_languageId = _language.Id;
_value = value;
Key = uniqueId;
}
internal DictionaryTranslation(int languageId, string value)
{
_languageId = languageId;
_value = value;
}
internal DictionaryTranslation(int languageId, string value, Guid uniqueId)
{
_languageId = languageId;
_value = value;
Key = uniqueId;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, ILanguage>(x => x.Language);
public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, string>(x => x.Value);
}
/// <summary>
/// Gets or sets the <see cref="Language"/> for the translation
/// </summary>
/// <remarks>
/// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem
/// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply
/// just referenced a language ID not the actual language object. In v8 we need to fix this.
/// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned
/// on a callback.
/// </remarks>
[DataMember]
[DoNotClone]
public ILanguage Language
{
get
{
if (_language != null)
return _language;
// else, must lazy-load
if (GetLanguage != null && _languageId > 0)
_language = GetLanguage(_languageId);
return _language;
}
set
{
SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector);
_languageId = _language == null ? -1 : _language.Id;
}
}
public int LanguageId
{
get { return _languageId; }
}
/// <summary>
/// Gets or sets the translated text
/// </summary>
[DataMember]
public string Value
{
get { return _value; }
set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); }
}
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
var clonedEntity = (DictionaryTranslation)clone;
// clear fields that were memberwise-cloned and that we don't want to clone
clonedEntity._language = null;
}
}
}
using System;
using System.Reflection;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a translation for a <see cref="DictionaryItem"/>
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class DictionaryTranslation : Entity, IDictionaryTranslation
{
internal Func<int, ILanguage> GetLanguage { get; set; }
private ILanguage _language;
private string _value;
//note: this will be memberwise cloned
private int _languageId;
public DictionaryTranslation(ILanguage language, string value)
{
if (language == null) throw new ArgumentNullException("language");
_language = language;
_languageId = _language.Id;
_value = value;
}
public DictionaryTranslation(ILanguage language, string value, Guid uniqueId)
{
if (language == null) throw new ArgumentNullException("language");
_language = language;
_languageId = _language.Id;
_value = value;
Key = uniqueId;
}
internal DictionaryTranslation(int languageId, string value)
{
_languageId = languageId;
_value = value;
}
internal DictionaryTranslation(int languageId, string value, Guid uniqueId)
{
_languageId = languageId;
_value = value;
Key = uniqueId;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, ILanguage>(x => x.Language);
public readonly PropertyInfo ValueSelector = ExpressionHelper.GetPropertyInfo<DictionaryTranslation, string>(x => x.Value);
}
/// <summary>
/// Gets or sets the <see cref="Language"/> for the translation
/// </summary>
/// <remarks>
/// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem
/// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply
/// just referenced a language ID not the actual language object. In v8 we need to fix this.
/// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned
/// on a callback.
/// </remarks>
[DataMember]
[DoNotClone]
public ILanguage Language
{
get
{
if (_language != null)
return _language;
// else, must lazy-load
if (GetLanguage != null && _languageId > 0)
_language = GetLanguage(_languageId);
return _language;
}
set
{
SetPropertyValueAndDetectChanges(value, ref _language, Ps.Value.LanguageSelector);
_languageId = _language == null ? -1 : _language.Id;
}
}
public int LanguageId
{
get { return _languageId; }
}
/// <summary>
/// Gets or sets the translated text
/// </summary>
[DataMember]
public string Value
{
get { return _value; }
set { SetPropertyValueAndDetectChanges(value, ref _value, Ps.Value.ValueSelector); }
}
public override object DeepClone()
{
var clone = (DictionaryTranslation)base.DeepClone();
// clear fields that were memberwise-cloned and that we don't want to clone
clone._language = null;
// turn off change tracking
clone.DisableChangeTracking();
// this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
// re-enable tracking
clone.EnableChangeTracking();
return clone;
}
}
}
@@ -1,4 +1,4 @@
using System;
using System;
namespace Umbraco.Core.Models
{
@@ -7,17 +7,17 @@ namespace Umbraco.Core.Models
/// that should be ignored for cloning when using the DeepCloneHelper
/// </summary>
/// <remarks>
///
///
/// This attribute must be used:
/// * when the property is backed by a field but the result of the property is the un-natural data stored in the field
///
///
/// This attribute should not be used:
/// * when the property is virtual
/// * when the setter performs additional required logic other than just setting the underlying field
///
///
/// </remarks>
internal class DoNotCloneAttribute : Attribute
{
}
}
}
@@ -1,45 +1,47 @@
using System;
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Represents data that has been submitted to be saved for a content property
/// </summary>
/// <remarks>
/// This object exists because we may need to save additional data for each property, more than just
/// the string representation of the value being submitted. An example of this is uploaded files.
/// </remarks>
public class ContentPropertyData
{
public ContentPropertyData(object value, object dataTypeConfiguration)
{
Value = value;
DataTypeConfiguration = dataTypeConfiguration;
}
/// <summary>
/// The value submitted for the property
/// </summary>
public object Value { get; }
/// <summary>
/// The data type configuration for the property.
/// </summary>
public object DataTypeConfiguration { get; }
/// <summary>
/// Gets or sets the unique identifier of the content owning the property.
/// </summary>
public Guid ContentKey { get; set; }
/// <summary>
/// Gets or sets the unique identifier of the property type.
/// </summary>
public Guid PropertyTypeKey { get; set; }
/// <summary>
/// Gets or sets the uploaded files.
/// </summary>
public ContentPropertyFile[] Files { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Represents data that has been submitted to be saved for a content property
/// </summary>
/// <remarks>
/// This object exists because we may need to save additional data for each property, more than just
/// the string representation of the value being submitted. An example of this is uploaded files.
/// </remarks>
public class ContentPropertyData
{
public ContentPropertyData(object value, PreValueCollection preValues)
: this(value, preValues, new Dictionary<string, object>())
{
}
public ContentPropertyData(object value, PreValueCollection preValues, IDictionary<string, object> additionalData)
{
Value = value;
PreValues = preValues;
AdditionalData = new ReadOnlyDictionary<string, object>(additionalData);
}
/// <summary>
/// The value submitted for the property
/// </summary>
public object Value { get; private set; }
/// <summary>
/// The pre-value collection for the content property
/// </summary>
public PreValueCollection PreValues { get; private set; }
/// <summary>
/// A dictionary containing any additional objects that are related to this property when saving
/// </summary>
public ReadOnlyDictionary<string, object> AdditionalData { get; private set; }
}
}
@@ -1,37 +0,0 @@
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Represents an uploaded file for a property.
/// </summary>
public class ContentPropertyFile
{
/// <summary>
/// Gets or sets the property alias.
/// </summary>
public string PropertyAlias { get; set; }
/// <summary>
/// When dealing with content variants, this is the culture for the variant
/// </summary>
public string Culture { get; set; }
/// <summary>
/// An array of metadata that is parsed out from the file info posted to the server which is set on the client.
/// </summary>
/// <remarks>
/// This can be used for property types like Nested Content that need to have special unique identifiers for each file since there might be multiple files
/// per property.
/// </remarks>
public string[] Metadata { get; set; }
/// <summary>
/// Gets or sets the name of the file.
/// </summary>
public string FileName { get; set; }
/// <summary>
/// Gets or sets the temporary path where the file has been uploaded.
/// </summary>
public string TempFilePath { get; set; }
}
}
@@ -1,37 +0,0 @@
using System.Collections.Generic;
using System.Reflection;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Provides a concrete implementation of <see cref="BeingDirtyBase"/>.
/// </summary>
/// <remarks>
/// <para>This class is provided for classes that cannot inherit from <see cref="BeingDirtyBase"/>
/// and therefore need to implement <see cref="IRememberBeingDirty"/>, by re-using some of
/// <see cref="BeingDirtyBase"/> logic.</para>
/// </remarks>
public sealed class BeingDirty : BeingDirtyBase
{
/// <summary>
/// Sets a property value, detects changes and manages the dirty flag.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The new value.</param>
/// <param name="valueRef">A reference to the value to set.</param>
/// <param name="propertySelector">The property selector.</param>
/// <param name="comparer">A comparer to compare property values.</param>
public new void SetPropertyValueAndDetectChanges<T>(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer<T> comparer = null)
{
base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertySelector, comparer);
}
/// <summary>
/// Registers that a property has changed.
/// </summary>
public new void OnPropertyChanged(PropertyInfo propertySelector)
{
base.OnPropertyChanged(propertySelector);
}
}
}
@@ -1,192 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Provides a base implementation of <see cref="ICanBeDirty"/> and <see cref="IRememberBeingDirty"/>.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public abstract class BeingDirtyBase : IRememberBeingDirty
{
private bool _withChanges = true; // should we track changes?
private Dictionary<string, bool> _currentChanges; // which properties have changed?
private Dictionary<string, bool> _savedChanges; // which properties had changed at last commit?
#region ICanBeDirty
/// <inheritdoc />
public virtual bool IsDirty()
{
return _currentChanges != null && _currentChanges.Any();
}
/// <inheritdoc />
public virtual bool IsPropertyDirty(string propertyName)
{
return _currentChanges != null && _currentChanges.Any(x => x.Key == propertyName);
}
/// <inheritdoc />
public virtual IEnumerable<string> GetDirtyProperties()
{
// ReSharper disable once MergeConditionalExpression
return _currentChanges == null
? Enumerable.Empty<string>()
: _currentChanges.Where(x => x.Value).Select(x => x.Key);
}
/// <inheritdoc />
/// <remarks>Saves dirty properties so they can be checked with WasDirty.</remarks>
public virtual void ResetDirtyProperties()
{
ResetDirtyProperties(true);
}
#endregion
#region IRememberBeingDirty
/// <inheritdoc />
public virtual bool WasDirty()
{
return _savedChanges != null && _savedChanges.Any();
}
/// <inheritdoc />
public virtual bool WasPropertyDirty(string propertyName)
{
return _savedChanges != null && _savedChanges.Any(x => x.Key == propertyName);
}
/// <inheritdoc />
public void ResetWereDirtyProperties()
{
// note: cannot .Clear() because when memberwise-cloning this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_savedChanges = null;
}
/// <inheritdoc />
public virtual void ResetDirtyProperties(bool rememberDirty)
{
// capture changes if remembering
// clone the dictionary in case it's shared by an entity clone
_savedChanges = rememberDirty && _currentChanges != null
? _currentChanges.ToDictionary(v => v.Key, v => v.Value)
: null;
// note: cannot .Clear() because when memberwise-clone this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_currentChanges = null;
}
/// <inheritdoc />
public virtual IEnumerable<string> GetWereDirtyProperties()
{
// ReSharper disable once MergeConditionalExpression
return _savedChanges == null
? Enumerable.Empty<string>()
: _savedChanges.Where(x => x.Value).Select(x => x.Key);
}
#endregion
#region Change Tracking
/// <summary>
/// Occurs when a property changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Registers that a property has changed.
/// </summary>
protected virtual void OnPropertyChanged(PropertyInfo propertyInfo)
{
if (_withChanges == false)
return;
if (_currentChanges == null)
_currentChanges = new Dictionary<string, bool>();
_currentChanges[propertyInfo.Name] = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name));
}
/// <summary>
/// Disables change tracking.
/// </summary>
public void DisableChangeTracking()
{
_withChanges = false;
}
/// <summary>
/// Enables change tracking.
/// </summary>
public void EnableChangeTracking()
{
_withChanges = true;
}
/// <summary>
/// Sets a property value, detects changes and manages the dirty flag.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The new value.</param>
/// <param name="valueRef">A reference to the value to set.</param>
/// <param name="propertySelector">The property selector.</param>
/// <param name="comparer">A comparer to compare property values.</param>
protected void SetPropertyValueAndDetectChanges<T>(T value, ref T valueRef, PropertyInfo propertySelector, IEqualityComparer<T> comparer = null)
{
if (comparer == null)
{
// if no comparer is provided, use the default provider, as long as the value is not
// an IEnumerable - exclude strings, which are IEnumerable but have a default comparer
var typeofT = typeof(T);
if (!(typeofT == typeof(string)) && typeof(IEnumerable).IsAssignableFrom(typeofT))
throw new ArgumentNullException(nameof(comparer), "A custom comparer must be supplied for IEnumerable values.");
comparer = EqualityComparer<T>.Default;
}
// compare values
var changed = _withChanges && comparer.Equals(valueRef, value) == false;
// assign the new value
valueRef = value;
// handle change
if (changed)
OnPropertyChanged(propertySelector);
}
/// <summary>
/// Detects changes and manages the dirty flag.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The new value.</param>
/// <param name="orig">The original value.</param>
/// <param name="propertySelector">The property selector.</param>
/// <param name="comparer">A comparer to compare property values.</param>
/// <param name="changed">A value indicating whether we know values have changed and no comparison is required.</param>
protected void DetectChanges<T>(T value, T orig, PropertyInfo propertySelector, IEqualityComparer<T> comparer, bool changed)
{
// compare values
changed = _withChanges && (changed || !comparer.Equals(orig, value));
// handle change
if (changed)
OnPropertyChanged(propertySelector);
}
#endregion
}
}
@@ -1,17 +0,0 @@
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Implements <see cref="IContentEntitySlim"/>.
/// </summary>
public class ContentEntitySlim : EntitySlim, IContentEntitySlim
{
/// <inheritdoc />
public string ContentTypeAlias { get; set; }
/// <inheritdoc />
public string ContentTypeIcon { get; set; }
/// <inheritdoc />
public string ContentTypeThumbnail { get; set; }
}
}
@@ -1,47 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Implements <see cref="IDocumentEntitySlim"/>.
/// </summary>
public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim
{
private static readonly IReadOnlyDictionary<string, string> Empty = new Dictionary<string, string>();
private IReadOnlyDictionary<string, string> _cultureNames;
private IEnumerable<string> _publishedCultures;
private IEnumerable<string> _editedCultures;
/// <inheritdoc />
public IReadOnlyDictionary<string, string> CultureNames
{
get => _cultureNames ?? Empty;
set => _cultureNames = value;
}
/// <inheritdoc />
public IEnumerable<string> PublishedCultures
{
get => _publishedCultures ?? Enumerable.Empty<string>();
set => _publishedCultures = value;
}
/// <inheritdoc />
public IEnumerable<string> EditedCultures
{
get => _editedCultures ?? Enumerable.Empty<string>();
set => _editedCultures = value;
}
public ContentVariation Variations { get; set; }
/// <inheritdoc />
public bool Published { get; set; }
/// <inheritdoc />
public bool Edited { get; set; }
}
}
@@ -1,197 +0,0 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Provides a base class for entities.
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
[DebuggerDisplay("Id: {" + nameof(Id) + "}")]
public abstract class EntityBase : BeingDirtyBase, IEntity
{
#if DEBUG_MODEL
public Guid InstanceId = Guid.NewGuid();
#endif
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private bool _hasIdentity;
private int _id;
private Guid _key;
private DateTime _createDate;
private DateTime _updateDate;
// ReSharper disable once ClassNeverInstantiated.Local
private class PropertySelectors
{
public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo<EntityBase, int>(x => x.Id);
public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<EntityBase, Guid>(x => x.Key);
public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo<EntityBase, DateTime>(x => x.CreateDate);
public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo<EntityBase, DateTime>(x => x.UpdateDate);
}
/// <inheritdoc />
[DataMember]
public int Id
{
get => _id;
set
{
SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector);
_hasIdentity = value != 0;
}
}
/// <inheritdoc />
[DataMember]
public Guid Key
{
get
{
// if an entity does NOT have a key yet, assign one now
if (_key == Guid.Empty)
_key = Guid.NewGuid();
return _key;
}
set => SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector);
}
/// <inheritdoc />
[DataMember]
public DateTime CreateDate
{
get => _createDate;
set => SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector);
}
/// <inheritdoc />
[DataMember]
public DateTime UpdateDate
{
get => _updateDate;
set => SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector);
}
/// <inheritdoc />
[DataMember]
public DateTime? DeleteDate { get; set; } // no change tracking - not persisted
/// <inheritdoc />
[DataMember]
public virtual bool HasIdentity => _hasIdentity;
/// <summary>
/// Resets the entity identity.
/// </summary>
internal virtual void ResetIdentity()
{
_id = default;
_key = Guid.Empty;
_hasIdentity = false;
}
/// <summary>
/// Updates the entity when it is being saved for the first time.
/// </summary>
internal virtual void AddingEntity()
{
var now = DateTime.Now;
// set the create and update dates, if not already set
if (IsPropertyDirty("CreateDate") == false || _createDate == default)
CreateDate = now;
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default)
UpdateDate = now;
}
/// <summary>
/// Updates the entity when it is being saved.
/// </summary>
internal virtual void UpdatingEntity()
{
var now = DateTime.Now;
// just in case
if (_createDate == default)
CreateDate = now;
// set the update date if not already set
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default)
UpdateDate = now;
}
public virtual bool Equals(EntityBase other)
{
return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other));
}
public override bool Equals(object obj)
{
return obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as EntityBase));
}
private bool SameIdentityAs(EntityBase other)
{
if (other == null) return false;
// same identity if
// - same object (reference equals)
// - or same Clr type, both have identities, and they are identical
if (ReferenceEquals(this, other))
return true;
return GetType() == other.GetType() && HasIdentity && other.HasIdentity && Id == other.Id;
}
public override int GetHashCode()
{
unchecked
{
var hashCode = HasIdentity.GetHashCode();
hashCode = (hashCode * 397) ^ Id;
hashCode = (hashCode * 397) ^ GetType().GetHashCode();
return hashCode;
}
}
public object DeepClone()
{
// memberwise-clone (ie shallow clone) the entity
var unused = Key; // ensure that 'this' has a key, before cloning
var clone = (EntityBase) MemberwiseClone();
#if DEBUG_MODEL
clone.InstanceId = Guid.NewGuid();
#endif
//disable change tracking while we deep clone IDeepCloneable properties
clone.DisableChangeTracking();
// deep clone ref properties that are IDeepCloneable
DeepCloneHelper.DeepCloneRefProperties(this, clone);
PerformDeepClone(clone);
// clear changes (ensures the clone has its own dictionaries)
clone.ResetDirtyProperties(false);
//re-enable change tracking
clone.EnableChangeTracking();
return clone;
}
/// <summary>
/// Used by inheritors to modify the DeepCloning logic
/// </summary>
/// <param name="clone"></param>
protected virtual void PerformDeepClone(object clone)
{
}
}
}
@@ -1,224 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core.Exceptions;
namespace Umbraco.Core.Models.Entities
{
// fixme - changing the name of some properties that were in additionalData => must update corresponding javascript?
/// <summary>
/// Implementation of <see cref="IEntitySlim"/> for internal use.
/// </summary>
/// <remarks>
/// <para>Although it implements <see cref="IEntitySlim"/>, this class does not
/// implement <see cref="IRememberBeingDirty"/> and everything this interface defines, throws.</para>
/// <para>Although it implements <see cref="IEntitySlim"/>, this class does not
/// implement <see cref="IDeepCloneable"/> and deep-cloning throws.</para>
/// </remarks>
public class EntitySlim : IEntitySlim
{
private IDictionary<string, object> _additionalData;
/// <summary>
/// Gets an entity representing "root".
/// </summary>
public static readonly IEntitySlim Root = new EntitySlim { Path = "-1", Name = "root", HasChildren = true };
// implement IEntity
/// <inheritdoc />
[DataMember]
public int Id { get; set; }
/// <inheritdoc />
[DataMember]
public Guid Key { get; set; }
/// <inheritdoc />
[DataMember]
public DateTime CreateDate { get; set; }
/// <inheritdoc />
[DataMember]
public DateTime UpdateDate { get; set; }
/// <inheritdoc />
[DataMember]
public DateTime? DeleteDate { get; set; }
/// <inheritdoc />
[DataMember]
public bool HasIdentity => Id != 0;
// implement ITreeEntity
/// <inheritdoc />
[DataMember]
public string Name { get; set; }
/// <inheritdoc />
[DataMember]
public int CreatorId { get; set; }
/// <inheritdoc />
[DataMember]
public int ParentId { get; set; }
/// <inheritdoc />
public void SetParent(ITreeEntity parent) => throw new WontImplementException();
/// <inheritdoc />
[DataMember]
public int Level { get; set; }
/// <inheritdoc />
[DataMember]
public string Path { get; set; }
/// <inheritdoc />
[DataMember]
public int SortOrder { get; set; }
/// <inheritdoc />
[DataMember]
public bool Trashed { get; set; }
// implement IUmbracoEntity
/// <inheritdoc />
[DataMember]
public IDictionary<string, object> AdditionalData => _additionalData ?? (_additionalData = new Dictionary<string, object>());
/// <inheritdoc />
[IgnoreDataMember]
public bool HasAdditionalData => _additionalData != null;
// implement IEntitySlim
/// <inheritdoc />
[DataMember]
public Guid NodeObjectType { get; set; }
/// <inheritdoc />
[DataMember]
public bool HasChildren { get; set; }
/// <inheritdoc />
[DataMember]
public virtual bool IsContainer { get; set; }
/// <summary>
/// Represents a lightweight property.
/// </summary>
public class PropertySlim
{
/// <summary>
/// Initializes a new instance of the <see cref="PropertySlim"/> class.
/// </summary>
public PropertySlim(string editorAlias, object value)
{
PropertyEditorAlias = editorAlias;
Value = value;
}
/// <summary>
/// Gets the property editor alias.
/// </summary>
public string PropertyEditorAlias { get; }
/// <summary>
/// Gets the property value.
/// </summary>
public object Value { get; }
protected bool Equals(PropertySlim other)
{
return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && Equals(Value, other.Value);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((PropertySlim) obj);
}
public override int GetHashCode()
{
unchecked
{
return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0);
}
}
}
#region IDeepCloneable
/// <inheritdoc />
public object DeepClone()
{
throw new WontImplementException();
}
#endregion
#region IRememberBeingDirty
// IEntitySlim does *not* track changes, but since it indirectly implements IUmbracoEntity,
// and therefore IRememberBeingDirty, we have to have those methods - which all throw.
public bool IsDirty()
{
throw new WontImplementException();
}
public bool IsPropertyDirty(string propName)
{
throw new WontImplementException();
}
public IEnumerable<string> GetDirtyProperties()
{
throw new WontImplementException();
}
public void ResetDirtyProperties()
{
throw new WontImplementException();
}
public bool WasDirty()
{
throw new WontImplementException();
}
public bool WasPropertyDirty(string propertyName)
{
throw new WontImplementException();
}
public void ResetWereDirtyProperties()
{
throw new WontImplementException();
}
public void ResetDirtyProperties(bool rememberDirty)
{
throw new WontImplementException();
}
public IEnumerable<string> GetWereDirtyProperties()
{
throw new WontImplementException();
}
#endregion
}
}
@@ -1,30 +0,0 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Defines an entity that tracks property changes and can be dirty.
/// </summary>
public interface ICanBeDirty
{
/// <summary>
/// Determines whether the current entity is dirty.
/// </summary>
bool IsDirty();
/// <summary>
/// Determines whether a specific property is dirty.
/// </summary>
bool IsPropertyDirty(string propName);
/// <summary>
/// Gets properties that are dirty.
/// </summary>
IEnumerable<string> GetDirtyProperties();
/// <summary>
/// Resets dirty properties.
/// </summary>
void ResetDirtyProperties();
}
}
@@ -1,23 +0,0 @@
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Represents a lightweight content entity, managed by the entity service.
/// </summary>
public interface IContentEntitySlim : IEntitySlim
{
/// <summary>
/// Gets the content type alias.
/// </summary>
string ContentTypeAlias { get; }
/// <summary>
/// Gets the content type icon.
/// </summary>
string ContentTypeIcon { get; }
/// <summary>
/// Gets the content type thumbnail.
/// </summary>
string ContentTypeThumbnail { get; }
}
}
@@ -1,41 +0,0 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Represents a lightweight document entity, managed by the entity service.
/// </summary>
public interface IDocumentEntitySlim : IContentEntitySlim
{
/// <summary>
/// Gets the variant name for each culture
/// </summary>
IReadOnlyDictionary<string, string> CultureNames { get; }
/// <summary>
/// Gets the published cultures.
/// </summary>
IEnumerable<string> PublishedCultures { get; }
/// <summary>
/// Gets the edited cultures.
/// </summary>
IEnumerable<string> EditedCultures { get; }
/// <summary>
/// Gets the content variation of the content type.
/// </summary>
ContentVariation Variations { get; }
/// <summary>
/// Gets a value indicating whether the content is published.
/// </summary>
bool Published { get; }
/// <summary>
/// Gets a value indicating whether the content has been edited.
/// </summary>
bool Edited { get; }
}
}
@@ -1,45 +0,0 @@
using System;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Defines an entity.
/// </summary>
public interface IEntity : IDeepCloneable
{
/// <summary>
/// Gets or sets the integer identifier of the entity.
/// </summary>
int Id { get; set; }
/// <summary>
/// Gets or sets the Guid unique identifier of the entity.
/// </summary>
Guid Key { get; set; }
/// <summary>
/// Gets or sets the creation date.
/// </summary>
DateTime CreateDate { get; set; }
/// <summary>
/// Gets or sets the last update date.
/// </summary>
DateTime UpdateDate { get; set; }
/// <summary>
/// Gets or sets the delete date.
/// </summary>
/// <remarks>
/// <para>The delete date is null when the entity has not been deleted.</para>
/// <para>The delete date has a value when the entity instance has been deleted, but this value
/// is transient and not persisted in database (since the entity does not exist anymore).</para>
/// </remarks>
DateTime? DeleteDate { get; set; }
/// <summary>
/// Gets a value indicating whether the entity has an identity.
/// </summary>
bool HasIdentity { get; }
}
}
@@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Represents a lightweight entity, managed by the entity service.
/// </summary>
public interface IEntitySlim : IUmbracoEntity, IHaveAdditionalData
{
/// <summary>
/// Gets or sets the entity object type.
/// </summary>
Guid NodeObjectType { get; }
/// <summary>
/// Gets or sets a value indicating whether the entity has children.
/// </summary>
bool HasChildren { get; }
/// <summary>
/// Gets a value indicating whether the entity is a container.
/// </summary>
bool IsContainer { get; }
}
}
@@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Provides support for additional data.
/// </summary>
/// <remarks>
/// <para>Additional data are transient, not deep-cloned.</para>
/// </remarks>
public interface IHaveAdditionalData
{
/// <summary>
/// Gets additional data for this entity.
/// </summary>
/// <remarks>Can be empty, but never null. To avoid allocating, do not
/// test for emptyness, but use <see cref="HasAdditionalData"/> instead.</remarks>
IDictionary<string, object> AdditionalData { get; }
/// <summary>
/// Determines whether this entity has additional data.
/// </summary>
/// <remarks>Use this property to check for additional data without
/// getting <see cref="AdditionalData"/>, to avoid allocating.</remarks>
bool HasAdditionalData { get; }
// how to implement:
/*
private IDictionary<string, object> _additionalData;
/// <inheritdoc />
[DataMember]
[DoNotClone]
PublicAccessEntry IDictionary<string, object> AdditionalData => _additionalData ?? (_additionalData = new Dictionary<string, object>());
/// <inheritdoc />
[IgnoreDataMember]
PublicAccessEntry bool HasAdditionalData => _additionalData != null;
*/
}
}
@@ -1,40 +0,0 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Defines an entity that tracks property changes and can be dirty, and remembers
/// which properties were dirty when the changes were committed.
/// </summary>
public interface IRememberBeingDirty : ICanBeDirty
{
/// <summary>
/// Determines whether the current entity is dirty.
/// </summary>
/// <remarks>A property was dirty if it had been changed and the changes were committed.</remarks>
bool WasDirty();
/// <summary>
/// Determines whether a specific property was dirty.
/// </summary>
/// <remarks>A property was dirty if it had been changed and the changes were committed.</remarks>
bool WasPropertyDirty(string propertyName);
/// <summary>
/// Resets properties that were dirty.
/// </summary>
void ResetWereDirtyProperties();
/// <summary>
/// Resets dirty properties.
/// </summary>
/// <param name="rememberDirty">A value indicating whether to remember dirty properties.</param>
/// <remarks>When <paramref name="rememberDirty"/> is true, dirty properties are saved so they can be checked with WasDirty.</remarks>
void ResetDirtyProperties(bool rememberDirty);
/// <summary>
/// Gets properties that were dirty.
/// </summary>
IEnumerable<string> GetWereDirtyProperties();
}
}
@@ -1,56 +0,0 @@
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Defines an entity that belongs to a tree.
/// </summary>
public interface ITreeEntity : IEntity
{
/// <summary>
/// Gets or sets the name of the entity.
/// </summary>
string Name { get; set; }
/// <summary>
/// Gets or sets the identifier of the user who created this entity.
/// </summary>
int CreatorId { get; set; }
/// <summary>
/// Gets or sets the identifier of the parent entity.
/// </summary>
int ParentId { get; set; }
/// <summary>
/// Sets the parent entity.
/// </summary>
/// <remarks>Use this method to set the parent entity when the parent entity is known, but has not
/// been persistent and does not yet have an identity. The parent identifier will we retrieved
/// from the parent entity when needed. If the parent entity still does not have an entity by that
/// time, an exception will be thrown by <see cref="ParentId"/> getter.</remarks>
void SetParent(ITreeEntity parent);
/// <summary>
/// Gets or sets the level of the entity.
/// </summary>
int Level { get; set; }
/// <summary>
/// Gets or sets the path to the entity.
/// </summary>
string Path { get; set; }
/// <summary>
/// Gets or sets the sort order of the entity.
/// </summary>
int SortOrder { get; set; }
/// <summary>
/// Gets a value indicating whether this entity is trashed.
/// </summary>
/// <remarks>
/// <para>Trashed entities are located in the recycle bin.</para>
/// <para>Always false for entities that do not support being trashed.</para>
/// </remarks>
bool Trashed { get; }
}
}
@@ -1,15 +0,0 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Represents an entity that can be managed by the entity service.
/// </summary>
/// <remarks>
/// <para>An IUmbracoEntity can be related to another via the IRelationService.</para>
/// <para>IUmbracoEntities can be retrieved with the IEntityService.</para>
/// <para>An IUmbracoEntity can participate in notifications.</para>
/// </remarks>
public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty
{ }
}
@@ -1,124 +0,0 @@
using System;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Provides a base class for tree entities.
/// </summary>
public abstract class TreeEntityBase : EntityBase, ITreeEntity
{
private static PropertySelectors _selectors;
private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors());
private string _name;
private int _creatorId;
private int _parentId;
private bool _hasParentId;
private ITreeEntity _parent;
private int _level;
private string _path;
private int _sortOrder;
private bool _trashed;
private class PropertySelectors
{
public readonly PropertyInfo Name = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Name);
public readonly PropertyInfo CreatorId = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.CreatorId);
public readonly PropertyInfo ParentId = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.ParentId);
public readonly PropertyInfo Level = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.Level);
public readonly PropertyInfo Path = ExpressionHelper.GetPropertyInfo<ContentBase, string>(x => x.Path);
public readonly PropertyInfo SortOrder = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.SortOrder);
public readonly PropertyInfo Trashed = ExpressionHelper.GetPropertyInfo<ContentBase, bool>(x => x.Trashed);
}
// fixme
// ParentId, Path, Level and Trashed all should be consistent, and all derive from parentId, really
/// <inheritdoc />
[DataMember]
public string Name
{
get => _name;
set => SetPropertyValueAndDetectChanges(value, ref _name, Selectors.Name);
}
/// <inheritdoc />
[DataMember]
public int CreatorId
{
get => _creatorId;
set => SetPropertyValueAndDetectChanges(value, ref _creatorId, Selectors.CreatorId);
}
/// <inheritdoc />
[DataMember]
public int ParentId
{
get
{
if (_hasParentId) return _parentId;
if (_parent == null) throw new InvalidOperationException("Content does not have a parent.");
if (!_parent.HasIdentity) throw new InvalidOperationException("Content's parent does not have an identity.");
_parentId = _parent.Id;
if (_parentId == 0)
throw new Exception("Panic: parent has an identity but id is zero.");
_hasParentId = true;
_parent = null;
return _parentId;
}
set
{
if (value == 0)
throw new ArgumentException("Value cannot be zero.", nameof(value));
SetPropertyValueAndDetectChanges(value, ref _parentId, Selectors.ParentId);
_hasParentId = true;
_parent = null;
}
}
/// <inheritdoc />
public void SetParent(ITreeEntity parent)
{
_hasParentId = false;
_parent = parent;
OnPropertyChanged(Selectors.ParentId);
}
/// <inheritdoc />
[DataMember]
public int Level
{
get => _level;
set => SetPropertyValueAndDetectChanges(value, ref _level, Selectors.Level);
}
/// <inheritdoc />
[DataMember]
public string Path
{
get => _path;
set => SetPropertyValueAndDetectChanges(value, ref _path, Selectors.Path);
}
/// <inheritdoc />
[DataMember]
public int SortOrder
{
get => _sortOrder;
set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, Selectors.SortOrder);
}
/// <inheritdoc />
[DataMember]
public bool Trashed
{
get => _trashed;
set => SetPropertyValueAndDetectChanges(value, ref _trashed, Selectors.Trashed);
}
}
}
@@ -1,18 +0,0 @@
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Represents the path of a tree entity.
/// </summary>
public class TreeEntityPath
{
/// <summary>
/// Gets or sets the identifier of the entity.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the path of the entity.
/// </summary>
public string Path { get; set; }
}
}
@@ -0,0 +1,231 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// Base Abstract Entity
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
[DebuggerDisplay("Id: {Id}")]
public abstract class Entity : TracksChangesEntityBase, IEntity, IRememberBeingDirty, ICanBeDirty
{
private bool _hasIdentity;
private int _id;
private Guid _key;
private DateTime _createDate;
private DateTime _updateDate;
private bool _wasCancelled;
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo<Entity, int>(x => x.Id);
public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<Entity, Guid>(x => x.Key);
public readonly PropertyInfo CreateDateSelector = ExpressionHelper.GetPropertyInfo<Entity, DateTime>(x => x.CreateDate);
public readonly PropertyInfo UpdateDateSelector = ExpressionHelper.GetPropertyInfo<Entity, DateTime>(x => x.UpdateDate);
public readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo<Entity, bool>(x => x.HasIdentity);
public readonly PropertyInfo WasCancelledSelector = ExpressionHelper.GetPropertyInfo<Entity, bool>(x => x.WasCancelled);
}
/// <summary>
/// Integer Id
/// </summary>
[DataMember]
public int Id
{
get { return _id; }
set
{
SetPropertyValueAndDetectChanges(value, ref _id, Ps.Value.IdSelector);
HasIdentity = true; //set the has Identity
}
}
/// <summary>
/// Guid based Id
/// </summary>
/// <remarks>The key is currectly used to store the Unique Id from the
/// umbracoNode table, which many of the entities are based on.</remarks>
[DataMember]
public Guid Key
{
get
{
// if an entity does NOT have a UniqueId yet, assign one now
if (_key == Guid.Empty)
_key = Guid.NewGuid();
return _key;
}
set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); }
}
/// <summary>
/// Gets or sets the Created Date
/// </summary>
[DataMember]
public DateTime CreateDate
{
get { return _createDate; }
set { SetPropertyValueAndDetectChanges(value, ref _createDate, Ps.Value.CreateDateSelector); }
}
/// <summary>
/// Gets or sets the WasCancelled flag, which is used to track
/// whether some action against an entity was cancelled through some event.
/// This only exists so we have a way to check if an event was cancelled through
/// the new api, which also needs to take effect in the legacy api.
/// </summary>
[IgnoreDataMember]
[Obsolete("Anytime there's a cancellable method it needs to return an Attempt so we know the outcome instead of this hack, not all services have been updated to use this though yet.")]
internal bool WasCancelled
{
get { return _wasCancelled; }
set { SetPropertyValueAndDetectChanges(value, ref _wasCancelled, Ps.Value.WasCancelledSelector); }
}
/// <summary>
/// Gets or sets the Modified Date
/// </summary>
[DataMember]
public DateTime UpdateDate
{
get { return _updateDate; }
set { SetPropertyValueAndDetectChanges(value, ref _updateDate, Ps.Value.UpdateDateSelector); }
}
[IgnoreDataMember]
public DateTime? DeletedDate { get; set; }
internal virtual void ResetIdentity()
{
_hasIdentity = false;
_id = default(int);
_key = Guid.Empty;
}
/// <summary>
/// Method to call on entity saved when first added
/// </summary>
internal virtual void AddingEntity()
{
if (IsPropertyDirty("CreateDate") == false || _createDate == default(DateTime))
CreateDate = DateTime.Now;
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
UpdateDate = DateTime.Now;
}
/// <summary>
/// Method to call on entity saved/updated
/// </summary>
internal virtual void UpdatingEntity()
{
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
UpdateDate = DateTime.Now;
//this is just in case
if (_createDate == default(DateTime))
CreateDate = DateTime.Now;
}
/// <summary>
/// Indicates whether the current entity has an identity, eg. Id.
/// </summary>
[DataMember]
public virtual bool HasIdentity
{
get
{
return _hasIdentity;
}
protected set { SetPropertyValueAndDetectChanges(value, ref _hasIdentity, Ps.Value.HasIdentitySelector); }
}
//TODO: Make this NOT virtual or even exist really!
public virtual bool SameIdentityAs(IEntity other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return SameIdentityAs(other as Entity);
}
public virtual bool Equals(Entity other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return SameIdentityAs(other);
}
//TODO: Make this NOT virtual or even exist really!
public virtual Type GetRealType()
{
return GetType();
}
//TODO: Make this NOT virtual or even exist really!
public virtual bool SameIdentityAs(Entity other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity)
return other.Id.Equals(Id);
return false;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
return SameIdentityAs(obj as IEntity);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = HasIdentity.GetHashCode();
hashCode = (hashCode * 397) ^ Id;
hashCode = (hashCode * 397) ^ GetType().GetHashCode();
return hashCode;
}
}
public virtual object DeepClone()
{
//Memberwise clone on Entity will work since it doesn't have any deep elements
// for any sub class this will work for standard properties as well that aren't complex object's themselves.
var ignored = this.Key; // ensure that 'this' has a key, before cloning
var clone = (Entity)MemberwiseClone();
//ensure the clone has it's own dictionaries
clone.ResetChangeTrackingCollections();
//turn off change tracking
clone.DisableChangeTracking();
//Automatically deep clone ref properties that are IDeepCloneable
DeepCloneHelper.DeepCloneRefProperties(this, clone);
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
}
}
@@ -0,0 +1,8 @@
namespace Umbraco.Core.Models.EntityBase
{
public class EntityPath
{
public int Id { get; set; }
public string Path { get; set; }
}
}
@@ -0,0 +1,10 @@
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// Marker interface for aggregate roots
/// </summary>
public interface IAggregateRoot : IDeletableEntity
{
}
}
@@ -0,0 +1,12 @@
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// An interface that defines the object is tracking property changes and if it is Dirty
/// </summary>
public interface ICanBeDirty
{
bool IsDirty();
bool IsPropertyDirty(string propName);
void ResetDirtyProperties();
}
}
@@ -0,0 +1,11 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.EntityBase
{
public interface IDeletableEntity : IEntity
{
[DataMember]
DateTime? DeletedDate { get; set; }
}
}
@@ -0,0 +1,46 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// Defines an Entity.
/// Entities should always have an Id, Created and Modified date
/// </summary>
/// <remarks>The current database schema doesn't provide a modified date
/// for all entities, so this will have to be changed at a later stage.</remarks>
public interface IEntity : IDeepCloneable
{
/// <summary>
/// The Id of the entity
/// </summary>
[DataMember]
int Id { get; set; }
/// <summary>
/// Guid based Id
/// </summary>
/// <remarks>The key is currectly used to store the Unique Id from the
/// umbracoNode table, which many of the entities are based on.</remarks>
[DataMember]
Guid Key { get; set; }
/// <summary>
/// Gets or sets the Created Date
/// </summary>
[DataMember]
DateTime CreateDate { get; set; }
/// <summary>
/// Gets or sets the Modified Date
/// </summary>
[DataMember]
DateTime UpdateDate { get; set; }
/// <summary>
/// Indicates whether the current entity has an identity, eg. Id.
/// </summary>
[IgnoreDataMember]
bool HasIdentity { get; }
}
}
@@ -0,0 +1,14 @@
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// An interface that defines if the object is tracking property changes and that is is also
/// remembering what property changes had been made after the changes were committed.
/// </summary>
public interface IRememberBeingDirty : ICanBeDirty
{
bool WasDirty();
bool WasPropertyDirty(string propertyName);
void ForgetPreviouslyDirtyProperties();
void ResetDirtyProperties(bool rememberPreviouslyChangedProperties);
}
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models.EntityBase
{
public interface IUmbracoEntity : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
/// <summary>
/// Profile of the user who created this Entity
/// </summary>
int CreatorId { get; set; }
/// <summary>
/// Gets or sets the level of the Entity
/// </summary>
int Level { get; set; }
/// <summary>
/// Gets or Sets the Name of the Entity
/// </summary>
string Name { get; set; }
/// <summary>
/// Gets or sets the Id of the Parent Entity
/// </summary>
int ParentId { get; set; }
/// <summary>
/// Gets or sets the path to the Entity
/// </summary>
string Path { get; set; }
/// <summary>
/// Gets or sets the sort order of the Entity
/// </summary>
int SortOrder { get; set; }
/// <summary>
/// Boolean indicating whether this Entity is Trashed or not.
/// If an Entity is Trashed it will be located in the Recyclebin.
/// </summary>
/// <remarks>
/// When content is trashed it should be unpublished.
/// Not all entities support being trashed, they'll always return false.
/// </remarks>
bool Trashed { get; }
/// <summary>
/// Some entities may expose additional data that other's might not, this custom data will be available in this collection
/// </summary>
IDictionary<string, object> AdditionalData { get; }
}
}
@@ -1,11 +1,11 @@
namespace Umbraco.Core.Models.Entities
{
/// <summary>
/// Marker interface for value object, eg. objects without
/// the same kind of identity as an Entity (with its Id).
/// </summary>
public interface IValueObject
{
}
}
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// Marker interface for value object, eg. objects without
/// the same kind of identity as an Entity (with its Id).
/// </summary>
public interface IValueObject
{
}
}
@@ -0,0 +1,231 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
namespace Umbraco.Core.Models.EntityBase
{
/// <summary>
/// A base class for use to implement IRememberBeingDirty/ICanBeDirty
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public abstract class TracksChangesEntityBase : IRememberBeingDirty
{
//TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662
public virtual IEnumerable<string> GetDirtyProperties()
{
return _propertyChangedInfo == null
? Enumerable.Empty<string>()
: _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
internal virtual IEnumerable<string> GetPreviouslyDirtyProperties()
{
return _lastPropertyChangedInfo == null
? Enumerable.Empty<string>()
: _lastPropertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
private bool _changeTrackingEnabled = true;
/// <summary>
/// Tracks the properties that have changed
/// </summary>
private IDictionary<string, bool> _propertyChangedInfo;
/// <summary>
/// Tracks the properties that we're changed before the last commit (or last call to ResetDirtyProperties)
/// </summary>
private IDictionary<string, bool> _lastPropertyChangedInfo;
/// <summary>
/// Property changed event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Method to call on a property setter.
/// </summary>
/// <param name="propertyInfo">The property info.</param>
protected virtual void OnPropertyChanged(PropertyInfo propertyInfo)
{
//return if we're not tracking changes
if (_changeTrackingEnabled == false) return;
if (_propertyChangedInfo == null)
_propertyChangedInfo = new Dictionary<string, bool>();
_propertyChangedInfo[propertyInfo.Name] = true;
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyInfo.Name));
}
/// <summary>
/// Indicates whether a specific property on the current entity is dirty.
/// </summary>
/// <param name="propertyName">Name of the property to check</param>
/// <returns>True if Property is dirty, otherwise False</returns>
public virtual bool IsPropertyDirty(string propertyName)
{
return _propertyChangedInfo != null && _propertyChangedInfo.Any(x => x.Key == propertyName);
}
/// <summary>
/// Indicates whether the current entity is dirty.
/// </summary>
/// <returns>True if entity is dirty, otherwise False</returns>
public virtual bool IsDirty()
{
return _propertyChangedInfo != null && _propertyChangedInfo.Any();
}
/// <summary>
/// Indicates that the entity had been changed and the changes were committed
/// </summary>
/// <returns></returns>
public virtual bool WasDirty()
{
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any();
}
/// <summary>
/// Indicates whether a specific property on the current entity was changed and the changes were committed
/// </summary>
/// <param name="propertyName">Name of the property to check</param>
/// <returns>True if Property was changed, otherwise False. Returns false if the entity had not been previously changed.</returns>
public virtual bool WasPropertyDirty(string propertyName)
{
return _lastPropertyChangedInfo != null && _lastPropertyChangedInfo.Any(x => x.Key == propertyName);
}
/// <summary>
/// Resets the remembered dirty properties from before the last commit
/// </summary>
public void ForgetPreviouslyDirtyProperties()
{
//NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_lastPropertyChangedInfo = null;
}
/// <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 virtual void ResetDirtyProperties()
{
ResetDirtyProperties(true);
}
/// <summary>
/// Resets dirty properties by clearing the dictionary used to track changes.
/// </summary>
/// <param name="rememberPreviouslyChangedProperties">
/// true if we are to remember the last changes made after resetting
/// </param>
/// <remarks>
/// Please note that resetting the dirty properties could potentially
/// obstruct the saving of a new or updated entity.
/// </remarks>
public virtual void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
{
if (rememberPreviouslyChangedProperties)
{
//copy the changed properties to the last changed properties
if (_propertyChangedInfo != null)
{
_lastPropertyChangedInfo = _propertyChangedInfo.ToDictionary(v => v.Key, v => v.Value);
}
}
//NOTE: We cannot .Clear() because when we memberwise clone this will be the SAME
// instance as the one on the clone, so we need to create a new instance.
_propertyChangedInfo = null;
}
public void ResetChangeTrackingCollections()
{
_propertyChangedInfo = null;
_lastPropertyChangedInfo = null;
}
public void DisableChangeTracking()
{
_changeTrackingEnabled = false;
}
public void EnableChangeTracking()
{
_changeTrackingEnabled = true;
}
/// <summary>
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
/// </summary>
/// <param name="newVal"></param>
/// <param name="origVal"></param>
/// <param name="propertySelector"></param>
/// <returns>returns true if the value changed</returns>
/// <remarks>
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// to the same value, so it's really not dirty.
/// </remarks>
internal void SetPropertyValueAndDetectChanges<T>(T newVal, ref T origVal, PropertyInfo propertySelector)
{
if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom<IEnumerable>(typeof(T)))
{
throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required");
}
SetPropertyValueAndDetectChanges(newVal, ref origVal, propertySelector, EqualityComparer<T>.Default);
}
/// <summary>
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
/// </summary>
/// <param name="newVal"></param>
/// <param name="origVal"></param>
/// <param name="propertySelector"></param>
/// <param name="comparer">The equality comparer to use</param>
/// <returns>returns true if the value changed</returns>
/// <remarks>
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
/// to the same value, so it's really not dirty.
/// </remarks>
internal void SetPropertyValueAndDetectChanges<T>(T newVal, ref T origVal, PropertyInfo propertySelector, IEqualityComparer<T> comparer)
{
//don't track changes, just set the value
if (_changeTrackingEnabled == false)
{
//set the original value
origVal = newVal;
}
else
{
//check changed
var changed = comparer.Equals(origVal, newVal) == false;
//set the original value
origVal = newVal;
//raise the event if it was changed
if (changed)
{
OnPropertyChanged(propertySelector);
}
}
}
}
}
+17 -14
View File
@@ -1,24 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a folder for organizing entities such as content types and data types.
/// </summary>
public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity
public sealed class EntityContainer : UmbracoEntity
{
private readonly Guid _containedObjectType;
private static readonly Dictionary<Guid, Guid> ObjectTypeMap = new Dictionary<Guid, Guid>
{
{ Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer },
{ Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer },
{ Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer }
{ Constants.ObjectTypes.DataTypeGuid, Constants.ObjectTypes.DataTypeContainerGuid },
{ Constants.ObjectTypes.DocumentTypeGuid, Constants.ObjectTypes.DocumentTypeContainerGuid },
{ Constants.ObjectTypes.MediaTypeGuid, Constants.ObjectTypes.MediaTypeContainerGuid }
};
/// <summary>
@@ -27,7 +24,7 @@ namespace Umbraco.Core.Models
public EntityContainer(Guid containedObjectType)
{
if (ObjectTypeMap.ContainsKey(containedObjectType) == false)
throw new ArgumentException("Not a contained object type.", nameof(containedObjectType));
throw new ArgumentException("Not a contained object type.", "containedObjectType");
_containedObjectType = containedObjectType;
ParentId = -1;
@@ -39,7 +36,7 @@ namespace Umbraco.Core.Models
/// <summary>
/// Initializes a new instance of an <see cref="EntityContainer"/> class.
/// </summary>
public EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, Guid containedObjectType, string name, int userId)
internal EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, Guid containedObjectType, string name, int userId)
: this(containedObjectType)
{
Id = id;
@@ -55,12 +52,18 @@ namespace Umbraco.Core.Models
/// <summary>
/// Gets or sets the node object type of the contained objects.
/// </summary>
public Guid ContainedObjectType => _containedObjectType;
public Guid ContainedObjectType
{
get { return _containedObjectType; }
}
/// <summary>
/// Gets the node object type of the container objects.
/// </summary>
public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType];
public Guid ContainerObjectType
{
get { return ObjectTypeMap[_containedObjectType]; }
}
/// <summary>
/// Gets the container object type corresponding to a contained object type.
@@ -70,7 +73,7 @@ namespace Umbraco.Core.Models
public static Guid GetContainerObjectType(Guid containedObjectType)
{
if (ObjectTypeMap.ContainsKey(containedObjectType) == false)
throw new ArgumentException("Not a contained object type.", nameof(containedObjectType));
throw new ArgumentException("Not a contained object type.", "containedObjectType");
return ObjectTypeMap[containedObjectType];
}
@@ -83,8 +86,8 @@ namespace Umbraco.Core.Models
{
var contained = ObjectTypeMap.FirstOrDefault(x => x.Value == containerObjectType).Key;
if (contained == null)
throw new ArgumentException("Not a container object type.", nameof(containerObjectType));
throw new ArgumentException("Not a container object type.", "containerObjectType");
return contained;
}
}
}
}
+14 -7
View File
@@ -1,17 +1,24 @@
using Umbraco.Core.Models.Entities;
using System.Linq;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
public static class EntityExtensions
{
/// <summary>
/// Gets additional data.
/// Returns true if this entity has just been created and persisted to the data store
/// </summary>
public static object GetAdditionalDataValueIgnoreCase(this IHaveAdditionalData entity, string key, object defaultValue)
/// <param name="entity"></param>
/// <returns></returns>
/// <remarks>
/// This is useful when handling events to determine if an entity is a brand new entity or was
/// already existing.
/// </remarks>
public static bool IsNewEntity(this IEntity entity)
{
if (!entity.HasAdditionalData) return defaultValue;
if (entity.AdditionalData.ContainsKeyIgnoreCase(key) == false) return defaultValue;
return entity.AdditionalData.GetValueIgnoreCase(key, defaultValue);
var dirty = (IRememberBeingDirty)entity;
return dirty.WasPropertyDirty("Id");
}
}
}
}
+187 -172
View File
@@ -1,172 +1,187 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using Umbraco.Core.IO;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents an abstract file which provides basic functionality for a File with an Alias and Name
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public abstract class File : EntityBase, IFile
{
private string _path;
private string _originalPath;
// initialize to string.Empty so that it is possible to save a new file,
// should use the lazyContent ctor to set it to null when loading existing.
// cannot simply use HasIdentity as some classes (eg Script) override it
// in a weird way.
private string _content;
internal Func<File, string> GetFileContent { get; set; }
protected File(string path, Func<File, string> getFileContent = null)
{
_path = SanitizePath(path);
_originalPath = _path;
GetFileContent = getFileContent;
_content = getFileContent != null ? null : string.Empty;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Content);
public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Path);
}
private string _alias;
private string _name;
private static string SanitizePath(string path)
{
return path
.Replace('\\', System.IO.Path.DirectorySeparatorChar)
.Replace('/', System.IO.Path.DirectorySeparatorChar);
//Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests
//.TrimStart(System.IO.Path.DirectorySeparatorChar)
//.TrimStart('/');
}
/// <summary>
/// Gets or sets the Name of the File including extension
/// </summary>
[DataMember]
public virtual string Name
{
get { return _name ?? (_name = System.IO.Path.GetFileName(Path)); }
}
/// <summary>
/// Gets or sets the Alias of the File, which is the name without the extension
/// </summary>
[DataMember]
public virtual string Alias
{
get
{
if (_alias == null)
{
var name = System.IO.Path.GetFileName(Path);
if (name == null) return string.Empty;
var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase);
_alias = name.Substring(0, lastIndexOf);
}
return _alias;
}
}
/// <summary>
/// Gets or sets the Path to the File from the root of the file's associated IFileSystem
/// </summary>
[DataMember]
public virtual string Path
{
get { return _path; }
set
{
//reset
_alias = null;
_name = null;
SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector);
}
}
/// <summary>
/// Gets the original path of the file
/// </summary>
public string OriginalPath
{
get { return _originalPath; }
}
/// <summary>
/// Called to re-set the OriginalPath to the Path
/// </summary>
public void ResetOriginalPath()
{
_originalPath = _path;
}
/// <summary>
/// Gets or sets the Content of a File
/// </summary>
/// <remarks>Marked as DoNotClone, because it should be lazy-reloaded from disk.</remarks>
[DataMember]
[DoNotClone]
public virtual string Content
{
get
{
if (_content != null)
return _content;
// else, must lazy-load, and ensure it's not null
if (GetFileContent != null)
_content = GetFileContent(this);
return _content ?? (_content = string.Empty);
}
set
{
SetPropertyValueAndDetectChanges(
value ?? string.Empty, // cannot set to null
ref _content, Ps.Value.ContentSelector);
}
}
/// <summary>
/// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website)
/// </summary>
public string VirtualPath { get; set; }
// this exists so that class that manage name and alias differently, eg Template,
// can implement their own cloning - (though really, not sure it's even needed)
protected virtual void DeepCloneNameAndAlias(File clone)
{
// set fields that have a lazy value, by forcing evaluation of the lazy
clone._name = Name;
clone._alias = Alias;
}
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
var clonedFile = (File)clone;
// clear fields that were memberwise-cloned and that we don't want to clone
clonedFile._content = null;
// ...
DeepCloneNameAndAlias(clonedFile);
}
}
}
using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using Umbraco.Core.IO;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents an abstract file which provides basic functionality for a File with an Alias and Name
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public abstract class File : Entity, IFile
{
private string _path;
private string _originalPath;
// initialize to string.Empty so that it is possible to save a new file,
// should use the lazyContent ctor to set it to null when loading existing.
// cannot simply use HasIdentity as some classes (eg Script) override it
// in a weird way.
private string _content;
internal Func<File, string> GetFileContent { get; set; }
protected File(string path, Func<File, string> getFileContent = null)
{
_path = SanitizePath(path);
_originalPath = _path;
GetFileContent = getFileContent;
_content = getFileContent != null ? null : string.Empty;
}
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
private class PropertySelectors
{
public readonly PropertyInfo ContentSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Content);
public readonly PropertyInfo PathSelector = ExpressionHelper.GetPropertyInfo<File, string>(x => x.Path);
}
private string _alias;
private string _name;
private static string SanitizePath(string path)
{
return path
.Replace('\\', System.IO.Path.DirectorySeparatorChar)
.Replace('/', System.IO.Path.DirectorySeparatorChar);
//Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests
//.TrimStart(System.IO.Path.DirectorySeparatorChar)
//.TrimStart('/');
}
/// <summary>
/// Gets or sets the Name of the File including extension
/// </summary>
[DataMember]
public virtual string Name
{
get { return _name ?? (_name = System.IO.Path.GetFileName(Path)); }
}
/// <summary>
/// Gets or sets the Alias of the File, which is the name without the extension
/// </summary>
[DataMember]
public virtual string Alias
{
get
{
if (_alias == null)
{
var name = System.IO.Path.GetFileName(Path);
if (name == null) return string.Empty;
var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase);
_alias = name.Substring(0, lastIndexOf);
}
return _alias;
}
}
/// <summary>
/// Gets or sets the Path to the File from the root of the file's associated IFileSystem
/// </summary>
[DataMember]
public virtual string Path
{
get { return _path; }
set
{
//reset
_alias = null;
_name = null;
SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path, Ps.Value.PathSelector);
}
}
/// <summary>
/// Gets the original path of the file
/// </summary>
public string OriginalPath
{
get { return _originalPath; }
}
/// <summary>
/// Called to re-set the OriginalPath to the Path
/// </summary>
public void ResetOriginalPath()
{
_originalPath = _path;
}
/// <summary>
/// Gets or sets the Content of a File
/// </summary>
/// <remarks>Marked as DoNotClone, because it should be lazy-reloaded from disk.</remarks>
[DataMember]
[DoNotClone]
public virtual string Content
{
get
{
if (_content != null)
return _content;
// else, must lazy-load, and ensure it's not null
if (GetFileContent != null)
_content = GetFileContent(this);
return _content ?? (_content = string.Empty);
}
set
{
SetPropertyValueAndDetectChanges(
value ?? string.Empty, // cannot set to null
ref _content, Ps.Value.ContentSelector);
}
}
/// <summary>
/// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website)
/// </summary>
public string VirtualPath { get; set; }
[Obsolete("This is no longer used and will be removed from the codebase in future versions")]
public virtual bool IsValid()
{
return true;
}
// this exists so that class that manage name and alias differently, eg Template,
// can implement their own cloning - (though really, not sure it's even needed)
protected virtual void DeepCloneNameAndAlias(File clone)
{
// set fields that have a lazy value, by forcing evaluation of the lazy
clone._name = Name;
clone._alias = Alias;
}
public override object DeepClone()
{
var clone = (File) base.DeepClone();
// clear fields that were memberwise-cloned and that we don't want to clone
clone._content = null;
// turn off change tracking
clone.DisableChangeTracking();
// ...
DeepCloneNameAndAlias(clone);
// this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
// re-enable tracking
clone.EnableChangeTracking();
return clone;
}
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
internal sealed class Folder : EntityBase
internal sealed class Folder : Entity
{
public Folder(string folderPath)
{
@@ -11,4 +11,4 @@ namespace Umbraco.Core.Models
public string Path { get; set; }
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -82,4 +82,4 @@ namespace Umbraco.Core.Models
public string View { get; set; }
}
}
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
using System;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Models
/// <para>Depending on audit loggers, these properties can be purely free-form text, or
/// contain json serialized objects.</para>
/// </remarks>
public interface IAuditEntry : IEntity, IRememberBeingDirty
public interface IAuditEntry : IAggregateRoot, IRememberBeingDirty
{
/// <summary>
/// Gets or sets the identifier of the user triggering the audited event.
+6 -29
View File
@@ -1,35 +1,12 @@
using Umbraco.Core.Models.Entities;
using System;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents an audit item.
/// </summary>
public interface IAuditItem : IEntity
public interface IAuditItem : IAggregateRoot
{
/// <summary>
/// Gets the audit type.
/// </summary>
AuditType AuditType { get; }
/// <summary>
/// Gets the audited entity type.
/// </summary>
string EntityType { get; }
/// <summary>
/// Gets the audit user identifier.
/// </summary>
int UserId { get; }
/// <summary>
/// Gets the audit comments.
/// </summary>
string Comment { get; }
/// <summary>
/// Gets optional additional data parameters.
/// </summary>
string Parameters { get; }
AuditType AuditType { get; }
int UserId { get; }
}
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Models
/// example, an application), and an action (whatever is consented).</para>
/// <para>A consent state registers the state of the consent (granted, revoked...).</para>
/// </remarks>
public interface IConsent : IEntity, IRememberBeingDirty
public interface IConsent : IAggregateRoot, IRememberBeingDirty
{
/// <summary>
/// Determines whether the consent entity represents the current state.
+92 -181
View File
@@ -1,181 +1,92 @@
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 used to render the content.
/// </summary>
ITemplate Template { get; set; }
/// <summary>
/// Gets a value indicating whether the content is published.
/// </summary>
bool Published { get; }
PublishedState PublishedState { get; }
/// <summary>
/// Gets a value indicating whether the content has been edited.
/// </summary>
bool Edited { get; }
/// <summary>
/// Gets the published version identifier.
/// </summary>
int PublishedVersionId { get; }
/// <summary>
/// Gets a value indicating whether the content item is a blueprint.
/// </summary>
bool Blueprint { get; }
/// <summary>
/// Gets the template 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>
ITemplate PublishTemplate { get; }
/// <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; }
/// <summary>
/// Gets the identifier of the user who published the content.
/// </summary>
int? PublisherId { get; }
/// <summary>
/// Gets the date and time the content was published.
/// </summary>
DateTime? PublishDate { get; }
/// <summary>
/// Gets the content type of this content.
/// </summary>
IContentType ContentType { get; }
/// <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 now 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 a value indicating whether a culture was published.
/// </summary>
/// <remarks>
/// <para>Mirrors <see cref="IsCulturePublished"/> whenever the content item is saved.</para>
/// </remarks>
bool WasCulturePublished(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>
IReadOnlyDictionary<string, ContentCultureInfos> PublishCultureInfos { get; }
/// <summary>
/// Gets the published cultures.
/// </summary>
IEnumerable<string> PublishedCultures { get; }
/// <summary>
/// Gets the edited cultures.
/// </summary>
IEnumerable<string> EditedCultures { get; }
// fixme - these two should move to some kind of service
/// <summary>
/// Changes the <see cref="IContentType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
void ChangeContentType(IContentType contentType);
/// <summary>
/// Changes the <see cref="IContentType"/> for the current content object and removes PropertyTypes,
/// which are not part of the new ContentType.
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
void ChangeContentType(IContentType contentType, bool clearProperties);
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <returns></returns>
IContent DeepCloneWithResetIdentities();
/// <summary>
/// Registers a culture to be published.
/// </summary>
/// <returns>A value indicating whether the culture can be published.</returns>
/// <remarks>
/// <para>Fails if properties don't pass variant validation rules.</para>
/// <para>Publishing must be finalized via the content service SavePublishing method.</para>
/// </remarks>
bool PublishCulture(string culture = "*");
/// <summary>
/// Registers a culture to be unpublished.
/// </summary>
/// <remarks>
/// <para>Unpublishing must be finalized via the content service SavePublishing method.</para>
/// </remarks>
void UnpublishCulture(string culture = "*");
}
}
using System;
using System.ComponentModel;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a Content object
/// </summary>
public interface IContent : IContentBase
{
/// <summary>
/// Gets or sets the template used by the Content.
/// This is used to override the default one from the ContentType.
/// </summary>
ITemplate Template { get; set; }
/// <summary>
/// Boolean indicating whether the Content is Published or not
/// </summary>
bool Published { get; }
[Obsolete("This will be removed in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
string Language { get; set; }
/// <summary>
/// Gets or Sets the date the Content should be released and thus be published
/// </summary>
DateTime? ReleaseDate { get; set; }
/// <summary>
/// Gets or Sets the date the Content should expire and thus be unpublished
/// </summary>
DateTime? ExpireDate { get; set; }
/// <summary>
/// Id of the user who wrote/updated the Content
/// </summary>
int WriterId { get; set; }
/// <summary>
/// Gets the ContentType used by this content object
/// </summary>
IContentType ContentType { get; }
/// <summary>
/// Gets the current status of the Content
/// </summary>
ContentStatus Status { get; }
/// <summary>
/// Changes the <see cref="IContentType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
void ChangeContentType(IContentType contentType);
/// <summary>
/// Changes the <see cref="IContentType"/> for the current content object and removes PropertyTypes,
/// which are not part of the new ContentType.
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
void ChangeContentType(IContentType contentType, bool clearProperties);
/// <summary>
/// Changes the Published state of the content object
/// </summary>
void ChangePublishedState(PublishedState state);
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <returns></returns>
IContent DeepCloneWithResetIdentities();
/// <summary>
/// Gets a value indicating whether the content has a published version.
/// </summary>
bool HasPublishedVersion { get; }
/// <summary>
/// Gets the unique identifier of the published version, if any.
/// </summary>
Guid PublishedVersionGuid { get; }
/// <summary>
/// Gets a value indicating whether the content item is a blueprint.
/// </summary>
bool IsBlueprint { get; }
}
}
+83 -149
View File
@@ -1,149 +1,83 @@
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 identifier of the writer.
/// </summary>
int WriterId { get; set; }
/// <summary>
/// Gets the version identifier.
/// </summary>
int VersionId { get; }
/// <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>
IReadOnlyDictionary<string, ContentCultureInfos> CultureInfos { get; }
/// <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>
/// List of PropertyGroups available on this Content object
/// </summary>
/// <remarks>PropertyGroups are kind of lazy loaded as part of the object graph</remarks>
IEnumerable<PropertyGroup> PropertyGroups { get; }
/// <summary>
/// List of PropertyTypes available on this Content object
/// </summary>
/// <remarks>PropertyTypes are kind of lazy loaded as part of the object graph</remarks>
IEnumerable<PropertyType> PropertyTypes { get; }
/// <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);
/// <summary>
/// Copies values from another document.
/// </summary>
void CopyFrom(IContent other, string culture = "*");
// fixme validate published cultures?
/// <summary>
/// Validates the content item's properties pass variant rules
/// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
Property[] ValidateProperties(string culture = "*");
}
}
using System;
using System.Collections.Generic;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines the base for a Content object with properties that
/// are shared between Content and Media.
/// </summary>
public interface IContentBase : IUmbracoEntity
{
/// <summary>
/// Integer Id of the default ContentType
/// </summary>
int ContentTypeId { get; }
/// <summary>
/// Gets the Guid Id of the Content's Version
/// </summary>
Guid Version { get; }
/// <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>
/// List of PropertyGroups available on this Content object
/// </summary>
/// <remarks>PropertyGroups are kind of lazy loaded as part of the object graph</remarks>
IEnumerable<PropertyGroup> PropertyGroups { get; }
/// <summary>
/// List of PropertyTypes available on this Content object
/// </summary>
/// <remarks>PropertyTypes are kind of lazy loaded as part of the object graph</remarks>
IEnumerable<PropertyType> PropertyTypes { get; }
/// <summary>
/// Indicates whether the content object has a property with the supplied alias
/// </summary>
/// <param name="propertyTypeAlias">Alias of the PropertyType</param>
/// <returns>True if Property with given alias exists, otherwise False</returns>
bool HasProperty(string propertyTypeAlias);
/// <summary>
/// Gets the value of a Property
/// </summary>
/// <param name="propertyTypeAlias">Alias of the PropertyType</param>
/// <returns><see cref="Property"/> Value as an <see cref="object"/></returns>
object GetValue(string propertyTypeAlias);
/// <summary>
/// Gets the value of a Property
/// </summary>
/// <typeparam name="TPassType">Type of the value to return</typeparam>
/// <param name="propertyTypeAlias">Alias of the PropertyType</param>
/// <returns><see cref="Property"/> Value as a <see cref="TPassType"/></returns>
TPassType GetValue<TPassType>(string propertyTypeAlias);
/// <summary>
/// Sets the <see cref="System.Object"/> value of a Property
/// </summary>
/// <param name="propertyTypeAlias">Alias of the PropertyType</param>
/// <param name="value">Value to set for the Property</param>
void SetValue(string propertyTypeAlias, object value);
/// <summary>
/// Boolean indicating whether the content and its properties are valid
/// </summary>
/// <returns>True if content is valid otherwise false</returns>
bool IsValid();
/// <summary>
/// Changes the Trashed state of the content object
/// </summary>
/// <param name="isTrashed">Boolean indicating whether content is trashed (true) or not trashed (false)</param>
/// <param name="parentId"> </param>
void ChangeTrashedState(bool isTrashed, int parentId = -20);
}
}
+54 -54
View File
@@ -1,54 +1,54 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a ContentType, which Content is based on
/// </summary>
public interface IContentType : IContentTypeComposition
{
/// <summary>
/// Gets the default Template of the ContentType
/// </summary>
ITemplate DefaultTemplate { get; }
/// <summary>
/// Gets or Sets a list of Templates which are allowed for the ContentType
/// </summary>
IEnumerable<ITemplate> AllowedTemplates { get; set; }
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateId">The template id to check</param>
/// <returns>True if AllowedTemplates contains the templateId else False</returns>
bool IsAllowedTemplate(int templateId);
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateAlias">The template alias to check</param>
/// <returns>True if AllowedTemplates contains the templateAlias else False</returns>
bool IsAllowedTemplate(string templateAlias);
/// <summary>
/// Sets the default template for the ContentType
/// </summary>
/// <param name="template">Default <see cref="ITemplate"/></param>
void SetDefaultTemplate(ITemplate template);
/// <summary>
/// Removes a template from the list of allowed templates
/// </summary>
/// <param name="template"><see cref="ITemplate"/> to remove</param>
/// <returns>True if template was removed, otherwise False</returns>
bool RemoveTemplate(ITemplate template);
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <param name="newAlias"></param>
/// <returns></returns>
IContentType DeepCloneWithResetIdentities(string newAlias);
}
}
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a ContentType, which Content is based on
/// </summary>
public interface IContentType : IContentTypeComposition
{
/// <summary>
/// Gets the default Template of the ContentType
/// </summary>
ITemplate DefaultTemplate { get; }
/// <summary>
/// Gets or Sets a list of Templates which are allowed for the ContentType
/// </summary>
IEnumerable<ITemplate> AllowedTemplates { get; set; }
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateId">The template id to check</param>
/// <returns>True if AllowedTemplates contains the templateId else False</returns>
bool IsAllowedTemplate(int templateId);
/// <summary>
/// Determines if AllowedTemplates contains templateId
/// </summary>
/// <param name="templateAlias">The template alias to check</param>
/// <returns>True if AllowedTemplates contains the templateAlias else False</returns>
bool IsAllowedTemplate(string templateAlias);
/// <summary>
/// Sets the default template for the ContentType
/// </summary>
/// <param name="template">Default <see cref="ITemplate"/></param>
void SetDefaultTemplate(ITemplate template);
/// <summary>
/// Removes a template from the list of allowed templates
/// </summary>
/// <param name="template"><see cref="ITemplate"/> to remove</param>
/// <returns>True if template was removed, otherwise False</returns>
bool RemoveTemplate(ITemplate template);
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <param name="newAlias"></param>
/// <returns></returns>
IContentType DeepCloneWithResetIdentities(string newAlias);
}
}
+127 -153
View File
@@ -1,153 +1,127 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines the base for a ContentType with properties that
/// are shared between ContentTypes and MediaTypes.
/// </summary>
public interface IContentTypeBase : IUmbracoEntity
{
/// <summary>
/// Gets or Sets the Alias of the ContentType
/// </summary>
string Alias { get; set; }
/// <summary>
/// Gets or Sets the Description for the ContentType
/// </summary>
string Description { get; set; }
/// <summary>
/// Gets or sets the icon for the content type. The value is a CSS class name representing
/// the icon (eg. <c>icon-home</c>) along with an optional CSS class name representing the
/// color (eg. <c>icon-blue</c>). Put together, the value for this scenario would be
/// <c>icon-home color-blue</c>.
///
/// If a class name for the color isn't specified, the icon color will default to black.
/// </summary>
string Icon { get; set; }
/// <summary>
/// Gets or Sets the Thumbnail for the ContentType
/// </summary>
string Thumbnail { get; set; }
/// <summary>
/// Gets or Sets a boolean indicating whether this ContentType is allowed at the root
/// </summary>
bool AllowedAsRoot { get; set; }
/// <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>
bool IsContainer { get; set; }
/// <summary>
/// Gets or sets the content variation of the content type.
/// </summary>
ContentVariation Variations { get; set; }
/// <summary>
/// Validates that a combination of culture and segment is valid for the content type.
/// </summary>
/// <param name="culture">The culture.</param>
/// <param name="segment">The segment.</param>
/// <param name="wildcards">A value indicating whether wilcards are supported.</param>
/// <returns>True if the combination is valid; otherwise false.</returns>
/// <remarks>
/// <para>The combination must match the content type variation exactly. For instance, if the content type varies by culture,
/// then an invariant culture would be invalid.</para>
/// </remarks>
bool SupportsVariation(string culture, string segment, bool wildcards = false);
/// <summary>
/// Validates that a combination of culture and segment is valid for the content type properties.
/// </summary>
/// <param name="culture">The culture.</param>
/// <param name="segment">The segment.</param>
/// <param name="wildcards">A value indicating whether wilcards are supported.</param>
/// <returns>True if the combination is valid; otherwise false.</returns>
/// <remarks>
/// <para>The combination must be valid for properties of the content type. For instance, if the content type varies by culture,
/// then an invariant culture is valid, because some properties may be invariant. On the other hand, if the content type is invariant,
/// then a variant culture is invalid, because no property could possibly vary by culture.</para>
/// </remarks>
bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false);
/// <summary>
/// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType
/// </summary>
IEnumerable<ContentTypeSort> AllowedContentTypes { get; set; }
/// <summary>
/// Gets or Sets a collection of Property Groups
/// </summary>
PropertyGroupCollection PropertyGroups { get; set; }
/// <summary>
/// Gets all property types, across all property groups.
/// </summary>
IEnumerable<PropertyType> PropertyTypes { get; }
/// <summary>
/// Gets or sets the property types that are not in a group.
/// </summary>
IEnumerable<PropertyType> NoGroupPropertyTypes { get; set; }
/// <summary>
/// Removes a PropertyType from the current ContentType
/// </summary>
/// <param name="propertyTypeAlias">Alias of the <see cref="PropertyType"/> to remove</param>
void RemovePropertyType(string propertyTypeAlias);
/// <summary>
/// Removes a PropertyGroup from the current ContentType
/// </summary>
/// <param name="propertyGroupName">Name of the <see cref="PropertyGroup"/> to remove</param>
void RemovePropertyGroup(string propertyGroupName);
/// <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>
bool PropertyTypeExists(string propertyTypeAlias);
/// <summary>
/// Adds a PropertyType to a specific PropertyGroup
/// </summary>
/// <param name="propertyType"><see cref="PropertyType"/> 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>
bool AddPropertyType(PropertyType propertyType, string propertyGroupName);
/// <summary>
/// Adds a PropertyType, which does not belong to a PropertyGroup.
/// </summary>
/// <param name="propertyType"><see cref="PropertyType"/> to add</param>
/// <returns>Returns <c>True</c> if PropertyType was added, otherwise <c>False</c></returns>
bool AddPropertyType(PropertyType propertyType);
/// <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>
bool AddPropertyGroup(string groupName);
/// <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>
bool MovePropertyType(string propertyTypeAlias, string propertyGroupName);
}
}
using System;
using System.Collections.Generic;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines the base for a ContentType with properties that
/// are shared between ContentTypes and MediaTypes.
/// </summary>
public interface IContentTypeBase : IUmbracoEntity
{
/// <summary>
/// Gets or Sets the Alias of the ContentType
/// </summary>
string Alias { get; set; }
/// <summary>
/// Gets or Sets the Description for the ContentType
/// </summary>
string Description { get; set; }
/// <summary>
/// Gets or sets the icon for the content type. The value is a CSS class name representing
/// the icon (eg. <c>icon-home</c>) along with an optional CSS class name representing the
/// color (eg. <c>icon-blue</c>). Put together, the value for this scenario would be
/// <c>icon-home color-blue</c>.
///
/// If a class name for the color isn't specified, the icon color will default to black.
/// </summary>
string Icon { get; set; }
/// <summary>
/// Gets or Sets the Thumbnail for the ContentType
/// </summary>
string Thumbnail { get; set; }
/// <summary>
/// Gets or Sets a boolean indicating whether this ContentType is allowed at the root
/// </summary>
bool AllowedAsRoot { get; set; }
/// <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>
bool IsContainer { get; set; }
/// <summary>
/// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType
/// </summary>
IEnumerable<ContentTypeSort> AllowedContentTypes { get; set; }
/// <summary>
/// Gets or Sets a collection of Property Groups
/// </summary>
PropertyGroupCollection PropertyGroups { get; set; }
/// <summary>
/// Gets all property types, across all property groups.
/// </summary>
IEnumerable<PropertyType> PropertyTypes { get; }
/// <summary>
/// Gets or sets the property types that are not in a group.
/// </summary>
IEnumerable<PropertyType> NoGroupPropertyTypes { get; set; }
/// <summary>
/// Removes a PropertyType from the current ContentType
/// </summary>
/// <param name="propertyTypeAlias">Alias of the <see cref="PropertyType"/> to remove</param>
void RemovePropertyType(string propertyTypeAlias);
/// <summary>
/// Removes a PropertyGroup from the current ContentType
/// </summary>
/// <param name="propertyGroupName">Name of the <see cref="PropertyGroup"/> to remove</param>
void RemovePropertyGroup(string propertyGroupName);
/// <summary>
/// Sets the ParentId from the lazy integer id
/// </summary>
/// <param name="id">Id of the Parent</param>
void SetLazyParentId(Lazy<int> id);
/// <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>
bool PropertyTypeExists(string propertyTypeAlias);
/// <summary>
/// Adds a PropertyType to a specific PropertyGroup
/// </summary>
/// <param name="propertyType"><see cref="PropertyType"/> 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>
bool AddPropertyType(PropertyType propertyType, string propertyGroupName);
/// <summary>
/// Adds a PropertyType, which does not belong to a PropertyGroup.
/// </summary>
/// <param name="propertyType"><see cref="PropertyType"/> to add</param>
/// <returns>Returns <c>True</c> if PropertyType was added, otherwise <c>False</c></returns>
bool AddPropertyType(PropertyType propertyType);
/// <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>
bool AddPropertyGroup(string groupName);
/// <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>
bool MovePropertyType(string propertyTypeAlias, string propertyGroupName);
}
}
@@ -1,59 +1,58 @@
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines the Composition of a ContentType
/// </summary>
public interface IContentTypeComposition : IContentTypeBase
{
/// <summary>
/// Gets or sets the content types that compose this content type.
/// </summary>
//fixme: we should be storing key references, not the object else we are caching way too much
IEnumerable<IContentTypeComposition> ContentTypeComposition { get; set; }
/// <summary>
/// Gets the property groups for the entire composition.
/// </summary>
IEnumerable<PropertyGroup> CompositionPropertyGroups { get; }
/// <summary>
/// Gets the property types for the entire composition.
/// </summary>
IEnumerable<PropertyType> CompositionPropertyTypes { get; }
/// <summary>
/// Adds a new ContentType to the list of composite ContentTypes
/// </summary>
/// <param name="contentType"><see cref="IContentType"/> to add</param>
/// <returns>True if ContentType was added, otherwise returns False</returns>
bool AddContentType(IContentTypeComposition contentType);
/// <summary>
/// Removes a ContentType with the supplied alias from the the list of composite ContentTypes
/// </summary>
/// <param name="alias">Alias of a <see cref="IContentType"/></param>
/// <returns>True if ContentType was removed, otherwise returns False</returns>
bool RemoveContentType(string alias);
/// <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="IContentType"/></param>
/// <returns>True if ContentType with alias exists, otherwise returns False</returns>
bool ContentTypeCompositionExists(string alias);
/// <summary>
/// Gets a list of ContentType aliases from the current composition
/// </summary>
/// <returns>An enumerable list of string aliases</returns>
IEnumerable<string> CompositionAliases();
/// <summary>
/// Gets a list of ContentType Ids from the current composition
/// </summary>
/// <returns>An enumerable list of integer ids</returns>
IEnumerable<int> CompositionIds();
}
}
using System.Collections.Generic;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines the Composition of a ContentType
/// </summary>
public interface IContentTypeComposition : IContentTypeBase
{
/// <summary>
/// Gets or sets the content types that compose this content type.
/// </summary>
IEnumerable<IContentTypeComposition> ContentTypeComposition { get; set; }
/// <summary>
/// Gets the property groups for the entire composition.
/// </summary>
IEnumerable<PropertyGroup> CompositionPropertyGroups { get; }
/// <summary>
/// Gets the property types for the entire composition.
/// </summary>
IEnumerable<PropertyType> CompositionPropertyTypes { get; }
/// <summary>
/// Adds a new ContentType to the list of composite ContentTypes
/// </summary>
/// <param name="contentType"><see cref="IContentType"/> to add</param>
/// <returns>True if ContentType was added, otherwise returns False</returns>
bool AddContentType(IContentTypeComposition contentType);
/// <summary>
/// Removes a ContentType with the supplied alias from the the list of composite ContentTypes
/// </summary>
/// <param name="alias">Alias of a <see cref="IContentType"/></param>
/// <returns>True if ContentType was removed, otherwise returns False</returns>
bool RemoveContentType(string alias);
/// <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="IContentType"/></param>
/// <returns>True if ContentType with alias exists, otherwise returns False</returns>
bool ContentTypeCompositionExists(string alias);
/// <summary>
/// Gets a list of ContentType aliases from the current composition
/// </summary>
/// <returns>An enumerable list of string aliases</returns>
IEnumerable<string> CompositionAliases();
/// <summary>
/// Gets a list of ContentType Ids from the current composition
/// </summary>
/// <returns>An enumerable list of integer ids</returns>
IEnumerable<int> CompositionIds();
}
}
-38
View File
@@ -1,38 +0,0 @@
using Umbraco.Core.Models.Entities;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a data type.
/// </summary>
public interface IDataType : IUmbracoEntity
{
/// <summary>
/// Gets or sets the property editor.
/// </summary>
IDataEditor Editor { get; set; }
/// <summary>
/// Gets the property editor alias.
/// </summary>
string EditorAlias { get; }
/// <summary>
/// Gets or sets the database type for the data type values.
/// </summary>
/// <remarks>In most cases this is imposed by the property editor, but some editors
/// may support storing different types.</remarks>
ValueStorageType DatabaseType { get; set; }
/// <summary>
/// Gets or sets the configuration object.
/// </summary>
/// <remarks>
/// <para>The configuration object is serialized to Json and stored into the database.</para>
/// <para>The serialized Json is deserialized by the property editor, which by default should
/// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration.</para>
/// </remarks>
object Configuration { get; set; }
}
}
@@ -0,0 +1,24 @@
using System;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
public interface IDataTypeDefinition : IUmbracoEntity
{
/// <summary>
/// The Property editor alias assigned to the data type
/// </summary>
string PropertyEditorAlias { get; set; }
/// <summary>
/// Id of the DataType control
/// </summary>
[Obsolete("Property editor's are defined by a string alias from version 7 onwards, use the PropertyEditorAlias property instead")]
Guid ControlId { get; set; }
/// <summary>
/// Gets or Sets the DatabaseType for which the DataType's value is saved as
/// </summary>
DataTypeDatabaseType DatabaseType { get; set; }
}
}
+4 -5
View File
@@ -1,10 +1,9 @@
namespace Umbraco.Core.Models
using System.Reflection;
namespace Umbraco.Core.Models
{
/// <summary>
/// Provides a mean to deep-clone an object.
/// </summary>
public interface IDeepCloneable
{
object DeepClone();
}
}
}
+29 -28
View File
@@ -1,28 +1,29 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
public interface IDictionaryItem : IEntity, IRememberBeingDirty
{
/// <summary>
/// Gets or Sets the Parent Id of the Dictionary Item
/// </summary>
[DataMember]
Guid? ParentId { get; set; }
/// <summary>
/// Gets or sets the Key for the Dictionary Item
/// </summary>
[DataMember]
string ItemKey { get; set; }
/// <summary>
/// Gets or sets a list of translations for the Dictionary Item
/// </summary>
[DataMember]
IEnumerable<IDictionaryTranslation> Translations { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
public interface IDictionaryItem : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
/// <summary>
/// Gets or Sets the Parent Id of the Dictionary Item
/// </summary>
[DataMember]
Guid? ParentId { get; set; }
/// <summary>
/// Gets or sets the Key for the Dictionary Item
/// </summary>
[DataMember]
string ItemKey { get; set; }
/// <summary>
/// Gets or sets a list of translations for the Dictionary Item
/// </summary>
[DataMember]
IEnumerable<IDictionaryTranslation> Translations { get; set; }
}
}
@@ -1,23 +1,23 @@
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
public interface IDictionaryTranslation : IEntity
{
/// <summary>
/// Gets or sets the <see cref="Language"/> for the translation
/// </summary>
[DataMember]
ILanguage Language { get; set; }
int LanguageId { get; }
/// <summary>
/// Gets or sets the translated text
/// </summary>
[DataMember]
string Value { get; set; }
}
}
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
public interface IDictionaryTranslation : IEntity
{
/// <summary>
/// Gets or sets the <see cref="Language"/> for the translation
/// </summary>
[DataMember]
ILanguage Language { get; set; }
int LanguageId { get; }
/// <summary>
/// Gets or sets the translated text
/// </summary>
[DataMember]
string Value { get; set; }
}
}
+3 -3
View File
@@ -1,8 +1,8 @@
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
public interface IDomain : IEntity, IRememberBeingDirty
public interface IDomain : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
int? LanguageId { get; set; }
string DomainName { get; set; }
@@ -14,4 +14,4 @@ namespace Umbraco.Core.Models
/// </summary>
string LanguageIsoCode { get; }
}
}
}
+50 -48
View File
@@ -1,48 +1,50 @@
using System;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a File
/// </summary>
/// <remarks>Used for Scripts, Stylesheets and Templates</remarks>
public interface IFile : IEntity
{
/// <summary>
/// Gets the Name of the File including extension
/// </summary>
string Name { get; }
/// <summary>
/// Gets the Alias of the File, which is the name without the extension
/// </summary>
string Alias { get; }
/// <summary>
/// Gets or sets the Path to the File from the root of the file's associated IFileSystem
/// </summary>
string Path { get; set; }
/// <summary>
/// Gets the original path of the file
/// </summary>
string OriginalPath { get; }
/// <summary>
/// Called to re-set the OriginalPath to the Path
/// </summary>
void ResetOriginalPath();
/// <summary>
/// Gets or sets the Content of a File
/// </summary>
string Content { get; set; }
/// <summary>
/// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website)
/// </summary>
string VirtualPath { get; set; }
}
}
using System;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a File
/// </summary>
/// <remarks>Used for Scripts, Stylesheets and Templates</remarks>
public interface IFile : IAggregateRoot
{
/// <summary>
/// Gets the Name of the File including extension
/// </summary>
string Name { get; }
/// <summary>
/// Gets the Alias of the File, which is the name without the extension
/// </summary>
string Alias { get; }
/// <summary>
/// Gets or sets the Path to the File from the root of the file's associated IFileSystem
/// </summary>
string Path { get; set; }
/// <summary>
/// Gets the original path of the file
/// </summary>
string OriginalPath { get; }
/// <summary>
/// Called to re-set the OriginalPath to the Path
/// </summary>
void ResetOriginalPath();
/// <summary>
/// Gets or sets the Content of a File
/// </summary>
string Content { get; set; }
/// <summary>
/// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website)
/// </summary>
string VirtualPath { get; set; }
[Obsolete("This is no longer used and will be removed from the codebase in future versions")]
bool IsValid();
}
}
+28 -57
View File
@@ -1,57 +1,28 @@
using System.Globalization;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a language.
/// </summary>
public interface ILanguage : IEntity, IRememberBeingDirty
{
/// <summary>
/// Gets or sets the ISO code of the language.
/// </summary>
[DataMember]
string IsoCode { get; set; }
/// <summary>
/// Gets or sets the culture name of the language.
/// </summary>
[DataMember]
string CultureName { get; set; }
/// <summary>
/// Gets the <see cref="CultureInfo"/> object for the language.
/// </summary>
[IgnoreDataMember]
CultureInfo CultureInfo { get; }
/// <summary>
/// Gets or sets a value indicating whether the language is the default language.
/// </summary>
[DataMember]
bool IsDefault { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the language is mandatory.
/// </summary>
/// <remarks>
/// <para>When a language is mandatory, a multi-lingual document cannot be published
/// without that language being published, and unpublishing that language unpublishes
/// the entire document.</para>
/// </remarks>
[DataMember]
bool IsMandatory { get; set; }
/// <summary>
/// Gets or sets the identifier of a fallback language.
/// </summary>
/// <remarks>
/// <para>The fallback language can be used in multi-lingual scenarios, to help
/// define fallback strategies when a value does not exist for a requested language.</para>
/// </remarks>
[DataMember]
int? FallbackLanguageId { get; set; }
}
}
using System.Globalization;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
public interface ILanguage : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
/// <summary>
/// Gets or sets the Iso Code for the Language
/// </summary>
[DataMember]
string IsoCode { get; set; }
/// <summary>
/// Gets or sets the Culture Name for the Language
/// </summary>
[DataMember]
string CultureName { get; set; }
/// <summary>
/// Returns a <see cref="CultureInfo"/> object for the current Language
/// </summary>
[IgnoreDataMember]
CultureInfo CultureInfo { get; }
}
}
+93 -79
View File
@@ -1,79 +1,93 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a Macro
/// </summary>
public interface IMacro : IEntity, IRememberBeingDirty
{
/// <summary>
/// Gets or sets the alias of the Macro
/// </summary>
[DataMember]
string Alias { get; set; }
/// <summary>
/// Gets or sets the name of the Macro
/// </summary>
[DataMember]
string Name { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro can be used in an Editor
/// </summary>
[DataMember]
bool UseInEditor { get; set; }
/// <summary>
/// Gets or sets the Cache Duration for the Macro
/// </summary>
[DataMember]
int CacheDuration { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached by Page
/// </summary>
[DataMember]
bool CacheByPage { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached Personally
/// </summary>
[DataMember]
bool CacheByMember { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor
/// </summary>
[DataMember]
bool DontRender { get; set; }
/// <summary>
/// Gets or set the path to the macro source to render
/// </summary>
[DataMember]
string MacroSource { get; set; }
/// <summary>
/// Gets or set the macro type
/// </summary>
[DataMember]
MacroTypes MacroType { get; set; }
/// <summary>
/// Gets or sets a list of Macro Properties
/// </summary>
[DataMember]
MacroPropertyCollection Properties { get; }
///// <summary>
///// Returns an enum <see cref="MacroTypes"/> based on the properties on the Macro
///// </summary>
///// <returns><see cref="MacroTypes"/></returns>
//MacroTypes MacroType();
}
}
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a Macro
/// </summary>
public interface IMacro : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
/// <summary>
/// Gets or sets the alias of the Macro
/// </summary>
[DataMember]
string Alias { get; set; }
/// <summary>
/// Gets or sets the name of the Macro
/// </summary>
[DataMember]
string Name { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro can be used in an Editor
/// </summary>
[DataMember]
bool UseInEditor { get; set; }
/// <summary>
/// Gets or sets the Cache Duration for the Macro
/// </summary>
[DataMember]
int CacheDuration { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached by Page
/// </summary>
[DataMember]
bool CacheByPage { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be Cached Personally
/// </summary>
[DataMember]
bool CacheByMember { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor
/// </summary>
[DataMember]
bool DontRender { get; set; }
/// <summary>
/// Gets or sets the path to user control or the Control Type to render
/// </summary>
[DataMember]
string ControlType { get; set; }
/// <summary>
/// Gets or sets the name of the assembly, which should be used by the Macro
/// </summary>
/// <remarks>Will usually only be filled if the ScriptFile is a Usercontrol</remarks>
[DataMember]
string ControlAssembly { get; set; }
/// <summary>
/// Gets or set the path to the Python file in use
/// </summary>
/// <remarks>Optional: Can only be one of three Script, Python or Xslt</remarks>
[DataMember]
string ScriptPath { get; set; }
/// <summary>
/// Gets or sets the path to the Xslt file in use
/// </summary>
/// <remarks>Optional: Can only be one of three Script, Python or Xslt</remarks>
[DataMember]
string XsltPath { get; set; }
/// <summary>
/// Gets or sets a list of Macro Properties
/// </summary>
[DataMember]
MacroPropertyCollection Properties { get; }
///// <summary>
///// Returns an enum <see cref="MacroTypes"/> based on the properties on the Macro
///// </summary>
///// <returns><see cref="MacroTypes"/></returns>
//MacroTypes MacroType();
}
}
+42 -42
View File
@@ -1,42 +1,42 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a Property for a Macro
/// </summary>
public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty
{
[DataMember]
int Id { get; set; }
[DataMember]
Guid Key { get; set; }
/// <summary>
/// Gets or sets the Alias of the Property
/// </summary>
[DataMember]
string Alias { get; set; }
/// <summary>
/// Gets or sets the Name of the Property
/// </summary>
[DataMember]
string Name { get; set; }
/// <summary>
/// Gets or sets the Sort Order of the Property
/// </summary>
[DataMember]
int SortOrder { get; set; }
/// <summary>
/// Gets or sets the parameter editor alias
/// </summary>
[DataMember]
string EditorAlias { get; set; }
}
}
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a Property for a Macro
/// </summary>
public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty
{
[DataMember]
int Id { get; set; }
[DataMember]
Guid Key { get; set; }
/// <summary>
/// Gets or sets the Alias of the Property
/// </summary>
[DataMember]
string Alias { get; set; }
/// <summary>
/// Gets or sets the Name of the Property
/// </summary>
[DataMember]
string Name { get; set; }
/// <summary>
/// Gets or sets the Sort Order of the Property
/// </summary>
[DataMember]
int SortOrder { get; set; }
/// <summary>
/// Gets or sets the parameter editor alias
/// </summary>
[DataMember]
string EditorAlias { get; set; }
}
}
@@ -0,0 +1,28 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a PropertyType (plugin) for a Macro
/// </summary>
internal interface IMacroPropertyType
{
/// <summary>
/// Gets the unique Alias of the Property Type
/// </summary>
string Alias { get; }
/// <summary>
/// Gets the name of the Assembly used to render the Property Type
/// </summary>
string RenderingAssembly { get; }
/// <summary>
/// Gets the name of the Type used to render the Property Type
/// </summary>
string RenderingType { get; }
/// <summary>
/// Gets the Base Type for storing the PropertyType (Int32, String, Boolean)
/// </summary>
MacroPropertyTypeBaseTypes BaseType { get; }
}
}
+27 -27
View File
@@ -1,27 +1,27 @@
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
public interface IMedia : IContentBase
{
/// <summary>
/// Gets the ContentType used by this Media object
/// </summary>
IMediaType ContentType { get; }
/// <summary>
/// Changes the <see cref="IMediaType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
void ChangeContentType(IMediaType contentType);
/// <summary>
/// Changes the <see cref="IMediaType"/> for the current content object and removes PropertyTypes,
/// which are not part of the new ContentType.
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
void ChangeContentType(IMediaType contentType, bool clearProperties);
}
}
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
public interface IMedia : IContentBase
{
/// <summary>
/// Gets the ContentType used by this Media object
/// </summary>
IMediaType ContentType { get; }
/// <summary>
/// Changes the <see cref="IMediaType"/> for the current content object
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <remarks>Leaves PropertyTypes intact after change</remarks>
void ChangeContentType(IMediaType contentType);
/// <summary>
/// Changes the <see cref="IMediaType"/> for the current content object and removes PropertyTypes,
/// which are not part of the new ContentType.
/// </summary>
/// <param name="contentType">New ContentType for this content</param>
/// <param name="clearProperties">Boolean indicating whether to clear PropertyTypes upon change</param>
void ChangeContentType(IMediaType contentType, bool clearProperties);
}
}
+18 -18
View File
@@ -1,18 +1,18 @@
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a ContentType, which Media is based on
/// </summary>
public interface IMediaType : IContentTypeComposition
{
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <param name="newAlias"></param>
/// <returns></returns>
IMediaType DeepCloneWithResetIdentities(string newAlias);
}
}
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a ContentType, which Media is based on
/// </summary>
public interface IMediaType : IContentTypeComposition
{
/// <summary>
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
/// </summary>
/// <param name="newAlias"></param>
/// <returns></returns>
IMediaType DeepCloneWithResetIdentities(string newAlias);
}
}
+18 -20
View File
@@ -1,20 +1,18 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Models
{
public interface IMember : IContentBase, IMembershipUser, IHaveAdditionalData
{
/// <summary>
/// String alias of the default ContentType
/// </summary>
string ContentTypeAlias { get; }
/// <summary>
/// Gets the ContentType used by this content object
/// </summary>
IMemberType ContentType { get; }
}
}
using System;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Core.Models
{
public interface IMember : IContentBase, IMembershipUser
{
/// <summary>
/// String alias of the default ContentType
/// </summary>
string ContentTypeAlias { get; }
/// <summary>
/// Gets the ContentType used by this content object
/// </summary>
IMemberType ContentType { get; }
}
}
+8 -3
View File
@@ -1,12 +1,12 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a member type
/// </summary>
public interface IMemberGroup : IEntity, IRememberBeingDirty, IHaveAdditionalData
public interface IMemberGroup : IAggregateRoot, IRememberBeingDirty, ICanBeDirty
{
/// <summary>
/// The name of the member group
@@ -17,5 +17,10 @@ namespace Umbraco.Core.Models
/// Profile of the user who created this Entity
/// </summary>
int CreatorId { get; set; }
/// <summary>
/// Some entities may expose additional data that other's might not, this custom data will be available in this collection
/// </summary>
IDictionary<string, object> AdditionalData { get; }
}
}
}
+50 -50
View File
@@ -1,50 +1,50 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a MemberType, which Member is based on
/// </summary>
public interface IMemberType : IContentTypeComposition
{
/// <summary>
/// Gets a boolean indicating whether a Property is editable by the Member.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool MemberCanEditProperty(string propertyTypeAlias);
/// <summary>
/// Gets a boolean indicating whether a Property is visible on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool MemberCanViewProperty(string propertyTypeAlias);
/// <summary>
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool IsSensitiveProperty(string propertyTypeAlias);
/// <summary>
/// Sets a boolean indicating whether a Property is editable by the Member.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetMemberCanEditProperty(string propertyTypeAlias, bool value);
/// <summary>
/// Sets a boolean indicating whether a Property is visible on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetMemberCanViewProperty(string propertyTypeAlias, bool value);
/// <summary>
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetIsSensitiveProperty(string propertyTypeAlias, bool value);
}
}
namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a MemberType, which Member is based on
/// </summary>
public interface IMemberType : IContentTypeComposition
{
/// <summary>
/// Gets a boolean indicating whether a Property is editable by the Member.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool MemberCanEditProperty(string propertyTypeAlias);
/// <summary>
/// Gets a boolean indicating whether a Property is visible on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool MemberCanViewProperty(string propertyTypeAlias);
/// <summary>
/// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to check</param>
/// <returns></returns>
bool IsSensitiveProperty(string propertyTypeAlias);
/// <summary>
/// Sets a boolean indicating whether a Property is editable by the Member.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetMemberCanEditProperty(string propertyTypeAlias, bool value);
/// <summary>
/// Sets a boolean indicating whether a Property is visible on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetMemberCanViewProperty(string propertyTypeAlias, bool value);
/// <summary>
/// Sets a boolean indicating whether a Property is a sensitive value on the Members profile.
/// </summary>
/// <param name="propertyTypeAlias">PropertyType Alias of the Property to set</param>
/// <param name="value">Boolean value, true or false</param>
void SetIsSensitiveProperty(string propertyTypeAlias, bool value);
}
}
+3 -3
View File
@@ -1,12 +1,12 @@
using System;
using Semver;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
public interface IMigrationEntry : IEntity, IRememberBeingDirty
public interface IMigrationEntry : IAggregateRoot, IRememberBeingDirty
{
string MigrationName { get; set; }
SemVersion Version { get; set; }
}
}
}
+1 -1
View File
@@ -4,4 +4,4 @@
{
PartialViewType ViewType { get; }
}
}
}
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a cached content.
/// </summary>
/// <remarks>
/// <para>SD: A replacement for INode which needs to occur since INode doesn't contain the document type alias
/// and INode is poorly formatted with mutable properties (i.e. Lists instead of IEnumerable).</para>
/// <para>Stephan: initially, that was for cached published content only. Now, we're using it also for
/// cached preview (so, maybe unpublished) content. A better name would therefore be ICachedContent, as
/// has been suggested. However, can't change now. Maybe in v7?</para>
/// </remarks>
public interface IPublishedContent
{
#region ContentSet
// Because of http://issues.umbraco.org/issue/U4-1797 and in order to implement
// Index() and methods that derive from it such as IsFirst(), IsLast(), etc... all
// content items must know about their containing content set.
/// <summary>
/// Gets the content set to which the content belongs.
/// </summary>
/// <remarks>The default set consists in the siblings of the content (including the content
/// itself) ordered by <c>sortOrder</c>.</remarks>
IEnumerable<IPublishedContent> ContentSet { get; }
#endregion
#region ContentType
/// <summary>
/// Gets the content type.
/// </summary>
PublishedContentType ContentType { get; }
#endregion
#region Content
int Id { get; }
int TemplateId { get; }
int SortOrder { get; }
string Name { get; }
string UrlName { get; }
string DocumentTypeAlias { get; }
int DocumentTypeId { get; }
string WriterName { get; }
string CreatorName { get; }
int WriterId { get; }
int CreatorId { get; }
string Path { get; }
DateTime CreateDate { get; }
DateTime UpdateDate { get; }
Guid Version { get; }
int Level { get; }
string Url { get; }
/// <summary>
/// Gets a value indicating whether the content is a content (aka a document) or a media.
/// </summary>
PublishedItemType ItemType { get; }
/// <summary>
/// Gets a value indicating whether the content is draft.
/// </summary>
/// <remarks>A content is draft when it is the unpublished version of a content, which may
/// have a published version, or not.</remarks>
bool IsDraft { get; }
/// <summary>
/// Gets the index of the published content within its current owning content set.
/// </summary>
/// <returns>The index of the published content within its current owning content set.</returns>
int GetIndex();
#endregion
#region Tree
/// <summary>
/// Gets the parent of the content.
/// </summary>
/// <remarks>The parent of root content is <c>null</c>.</remarks>
IPublishedContent Parent { get; }
/// <summary>
/// Gets the children of the content.
/// </summary>
/// <remarks>Children are sorted by their sortOrder.</remarks>
IEnumerable<IPublishedContent> Children { get; }
#endregion
#region Properties
/// <summary>
/// Gets the properties of the content.
/// </summary>
/// <remarks>
/// <para>Contains one <c>IPublishedProperty</c> for each property defined for the content type, including
/// inherited properties. Some properties may have no value.</para>
/// <para>The properties collection of an IPublishedContent instance should be read-only ie it is illegal
/// to add properties to the collection.</para>
/// </remarks>
ICollection<IPublishedProperty> Properties { get; }
/// <summary>
/// Gets a property identified by its alias.
/// </summary>
/// <param name="alias">The property alias.</param>
/// <returns>The property identified by the alias.</returns>
/// <remarks>
/// <para>If the content type has no property with that alias, including inherited properties, returns <c>null</c>,</para>
/// <para>otherwise return a property -- that may have no value (ie <c>HasValue</c> is <c>false</c>).</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
IPublishedProperty GetProperty(string alias);
/// <summary>
/// Gets a property identified by its alias.
/// </summary>
/// <param name="alias">The property alias.</param>
/// <param name="recurse">A value indicating whether to navigate the tree upwards until a property with a value is found.</param>
/// <returns>The property identified by the alias.</returns>
/// <remarks>
/// <para>Navigate the tree upwards and look for a property with that alias and with a value (ie <c>HasValue</c> is <c>true</c>).
/// If found, return the property. If no property with that alias is found, having a value or not, return <c>null</c>. Otherwise
/// return the first property that was found with the alias but had no value (ie <c>HasValue</c> is <c>false</c>).</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
IPublishedProperty GetProperty(string alias, bool recurse);
/// <summary>
/// Gets the value of a property identified by its alias.
/// </summary>
/// <param name="alias">The property alias.</param>
/// <returns>The value of the property identified by the alias.</returns>
/// <remarks>
/// <para>If <c>GetProperty(alias)</c> is <c>null</c> then returns <c>null</c> else return <c>GetProperty(alias).Value</c>.</para>
/// <para>So if the property has no value, returns the default value for that property type.</para>
/// <para>This one is defined here really because we cannot define index extension methods, but all it should do is:
/// <code>var p = GetProperty(alias); return p == null ? null : p.Value;</code> and nothing else.</para>
/// <para>The recursive syntax (eg "_title") is _not_ supported here.</para>
/// <para>The alias is case-insensitive.</para>
/// </remarks>
object this[string alias] { get; } // todo - should obsolete this[alias] (when?)
#endregion
}
}
@@ -0,0 +1,14 @@
using System;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a cached content with a GUID key.
/// </summary>
/// <remarks>This is temporary, because we cannot add the Key property to IPublishedContent without
/// breaking backward compatibility. With v8, it will be merged into IPublishedContent.</remarks>
public interface IPublishedContentWithKey : IPublishedContent
{
Guid Key { get; }
}
}
@@ -1,62 +1,60 @@
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Represents a property of an <c>IPublishedElement</c>.
/// </summary>
public interface IPublishedProperty
{
PublishedPropertyType PropertyType { get; }
/// <summary>
/// Gets the alias of the property.
/// </summary>
string Alias { get; }
/// <summary>
/// Gets a value indicating whether the property has a value.
/// </summary>
/// <remarks>
/// <para>This is somewhat implementation-dependent -- depending on whatever IPublishedCache considers
/// a missing value.</para>
/// <para>The XmlPublishedCache raw values are strings, and it will consider missing, null or empty (and
/// that includes whitespace-only) strings as "no value".</para>
/// <para>Other caches that get their raw value from the database would consider that a property has "no
/// value" if it is missing, null, or an empty string (including whitespace-only).</para>
/// </remarks>
bool HasValue(string culture = null, string segment = null);
/// <summary>
/// Gets the source value of the property.
/// </summary>
/// <remarks>
/// <para>The source value is whatever was passed to the property when it was instanciated, and it is
/// somewhat implementation-dependent -- depending on how the IPublishedCache is implemented.</para>
/// <para>The XmlPublishedCache source values are strings exclusively since they come from the Xml cache.</para>
/// <para>For other caches that get their source value from the database, it would be either a string,
/// an integer (Int32), a date and time (DateTime) or a decimal (double).</para>
/// <para>If you're using that value, you're probably wrong, unless you're doing some internal
/// Umbraco stuff.</para>
/// </remarks>
object GetSourceValue(string culture = null, string segment = null);
/// <summary>
/// Gets the object value of the property.
/// </summary>
/// <remarks>
/// <para>The value is what you want to use when rendering content in an MVC view ie in C#.</para>
/// <para>It can be null, or any type of CLR object.</para>
/// <para>It has been fully prepared and processed by the appropriate converter.</para>
/// </remarks>
object GetValue(string culture = null, string segment = null);
/// <summary>
/// Gets the XPath value of the property.
/// </summary>
/// <remarks>
/// <para>The XPath value is what you want to use when navigating content via XPath eg in the XSLT engine.</para>
/// <para>It must be either null, or a string, or an XPathNavigator.</para>
/// <para>It has been fully prepared and processed by the appropriate converter.</para>
/// </remarks>
object GetXPathValue(string culture = null, string segment = null);
}
}
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a property of an <c>IPublishedContent</c>.
/// </summary>
public interface IPublishedProperty
{
/// <summary>
/// Gets the alias of the property.
/// </summary>
string PropertyTypeAlias { get; }
/// <summary>
/// Gets a value indicating whether the property has a value.
/// </summary>
/// <remarks>
/// <para>This is somewhat implementation-dependent -- depending on whatever IPublishedCache considers
/// a missing value.</para>
/// <para>The XmlPublishedCache raw values are strings, and it will consider missing, null or empty (and
/// that includes whitespace-only) strings as "no value".</para>
/// <para>Other caches that get their raw value from the database would consider that a property has "no
/// value" if it is missing, null, or an empty string (including whitespace-only).</para>
/// </remarks>
bool HasValue { get; }
/// <summary>
/// Gets the data value of the property.
/// </summary>
/// <remarks>
/// <para>The data value is whatever was passed to the property when it was instanciated, and it is
/// somewhat implementation-dependent -- depending on how the IPublishedCache is implemented.</para>
/// <para>The XmlPublishedCache raw values are strings exclusively since they come from the Xml cache.</para>
/// <para>For other caches that get their raw value from the database, it would be either a string,
/// an integer (Int32), or a date and time (DateTime).</para>
/// <para>If you're using that value, you're probably wrong, unless you're doing some internal
/// Umbraco stuff.</para>
/// </remarks>
object DataValue { get; }
/// <summary>
/// Gets the object value of the property.
/// </summary>
/// <remarks>
/// <para>The value is what you want to use when rendering content in an MVC view ie in C#.</para>
/// <para>It can be null, or any type of CLR object.</para>
/// <para>It has been fully prepared and processed by the appropriate converter.</para>
/// </remarks>
object Value { get; }
/// <summary>
/// Gets the XPath value of the property.
/// </summary>
/// <remarks>
/// <para>The XPath value is what you want to use when navigating content via XPath eg in the XSLT engine.</para>
/// <para>It must be either null, or a string, or an XPathNavigator.</para>
/// <para>It has been fully prepared and processed by the appropriate converter.</para>
/// </remarks>
object XPathValue { get; }
}
}
+2 -2
View File
@@ -1,13 +1,13 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
/// <summary>
/// Represents a redirect url.
/// </summary>
public interface IRedirectUrl : IEntity, IRememberBeingDirty
public interface IRedirectUrl : IAggregateRoot, IRememberBeingDirty
{
/// <summary>
/// Gets or sets the identifier of the content item.

Some files were not shown because too many files have changed in this diff Show More