Merge pull request #7454 from umbraco/v8/feature/7212-segment-support
V8/feature/7212 segment support
This commit is contained in:
@@ -19,6 +19,11 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the content type varies by segment.
|
||||
/// </summary>
|
||||
public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the content type is invariant.
|
||||
/// </summary>
|
||||
|
||||
@@ -49,18 +49,15 @@ namespace Umbraco.Web.Editors.Binders
|
||||
{
|
||||
foreach (var variant in model.Variants)
|
||||
{
|
||||
if (variant.Culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
//map the property dto collection (no culture is passed to the mapping context so it will be invariant)
|
||||
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(model.PersistedContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
//map the property dto collection with the culture of the current variant
|
||||
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(
|
||||
model.PersistedContent,
|
||||
context => context.SetCulture(variant.Culture));
|
||||
}
|
||||
//map the property dto collection with the culture of the current variant
|
||||
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(
|
||||
model.PersistedContent,
|
||||
context =>
|
||||
{
|
||||
// either of these may be null and that is ok, if it's invariant they will be null which is what is expected
|
||||
context.SetCulture(variant.Culture);
|
||||
context.SetSegment(variant.Segment);
|
||||
});
|
||||
|
||||
//now map all of the saved values to the dto
|
||||
_modelBinderHelper.MapPropertyValuesFromSaved(variant, variant.PropertyCollectionDto);
|
||||
@@ -87,6 +84,5 @@ namespace Umbraco.Web.Editors.Binders
|
||||
model.ParentId,
|
||||
contentType);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1836,8 +1836,13 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="contentSave"></param>
|
||||
private void MapValuesForPersistence(ContentItemSave contentSave)
|
||||
{
|
||||
// inline method to determine if a property type varies
|
||||
bool Varies(Property property) => property.PropertyType.VariesByCulture();
|
||||
// inline method to determine the culture and segment to persist the property
|
||||
(string culture, string segment) PropertyCultureAndSegment(Property property, ContentVariantSave variant)
|
||||
{
|
||||
var culture = property.PropertyType.VariesByCulture() ? variant.Culture : null;
|
||||
var segment = property.PropertyType.VariesBySegment() ? variant.Segment : null;
|
||||
return (culture, segment);
|
||||
}
|
||||
|
||||
var variantIndex = 0;
|
||||
|
||||
@@ -1876,8 +1881,18 @@ namespace Umbraco.Web.Editors
|
||||
MapPropertyValuesForPersistence<IContent, ContentItemSave>(
|
||||
contentSave,
|
||||
propertyCollection,
|
||||
(save, property) => Varies(property) ? property.GetValue(variant.Culture) : property.GetValue(), //get prop val
|
||||
(save, property, v) => { if (Varies(property)) property.SetValue(v, variant.Culture); else property.SetValue(v); }, //set prop val
|
||||
(save, property) =>
|
||||
{
|
||||
// Get property value
|
||||
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
|
||||
return property.GetValue(culture, segment);
|
||||
},
|
||||
(save, property, v) =>
|
||||
{
|
||||
// Set property value
|
||||
(var culture, var segment) = PropertyCultureAndSegment(property, variant);
|
||||
property.SetValue(v, culture, segment);
|
||||
},
|
||||
variant.Culture);
|
||||
|
||||
variantIndex++;
|
||||
|
||||
@@ -53,11 +53,21 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[ReadOnly(true)]
|
||||
public string Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The segment of the property
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The segment value of a property can always be null but can only have a non-null value
|
||||
/// when the property can be varied by segment.
|
||||
/// </remarks>
|
||||
[DataMember(Name = "segment")]
|
||||
[ReadOnly(true)]
|
||||
public string Segment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used internally during model mapping
|
||||
/// </summary>
|
||||
[IgnoreDataMember]
|
||||
internal IDataEditor PropertyEditor { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[DataMember(Name = "culture")]
|
||||
public string Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The segment of this variant, if this is invariant than this is null or empty
|
||||
/// </summary>
|
||||
[DataMember(Name = "segment")]
|
||||
public string Segment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the variant should be updated
|
||||
/// </summary>
|
||||
|
||||
@@ -70,8 +70,13 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
dest.Culture = culture;
|
||||
|
||||
// Get the segment, which is always allowed to be null even if the propertyType *can* be varied by segment.
|
||||
// There is therefore no need to perform the null check like with culture above.
|
||||
var segment = !property.PropertyType.VariesBySegment() ? null : context.GetSegment();
|
||||
dest.Segment = segment;
|
||||
|
||||
// if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return.
|
||||
dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture);
|
||||
dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture, segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,52 +21,117 @@ namespace Umbraco.Web.Models.Mapping
|
||||
|
||||
public IEnumerable<ContentVariantDisplay> Map(IContent source, MapperContext context)
|
||||
{
|
||||
var result = new List<ContentVariantDisplay>();
|
||||
if (!source.ContentType.VariesByCulture())
|
||||
var variesByCulture = source.ContentType.VariesByCulture();
|
||||
var variesBySegment = source.ContentType.VariesBySegment();
|
||||
|
||||
IList<ContentVariantDisplay> variants = new List<ContentVariantDisplay>();
|
||||
|
||||
if (!variesByCulture && !variesBySegment)
|
||||
{
|
||||
//this is invariant so just map the IContent instance to ContentVariationDisplay
|
||||
result.Add(context.Map<ContentVariantDisplay>(source));
|
||||
// this is invariant so just map the IContent instance to ContentVariationDisplay
|
||||
var variantDisplay = context.Map<ContentVariantDisplay>(source);
|
||||
variants.Add(variantDisplay);
|
||||
}
|
||||
else if (variesByCulture && !variesBySegment)
|
||||
{
|
||||
var languages = GetLanguages(context);
|
||||
variants = languages
|
||||
.Select(language => CreateVariantDisplay(context, source, language, null))
|
||||
.ToList();
|
||||
}
|
||||
else if (variesBySegment && !variesByCulture)
|
||||
{
|
||||
// Segment only
|
||||
var segments = GetSegments(source);
|
||||
variants = segments
|
||||
.Select(segment => CreateVariantDisplay(context, source, null, segment))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
|
||||
if (allLanguages.Count == 0) return Enumerable.Empty<ContentVariantDisplay>(); //this should never happen
|
||||
// Culture and segment
|
||||
var languages = GetLanguages(context).ToList();
|
||||
var segments = GetSegments(source).ToList();
|
||||
|
||||
var langs = context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
|
||||
|
||||
//create a variant for each language, then we'll populate the values
|
||||
var variants = langs.Select(x =>
|
||||
if (languages.Count == 0 || segments.Count == 0)
|
||||
{
|
||||
//We need to set the culture in the mapping context since this is needed to ensure that the correct property values
|
||||
//are resolved during the mapping
|
||||
context.SetCulture(x.IsoCode);
|
||||
return context.Map<ContentVariantDisplay>(source);
|
||||
}).ToList();
|
||||
|
||||
for (int i = 0; i < langs.Count; i++)
|
||||
{
|
||||
var x = langs[i];
|
||||
var variant = variants[i];
|
||||
|
||||
variant.Language = x;
|
||||
variant.Name = source.GetCultureName(x.IsoCode);
|
||||
// This should not happen
|
||||
throw new InvalidOperationException("No languages or segments available");
|
||||
}
|
||||
|
||||
//Put the default language first in the list & then sort rest by a-z
|
||||
var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault);
|
||||
variants = languages
|
||||
.SelectMany(language => segments
|
||||
.Select(segment => CreateVariantDisplay(context, source, language, segment)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
//Remove the default language from the list for now
|
||||
variants.Remove(defaultLang);
|
||||
|
||||
//Sort the remaining languages a-z
|
||||
variants = variants.OrderBy(x => x.Language.Name).ToList();
|
||||
|
||||
//Insert the default language as the first item
|
||||
variants.Insert(0, defaultLang);
|
||||
return SortVariants(variants);
|
||||
}
|
||||
|
||||
private IList<ContentVariantDisplay> SortVariants(IList<ContentVariantDisplay> variants)
|
||||
{
|
||||
if (variants == null || variants.Count <= 1)
|
||||
{
|
||||
return variants;
|
||||
}
|
||||
return result;
|
||||
|
||||
// Default variant first, then order by language, segment.
|
||||
return variants
|
||||
.OrderBy(v => IsDefaultLanguage(v) ? 0 : 1)
|
||||
.ThenBy(v => IsDefaultSegment(v) ? 0 : 1)
|
||||
.ThenBy(v => v?.Language?.Name)
|
||||
.ThenBy(v => v.Segment)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static bool IsDefaultSegment(ContentVariantDisplay variant)
|
||||
{
|
||||
return variant.Segment == null;
|
||||
}
|
||||
|
||||
private static bool IsDefaultLanguage(ContentVariantDisplay variant)
|
||||
{
|
||||
return variant.Language == null || variant.Language.IsDefault;
|
||||
}
|
||||
|
||||
private IEnumerable<Language> GetLanguages(MapperContext context)
|
||||
{
|
||||
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
|
||||
if (allLanguages.Count == 0)
|
||||
{
|
||||
// This should never happen
|
||||
return Enumerable.Empty<Language>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all segments assigned to the content
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <returns>
|
||||
/// Returns all segments assigned to the content including 'null' values
|
||||
/// </returns>
|
||||
private IEnumerable<string> GetSegments(IContent content)
|
||||
{
|
||||
return content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)).Distinct();
|
||||
}
|
||||
|
||||
private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment)
|
||||
{
|
||||
context.SetCulture(language?.IsoCode);
|
||||
context.SetSegment(segment);
|
||||
|
||||
var variantDisplay = context.Map<ContentVariantDisplay>(content);
|
||||
|
||||
variantDisplay.Segment = segment;
|
||||
variantDisplay.Language = language;
|
||||
variantDisplay.Name = content.GetCultureName(language?.IsoCode);
|
||||
|
||||
return variantDisplay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
internal static class MapperContextExtensions
|
||||
{
|
||||
private const string CultureKey = "Map.Culture";
|
||||
private const string SegmentKey = "Map.Segment";
|
||||
private const string IncludedPropertiesKey = "Map.IncludedProperties";
|
||||
|
||||
/// <summary>
|
||||
@@ -18,6 +19,14 @@ namespace Umbraco.Web.Models.Mapping
|
||||
return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the context segment.
|
||||
/// </summary>
|
||||
public static string GetSegment(this MapperContext context)
|
||||
{
|
||||
return context.HasItems && context.Items.TryGetValue(SegmentKey, out var obj) && obj is string s ? s : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a context culture.
|
||||
/// </summary>
|
||||
@@ -26,6 +35,14 @@ namespace Umbraco.Web.Models.Mapping
|
||||
context.Items[CultureKey] = culture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a context segment.
|
||||
/// </summary>
|
||||
public static void SetSegment(this MapperContext context, string segment)
|
||||
{
|
||||
context.Items[SegmentKey] = segment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get included properties.
|
||||
/// </summary>
|
||||
@@ -42,4 +59,4 @@ namespace Umbraco.Web.Models.Mapping
|
||||
context.Items[IncludedPropertiesKey] = properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user