Starts wiring up the back office to the c# bits, updates controllers, mappers, models, property editors to support getting and saving data by language. The content editor now "works" with multi-lingual properties
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends AutoMapper's <see cref="Mapper"/> class to handle Umbraco's context.
|
||||
/// </summary>
|
||||
internal static class ContextMapper
|
||||
{
|
||||
private const string UmbracoContextKey = "ContextMapper.UmbracoContext";
|
||||
|
||||
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext)
|
||||
=> Mapper.Map<TSource, TDestination>(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext);
|
||||
|
||||
public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true)
|
||||
{
|
||||
if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext)
|
||||
return umbracoContext;
|
||||
|
||||
// fixme - not a good idea at all
|
||||
// because this falls back to magic singletons
|
||||
// so really we should remove this line, but then some tests+app breaks ;(
|
||||
return Umbraco.Web.Composing.Current.UmbracoContext;
|
||||
|
||||
// better fail fast
|
||||
if (throwIfMissing)
|
||||
throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext.");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,11 @@ using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using ContentVariation = Umbraco.Core.Models.ContentVariation;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
@@ -15,10 +17,14 @@ namespace Umbraco.Web.Models.Mapping
|
||||
internal class ContentPropertyBasicConverter<TDestination> : ITypeConverter<Property, TDestination>
|
||||
where TDestination : ContentPropertyBasic, new()
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
protected IDataTypeService DataTypeService { get; }
|
||||
|
||||
public ContentPropertyBasicConverter(IDataTypeService dataTypeService)
|
||||
public ContentPropertyBasicConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
_logger = logger;
|
||||
_propertyEditors = propertyEditors;
|
||||
DataTypeService = dataTypeService;
|
||||
}
|
||||
|
||||
@@ -28,19 +34,28 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// <returns></returns>
|
||||
public virtual TDestination Convert(Property property, TDestination dest, ResolutionContext context)
|
||||
{
|
||||
var editor = Current.PropertyEditors[property.PropertyType.PropertyEditorAlias];
|
||||
var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias];
|
||||
if (editor == null)
|
||||
{
|
||||
Current.Logger.Error<ContentPropertyBasicConverter<TDestination>>(
|
||||
_logger.Error<ContentPropertyBasicConverter<TDestination>>(
|
||||
"No property editor found, converting to a Label",
|
||||
new NullReferenceException("The property editor with alias " + property.PropertyType.PropertyEditorAlias + " does not exist"));
|
||||
|
||||
editor = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit];
|
||||
editor = _propertyEditors[Constants.PropertyEditors.Aliases.NoEdit];
|
||||
}
|
||||
|
||||
var languageId = context.GetLanguageId();
|
||||
|
||||
if (!languageId.HasValue && property.PropertyType.Variations == ContentVariation.CultureNeutral)
|
||||
{
|
||||
//a language Id needs to be set for a property type that can be varried by language
|
||||
throw new InvalidOperationException($"No languageId found in mapping operation when one is required for the culture neutral property type {property.PropertyType.Alias}");
|
||||
}
|
||||
|
||||
var result = new TDestination
|
||||
{
|
||||
Id = property.Id,
|
||||
Value = editor.GetValueEditor().ToEditor(property, DataTypeService),
|
||||
Value = editor.GetValueEditor().ToEditor(property, DataTypeService, languageId),
|
||||
Alias = property.Alias,
|
||||
PropertyEditor = editor,
|
||||
Editor = editor.Alias
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -17,8 +18,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
|
||||
public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService)
|
||||
: base(dataTypeService)
|
||||
public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
: base(dataTypeService, logger, propertyEditors)
|
||||
{
|
||||
_textService = textService;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
@@ -11,8 +13,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// </summary>
|
||||
internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter<ContentPropertyDto>
|
||||
{
|
||||
public ContentPropertyDtoConverter(IDataTypeService dataTypeService)
|
||||
: base(dataTypeService)
|
||||
public ContentPropertyDtoConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
: base(dataTypeService, logger, propertyEditors)
|
||||
{ }
|
||||
|
||||
public override ContentPropertyDto Convert(Property originalProperty, ContentPropertyDto dest, ResolutionContext context)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
@@ -11,11 +13,11 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// </summary>
|
||||
internal class ContentPropertyMapperProfile : Profile
|
||||
{
|
||||
public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService)
|
||||
public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
var contentPropertyBasicConverter = new ContentPropertyBasicConverter<ContentPropertyBasic>(dataTypeService);
|
||||
var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService);
|
||||
var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService);
|
||||
var contentPropertyBasicConverter = new ContentPropertyBasicConverter<ContentPropertyBasic>(dataTypeService, logger, propertyEditors);
|
||||
var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService, logger, propertyEditors);
|
||||
var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService, logger, propertyEditors);
|
||||
|
||||
//FROM Property TO ContentPropertyBasic
|
||||
CreateMap<PropertyGroup, Tab<ContentPropertyDisplay>>()
|
||||
@@ -25,13 +27,13 @@ namespace Umbraco.Web.Models.Mapping
|
||||
.ForMember(tab => tab.Alias, expression => expression.Ignore());
|
||||
|
||||
//FROM Property TO ContentPropertyBasic
|
||||
CreateMap<Property, ContentPropertyBasic>().ConvertUsing(contentPropertyBasicConverter);
|
||||
CreateMap<Property, ContentPropertyBasic>().ConvertUsing((property, basic, arg3) => contentPropertyBasicConverter.Convert(property, basic, arg3));
|
||||
|
||||
//FROM Property TO ContentPropertyDto
|
||||
CreateMap<Property, ContentPropertyDto>().ConvertUsing(contentPropertyDtoConverter);
|
||||
|
||||
//FROM Property TO ContentPropertyDisplay
|
||||
CreateMap<Property, ContentPropertyDisplay>().ConvertUsing(contentPropertyDisplayConverter);
|
||||
CreateMap<Property, ContentPropertyDisplay>().ConvertUsing((property, basic, arg3) => contentPropertyDisplayConverter.Convert(property, basic, arg3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
112
src/Umbraco.Web/Models/Mapping/ContextMapper.cs
Normal file
112
src/Umbraco.Web/Models/Mapping/ContextMapper.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using ClientDependency.Core;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Extends AutoMapper's <see cref="Mapper"/> class to handle Umbraco's context and other contextual data
|
||||
/// </summary>
|
||||
internal static class ContextMapper
|
||||
{
|
||||
public const string UmbracoContextKey = "ContextMapper.UmbracoContext";
|
||||
public const string LanguageKey = "ContextMapper.LanguageId";
|
||||
|
||||
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext)
|
||||
=> Mapper.Map<TSource, TDestination>(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext);
|
||||
|
||||
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext, IDictionary<string, object> contextVals)
|
||||
=> Mapper.Map<TSource, TDestination>(obj, opt =>
|
||||
{
|
||||
//set the umb ctx
|
||||
opt.Items[UmbracoContextKey] = umbracoContext;
|
||||
//set other supplied context vals
|
||||
if (contextVals != null)
|
||||
{
|
||||
foreach (var contextVal in contextVals)
|
||||
{
|
||||
opt.Items[contextVal.Key] = contextVal.Value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext, object contextVals)
|
||||
=> Mapper.Map<TSource, TDestination>(obj, opt =>
|
||||
{
|
||||
//set the umb ctx
|
||||
opt.Items[UmbracoContextKey] = umbracoContext;
|
||||
//set other supplied context vals
|
||||
if (contextVals != null)
|
||||
{
|
||||
foreach (var contextVal in contextVals.ToDictionary())
|
||||
{
|
||||
opt.Items[contextVal.Key] = contextVal.Value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static TDestination Map<TSource, TDestination>(TSource obj, IDictionary<string, object> contextVals)
|
||||
=> Mapper.Map<TSource, TDestination>(obj, opt =>
|
||||
{
|
||||
//set other supplied context vals
|
||||
if (contextVals != null)
|
||||
{
|
||||
foreach (var contextVal in contextVals)
|
||||
{
|
||||
opt.Items[contextVal.Key] = contextVal.Value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static TDestination Map<TSource, TDestination>(TSource obj, object contextVals)
|
||||
=> Mapper.Map<TSource, TDestination>(obj, opt =>
|
||||
{
|
||||
//set other supplied context vals
|
||||
if (contextVals != null)
|
||||
{
|
||||
foreach (var contextVal in contextVals.ToDictionary())
|
||||
{
|
||||
opt.Items[contextVal.Key] = contextVal.Value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Returns the language Id in the mapping context if one is found
|
||||
/// </summary>
|
||||
/// <param name="resolutionContext"></param>
|
||||
/// <returns></returns>
|
||||
public static int? GetLanguageId(this ResolutionContext resolutionContext)
|
||||
{
|
||||
if (!resolutionContext.Options.Items.TryGetValue(LanguageKey, out var obj)) return null;
|
||||
|
||||
if (obj is int i)
|
||||
return i;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="UmbracoContext"/> in the mapping context if one is found
|
||||
/// </summary>
|
||||
/// <param name="resolutionContext"></param>
|
||||
/// <param name="throwIfMissing"></param>
|
||||
/// <returns></returns>
|
||||
public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true)
|
||||
{
|
||||
if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext)
|
||||
return umbracoContext;
|
||||
|
||||
// better fail fast
|
||||
if (throwIfMissing)
|
||||
throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext.");
|
||||
|
||||
// fixme - not a good idea at all
|
||||
// because this falls back to magic singletons
|
||||
// so really we should remove this line, but then some tests+app breaks ;(
|
||||
return Umbraco.Web.Composing.Current.UmbracoContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -12,6 +14,38 @@ namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
CreateMap<ILanguage, Language>()
|
||||
.ForMember(l => l.Name, expression => expression.MapFrom(x => x.CultureInfo.DisplayName));
|
||||
|
||||
CreateMap<IEnumerable<ILanguage>, IEnumerable<Language>>()
|
||||
.ConvertUsing<LanguageCollectionTypeConverter>();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a list of <see cref="ILanguage"/> to a list of <see cref="Language"/> and ensures the correct order and defaults are set
|
||||
/// </summary>
|
||||
// ReSharper disable once ClassNeverInstantiated.Local
|
||||
private class LanguageCollectionTypeConverter : ITypeConverter<IEnumerable<ILanguage>, IEnumerable<Language>>
|
||||
{
|
||||
public IEnumerable<Language> Convert(IEnumerable<ILanguage> source, IEnumerable<Language> destination, ResolutionContext context)
|
||||
{
|
||||
var allLanguages = source.OrderBy(x => x.Id).ToList();
|
||||
var langs = new List<Language>(allLanguages.Select(x => context.Mapper.Map<ILanguage, Language>(x, null, context)));
|
||||
|
||||
//if there's only one language, by default it is the default
|
||||
if (langs.Count == 1)
|
||||
{
|
||||
langs[0].IsDefaultVariantLanguage = true;
|
||||
langs[0].Mandatory = true;
|
||||
}
|
||||
else if (allLanguages.All(x => !x.IsDefaultVariantLanguage))
|
||||
{
|
||||
//if no language has the default flag, then the defaul language is the one with the lowest id
|
||||
langs[0].IsDefaultVariantLanguage = true;
|
||||
langs[0].Mandatory = true;
|
||||
}
|
||||
|
||||
return langs.OrderBy(x => x.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,10 +169,11 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="properties"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
protected override List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
|
||||
protected override List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties, ResolutionContext context)
|
||||
{
|
||||
var result = base.MapProperties(umbracoContext, content, properties);
|
||||
var result = base.MapProperties(umbracoContext, content, properties, context);
|
||||
var member = (IMember)content;
|
||||
var memberType = member.ContentType;
|
||||
|
||||
|
||||
@@ -152,18 +152,19 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="tabs"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <remarks>
|
||||
/// The generic properties tab is responsible for
|
||||
/// setting up the properties such as Created date, updated date, template selected, etc...
|
||||
/// </remarks>
|
||||
protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List<Tab<ContentPropertyDisplay>> tabs)
|
||||
protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List<Tab<ContentPropertyDisplay>> tabs, ResolutionContext context)
|
||||
{
|
||||
// add the generic properties tab, for properties that don't belong to a tab
|
||||
// get the properties, map and translate them, then add the tab
|
||||
var noGroupProperties = content.GetNonGroupedProperties()
|
||||
.Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored
|
||||
.ToList();
|
||||
var genericproperties = MapProperties(umbracoContext, content, noGroupProperties);
|
||||
var genericproperties = MapProperties(umbracoContext, content, noGroupProperties, context);
|
||||
|
||||
tabs.Add(new Tab<ContentPropertyDisplay>
|
||||
{
|
||||
@@ -212,12 +213,16 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// <param name="umbracoContext"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="properties"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
|
||||
{
|
||||
var result = Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(
|
||||
protected virtual List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties, ResolutionContext context)
|
||||
{
|
||||
//we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588
|
||||
var result = context.Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(
|
||||
// Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties.
|
||||
properties.OrderBy(prop => prop.PropertyType.SortOrder))
|
||||
properties.OrderBy(prop => prop.PropertyType.SortOrder),
|
||||
null,
|
||||
context)
|
||||
.ToList();
|
||||
|
||||
return result;
|
||||
@@ -265,7 +270,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
continue;
|
||||
|
||||
//map the properties
|
||||
var mappedProperties = MapProperties(umbracoContext, source, properties);
|
||||
var mappedProperties = MapProperties(umbracoContext, source, properties, context);
|
||||
|
||||
// add the tab
|
||||
// we need to pick an identifier... there is no "right" way...
|
||||
@@ -283,7 +288,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
});
|
||||
}
|
||||
|
||||
MapGenericProperties(umbracoContext, source, tabs);
|
||||
MapGenericProperties(umbracoContext, source, tabs, context);
|
||||
|
||||
// activate the first tab, if any
|
||||
if (tabs.Count > 0)
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
|
||||
if (allLanguages.Count == 0) return Enumerable.Empty<ContentVariation>(); //there's only 1 language defined so we don't have language variants enabled
|
||||
|
||||
var langs = Mapper.Map<IEnumerable<Language>>(allLanguages).ToList();
|
||||
var langs = context.Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(allLanguages, null, context);
|
||||
var variants = langs.Select(x => new ContentVariation
|
||||
{
|
||||
Language = x,
|
||||
@@ -33,26 +33,14 @@ namespace Umbraco.Web.Models.Mapping
|
||||
ExpireDate = source.ExpireDate,
|
||||
PublishDate = source.PublishDate,
|
||||
ReleaseDate = source.ReleaseDate,
|
||||
Exists = source.HasVariation(x.Id),
|
||||
Exists = source.HasVariation(x.Id), //TODO: This needs to be wired up with new APIs when they are ready
|
||||
PublishedState = source.PublishedState.ToString()
|
||||
}).ToList();
|
||||
|
||||
//if there's only one language, by default it is the default
|
||||
if (langs.Count == 1)
|
||||
{
|
||||
langs[0].IsDefaultVariantLanguage = true;
|
||||
langs[0].Mandatory = true;
|
||||
}
|
||||
else if (allLanguages.All(x => !x.IsDefaultVariantLanguage))
|
||||
{
|
||||
//if no language has the default flag, then the defaul language is the one with the lowest id
|
||||
langs[0].IsDefaultVariantLanguage = true;
|
||||
langs[0].Mandatory = true;
|
||||
}
|
||||
var langId = context.GetLanguageId();
|
||||
|
||||
//TODO: Not sure if this is required right now, IsCurrent could purely be a UI thing, we'll see
|
||||
//set the 'current'
|
||||
variants.First(x => x.Language.IsDefaultVariantLanguage).IsCurrent = true;
|
||||
//set the current variant being edited to the one found in the context or the default, whichever matches
|
||||
variants.First(x => (langId.HasValue && langId.Value == x.Language.Id) || x.Language.IsDefaultVariantLanguage).IsCurrent = true;
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user