Merge branch 'temp8-U4-11227' into temp8-U4-11282
# Conflicts: # src/Umbraco.Core/Models/ContentBase.cs # src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs # src/Umbraco.Tests/Services/EntityServiceTests.cs # src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs # src/Umbraco.Web/PublishedContentExtensions.cs # src/Umbraco.Web/Trees/ContentTreeControllerBase.cs
This commit is contained in:
@@ -232,8 +232,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
/// <inheritdoc/>
|
||||
[IgnoreDataMember]
|
||||
//public IReadOnlyDictionary<string, string> PublishNames => _publishNames ?? NoNames;
|
||||
public IReadOnlyDictionary<string, string> PublishNames => _publishInfos?.ToDictionary(x => x.Key, x => x.Value.Name) ?? NoNames;
|
||||
public IReadOnlyDictionary<string, string> PublishNames => _publishInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetPublishName(string culture)
|
||||
@@ -270,7 +269,7 @@ namespace Umbraco.Core.Models
|
||||
=> !string.IsNullOrWhiteSpace(GetPublishName(culture));
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime GetDateCulturePublished(string culture)
|
||||
public DateTime GetCulturePublishDate(string culture)
|
||||
{
|
||||
if (_publishInfos != null && _publishInfos.TryGetValue(culture, out var infos))
|
||||
return infos.Date;
|
||||
@@ -358,7 +357,7 @@ namespace Umbraco.Core.Models
|
||||
var name = GetName(culture);
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return false; //fixme this should return an attempt with error results
|
||||
|
||||
|
||||
SetPublishInfos(culture, name, DateTime.Now);
|
||||
}
|
||||
|
||||
@@ -478,8 +477,8 @@ namespace Umbraco.Core.Models
|
||||
|
||||
// copy names
|
||||
ClearNames();
|
||||
foreach (var (languageId, name) in other.Names)
|
||||
SetName(languageId, name);
|
||||
foreach (var (culture, name) in other.Names)
|
||||
SetName(name, culture);
|
||||
Name = other.Name;
|
||||
}
|
||||
|
||||
@@ -519,7 +518,7 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
// copy name
|
||||
SetName(culture, other.GetName(culture));
|
||||
SetName(other.GetName(culture), culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -553,7 +552,7 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
// copy name
|
||||
SetName(culture, other.GetName(culture));
|
||||
SetName(other.GetName(culture), culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -18,14 +19,14 @@ namespace Umbraco.Core.Models
|
||||
[DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")]
|
||||
public abstract class ContentBase : TreeEntityBase, IContentBase
|
||||
{
|
||||
protected static readonly Dictionary<string, string> NoNames = new Dictionary<string, string>();
|
||||
protected static readonly Dictionary<string, string> NoNames = new Dictionary<string, string>();
|
||||
private static readonly Lazy<PropertySelectors> Ps = new Lazy<PropertySelectors>();
|
||||
|
||||
private int _contentTypeId;
|
||||
protected IContentTypeComposition ContentTypeBase;
|
||||
private int _writerId;
|
||||
private PropertyCollection _properties;
|
||||
private Dictionary<string, string> _names;
|
||||
private Dictionary<string, (string Name, DateTime Date)> _cultureInfos;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentBase"/> class.
|
||||
@@ -54,8 +55,8 @@ namespace Umbraco.Core.Models
|
||||
// initially, all new instances have
|
||||
Id = 0; // no identity
|
||||
VersionId = 0; // no versions
|
||||
|
||||
SetName(culture, name);
|
||||
|
||||
SetName(name, culture);
|
||||
|
||||
_contentTypeId = contentType.Id;
|
||||
_properties = properties ?? throw new ArgumentNullException(nameof(properties));
|
||||
@@ -67,7 +68,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public readonly PropertyInfo DefaultContentTypeIdSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.ContentTypeId);
|
||||
public readonly PropertyInfo PropertyCollectionSelector = ExpressionHelper.GetPropertyInfo<ContentBase, PropertyCollection>(x => x.Properties);
|
||||
public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.WriterId);
|
||||
public readonly PropertyInfo WriterSelector = ExpressionHelper.GetPropertyInfo<ContentBase, int>(x => x.WriterId);
|
||||
public readonly PropertyInfo NamesSelector = ExpressionHelper.GetPropertyInfo<ContentBase, IReadOnlyDictionary<string, string>>(x => x.Names);
|
||||
}
|
||||
|
||||
@@ -135,23 +136,37 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<PropertyType> PropertyTypes => ContentTypeBase.CompositionPropertyTypes;
|
||||
|
||||
|
||||
#region Cultures
|
||||
|
||||
/// <inheritdoc />
|
||||
[DataMember]
|
||||
public virtual IReadOnlyDictionary<string, string> Names
|
||||
public virtual IReadOnlyDictionary<string, string> Names => _cultureInfos?.ToDictionary(x => x.Key, x => x.Value.Name, StringComparer.OrdinalIgnoreCase) ?? NoNames;
|
||||
|
||||
// sets culture infos
|
||||
// internal for repositories
|
||||
// clear by clearing name
|
||||
internal void SetCultureInfos(string culture, string name, DateTime date)
|
||||
{
|
||||
get => _names ?? NoNames;
|
||||
set
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentNullOrEmptyException(nameof(name));
|
||||
|
||||
if (culture == null)
|
||||
{
|
||||
foreach (var (culture, name) in value)
|
||||
SetName(culture, name);
|
||||
Name = name;
|
||||
return;
|
||||
}
|
||||
|
||||
// private method, assume that culture is valid
|
||||
|
||||
if (_cultureInfos == null)
|
||||
_cultureInfos = new Dictionary<string, (string Name, DateTime Date)>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_cultureInfos[culture] = (name, date);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void SetName(string culture, string name)
|
||||
public virtual void SetName(string name, string culture)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
@@ -168,10 +183,10 @@ namespace Umbraco.Core.Models
|
||||
if (!ContentTypeBase.Variations.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment))
|
||||
throw new NotSupportedException("Content type does not support varying name by culture.");
|
||||
|
||||
if (_names == null)
|
||||
_names = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
if (_cultureInfos == null)
|
||||
_cultureInfos = new Dictionary<string, (string Name, DateTime Date)>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_names[culture] = name;
|
||||
_cultureInfos[culture] = (name, DateTime.Now) ;
|
||||
OnPropertyChanged(Ps.Value.NamesSelector);
|
||||
}
|
||||
|
||||
@@ -179,14 +194,14 @@ namespace Umbraco.Core.Models
|
||||
public virtual string GetName(string culture)
|
||||
{
|
||||
if (culture == null) return Name;
|
||||
if (_names == null) return null;
|
||||
return _names.TryGetValue(culture, out var name) ? name : null;
|
||||
if (_cultureInfos == null) return null;
|
||||
return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Name : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCultureAvailable(string culture)
|
||||
=> !string.IsNullOrWhiteSpace(GetName(culture));
|
||||
|
||||
|
||||
private void ClearName(string culture)
|
||||
{
|
||||
if (culture == null)
|
||||
@@ -194,28 +209,37 @@ namespace Umbraco.Core.Models
|
||||
Name = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ContentTypeBase.Variations.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment))
|
||||
throw new NotSupportedException("Content type does not support varying name by culture.");
|
||||
|
||||
if (_names == null) return;
|
||||
if (!_names.ContainsKey(culture))
|
||||
throw new InvalidOperationException($"Cannot unpublish culture {culture}, the document contains only cultures {string.Join(", ", _names.Keys)}");
|
||||
_names.Remove(culture);
|
||||
if (_names.Count == 0)
|
||||
_names = null;
|
||||
if (!ContentTypeBase.Variations.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment))
|
||||
throw new NotSupportedException("Content type does not support varying name by culture.");
|
||||
|
||||
if (_cultureInfos == null) return;
|
||||
if (!_cultureInfos.ContainsKey(culture))
|
||||
throw new InvalidOperationException($"Cannot unpublish culture {culture}, the document contains only cultures {string.Join(", ", _cultureInfos.Keys)}");
|
||||
|
||||
_cultureInfos.Remove(culture);
|
||||
if (_cultureInfos.Count == 0)
|
||||
_cultureInfos = null;
|
||||
}
|
||||
|
||||
protected virtual void ClearNames()
|
||||
{
|
||||
{
|
||||
if (!ContentTypeBase.Variations.HasAny(ContentVariation.CultureNeutral | ContentVariation.CultureSegment))
|
||||
throw new NotSupportedException("Content type does not support varying name by culture.");
|
||||
throw new NotSupportedException("Content type does not support varying name by culture.");
|
||||
|
||||
_names = null;
|
||||
_cultureInfos = null;
|
||||
OnPropertyChanged(Ps.Value.NamesSelector);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime GetCultureDate(string culture)
|
||||
{
|
||||
if (_cultureInfos != null && _cultureInfos.TryGetValue(culture, out var infos))
|
||||
return infos.Date;
|
||||
throw new InvalidOperationException($"Culture \"{culture}\" is not available.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Has, Get, Set, Publish Property Value
|
||||
|
||||
@@ -380,7 +404,7 @@ namespace Umbraco.Core.Models
|
||||
var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias);
|
||||
return instanceProperties.Concat(propertyTypes);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,16 @@ namespace Umbraco.Core.Models.Entities
|
||||
/// </summary>
|
||||
public static readonly IEntitySlim Root = new EntitySlim { Path = "-1", Name = "root", HasChildren = true };
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AdditionalData key for culture names.
|
||||
/// </summary>
|
||||
public const string AdditionalCultureNames = "CultureNames";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AdditionalData key for variations.
|
||||
/// </summary>
|
||||
public const string AdditionalVariations = "Variations";
|
||||
|
||||
|
||||
// implement IEntity
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets the date a culture was published.
|
||||
/// </summary>
|
||||
DateTime GetDateCulturePublished(string culture);
|
||||
DateTime GetCulturePublishDate(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicated whether a given culture is edited.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -34,7 +35,7 @@ namespace Umbraco.Core.Models
|
||||
/// <para>When <paramref name="culture"/> is <c>null</c>, sets the invariant
|
||||
/// language, which sets the <see cref="TreeEntityBase.Name"/> property.</para>
|
||||
/// </remarks>
|
||||
void SetName(string culture, string value);
|
||||
void SetName(string value, string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the content item for a specified language.
|
||||
@@ -46,13 +47,13 @@ namespace Umbraco.Core.Models
|
||||
string GetName(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the names of the content item.
|
||||
/// Gets the names of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Because a dictionary key cannot be <c>null</c> this cannot get nor set the invariant
|
||||
/// <para>Because a dictionary key cannot be <c>null</c> this cannot get the invariant
|
||||
/// name, which must be get or set via the <see cref="TreeEntityBase.Name"/> property.</para>
|
||||
/// </remarks>
|
||||
IReadOnlyDictionary<string, string> Names { get; set; }
|
||||
IReadOnlyDictionary<string, string> Names { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a given culture is available.
|
||||
@@ -63,6 +64,11 @@ namespace Umbraco.Core.Models
|
||||
/// </remarks>
|
||||
bool IsCultureAvailable(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date a culture was created.
|
||||
/// </summary>
|
||||
DateTime GetCultureDate(string culture);
|
||||
|
||||
/// <summary>
|
||||
/// List of properties, which make up all the data available for this Content object
|
||||
/// </summary>
|
||||
|
||||
@@ -6,80 +6,172 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Represents a cached content.
|
||||
/// Represents a published content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Can be a published document, media or member.</para>
|
||||
/// </remarks>
|
||||
public interface IPublishedContent : IPublishedElement
|
||||
{
|
||||
#region Content
|
||||
|
||||
// fixme - all these are colliding with models => ?
|
||||
// or could we force them to be 'new' in models?
|
||||
|
||||
int Id { get; }
|
||||
int TemplateId { get; }
|
||||
int SortOrder { get; }
|
||||
string Name { get; }
|
||||
string UrlName { get; } // fixme rename
|
||||
string DocumentTypeAlias { get; } // fixme obsolete
|
||||
int DocumentTypeId { get; } // fixme obsolete
|
||||
string WriterName { get; }
|
||||
string CreatorName { get; }
|
||||
int WriterId { get; }
|
||||
int CreatorId { get; }
|
||||
string Path { get; }
|
||||
DateTime CreateDate { get; }
|
||||
DateTime UpdateDate { get; }
|
||||
int Level { get; }
|
||||
string Url { get; }
|
||||
|
||||
IReadOnlyDictionary<string, PublishedCultureName> CultureNames { get; }
|
||||
// todo - IPublishedContent properties colliding with models
|
||||
// we need to find a way to remove as much clutter as possible from IPublishedContent,
|
||||
// since this is preventing someone from creating a property named 'Path' and have it
|
||||
// in a model, for instance. we could move them all under one unique property eg
|
||||
// Infos, so we would do .Infos.SortOrder - just an idea - not going to do it in v8
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is a content (aka a document) or a media.
|
||||
/// Gets the unique identifier of the content item.
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The value of this property is contextual. When the content type is multi-lingual,
|
||||
/// this is the name for the 'current' culture. Otherwise, it is the invariant name.</para>
|
||||
/// </remarks>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url segment of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The value of this property is contextual. When the content type is multi-lingual,
|
||||
/// this is the name for the 'current' culture. Otherwise, it is the invariant url segment.</para>
|
||||
/// </remarks>
|
||||
string UrlSegment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sort order of the content item.
|
||||
/// </summary>
|
||||
int SortOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree level of the content item.
|
||||
/// </summary>
|
||||
int Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tree path of the content item.
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the template to use to render the content item.
|
||||
/// </summary>
|
||||
int TemplateId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who created the content item.
|
||||
/// </summary>
|
||||
int CreatorId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the user who created the content item.
|
||||
/// </summary>
|
||||
string CreatorName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date the content item was created.
|
||||
/// </summary>
|
||||
DateTime CreateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of the user who last updated the content item.
|
||||
/// </summary>
|
||||
int WriterId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the user who last updated the content item.
|
||||
/// </summary>
|
||||
string WriterName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date the content item was last updated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>For published content items, this is also the date the item was published.</para>
|
||||
/// <para>This date is always global to the content item, see GetCulture().Date for the
|
||||
/// date each culture was published.</para>
|
||||
/// </remarks>
|
||||
DateTime UpdateDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The value of this property is contextual. It depends on the 'current' request uri,
|
||||
/// if any. In addition, when the content type is multi-lingual, this is the url for the
|
||||
/// 'current' culture. Otherwise, it is the invariant url.</para>
|
||||
/// </remarks>
|
||||
string Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The value of this property is contextual. It depends on the 'current' request uri,
|
||||
/// if any. In addition, when the content type is multi-lingual, this is the url for the
|
||||
/// specified culture. Otherwise, it is the invariant url.</para>
|
||||
/// </remarks>
|
||||
string GetUrl(string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets culture infos for a culture.
|
||||
/// </summary>
|
||||
PublishedCultureInfos GetCulture(string culture = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets culture infos.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Contains only those culture that are available. For a published content, these are
|
||||
/// the cultures that are published. For a draft content, those that are 'available' ie
|
||||
/// have a non-empty content name.</para>
|
||||
/// </remarks>
|
||||
IReadOnlyDictionary<string, PublishedCultureInfos> Cultures { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the content item (document, 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>
|
||||
/// <remarks>
|
||||
/// <para>A content is draft when it is the unpublished version of a content, which may
|
||||
/// have a published version, or not.</para>
|
||||
/// <para>When retrieving documents from cache in non-preview mode, IsDraft is always false,
|
||||
/// as only published documents are returned. When retrieving in preview mode, IsDraft can
|
||||
/// either be true (document is not published, or has been edited, and what is returned
|
||||
/// is the edited version) or false (document is published, and has not been edited, and
|
||||
/// what is returned is the published version).</para>
|
||||
/// </remarks>
|
||||
bool IsDraft { get; }
|
||||
|
||||
// fixme - consider having an IsPublished flag too
|
||||
// so that when IsDraft is true, we can check whether there is a published version?
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tree
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of the content.
|
||||
/// Gets the parent of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>The parent of root content is <c>null</c>.</remarks>
|
||||
IPublishedContent Parent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the children of the content.
|
||||
/// Gets the children of the content item.
|
||||
/// </summary>
|
||||
/// <remarks>Children are sorted by their sortOrder.</remarks>
|
||||
IEnumerable<IPublishedContent> Children { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <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);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
#region PublishedElement
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique key of the published snapshot item.
|
||||
/// Gets the unique key of the published element.
|
||||
/// </summary>
|
||||
Guid Key { get; }
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a fallback strategy for getting <see cref="IPublishedElement"/> values.
|
||||
/// </summary>
|
||||
// fixme - IPublishedValueFallback is still WorkInProgress
|
||||
// todo - properly document methods, etc
|
||||
// todo - understand caching vs fallback (recurse etc)
|
||||
public interface IPublishedValueFallback
|
||||
{
|
||||
// note that at property level, property.GetValue() does NOT implement fallback, and one has
|
||||
// to get property.Value() or property.Value<T>() to trigger fallback
|
||||
|
||||
// this method is called whenever property.Value(culture, segment, defaultValue) is called, and
|
||||
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
|
||||
|
||||
object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue);
|
||||
|
||||
// this method is called whenever property.Value<T>(culture, segment, defaultValue) is called, and
|
||||
// property.HasValue(culture, segment) is false. it can only fallback at property level (no recurse).
|
||||
|
||||
T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue);
|
||||
|
||||
// these methods to be called whenever getting the property value for the specified alias, culture and segment,
|
||||
// either returned no property at all, or a property that does not HasValue for the specified culture and segment.
|
||||
|
||||
object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue);
|
||||
|
||||
T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue);
|
||||
|
||||
object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse);
|
||||
|
||||
T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gives access to the current <see cref="VariationContext"/>.
|
||||
/// </summary>
|
||||
public interface IVariationContextAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the current <see cref="VariationContext"/>.
|
||||
/// </summary>
|
||||
VariationContext VariationContext { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a noop implementation for <see cref="IPublishedValueFallback"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This is for tests etc - does not implement fallback at all.</para>
|
||||
/// </remarks>
|
||||
public class NoopPublishedValueFallback : IPublishedValueFallback
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue) => defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue) => defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue) => defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue) => defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) => defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) => defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
if (content == null)
|
||||
return null;
|
||||
|
||||
// in order to provide a nice, "fluent" experience, this extension method
|
||||
// needs to access Current, which is not always initialized in tests - not
|
||||
// very elegant, but works
|
||||
if (!Current.HasContainer) return content;
|
||||
|
||||
// get model
|
||||
// if factory returns nothing, throw
|
||||
var model = Current.PublishedModelFactory.CreateModel(content);
|
||||
|
||||
@@ -39,77 +39,101 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
public IPublishedContent Unwrap() => _content;
|
||||
|
||||
#region ContentType
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual PublishedContentType ContentType => _content.ContentType;
|
||||
|
||||
#endregion
|
||||
|
||||
#region PublishedElement
|
||||
|
||||
#region Content
|
||||
|
||||
public virtual int Id => _content.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Key => _content.Key;
|
||||
|
||||
public virtual int TemplateId => _content.TemplateId;
|
||||
#endregion
|
||||
|
||||
#region PublishedContent
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual int Id => _content.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Name => _content.Name;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string UrlSegment => _content.UrlSegment;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual int SortOrder => _content.SortOrder;
|
||||
|
||||
public virtual string Name => _content.Name;
|
||||
|
||||
public virtual IReadOnlyDictionary<string, PublishedCultureName> CultureNames => _content.CultureNames;
|
||||
|
||||
public virtual string UrlName => _content.UrlName;
|
||||
|
||||
public virtual string DocumentTypeAlias => _content.DocumentTypeAlias;
|
||||
|
||||
public virtual int DocumentTypeId => _content.DocumentTypeId;
|
||||
|
||||
public virtual string WriterName => _content.WriterName;
|
||||
|
||||
public virtual string CreatorName => _content.CreatorName;
|
||||
|
||||
public virtual int WriterId => _content.WriterId;
|
||||
|
||||
public virtual int CreatorId => _content.CreatorId;
|
||||
|
||||
public virtual string Path => _content.Path;
|
||||
|
||||
public virtual DateTime CreateDate => _content.CreateDate;
|
||||
|
||||
public virtual DateTime UpdateDate => _content.UpdateDate;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual int Level => _content.Level;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Path => _content.Path;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual int TemplateId => _content.TemplateId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual int CreatorId => _content.CreatorId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string CreatorName => _content.CreatorName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual DateTime CreateDate => _content.CreateDate;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual int WriterId => _content.WriterId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string WriterName => _content.WriterName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual DateTime UpdateDate => _content.UpdateDate;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string Url => _content.Url;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string GetUrl(string culture = null) => _content.GetUrl(culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public PublishedCultureInfos GetCulture(string culture = null) => _content.GetCulture(culture);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<string, PublishedCultureInfos> Cultures => _content.Cultures;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual PublishedItemType ItemType => _content.ItemType;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool IsDraft => _content.IsDraft;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tree
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IPublishedContent Parent => _content.Parent;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IEnumerable<IPublishedContent> Children => _content.Children;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <inheritdoc cref="IPublishedElement.Properties"/>
|
||||
public virtual IEnumerable<IPublishedProperty> Properties => _content.Properties;
|
||||
|
||||
/// <inheritdoc cref="IPublishedElement.GetProperty(string)"/>
|
||||
public virtual IPublishedProperty GetProperty(string alias)
|
||||
{
|
||||
return _content.GetProperty(alias);
|
||||
}
|
||||
|
||||
public virtual IPublishedProperty GetProperty(string alias, bool recurse)
|
||||
{
|
||||
return _content.GetProperty(alias, recurse);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Umbraco.Core.Exceptions;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains culture specific values for <see cref="IPublishedContent"/>.
|
||||
/// </summary>
|
||||
public class PublishedCultureInfos
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedCultureInfos"/> class.
|
||||
/// </summary>
|
||||
public PublishedCultureInfos(string culture, string name, DateTime date)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentNullOrEmptyException(nameof(culture));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullOrEmptyException(nameof(name));
|
||||
|
||||
Culture = culture;
|
||||
Name = name;
|
||||
UrlSegment = name.ToUrlSegment(culture);
|
||||
Date = date;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the culture.
|
||||
/// </summary>
|
||||
public string Culture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the item.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url segment of the item.
|
||||
/// </summary>
|
||||
public string UrlSegment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date associated with the culture.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>For published culture, this is the date the culture was published. For draft
|
||||
/// cultures, this is the date the culture was made available, ie the last time its
|
||||
/// name changed.</para>
|
||||
/// </remarks>
|
||||
public DateTime Date { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the culture specific data for a <see cref="IPublishedContent"/> item
|
||||
/// </summary>
|
||||
public struct PublishedCultureName
|
||||
{
|
||||
public PublishedCultureName(string name, string urlName) : this()
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
UrlName = urlName ?? throw new ArgumentNullException(nameof(urlName));
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string UrlName { get; }
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,11 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
private readonly Lazy<object> _objectValue;
|
||||
private readonly Lazy<object> _xpathValue;
|
||||
|
||||
// RawValueProperty does not (yet?) support variants,
|
||||
// only manages the current "default" value
|
||||
|
||||
public override object GetSourceValue(string culture = null, string segment = null)
|
||||
=> culture == null & segment == null ? _sourceValue : null;
|
||||
=> string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _sourceValue : null;
|
||||
|
||||
public override bool HasValue(string culture = null, string segment = null)
|
||||
{
|
||||
@@ -30,10 +33,10 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
}
|
||||
|
||||
public override object GetValue(string culture = null, string segment = null)
|
||||
=> culture == null & segment == null ? _objectValue.Value : null;
|
||||
=> string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _objectValue.Value : null;
|
||||
|
||||
public override object GetXPathValue(string culture = null, string segment = null)
|
||||
=> culture == null & segment == null ? _xpathValue.Value : null;
|
||||
=> string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _xpathValue.Value : null;
|
||||
|
||||
public RawValueProperty(PublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false)
|
||||
: base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a CurrentUICulture-based implementation of <see cref="IVariationContextAccessor"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This accessor does not support segments. There is no need to set the current context.</para>
|
||||
/// </remarks>
|
||||
public class ThreadCultureVariationContextAccessor : IVariationContextAccessor
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, VariationContext> _contexts = new ConcurrentDictionary<string, VariationContext>();
|
||||
|
||||
public VariationContext VariationContext
|
||||
{
|
||||
get => _contexts.GetOrAdd(Thread.CurrentThread.CurrentUICulture.Name, culture => new VariationContext(culture));
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a ThreadStatic-based implementation of <see cref="IVariationContextAccessor"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Something must set the current context.</para>
|
||||
/// </remarks>
|
||||
public class ThreadStaticVariationContextAccessor : IVariationContextAccessor
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static VariationContext _context;
|
||||
|
||||
/// <inheritdoc />
|
||||
public VariationContext VariationContext
|
||||
{
|
||||
get => _context;
|
||||
set => _context = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/Umbraco.Core/Models/PublishedContent/VariationContext.cs
Normal file
27
src/Umbraco.Core/Models/PublishedContent/VariationContext.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the variation context.
|
||||
/// </summary>
|
||||
public class VariationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VariationContext"/> class.
|
||||
/// </summary>
|
||||
public VariationContext(string culture = null, string segment = null)
|
||||
{
|
||||
Culture = culture ?? ""; // cannot be null, default to invariant
|
||||
Segment = segment ?? ""; // cannot be null, default to neutral
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the culture.
|
||||
/// </summary>
|
||||
public string Culture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segment.
|
||||
/// </summary>
|
||||
public string Segment { get; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user