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:
Shannon
2018-05-08 12:31:03 +10:00
144 changed files with 2598 additions and 1700 deletions

View File

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

View File

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

View File

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

View File

@@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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