Skip to content

Commit

Permalink
Merge branch 'v8/dev' into v8/contrib
Browse files Browse the repository at this point in the history
  • Loading branch information
nul800sebastiaan committed Jan 14, 2020
2 parents 8176054 + 33f7337 commit 0ba5a0a
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 52 deletions.
5 changes: 5 additions & 0 deletions src/Umbraco.Core/ContentVariationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public static class ContentVariationExtensions
/// </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>
Expand Down
22 changes: 9 additions & 13 deletions src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,15 @@ public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindi
{
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);
Expand All @@ -87,6 +84,5 @@ private IContent CreateNew(ContentItemSave model)
model.ParentId,
contentType);
}

}
}
23 changes: 19 additions & 4 deletions src/Umbraco.Web/Editors/ContentController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1836,8 +1836,13 @@ private void HandleInvalidModelState(ContentItemDisplay display, string cultureF
/// <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;

Expand Down Expand Up @@ -1876,8 +1881,18 @@ private void MapValuesForPersistence(ContentItemSave contentSave)
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++;
Expand Down
12 changes: 11 additions & 1 deletion src/Umbraco.Web/Models/ContentEditing/ContentPropertyBasic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,21 @@ public class ContentPropertyBasic
[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; }

}
}
6 changes: 6 additions & 0 deletions src/Umbraco.Web/Models/ContentEditing/ContentVariantSave.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public ContentVariantSave()
[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>
Expand Down
7 changes: 6 additions & 1 deletion src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,13 @@ public virtual void Map(Property property, TDestination dest, MapperContext cont

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);
}
}
}
129 changes: 97 additions & 32 deletions src/Umbraco.Web/Models/Mapping/ContentVariantMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,52 +21,117 @@ public ContentVariantMapper(ILocalizationService localizationService)

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

var langs = context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
// Culture and segment
var languages = GetLanguages(context).ToList();
var segments = GetSegments(source).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();
// This should not happen
throw new InvalidOperationException("No languages or segments available");
}

for (int i = 0; i < langs.Count; i++)
{
var x = langs[i];
var variant = variants[i];
variants = languages
.SelectMany(language => segments
.Select(segment => CreateVariantDisplay(context, source, language, segment)))
.ToList();
}

variant.Language = x;
variant.Name = source.GetCultureName(x.IsoCode);
}
return SortVariants(variants);
}

//Put the default language first in the list & then sort rest by a-z
var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault);
private IList<ContentVariantDisplay> SortVariants(IList<ContentVariantDisplay> variants)
{
if (variants == null || variants.Count <= 1)
{
return variants;
}

//Remove the default language from the list for now
variants.Remove(defaultLang);
// 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();
}

//Sort the remaining languages a-z
variants = variants.OrderBy(x => x.Language.Name).ToList();
private static bool IsDefaultSegment(ContentVariantDisplay variant)
{
return variant.Segment == null;
}

//Insert the default language as the first item
variants.Insert(0, defaultLang);
private static bool IsDefaultLanguage(ContentVariantDisplay variant)
{
return variant.Language == null || variant.Language.IsDefault;
}

return variants;
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();
}
return result;
}

/// <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;
}
}
}
19 changes: 18 additions & 1 deletion src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -18,6 +19,14 @@ public static string GetCulture(this MapperContext context)
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>
Expand All @@ -26,6 +35,14 @@ public static void SetCulture(this MapperContext context, string culture)
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>
Expand All @@ -42,4 +59,4 @@ public static void SetIncludedProperties(this MapperContext context, string[] pr
context.Items[IncludedPropertiesKey] = properties;
}
}
}
}

0 comments on commit 0ba5a0a

Please sign in to comment.