Merge branch 'v8/dev' into v8-fix-invalid-hostname
# Resolved Conflicts: # src/Umbraco.Web.UI/Umbraco/config/lang/en.xml # src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml
This commit is contained in:
@@ -27,9 +27,8 @@ namespace Umbraco.Web
|
||||
private readonly IUmbracoDatabaseFactory _databaseFactory;
|
||||
|
||||
public BatchedDatabaseServerMessenger(
|
||||
IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings,
|
||||
bool enableDistCalls, DatabaseServerMessengerOptions options)
|
||||
: base(runtime, scopeProvider, sqlContext, proflog, globalSettings, enableDistCalls, options)
|
||||
IRuntimeState runtime, IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, ISqlContext sqlContext, IProfilingLogger proflog, IGlobalSettings globalSettings, DatabaseServerMessengerOptions options)
|
||||
: base(runtime, scopeProvider, sqlContext, proflog, globalSettings, true, options)
|
||||
{
|
||||
_databaseFactory = databaseFactory;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Changes;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
@@ -14,14 +16,16 @@ namespace Umbraco.Web.Cache
|
||||
{
|
||||
private readonly IPublishedSnapshotService _publishedSnapshotService;
|
||||
private readonly IPublishedModelFactory _publishedModelFactory;
|
||||
private readonly IContentTypeCommonRepository _contentTypeCommonRepository;
|
||||
private readonly IdkMap _idkMap;
|
||||
|
||||
public ContentTypeCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IdkMap idkMap)
|
||||
public ContentTypeCacheRefresher(AppCaches appCaches, IPublishedSnapshotService publishedSnapshotService, IPublishedModelFactory publishedModelFactory, IdkMap idkMap, IContentTypeCommonRepository contentTypeCommonRepository)
|
||||
: base(appCaches)
|
||||
{
|
||||
_publishedSnapshotService = publishedSnapshotService;
|
||||
_publishedModelFactory = publishedModelFactory;
|
||||
_idkMap = idkMap;
|
||||
_contentTypeCommonRepository = contentTypeCommonRepository;
|
||||
}
|
||||
|
||||
#region Define
|
||||
@@ -44,6 +48,8 @@ namespace Umbraco.Web.Cache
|
||||
// we should NOT directly clear caches here, but instead ask whatever class
|
||||
// is managing the cache to please clear that cache properly
|
||||
|
||||
_contentTypeCommonRepository.ClearCache(); // always
|
||||
|
||||
if (payloads.Any(x => x.ItemType == typeof(IContentType).Name))
|
||||
{
|
||||
ClearAllIsolatedCacheByEntityType<IContent>();
|
||||
|
||||
@@ -119,8 +119,8 @@ namespace Umbraco.Web.Cache
|
||||
() => MemberGroupService.Deleted -= MemberGroupService_Deleted);
|
||||
|
||||
// bind to media events - handles all media changes
|
||||
Bind(() => MediaService.TreeChanged += MediaService_Changed,
|
||||
() => MediaService.TreeChanged -= MediaService_Changed);
|
||||
Bind(() => MediaService.TreeChanged += MediaService_TreeChanged,
|
||||
() => MediaService.TreeChanged -= MediaService_TreeChanged);
|
||||
|
||||
// bind to content events
|
||||
Bind(() => ContentService.Saved += ContentService_Saved, // needed for permissions
|
||||
@@ -403,7 +403,7 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
#region MediaService
|
||||
|
||||
private void MediaService_Changed(IMediaService sender, TreeChange<IMedia>.EventArgs args)
|
||||
private void MediaService_TreeChanged(IMediaService sender, TreeChange<IMedia>.EventArgs args)
|
||||
{
|
||||
_distributedCache.RefreshMediaCache(args.Changes.ToArray());
|
||||
}
|
||||
|
||||
@@ -38,54 +38,44 @@ namespace Umbraco.Web.Compose
|
||||
|
||||
public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer<DatabaseServerRegistrarAndMessengerComponent>, ICoreComposer
|
||||
{
|
||||
public static DatabaseServerMessengerOptions GetDefaultOptions(IFactory factory)
|
||||
{
|
||||
var logger = factory.GetInstance<ILogger>();
|
||||
var indexRebuilder = factory.GetInstance<IndexRebuilder>();
|
||||
|
||||
return new DatabaseServerMessengerOptions
|
||||
{
|
||||
//These callbacks will be executed if the server has not been synced
|
||||
// (i.e. it is a new server or the lastsynced.txt file has been removed)
|
||||
InitializingCallbacks = new Action[]
|
||||
{
|
||||
//rebuild the xml cache file if the server is not synced
|
||||
() =>
|
||||
{
|
||||
// rebuild the published snapshot caches entirely, if the server is not synced
|
||||
// this is equivalent to DistributedCache RefreshAll... but local only
|
||||
// (we really should have a way to reuse RefreshAll... locally)
|
||||
// note: refresh all content & media caches does refresh content types too
|
||||
var svc = Current.PublishedSnapshotService;
|
||||
svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
|
||||
svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _);
|
||||
svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _);
|
||||
},
|
||||
|
||||
//rebuild indexes if the server is not synced
|
||||
// NOTE: This will rebuild ALL indexes including the members, if developers want to target specific
|
||||
// indexes then they can adjust this logic themselves.
|
||||
() => { ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000); }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void Compose(Composition composition)
|
||||
{
|
||||
base.Compose(composition);
|
||||
|
||||
composition.SetServerMessenger(factory =>
|
||||
{
|
||||
var runtime = factory.GetInstance<IRuntimeState>();
|
||||
var databaseFactory = factory.GetInstance<IUmbracoDatabaseFactory>();
|
||||
var globalSettings = factory.GetInstance<IGlobalSettings>();
|
||||
var proflog = factory.GetInstance<IProfilingLogger>();
|
||||
var scopeProvider = factory.GetInstance<IScopeProvider>();
|
||||
var sqlContext = factory.GetInstance<ISqlContext>();
|
||||
var logger = factory.GetInstance<ILogger>();
|
||||
var indexRebuilder = factory.GetInstance<IndexRebuilder>();
|
||||
|
||||
return new BatchedDatabaseServerMessenger(
|
||||
runtime, databaseFactory, scopeProvider, sqlContext, proflog, globalSettings,
|
||||
true,
|
||||
//Default options for web including the required callbacks to build caches
|
||||
new DatabaseServerMessengerOptions
|
||||
{
|
||||
//These callbacks will be executed if the server has not been synced
|
||||
// (i.e. it is a new server or the lastsynced.txt file has been removed)
|
||||
InitializingCallbacks = new Action[]
|
||||
{
|
||||
//rebuild the xml cache file if the server is not synced
|
||||
() =>
|
||||
{
|
||||
// rebuild the published snapshot caches entirely, if the server is not synced
|
||||
// this is equivalent to DistributedCache RefreshAll... but local only
|
||||
// (we really should have a way to reuse RefreshAll... locally)
|
||||
// note: refresh all content & media caches does refresh content types too
|
||||
var svc = Current.PublishedSnapshotService;
|
||||
svc.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) });
|
||||
svc.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _, out _);
|
||||
svc.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, TreeChangeTypes.RefreshAll) }, out _);
|
||||
},
|
||||
|
||||
//rebuild indexes if the server is not synced
|
||||
// NOTE: This will rebuild ALL indexes including the members, if developers want to target specific
|
||||
// indexes then they can adjust this logic themselves.
|
||||
() =>
|
||||
{
|
||||
ExamineComponent.RebuildIndexes(indexRebuilder, logger, false, 5000);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
composition.SetDatabaseServerMessengerOptions(GetDefaultOptions);
|
||||
composition.SetServerMessenger<BatchedDatabaseServerMessenger>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +118,7 @@ namespace Umbraco.Web.Compose
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
{
|
||||
//We will start the whole process when a successful request is made
|
||||
if (_registrar != null || _messenger != null)
|
||||
UmbracoModule.RouteAttempt += RegisterBackgroundTasksOnce;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Composing.CompositionExtensions
|
||||
{
|
||||
@@ -12,37 +9,28 @@ namespace Umbraco.Web.Composing.CompositionExtensions
|
||||
{
|
||||
public static Composition ComposeWebMappingProfiles(this Composition composition)
|
||||
{
|
||||
//register the profiles
|
||||
composition.Register<Profile, AuditMapperProfile>();
|
||||
composition.Register<Profile, CodeFileMapperProfile>();
|
||||
composition.Register<Profile, ContentMapperProfile>();
|
||||
composition.Register<Profile, ContentPropertyMapperProfile>();
|
||||
composition.Register<Profile, ContentTypeMapperProfile>();
|
||||
composition.Register<Profile, DataTypeMapperProfile>();
|
||||
composition.Register<Profile, EntityMapperProfile>();
|
||||
composition.Register<Profile, DictionaryMapperProfile>();
|
||||
composition.Register<Profile, MacroMapperProfile>();
|
||||
composition.Register<Profile, MediaMapperProfile>();
|
||||
composition.Register<Profile, MemberMapperProfile>();
|
||||
composition.Register<Profile, RedirectUrlMapperProfile>();
|
||||
composition.Register<Profile, RelationMapperProfile>();
|
||||
composition.Register<Profile, SectionMapperProfile>();
|
||||
composition.Register<Profile, TagMapperProfile>();
|
||||
composition.Register<Profile, TemplateMapperProfile>();
|
||||
composition.Register<Profile, UserMapperProfile>();
|
||||
composition.Register<Profile, LanguageMapperProfile>();
|
||||
composition.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<AuditMapDefinition>()
|
||||
.Add<CodeFileMapDefinition>()
|
||||
.Add<ContentMapDefinition>()
|
||||
.Add<ContentPropertyMapDefinition>()
|
||||
.Add<ContentTypeMapDefinition>()
|
||||
.Add<DataTypeMapDefinition>()
|
||||
.Add<EntityMapDefinition>()
|
||||
.Add<DictionaryMapDefinition>()
|
||||
.Add<MacroMapDefinition>()
|
||||
.Add<MediaMapDefinition>()
|
||||
.Add<MemberMapDefinition>()
|
||||
.Add<RedirectUrlMapDefinition>()
|
||||
.Add<RelationMapDefinition>()
|
||||
.Add<SectionMapDefinition>()
|
||||
.Add<TagMapDefinition>()
|
||||
.Add<TemplateMapDefinition>()
|
||||
.Add<UserMapDefinition>()
|
||||
.Add<LanguageMapDefinition>();
|
||||
|
||||
//register any resolvers, etc.. that the profiles use
|
||||
composition.Register<ContentUrlResolver>();
|
||||
composition.Register<ContentTreeNodeUrlResolver<IContent, ContentTreeController>>();
|
||||
composition.Register<TabsAndPropertiesResolver<IContent, ContentVariantDisplay>>();
|
||||
composition.Register<TabsAndPropertiesResolver<IMedia, MediaItemDisplay>>();
|
||||
composition.Register<ContentTreeNodeUrlResolver<IMedia, MediaTreeController>>();
|
||||
composition.Register<MemberTabsAndPropertiesResolver>();
|
||||
composition.Register<MemberTreeNodeUrlResolver>();
|
||||
composition.Register<MemberBasicPropertiesResolver>();
|
||||
composition.Register<MediaAppResolver>();
|
||||
composition.Register<ContentAppResolver>();
|
||||
composition.Register<CommonMapper>();
|
||||
composition.Register<MemberTabsAndPropertiesMapper>();
|
||||
|
||||
return composition;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.PackageActions;
|
||||
using Umbraco.Core.Packaging;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
@@ -164,6 +165,8 @@ namespace Umbraco.Web.Composing
|
||||
|
||||
// proxy Core for convenience
|
||||
|
||||
public static UmbracoMapper Mapper => CoreCurrent.Mapper;
|
||||
|
||||
public static IRuntimeState RuntimeState => CoreCurrent.RuntimeState;
|
||||
|
||||
public static TypeLoader TypeLoader => CoreCurrent.TypeLoader;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Umbraco.Web.ContentApps
|
||||
Weight = Weight
|
||||
});
|
||||
|
||||
case IMedia media when !media.ContentType.IsContainer && media.ContentType.Alias != Core.Constants.Conventions.MediaTypes.Folder:
|
||||
case IMedia media when !media.ContentType.IsContainer || media.Properties.Count > 0:
|
||||
return _mediaApp ?? (_mediaApp = new ContentApp
|
||||
{
|
||||
Alias = "umbContent",
|
||||
|
||||
@@ -8,14 +8,12 @@ using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Mvc;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Microsoft.AspNet.Identity.Owin;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
@@ -45,10 +45,6 @@ namespace Umbraco.Web.Editors
|
||||
private BackOfficeUserManager<BackOfficeIdentityUser> _userManager;
|
||||
private BackOfficeSignInManager _signInManager;
|
||||
|
||||
private const string TokenExternalSignInError = "ExternalSignInError";
|
||||
private const string TokenPasswordResetCode = "PasswordResetCode";
|
||||
private static readonly string[] TempDataTokenNames = { TokenExternalSignInError, TokenPasswordResetCode };
|
||||
|
||||
public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
|
||||
: base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger, umbracoHelper)
|
||||
{
|
||||
@@ -294,13 +290,13 @@ namespace Umbraco.Web.Editors
|
||||
if (result)
|
||||
{
|
||||
//Add a flag and redirect for it to be displayed
|
||||
TempData[TokenPasswordResetCode] = new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode };
|
||||
TempData[ViewDataExtensions.TokenPasswordResetCode] = new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode };
|
||||
return RedirectToLocal(Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
}
|
||||
|
||||
//Add error and redirect for it to be displayed
|
||||
TempData[TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") };
|
||||
TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") };
|
||||
return RedirectToLocal(Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
|
||||
@@ -314,7 +310,7 @@ namespace Umbraco.Web.Editors
|
||||
if (loginInfo == null)
|
||||
{
|
||||
//Add error and redirect for it to be displayed
|
||||
TempData[TokenExternalSignInError] = new[] { "An error occurred, could not get external login info" };
|
||||
TempData[ViewDataExtensions.TokenExternalSignInError] = new[] { "An error occurred, could not get external login info" };
|
||||
return RedirectToLocal(Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
|
||||
@@ -325,7 +321,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
//Add errors and redirect for it to be displayed
|
||||
TempData[TokenExternalSignInError] = result.Errors;
|
||||
TempData[ViewDataExtensions.TokenExternalSignInError] = result.Errors;
|
||||
return RedirectToLocal(Url.Action("Default", "BackOffice"));
|
||||
}
|
||||
|
||||
@@ -341,17 +337,12 @@ namespace Umbraco.Web.Editors
|
||||
if (defaultResponse == null) throw new ArgumentNullException("defaultResponse");
|
||||
if (externalSignInResponse == null) throw new ArgumentNullException("externalSignInResponse");
|
||||
|
||||
ViewBag.UmbracoPath = GlobalSettings.GetUmbracoMvcArea();
|
||||
ViewData.SetUmbracoPath(GlobalSettings.GetUmbracoMvcArea());
|
||||
|
||||
//check if there is the TempData with the any token name specified, if so, assign to view bag and render the view
|
||||
foreach (var tempDataTokenName in TempDataTokenNames)
|
||||
{
|
||||
if (TempData[tempDataTokenName] != null)
|
||||
{
|
||||
ViewData[tempDataTokenName] = TempData[tempDataTokenName];
|
||||
return defaultResponse();
|
||||
}
|
||||
}
|
||||
if (ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
|
||||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenPasswordResetCode))
|
||||
return defaultResponse();
|
||||
|
||||
//First check if there's external login info, if there's not proceed as normal
|
||||
var loginInfo = await OwinContext.Authentication.GetExternalLoginInfoAsync(
|
||||
@@ -416,7 +407,7 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
if (await AutoLinkAndSignInExternalAccount(loginInfo, autoLinkOptions) == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to an account" };
|
||||
ViewData.SetExternalSignInError(new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not been linked to an account" });
|
||||
}
|
||||
|
||||
//Remove the cookie otherwise this message will keep appearing
|
||||
@@ -440,7 +431,7 @@ namespace Umbraco.Web.Editors
|
||||
//we are allowing auto-linking/creating of local accounts
|
||||
if (loginInfo.Email.IsNullOrWhiteSpace())
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." };
|
||||
ViewData.SetExternalSignInError(new[] { "The requested provider (" + loginInfo.Login.LoginProvider + ") has not provided an email address, the account cannot be linked." });
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -448,7 +439,7 @@ namespace Umbraco.Web.Editors
|
||||
var foundByEmail = Services.UserService.GetByEmail(loginInfo.Email);
|
||||
if (foundByEmail != null)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider };
|
||||
ViewData.SetExternalSignInError(new[] { "A user with this email address already exists locally. You will need to login locally to Umbraco and link this external provider: " + loginInfo.Login.LoginProvider });
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -477,21 +468,21 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
if (userCreationResult.Succeeded == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = userCreationResult.Errors;
|
||||
ViewData.SetExternalSignInError(userCreationResult.Errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
var linkResult = await UserManager.AddLoginAsync(autoLinkUser.Id, loginInfo.Login);
|
||||
if (linkResult.Succeeded == false)
|
||||
{
|
||||
ViewData[TokenExternalSignInError] = linkResult.Errors;
|
||||
ViewData.SetExternalSignInError(linkResult.Errors);
|
||||
|
||||
//If this fails, we should really delete the user since it will be in an inconsistent state!
|
||||
var deleteResult = await UserManager.DeleteAsync(autoLinkUser);
|
||||
if (deleteResult.Succeeded == false)
|
||||
{
|
||||
//DOH! ... this isn't good, combine all errors to be shown
|
||||
ViewData[TokenExternalSignInError] = linkResult.Errors.Concat(deleteResult.Errors);
|
||||
ViewData.SetExternalSignInError(linkResult.Errors.Concat(deleteResult.Errors));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Linq;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -53,14 +52,14 @@ namespace Umbraco.Web.Editors.Binders
|
||||
if (variant.Culture.IsNullOrWhiteSpace())
|
||||
{
|
||||
//map the property dto collection (no culture is passed to the mapping context so it will be invariant)
|
||||
variant.PropertyCollectionDto = Mapper.Map<ContentPropertyCollectionDto>(model.PersistedContent);
|
||||
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(model.PersistedContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
//map the property dto collection with the culture of the current variant
|
||||
variant.PropertyCollectionDto = Mapper.Map<ContentPropertyCollectionDto>(
|
||||
variant.PropertyCollectionDto = Current.Mapper.Map<ContentPropertyCollectionDto>(
|
||||
model.PersistedContent,
|
||||
options => options.SetCulture(variant.Culture));
|
||||
context => context.SetCulture(variant.Culture));
|
||||
}
|
||||
|
||||
//now map all of the saved values to the dto
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
using System;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Editors.Binders
|
||||
{
|
||||
@@ -46,7 +42,7 @@ namespace Umbraco.Web.Editors.Binders
|
||||
//create the dto from the persisted model
|
||||
if (model.PersistedContent != null)
|
||||
{
|
||||
model.PropertyCollectionDto = Mapper.Map<IMedia, ContentPropertyCollectionDto>(model.PersistedContent);
|
||||
model.PropertyCollectionDto = Current.Mapper.Map<IMedia, ContentPropertyCollectionDto>(model.PersistedContent);
|
||||
//now map all of the saved values to the dto
|
||||
_modelBinderHelper.MapPropertyValuesFromSaved(model, model.PropertyCollectionDto);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
@@ -50,7 +49,7 @@ namespace Umbraco.Web.Editors.Binders
|
||||
//create the dto from the persisted model
|
||||
if (model.PersistedContent != null)
|
||||
{
|
||||
model.PropertyCollectionDto = Mapper.Map<IMember, ContentPropertyCollectionDto>(model.PersistedContent);
|
||||
model.PropertyCollectionDto = Current.Mapper.Map<IMember, ContentPropertyCollectionDto>(model.PersistedContent);
|
||||
//now map all of the saved values to the dto
|
||||
_modelBinderHelper.MapPropertyValuesFromSaved(model, model.PropertyCollectionDto);
|
||||
}
|
||||
@@ -106,7 +105,7 @@ namespace Umbraco.Web.Editors.Binders
|
||||
//}
|
||||
//member.Key = convertResult.Result;
|
||||
|
||||
var member = Mapper.Map<MembershipUser, IMember>(membershipUser);
|
||||
var member = Current.Mapper.Map<MembershipUser, IMember>(membershipUser);
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -94,7 +93,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
// if the parentId is root (-1) then we just need an empty string as we are
|
||||
// creating the path below and we don't want -1 in the path
|
||||
if (parentId == Core.Constants.System.Root.ToInvariantString())
|
||||
if (parentId == Core.Constants.System.RootString)
|
||||
{
|
||||
parentId = string.Empty;
|
||||
}
|
||||
@@ -276,7 +275,7 @@ namespace Umbraco.Web.Editors
|
||||
// Make sure that the root virtual path ends with '/'
|
||||
codeFileDisplay.VirtualPath = codeFileDisplay.VirtualPath.EnsureEndsWith("/");
|
||||
|
||||
if (id != Core.Constants.System.Root.ToInvariantString())
|
||||
if (id != Core.Constants.System.RootString)
|
||||
{
|
||||
codeFileDisplay.VirtualPath += id.TrimStart("/").EnsureEndsWith("/");
|
||||
//if it's not new then it will have a path, otherwise it won't
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -192,7 +191,7 @@ namespace Umbraco.Web.Editors
|
||||
//get all user groups and map their default permissions to the AssignedUserGroupPermissions model.
|
||||
//we do this because not all groups will have true assigned permissions for this node so if they don't have assigned permissions, we need to show the defaults.
|
||||
|
||||
var defaultPermissionsByGroup = Mapper.Map<IEnumerable<AssignedUserGroupPermissions>>(allUserGroups).ToArray();
|
||||
var defaultPermissionsByGroup = Mapper.MapEnumerable<IUserGroup, AssignedUserGroupPermissions>(allUserGroups);
|
||||
|
||||
var defaultPermissionsAsDictionary = defaultPermissionsByGroup
|
||||
.ToDictionary(x => Convert.ToInt32(x.Id), x => x);
|
||||
@@ -360,7 +359,7 @@ namespace Umbraco.Web.Editors
|
||||
// translate the content type name if applicable
|
||||
mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName);
|
||||
// if your user type doesn't have access to the Settings section it would not get this property mapped
|
||||
if(mapped.DocumentType != null)
|
||||
if (mapped.DocumentType != null)
|
||||
mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name);
|
||||
|
||||
//remove the listview app if it exists
|
||||
@@ -506,14 +505,14 @@ namespace Umbraco.Web.Editors
|
||||
var pagedResult = new PagedResult<ContentItemBasic<ContentPropertyBasic>>(totalChildren, pageNumber, pageSize);
|
||||
pagedResult.Items = children.Select(content =>
|
||||
Mapper.Map<IContent, ContentItemBasic<ContentPropertyBasic>>(content,
|
||||
opts =>
|
||||
context =>
|
||||
{
|
||||
|
||||
opts.SetCulture(cultureName);
|
||||
context.SetCulture(cultureName);
|
||||
|
||||
// if there's a list of property aliases to map - we will make sure to store this in the mapping context.
|
||||
if (!includeProperties.IsNullOrWhiteSpace())
|
||||
opts.SetIncludedProperties(includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries));
|
||||
context.SetIncludedProperties(includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries));
|
||||
}))
|
||||
.ToList(); // evaluate now
|
||||
|
||||
@@ -617,56 +616,19 @@ namespace Umbraco.Web.Editors
|
||||
// * Permissions are valid
|
||||
MapValuesForPersistence(contentItem);
|
||||
|
||||
//This a custom check for any variants not being flagged for Saving since we'll need to manually
|
||||
//remove the ModelState validation for the Name.
|
||||
//We are also tracking which cultures have an invalid Name
|
||||
var variantCount = 0;
|
||||
var variantNameErrors = new List<string>();
|
||||
foreach (var variant in contentItem.Variants)
|
||||
{
|
||||
var msKey = $"Variants[{variantCount}].Name";
|
||||
if (ModelState.ContainsKey(msKey))
|
||||
{
|
||||
if (!variant.Save || IsCreatingAction(contentItem.Action))
|
||||
ModelState.Remove(msKey);
|
||||
else
|
||||
variantNameErrors.Add(variant.Culture);
|
||||
}
|
||||
variantCount++;
|
||||
}
|
||||
var passesCriticalValidationRules = ValidateCriticalData(contentItem, out var variantCount);
|
||||
|
||||
//We need to manually check the validation results here because:
|
||||
// * We still need to save the entity even if there are validation value errors
|
||||
// * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null)
|
||||
// then we cannot continue saving, we can only display errors
|
||||
// * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display
|
||||
// a message indicating this
|
||||
if (ModelState.IsValid == false)
|
||||
//we will continue to save if model state is invalid, however we cannot save if critical data is missing.
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
//another special case, if there's more than 1 variant, then we need to add the culture specific error
|
||||
//messages based on the variants in error so that the messages show in the publish/save dialog
|
||||
if (variantCount > 1)
|
||||
//check for critical data validation issues, we can't continue saving if this data is invalid
|
||||
if (!passesCriticalValidationRules)
|
||||
{
|
||||
foreach (var c in variantNameErrors)
|
||||
{
|
||||
AddCultureValidationError(c, "speechBubbles/contentCultureValidationError");
|
||||
}
|
||||
}
|
||||
|
||||
if (IsCreatingAction(contentItem.Action))
|
||||
{
|
||||
if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem)
|
||||
|| contentItem.Variants
|
||||
.Where(x => x.Save)
|
||||
.Select(RequiredForPersistenceAttribute.HasRequiredValuesForPersistence)
|
||||
.Any(x => x == false))
|
||||
{
|
||||
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
|
||||
// add the model state to the outgoing object and throw a validation message
|
||||
var forDisplay = MapToDisplay(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
}
|
||||
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
|
||||
// add the model state to the outgoing object and throw a validation message
|
||||
var forDisplay = MapToDisplay(contentItem.PersistedContent);
|
||||
forDisplay.Errors = ModelState.ToErrorDictionary();
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
|
||||
}
|
||||
|
||||
//if there's only one variant and the model state is not valid we cannot publish so change it to save
|
||||
@@ -690,7 +652,6 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool wasCancelled;
|
||||
@@ -703,11 +664,19 @@ namespace Umbraco.Web.Editors
|
||||
[string.Empty] = globalNotifications
|
||||
};
|
||||
|
||||
//The default validation language will be either: The default languauge, else if the content is brand new and the default culture is
|
||||
// not marked to be saved, it will be the first culture in the list marked for saving.
|
||||
var defaultCulture = _allLangs.Value.Values.FirstOrDefault(x => x.IsDefault)?.CultureName;
|
||||
var cultureForInvariantErrors = CultureImpact.GetCultureForInvariantErrors(
|
||||
contentItem.PersistedContent,
|
||||
contentItem.Variants.Where(x => x.Save).Select(x => x.Culture).ToArray(),
|
||||
defaultCulture);
|
||||
|
||||
switch (contentItem.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
case ContentSaveAction.SaveNew:
|
||||
SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentSavedText", "editVariantSavedText", out wasCancelled);
|
||||
SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentSavedText", "editVariantSavedText", cultureForInvariantErrors, out wasCancelled);
|
||||
break;
|
||||
case ContentSaveAction.Schedule:
|
||||
case ContentSaveAction.ScheduleNew:
|
||||
@@ -717,7 +686,7 @@ namespace Umbraco.Web.Editors
|
||||
wasCancelled = false;
|
||||
break;
|
||||
}
|
||||
SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentScheduledSavedText", "editVariantSavedText", out wasCancelled);
|
||||
SaveAndNotify(contentItem, saveMethod, variantCount, notifications, globalNotifications, "editContentScheduledSavedText", "editVariantSavedText", cultureForInvariantErrors, out wasCancelled);
|
||||
break;
|
||||
|
||||
case ContentSaveAction.SendPublish:
|
||||
@@ -728,7 +697,7 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
if (variantCount > 1)
|
||||
{
|
||||
var cultureErrors = ModelState.GetCulturesWithPropertyErrors();
|
||||
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors);
|
||||
foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray())
|
||||
{
|
||||
AddSuccessNotification(notifications, c,
|
||||
@@ -747,7 +716,7 @@ namespace Umbraco.Web.Editors
|
||||
case ContentSaveAction.Publish:
|
||||
case ContentSaveAction.PublishNew:
|
||||
{
|
||||
var publishStatus = PublishInternal(contentItem, out wasCancelled, out var successfulCultures);
|
||||
var publishStatus = PublishInternal(contentItem, defaultCulture, cultureForInvariantErrors, out wasCancelled, out var successfulCultures);
|
||||
//global notifications
|
||||
AddMessageForPublishStatus(new[] { publishStatus }, globalNotifications, successfulCultures);
|
||||
//variant specific notifications
|
||||
@@ -767,7 +736,7 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
}
|
||||
|
||||
var publishStatus = PublishBranchInternal(contentItem, false, out wasCancelled, out var successfulCultures);
|
||||
var publishStatus = PublishBranchInternal(contentItem, false, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList();
|
||||
|
||||
//global notifications
|
||||
AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures);
|
||||
@@ -788,7 +757,7 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
}
|
||||
|
||||
var publishStatus = PublishBranchInternal(contentItem, true, out wasCancelled, out var successfulCultures);
|
||||
var publishStatus = PublishBranchInternal(contentItem, true, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList();
|
||||
|
||||
//global notifications
|
||||
AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures);
|
||||
@@ -812,8 +781,8 @@ namespace Umbraco.Web.Editors
|
||||
v.Notifications.AddRange(n.Notifications);
|
||||
}
|
||||
|
||||
//lastly, if it is not valid, add the model state to the outgoing object and throw a 403
|
||||
HandleInvalidModelState(display);
|
||||
//lastly, if it is not valid, add the model state to the outgoing object and throw a 400
|
||||
HandleInvalidModelState(display, cultureForInvariantErrors);
|
||||
|
||||
if (wasCancelled)
|
||||
{
|
||||
@@ -832,6 +801,72 @@ namespace Umbraco.Web.Editors
|
||||
return display;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates critical data for persistence and updates the ModelState and result accordingly
|
||||
/// </summary>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="variantCount">Returns the total number of variants (will be one if it's an invariant content item)</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// For invariant, the variants collection count will be 1 and this will check if that invariant item has the critical values for persistence (i.e. Name)
|
||||
///
|
||||
/// For variant, each variant will be checked for critical data for persistence and if it's not there then it's flags will be reset and it will not
|
||||
/// be persisted. However, we also need to deal with the case where all variants don't pass this check and then there is nothing to save. This also deals
|
||||
/// with removing the Name validation keys based on data annotations validation for items that haven't been marked to be saved.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// returns false if persistence cannot take place, returns true if persistence can take place even if there are validation errors
|
||||
/// </returns>
|
||||
private bool ValidateCriticalData(ContentItemSave contentItem, out int variantCount)
|
||||
{
|
||||
var variants = contentItem.Variants.ToList();
|
||||
variantCount = variants.Count;
|
||||
var savedCount = 0;
|
||||
var variantCriticalValidationErrors = new List<string>();
|
||||
for (var i = 0; i < variants.Count; i++)
|
||||
{
|
||||
var variant = variants[i];
|
||||
if (variant.Save)
|
||||
{
|
||||
//ensure the variant has all critical required data to be persisted
|
||||
if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(variant))
|
||||
{
|
||||
variantCriticalValidationErrors.Add(variant.Culture);
|
||||
//if there's no Name, it cannot be persisted at all reset the flags, this cannot be saved or published
|
||||
variant.Save = variant.Publish = false;
|
||||
|
||||
//if there's more than 1 variant, then we need to add the culture specific error
|
||||
//messages based on the variants in error so that the messages show in the publish/save dialog
|
||||
if (variants.Count > 1)
|
||||
AddCultureValidationError(variant.Culture, "publish/contentPublishedFailedByMissingName");
|
||||
else
|
||||
return false; //It's invariant and is missing critical data, it cannot be saved
|
||||
}
|
||||
|
||||
savedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var msKey = $"Variants[{i}].Name";
|
||||
if (ModelState.ContainsKey(msKey))
|
||||
{
|
||||
//if it's not being saved, remove the validation key
|
||||
if (!variant.Save) ModelState.Remove(msKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (savedCount == variantCriticalValidationErrors.Count)
|
||||
{
|
||||
//in this case there can be nothing saved since all variants marked to be saved haven't passed critical validation rules
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to perform the saving of the content and add the notifications to the result
|
||||
/// </summary>
|
||||
@@ -848,7 +883,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </remarks>
|
||||
private void SaveAndNotify(ContentItemSave contentItem, Func<IContent, OperationResult> saveMethod, int variantCount,
|
||||
Dictionary<string, SimpleNotificationModel> notifications, SimpleNotificationModel globalNotifications,
|
||||
string invariantSavedLocalizationKey, string variantSavedLocalizationKey,
|
||||
string invariantSavedLocalizationKey, string variantSavedLocalizationKey, string cultureForInvariantErrors,
|
||||
out bool wasCancelled)
|
||||
{
|
||||
var saveResult = saveMethod(contentItem.PersistedContent);
|
||||
@@ -857,7 +892,7 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
if (variantCount > 1)
|
||||
{
|
||||
var cultureErrors = ModelState.GetCulturesWithPropertyErrors();
|
||||
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors);
|
||||
foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray())
|
||||
{
|
||||
AddSuccessNotification(notifications, c,
|
||||
@@ -1099,7 +1134,7 @@ namespace Umbraco.Web.Editors
|
||||
return denied.Count == 0;
|
||||
}
|
||||
|
||||
private IEnumerable<PublishResult> PublishBranchInternal(ContentItemSave contentItem, bool force,
|
||||
private IEnumerable<PublishResult> PublishBranchInternal(ContentItemSave contentItem, bool force, string cultureForInvariantErrors,
|
||||
out bool wasCancelled, out string[] successfulCultures)
|
||||
{
|
||||
if (!contentItem.PersistedContent.ContentType.VariesByCulture())
|
||||
@@ -1117,16 +1152,19 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var mandatoryCultures = _allLangs.Value.Values.Where(x => x.IsMandatory).Select(x => x.IsoCode).ToList();
|
||||
|
||||
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors);
|
||||
|
||||
//validate if we can publish based on the mandatory language requirements
|
||||
var canPublish = ValidatePublishingMandatoryLanguages(
|
||||
contentItem, cultureVariants, mandatoryCultures, "speechBubbles/contentReqCulturePublishError",
|
||||
mandatoryVariant => mandatoryVariant.Publish, out var _);
|
||||
cultureErrors,
|
||||
contentItem, cultureVariants, mandatoryCultures,
|
||||
mandatoryVariant => mandatoryVariant.Publish);
|
||||
|
||||
//Now check if there are validation errors on each variant.
|
||||
//If validation errors are detected on a variant and it's state is set to 'publish', then we
|
||||
//need to change it to 'save'.
|
||||
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
|
||||
var cultureErrors = ModelState.GetCulturesWithPropertyErrors();
|
||||
|
||||
foreach (var variant in contentItem.Variants)
|
||||
{
|
||||
if (cultureErrors.Contains(variant.Culture))
|
||||
@@ -1170,7 +1208,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <remarks>
|
||||
/// If this is a culture variant than we need to do some validation, if it's not we'll publish as normal
|
||||
/// </remarks>
|
||||
private PublishResult PublishInternal(ContentItemSave contentItem, out bool wasCancelled, out string[] successfulCultures)
|
||||
private PublishResult PublishInternal(ContentItemSave contentItem, string defaultCulture, string cultureForInvariantErrors, out bool wasCancelled, out string[] successfulCultures)
|
||||
{
|
||||
if (!contentItem.PersistedContent.ContentType.VariesByCulture())
|
||||
{
|
||||
@@ -1186,16 +1224,21 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var mandatoryCultures = _allLangs.Value.Values.Where(x => x.IsMandatory).Select(x => x.IsoCode).ToList();
|
||||
|
||||
//validate if we can publish based on the mandatory language requirements
|
||||
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors);
|
||||
|
||||
//validate if we can publish based on the mandatory languages selected
|
||||
var canPublish = ValidatePublishingMandatoryLanguages(
|
||||
contentItem, cultureVariants, mandatoryCultures, "speechBubbles/contentReqCulturePublishError",
|
||||
mandatoryVariant => mandatoryVariant.Publish, out var _);
|
||||
cultureErrors,
|
||||
contentItem, cultureVariants, mandatoryCultures,
|
||||
mandatoryVariant => mandatoryVariant.Publish);
|
||||
|
||||
//if none are published and there are validation errors for mandatory cultures, then we can't publish anything
|
||||
|
||||
|
||||
//Now check if there are validation errors on each variant.
|
||||
//If validation errors are detected on a variant and it's state is set to 'publish', then we
|
||||
//need to change it to 'save'.
|
||||
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
|
||||
var cultureErrors = ModelState.GetCulturesWithPropertyErrors();
|
||||
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
|
||||
foreach (var variant in contentItem.Variants)
|
||||
{
|
||||
if (cultureErrors.Contains(variant.Culture))
|
||||
@@ -1210,7 +1253,7 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
//try to publish all the values on the model - this will generally only fail if someone is tampering with the request
|
||||
//since there's no reason variant rules would be violated in normal cases.
|
||||
canPublish = PublishCulture(contentItem.PersistedContent, cultureVariants);
|
||||
canPublish = PublishCulture(contentItem.PersistedContent, cultureVariants, defaultCulture);
|
||||
}
|
||||
|
||||
if (canPublish)
|
||||
@@ -1235,23 +1278,21 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// Validate if publishing is possible based on the mandatory language requirements
|
||||
/// </summary>
|
||||
/// <param name="culturesWithValidationErrors"></param>
|
||||
/// <param name="contentItem"></param>
|
||||
/// <param name="cultureVariants"></param>
|
||||
/// <param name="mandatoryCultures"></param>
|
||||
/// <param name="localizationKey"></param>
|
||||
/// <param name="publishingCheck"></param>
|
||||
/// <param name="mandatoryVariants"></param>
|
||||
/// <returns></returns>
|
||||
private bool ValidatePublishingMandatoryLanguages(
|
||||
IReadOnlyCollection<string> culturesWithValidationErrors,
|
||||
ContentItemSave contentItem,
|
||||
IReadOnlyCollection<ContentVariantSave> cultureVariants,
|
||||
IReadOnlyList<string> mandatoryCultures,
|
||||
string localizationKey,
|
||||
Func<ContentVariantSave, bool> publishingCheck,
|
||||
out IReadOnlyList<(ContentVariantSave mandatoryVariant, bool isPublished)> mandatoryVariants)
|
||||
Func<ContentVariantSave, bool> publishingCheck)
|
||||
{
|
||||
var canPublish = true;
|
||||
var result = new List<(ContentVariantSave, bool)>();
|
||||
var result = new List<(ContentVariantSave model, bool publishing, bool isValid)>();
|
||||
|
||||
foreach (var culture in mandatoryCultures)
|
||||
{
|
||||
@@ -1260,18 +1301,39 @@ namespace Umbraco.Web.Editors
|
||||
var mandatoryVariant = cultureVariants.First(x => x.Culture.InvariantEquals(culture));
|
||||
|
||||
var isPublished = contentItem.PersistedContent.Published && contentItem.PersistedContent.IsCulturePublished(culture);
|
||||
result.Add((mandatoryVariant, isPublished));
|
||||
|
||||
var isPublishing = isPublished || publishingCheck(mandatoryVariant);
|
||||
var isValid = !culturesWithValidationErrors.InvariantContains(culture);
|
||||
|
||||
if (isPublished || isPublishing) continue;
|
||||
|
||||
//cannot continue publishing since a required language that is not currently being published isn't published
|
||||
AddCultureValidationError(culture, localizationKey);
|
||||
canPublish = false;
|
||||
result.Add((mandatoryVariant, isPublished || isPublishing, isValid));
|
||||
}
|
||||
|
||||
//iterate over the results by invalid first
|
||||
string firstInvalidMandatoryCulture = null;
|
||||
foreach (var r in result.OrderBy(x => x.isValid))
|
||||
{
|
||||
if (!r.isValid)
|
||||
firstInvalidMandatoryCulture = r.model.Culture;
|
||||
|
||||
if (r.publishing && !r.isValid)
|
||||
{
|
||||
//flagged for publishing but the mandatory culture is invalid
|
||||
AddCultureValidationError(r.model.Culture, "publish/contentPublishedFailedReqCultureValidationError");
|
||||
canPublish = false;
|
||||
}
|
||||
else if (r.publishing && r.isValid && firstInvalidMandatoryCulture != null)
|
||||
{
|
||||
//in this case this culture also cannot be published because another mandatory culture is invalid
|
||||
AddCultureValidationError(r.model.Culture, "publish/contentPublishedFailedReqCultureValidationError", firstInvalidMandatoryCulture);
|
||||
canPublish = false;
|
||||
}
|
||||
else if (!r.publishing)
|
||||
{
|
||||
//cannot continue publishing since a required culture that is not currently being published isn't published
|
||||
AddCultureValidationError(r.model.Culture, "speechBubbles/contentReqCulturePublishError");
|
||||
canPublish = false;
|
||||
}
|
||||
}
|
||||
|
||||
mandatoryVariants = result;
|
||||
return canPublish;
|
||||
}
|
||||
|
||||
@@ -1284,12 +1346,12 @@ namespace Umbraco.Web.Editors
|
||||
/// <remarks>
|
||||
/// This would generally never fail unless someone is tampering with the request
|
||||
/// </remarks>
|
||||
private bool PublishCulture(IContent persistentContent, IEnumerable<ContentVariantSave> cultureVariants)
|
||||
private bool PublishCulture(IContent persistentContent, IEnumerable<ContentVariantSave> cultureVariants, string defaultCulture)
|
||||
{
|
||||
foreach (var variant in cultureVariants.Where(x => x.Publish))
|
||||
{
|
||||
// publishing any culture, implies the invariant culture
|
||||
var valid = persistentContent.PublishCulture(variant.Culture);
|
||||
var valid = persistentContent.PublishCulture(CultureImpact.Explicit(variant.Culture, defaultCulture.InvariantEquals(variant.Culture)));
|
||||
if (!valid)
|
||||
{
|
||||
AddCultureValidationError(variant.Culture, "speechBubbles/contentCultureValidationError");
|
||||
@@ -1303,14 +1365,15 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="culture">Culture to assign the error to</param>
|
||||
/// <param name="localizationKey"></param>
|
||||
private void AddCultureValidationError(string culture, string localizationKey)
|
||||
/// <param name="cultureToken">
|
||||
/// The culture used in the localization message, null by default which means <see cref="culture"/> will be used.
|
||||
/// </param>
|
||||
private void AddCultureValidationError(string culture, string localizationKey, string cultureToken = null)
|
||||
{
|
||||
var key = "_content_variant_" + culture + "_";
|
||||
if (ModelState.ContainsKey(key)) return;
|
||||
var errMsg = Services.TextService.Localize(localizationKey, new[] { _allLangs.Value[culture].CultureName });
|
||||
ModelState.AddModelError(key, errMsg);
|
||||
var errMsg = Services.TextService.Localize(localizationKey, new[] { cultureToken == null ? _allLangs.Value[culture].CultureName : _allLangs.Value[cultureToken].CultureName });
|
||||
ModelState.AddCultureValidationError(culture, errMsg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1337,7 +1400,7 @@ namespace Umbraco.Web.Editors
|
||||
if (publishResult.Success == false)
|
||||
{
|
||||
var notificationModel = new SimpleNotificationModel();
|
||||
AddMessageForPublishStatus(new [] { publishResult }, notificationModel);
|
||||
AddMessageForPublishStatus(new[] { publishResult }, notificationModel);
|
||||
return Request.CreateValidationErrorResponse(notificationModel);
|
||||
}
|
||||
|
||||
@@ -1739,18 +1802,19 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to ensure there is culture specific errors in the result if any errors are for culture properties
|
||||
/// Ensure there is culture specific errors in the result if any errors are for culture properties
|
||||
/// and we're dealing with variant content, then call the base class HandleInvalidModelState
|
||||
/// </summary>
|
||||
/// <param name="display"></param>
|
||||
/// <remarks>
|
||||
/// This is required to wire up the validation in the save/publish dialog
|
||||
/// </remarks>
|
||||
protected override void HandleInvalidModelState(IErrorModel display)
|
||||
private void HandleInvalidModelState(ContentItemDisplay display, string cultureForInvariantErrors)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
if (!ModelState.IsValid && display.Variants.Count() > 1)
|
||||
{
|
||||
//Add any culture specific errors here
|
||||
var cultureErrors = ModelState.GetCulturesWithPropertyErrors();
|
||||
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService, cultureForInvariantErrors);
|
||||
|
||||
foreach (var cultureError in cultureErrors)
|
||||
{
|
||||
@@ -1759,8 +1823,9 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
base.HandleInvalidModelState(display);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the dto property values and names to the persisted model
|
||||
/// </summary>
|
||||
@@ -1900,12 +1965,12 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// Adds notification messages to the outbound display model for a given published status
|
||||
/// </summary>
|
||||
/// <param name="status"></param>
|
||||
/// <param name="statuses"></param>
|
||||
/// <param name="display"></param>
|
||||
/// <param name="successfulCultures">
|
||||
/// This is null when dealing with invariant content, else it's the cultures that were successfully published
|
||||
/// </param>
|
||||
private void AddMessageForPublishStatus(IEnumerable<PublishResult> statuses, INotificationModel display, string[] successfulCultures = null)
|
||||
private void AddMessageForPublishStatus(IReadOnlyCollection<PublishResult> statuses, INotificationModel display, string[] successfulCultures = null)
|
||||
{
|
||||
var totalStatusCount = statuses.Count();
|
||||
|
||||
@@ -2004,7 +2069,8 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
case PublishResultType.FailedPublishPathNotPublished:
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})"));
|
||||
//TODO: This doesn't take into account variations with the successfulCultures param
|
||||
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
|
||||
display.AddWarningNotification(
|
||||
Services.TextService.Localize("publish"),
|
||||
Services.TextService.Localize("publish/contentPublishedFailedByParent",
|
||||
@@ -2013,13 +2079,15 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
case PublishResultType.FailedPublishCancelledByEvent:
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})"));
|
||||
//TODO: This doesn't take into account variations with the successfulCultures param
|
||||
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
|
||||
AddCancelMessage(display, message: "publish/contentPublishedFailedByEvent", messageParams: new[] { names });
|
||||
}
|
||||
break;
|
||||
case PublishResultType.FailedPublishAwaitingRelease:
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})"));
|
||||
//TODO: This doesn't take into account variations with the successfulCultures param
|
||||
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
|
||||
display.AddWarningNotification(
|
||||
Services.TextService.Localize("publish"),
|
||||
Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease",
|
||||
@@ -2028,7 +2096,8 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
case PublishResultType.FailedPublishHasExpired:
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})"));
|
||||
//TODO: This doesn't take into account variations with the successfulCultures param
|
||||
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
|
||||
display.AddWarningNotification(
|
||||
Services.TextService.Localize("publish"),
|
||||
Services.TextService.Localize("publish/contentPublishedFailedExpired",
|
||||
@@ -2037,7 +2106,8 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
case PublishResultType.FailedPublishIsTrashed:
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})"));
|
||||
//TODO: This doesn't take into account variations with the successfulCultures param
|
||||
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
|
||||
display.AddWarningNotification(
|
||||
Services.TextService.Localize("publish"),
|
||||
Services.TextService.Localize("publish/contentPublishedFailedIsTrashed",
|
||||
@@ -2046,11 +2116,25 @@ namespace Umbraco.Web.Editors
|
||||
break;
|
||||
case PublishResultType.FailedPublishContentInvalid:
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})"));
|
||||
display.AddWarningNotification(
|
||||
Services.TextService.Localize("publish"),
|
||||
Services.TextService.Localize("publish/contentPublishedFailedInvalid",
|
||||
new[] { names }).Trim());
|
||||
if (successfulCultures == null)
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"'{x.Content.Name}'"));
|
||||
display.AddWarningNotification(
|
||||
Services.TextService.Localize("publish"),
|
||||
Services.TextService.Localize("publish/contentPublishedFailedInvalid",
|
||||
new[] { names }).Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var c in successfulCultures)
|
||||
{
|
||||
var names = string.Join(", ", status.Select(x => $"'{x.Content.GetCultureName(c)}'"));
|
||||
display.AddWarningNotification(
|
||||
Services.TextService.Localize("publish"),
|
||||
Services.TextService.Localize("publish/contentPublishedFailedInvalid",
|
||||
new[] { names }).Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PublishResultType.FailedPublishMandatoryCultureMissing:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using AutoMapper;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -323,6 +322,23 @@ namespace Umbraco.Web.Editors
|
||||
return display;
|
||||
}
|
||||
|
||||
public TemplateDisplay PostCreateDefaultTemplate(int id)
|
||||
{
|
||||
var contentType = Services.ContentTypeService.Get(id);
|
||||
if (contentType == null)
|
||||
{
|
||||
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound, "No content type found with id " + id));
|
||||
}
|
||||
|
||||
var template = CreateTemplateForContentType(contentType.Alias, contentType.Name);
|
||||
if (template == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not create default template for content type with id " + id);
|
||||
}
|
||||
|
||||
return Mapper.Map<TemplateDisplay>(template);
|
||||
}
|
||||
|
||||
private ITemplate CreateTemplateForContentType(string contentTypeAlias, string contentTypeName)
|
||||
{
|
||||
var template = Services.FileService.GetTemplate(contentTypeAlias);
|
||||
@@ -552,9 +568,15 @@ namespace Umbraco.Web.Editors
|
||||
var file = result.FileData[0];
|
||||
var fileName = file.Headers.ContentDisposition.FileName.Trim('\"');
|
||||
var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower();
|
||||
|
||||
// renaming the file because MultipartFormDataStreamProvider has created a random fileName instead of using the name from the
|
||||
// content-disposition for more than 6 years now. Creating a CustomMultipartDataStreamProvider deriving from MultipartFormDataStreamProvider
|
||||
// seems like a cleaner option, but I'm not sure where to put it and renaming only takes one line of code.
|
||||
System.IO.File.Move(result.FileData[0].LocalFileName, root + "\\" + fileName);
|
||||
|
||||
if (ext.InvariantEquals("udt"))
|
||||
{
|
||||
model.TempFileName = Path.Combine(root, model.TempFileName);
|
||||
model.TempFileName = Path.Combine(root, fileName);
|
||||
|
||||
model.UploadedFiles.Add(new ContentPropertyFile
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -15,7 +14,6 @@ using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
@@ -502,13 +500,13 @@ namespace Umbraco.Web.Editors
|
||||
where TPropertyType : PropertyTypeBasic
|
||||
{
|
||||
InvalidCompositionException invalidCompositionException = null;
|
||||
if (ex is AutoMapperMappingException && ex.InnerException is InvalidCompositionException)
|
||||
if (ex is InvalidCompositionException)
|
||||
{
|
||||
invalidCompositionException = (InvalidCompositionException)ex.InnerException;
|
||||
invalidCompositionException = (InvalidCompositionException)ex;
|
||||
}
|
||||
else if (ex.InnerException is InvalidCompositionException)
|
||||
{
|
||||
invalidCompositionException = (InvalidCompositionException)ex;
|
||||
invalidCompositionException = (InvalidCompositionException)ex.InnerException;
|
||||
}
|
||||
if (invalidCompositionException != null)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -15,7 +12,6 @@ using Umbraco.Web.WebApi;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace Umbraco.Web.Editors
|
||||
Alias = y.Alias,
|
||||
View = y.View
|
||||
})
|
||||
});
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Data;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -71,12 +70,12 @@ namespace Umbraco.Web.Editors
|
||||
return;
|
||||
}
|
||||
// map the model to the persisted instance
|
||||
Mapper.Map(dataType, persisted);
|
||||
Current.Mapper.Map(dataType, persisted);
|
||||
break;
|
||||
|
||||
case ContentSaveAction.SaveNew:
|
||||
// create the persisted model from mapping the saved model
|
||||
persisted = Mapper.Map<IDataType>(dataType);
|
||||
persisted = Current.Mapper.Map<IDataType>(dataType);
|
||||
((DataType) persisted).ResetIdentity();
|
||||
break;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Dashboards;
|
||||
using Umbraco.Core.Events;
|
||||
@@ -15,11 +17,15 @@ namespace Umbraco.Web.Editors
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MediaItemDisplay>> SendingMediaModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MemberDisplay>> SendingMemberModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<UserDisplay>> SendingUserModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<IEnumerable<Tab<IDashboard>>>> SendingDashboardModel;
|
||||
|
||||
private static void OnSendingDashboardModel(HttpActionExecutedContext sender, EditorModelEventArgs<IEnumerable<Tab<IDashboard>>> e)
|
||||
[Obsolete("Please Use SendingDashboardSlimModel")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<IEnumerable<Tab<IDashboard>>>> SendingDashboardModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<IEnumerable<Tab<IDashboardSlim>>>> SendingDashboardSlimModel;
|
||||
|
||||
private static void OnSendingDashboardModel(HttpActionExecutedContext sender, EditorModelEventArgs<IEnumerable<Tab<IDashboardSlim>>> e)
|
||||
{
|
||||
var handler = SendingDashboardModel;
|
||||
var handler = SendingDashboardSlimModel;
|
||||
handler?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
@@ -66,8 +72,8 @@ namespace Umbraco.Web.Editors
|
||||
if (e.Model is UserDisplay)
|
||||
OnSendingUserModel(sender, new EditorModelEventArgs<UserDisplay>(e));
|
||||
|
||||
if (e.Model is IEnumerable<IDashboard>)
|
||||
OnSendingDashboardModel(sender, new EditorModelEventArgs<IEnumerable<Tab<IDashboard>>>(e));
|
||||
if (e.Model is IEnumerable<Tab<IDashboardSlim>>)
|
||||
OnSendingDashboardModel(sender, new EditorModelEventArgs<IEnumerable<Tab<IDashboardSlim>>>(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -507,13 +506,15 @@ namespace Umbraco.Web.Editors
|
||||
var culture = ClientCulture();
|
||||
var pagedResult = new PagedResult<EntityBasic>(totalRecords, pageNumber, pageSize)
|
||||
{
|
||||
Items = entities.Select(entity => Mapper.Map<IEntitySlim, EntityBasic>(entity, options =>
|
||||
{
|
||||
options.SetCulture(culture);
|
||||
options.AfterMap((src, dest) => { dest.AdditionalData["hasChildren"] = src.HasChildren; });
|
||||
}
|
||||
)
|
||||
)
|
||||
Items = entities.Select(source =>
|
||||
{
|
||||
var target = Mapper.Map<IEntitySlim, EntityBasic>(source, context =>
|
||||
{
|
||||
context.SetCulture(culture);
|
||||
});
|
||||
target.AdditionalData["hasChildren"] = source.HasChildren;
|
||||
return target;
|
||||
})
|
||||
};
|
||||
|
||||
return pagedResult;
|
||||
@@ -916,7 +917,7 @@ namespace Umbraco.Web.Editors
|
||||
.SelectMany(x => x.PropertyTypes)
|
||||
.DistinctBy(composition => composition.Alias);
|
||||
var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter);
|
||||
return Mapper.Map<IEnumerable<PropertyType>, IEnumerable<EntityBasic>>(filteredPropertyTypes);
|
||||
return Mapper.MapEnumerable<PropertyType, EntityBasic>(filteredPropertyTypes);
|
||||
|
||||
case UmbracoEntityTypes.PropertyGroup:
|
||||
|
||||
@@ -927,13 +928,13 @@ namespace Umbraco.Web.Editors
|
||||
.SelectMany(x => x.PropertyGroups)
|
||||
.DistinctBy(composition => composition.Name);
|
||||
var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter);
|
||||
return Mapper.Map<IEnumerable<PropertyGroup>, IEnumerable<EntityBasic>>(filteredpropertyGroups);
|
||||
return Mapper.MapEnumerable<PropertyGroup, EntityBasic>(filteredpropertyGroups);
|
||||
|
||||
case UmbracoEntityTypes.User:
|
||||
|
||||
var users = Services.UserService.GetAll(0, int.MaxValue, out _);
|
||||
var filteredUsers = ExecutePostFilter(users, postFilter);
|
||||
return Mapper.Map<IEnumerable<IUser>, IEnumerable<EntityBasic>>(filteredUsers);
|
||||
return Mapper.MapEnumerable<IUser, EntityBasic>(filteredUsers);
|
||||
|
||||
case UmbracoEntityTypes.Stylesheet:
|
||||
|
||||
@@ -1052,7 +1053,7 @@ namespace Umbraco.Web.Editors
|
||||
private EntityBasic MapEntity(object entity, string culture = null)
|
||||
{
|
||||
culture = culture ?? ClientCulture();
|
||||
return Mapper.Map<EntityBasic>(entity, opts => { opts.SetCulture(culture); });
|
||||
return Mapper.Map<EntityBasic>(entity, context => { context.SetCulture(culture); });
|
||||
}
|
||||
|
||||
private string ClientCulture() => Request.ClientCulture();
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Web.Http.ModelBinding;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
@@ -15,12 +16,12 @@ namespace Umbraco.Web.Editors.Filters
|
||||
/// <summary>
|
||||
/// A base class purely used for logging without generics
|
||||
/// </summary>
|
||||
internal class ContentItemValidationHelper
|
||||
internal abstract class ContentModelValidator
|
||||
{
|
||||
protected IUmbracoContextAccessor UmbracoContextAccessor { get; }
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
public ContentItemValidationHelper(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor)
|
||||
{
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
UmbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
@@ -32,26 +33,20 @@ namespace Umbraco.Web.Editors.Filters
|
||||
/// </summary>
|
||||
/// <typeparam name="TPersisted"></typeparam>
|
||||
/// <typeparam name="TModelSave"></typeparam>
|
||||
/// <typeparam name="TModelWithProperties"></typeparam>
|
||||
/// <remarks>
|
||||
/// If any severe errors occur then the response gets set to an error and execution will not continue. Property validation
|
||||
/// errors will just be added to the ModelState.
|
||||
/// </remarks>
|
||||
internal class ContentItemValidationHelper<TPersisted, TModelSave>: ContentItemValidationHelper
|
||||
internal abstract class ContentModelValidator<TPersisted, TModelSave, TModelWithProperties>: ContentModelValidator
|
||||
where TPersisted : class, IContentBase
|
||||
where TModelSave: IContentSave<TPersisted>
|
||||
where TModelWithProperties : IContentProperties<ContentPropertyBasic>
|
||||
{
|
||||
public ContentItemValidationHelper(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
|
||||
protected ContentModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
//public void ValidateItem(HttpActionContext actionContext, TModelSave model, IContentProperties<ContentPropertyBasic> modelWithProperties, ContentPropertyCollectionDto dto)
|
||||
//{
|
||||
// //now do each validation step
|
||||
// if (ValidateExistingContent(model, actionContext) == false) return;
|
||||
// if (ValidateProperties(model, modelWithProperties, actionContext) == false) return;
|
||||
// if (ValidatePropertyData(model, modelWithProperties, dto, actionContext.ModelState) == false) return;
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the content exists
|
||||
/// </summary>
|
||||
@@ -119,13 +114,13 @@ namespace Umbraco.Web.Editors.Filters
|
||||
/// <remarks>
|
||||
/// All property data validation goes into the model state with a prefix of "Properties"
|
||||
/// </remarks>
|
||||
public virtual bool ValidatePropertyData(
|
||||
public virtual bool ValidatePropertiesData(
|
||||
TModelSave model,
|
||||
IContentProperties<ContentPropertyBasic> modelWithProperties,
|
||||
TModelWithProperties modelWithProperties,
|
||||
ContentPropertyCollectionDto dto,
|
||||
ModelStateDictionary modelState)
|
||||
{
|
||||
var properties = modelWithProperties.Properties.ToList();
|
||||
var properties = modelWithProperties.Properties.ToDictionary(x => x.Alias, x => x);
|
||||
|
||||
foreach (var p in dto.Properties)
|
||||
{
|
||||
@@ -135,35 +130,49 @@ namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
var message = $"Could not find property editor \"{p.DataType.EditorAlias}\" for property with id {p.Id}.";
|
||||
|
||||
Logger.Warn<ContentItemValidationHelper>(message);
|
||||
Logger.Warn<ContentModelValidator>(message);
|
||||
continue;
|
||||
}
|
||||
|
||||
//get the posted value for this property, this may be null in cases where the property was marked as readonly which means
|
||||
//the angular app will not post that value.
|
||||
var postedProp = properties.FirstOrDefault(x => x.Alias == p.Alias);
|
||||
if (postedProp == null) continue;
|
||||
if (!properties.TryGetValue(p.Alias, out var postedProp))
|
||||
continue;
|
||||
|
||||
var postedValue = postedProp.Value;
|
||||
|
||||
// validate
|
||||
var valueEditor = editor.GetValueEditor(p.DataType.Configuration);
|
||||
foreach (var r in valueEditor.Validate(postedValue, p.IsRequired, p.ValidationRegExp))
|
||||
{
|
||||
//this could be a thing, but it does make the errors seem very verbose
|
||||
////update the error message to include the property name and culture if available
|
||||
//r.ErrorMessage = p.Culture.IsNullOrWhiteSpace()
|
||||
// ? $"'{p.Label}' - {r.ErrorMessage}"
|
||||
// : $"'{p.Label}' ({p.Culture}) - {r.ErrorMessage}";
|
||||
|
||||
modelState.AddPropertyError(r, p.Alias, p.Culture);
|
||||
}
|
||||
|
||||
ValidatePropertyValue(model, modelWithProperties, editor, p, postedValue, modelState);
|
||||
|
||||
}
|
||||
|
||||
return modelState.IsValid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates a property's value and adds the error to model state if found
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="modelWithProperties"></param>
|
||||
/// <param name="editor"></param>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="postedValue"></param>
|
||||
/// <param name="modelState"></param>
|
||||
protected virtual void ValidatePropertyValue(
|
||||
TModelSave model,
|
||||
TModelWithProperties modelWithProperties,
|
||||
IDataEditor editor,
|
||||
ContentPropertyDto property,
|
||||
object postedValue,
|
||||
ModelStateDictionary modelState)
|
||||
{
|
||||
// validate
|
||||
var valueEditor = editor.GetValueEditor(property.DataType.Configuration);
|
||||
foreach (var r in valueEditor.Validate(postedValue, property.IsRequired, property.ValidationRegExp))
|
||||
{
|
||||
modelState.AddPropertyError(r, property.Alias, property.Culture);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
19
src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs
Normal file
19
src/Umbraco.Web/Editors/Filters/ContentSaveModelValidator.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Web.Http.ModelBinding;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Validator for <see cref="ContentItemSave"/>
|
||||
/// </summary>
|
||||
internal class ContentSaveModelValidator : ContentModelValidator<IContent, ContentItemSave, ContentVariantSave>
|
||||
{
|
||||
public ContentSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var model = (ContentItemSave)actionContext.ActionArguments["contentItem"];
|
||||
var contentItemValidator = new ContentItemValidationHelper<IContent, ContentItemSave>(_logger, _umbracoContextAccessor);
|
||||
var contentItemValidator = new ContentSaveModelValidator(_logger, _umbracoContextAccessor);
|
||||
|
||||
if (!ValidateAtLeastOneVariantIsBeingSaved(model, actionContext)) return;
|
||||
if (!contentItemValidator.ValidateExistingContent(model, actionContext)) return;
|
||||
@@ -54,7 +54,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
foreach (var variant in model.Variants.Where(x => x.Save))
|
||||
{
|
||||
if (contentItemValidator.ValidateProperties(model, variant, actionContext))
|
||||
contentItemValidator.ValidatePropertyData(model, variant, variant.PropertyCollectionDto, actionContext.ModelState);
|
||||
contentItemValidator.ValidatePropertiesData(model, variant, variant.PropertyCollectionDto, actionContext.ModelState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,14 +39,14 @@ namespace Umbraco.Web.Editors.Filters
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var model = (MediaItemSave)actionContext.ActionArguments["contentItem"];
|
||||
var contentItemValidator = new ContentItemValidationHelper<IMedia, MediaItemSave>(_logger, _umbracoContextAccessor);
|
||||
var contentItemValidator = new MediaSaveModelValidator(_logger, _umbracoContextAccessor);
|
||||
|
||||
if (ValidateUserAccess(model, actionContext))
|
||||
{
|
||||
//now do each validation step
|
||||
if (contentItemValidator.ValidateExistingContent(model, actionContext))
|
||||
if (contentItemValidator.ValidateProperties(model, model, actionContext))
|
||||
contentItemValidator.ValidatePropertyData(model, model, model.PropertyCollectionDto, actionContext.ModelState);
|
||||
contentItemValidator.ValidatePropertiesData(model, model, model.PropertyCollectionDto, actionContext.ModelState);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/Umbraco.Web/Editors/Filters/MediaSaveModelValidator.cs
Normal file
16
src/Umbraco.Web/Editors/Filters/MediaSaveModelValidator.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Validator for <see cref="MediaItemSave"/>
|
||||
/// </summary>
|
||||
internal class MediaSaveModelValidator : ContentModelValidator<IMedia, MediaItemSave, IContentProperties<ContentPropertyBasic>>
|
||||
{
|
||||
public MediaSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, umbracoContextAccessor)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,14 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors.Filters
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Custom validation helper so that we can exclude the Member.StandardPropertyTypeStubs from being validating for existence
|
||||
/// </summary>
|
||||
internal class MemberValidationHelper : ContentItemValidationHelper<IMember, MemberSave>
|
||||
internal class MemberSaveModelValidator : ContentModelValidator<IMember, MemberSave, IContentProperties<ContentPropertyBasic>>
|
||||
{
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
|
||||
public MemberValidationHelper(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMemberTypeService memberTypeService)
|
||||
public MemberSaveModelValidator(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IMemberTypeService memberTypeService)
|
||||
: base(logger, umbracoContextAccessor)
|
||||
{
|
||||
_memberTypeService = memberTypeService;
|
||||
@@ -36,7 +35,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="modelWithProperties"></param>
|
||||
/// <returns></returns>
|
||||
public override bool ValidatePropertyData(MemberSave model, IContentProperties<ContentPropertyBasic> modelWithProperties, ContentPropertyCollectionDto dto, ModelStateDictionary modelState)
|
||||
public override bool ValidatePropertiesData(MemberSave model, IContentProperties<ContentPropertyBasic> modelWithProperties, ContentPropertyCollectionDto dto, ModelStateDictionary modelState)
|
||||
{
|
||||
if (model.Username.IsNullOrWhiteSpace())
|
||||
{
|
||||
@@ -71,7 +70,7 @@ namespace Umbraco.Web.Editors.Filters
|
||||
$"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login");
|
||||
}
|
||||
|
||||
return base.ValidatePropertyData(model, modelWithProperties, dto, modelState);
|
||||
return base.ValidatePropertiesData(model, modelWithProperties, dto, modelState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,11 +30,11 @@ namespace Umbraco.Web.Editors.Filters
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
{
|
||||
var model = (MemberSave)actionContext.ActionArguments["contentItem"];
|
||||
var contentItemValidator = new MemberValidationHelper(_logger, _umbracoContextAccessor, _memberTypeService);
|
||||
var contentItemValidator = new MemberSaveModelValidator(_logger, _umbracoContextAccessor, _memberTypeService);
|
||||
//now do each validation step
|
||||
if (contentItemValidator.ValidateExistingContent(model, actionContext))
|
||||
if (contentItemValidator.ValidateProperties(model, model, actionContext))
|
||||
contentItemValidator.ValidatePropertyData(model, model, model.PropertyCollectionDto, actionContext.ModelState);
|
||||
contentItemValidator.ValidatePropertiesData(model, model, model.PropertyCollectionDto, actionContext.ModelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Http.Filters;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -27,6 +27,8 @@ namespace Umbraco.Web.Editors.Filters
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
private static UmbracoMapper Mapper => Current.Mapper;
|
||||
|
||||
private IUserService UserService => _userService ?? Current.Services.UserService; // TODO: inject
|
||||
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
|
||||
@@ -3,9 +3,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
@@ -48,7 +46,7 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
var allLanguages = Services.LocalizationService.GetAllLanguages();
|
||||
|
||||
return Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(allLanguages);
|
||||
return Mapper.MapEnumerable<ILanguage, Language>(allLanguages);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -103,6 +101,13 @@ namespace Umbraco.Web.Editors
|
||||
// this is prone to race conditions but the service will not let us proceed anyways
|
||||
var existing = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode);
|
||||
|
||||
// the localization service might return the generic language even when queried for specific ones (e.g. "da" when queried for "da-DK")
|
||||
// - we need to handle that explicitly
|
||||
if (existing?.IsoCode != language.IsoCode)
|
||||
{
|
||||
existing = null;
|
||||
}
|
||||
|
||||
if (existing != null && language.Id != existing.Id)
|
||||
{
|
||||
//someone is trying to create a language that already exist
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using AutoMapper;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -47,7 +45,7 @@ namespace Umbraco.Web.Editors
|
||||
var dateQuery = sinceDate.HasValue ? SqlContext.Query<IAuditItem>().Where(x => x.CreateDate >= sinceDate) : null;
|
||||
var userId = Security.GetUserId().ResultOr(0);
|
||||
var result = Services.AuditService.GetPagedItemsByUser(userId, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter:dateQuery);
|
||||
var mapped = Mapper.Map<IEnumerable<AuditLog>>(result);
|
||||
var mapped = Mapper.MapEnumerable<IAuditItem, AuditLog>(result);
|
||||
return new PagedResult<AuditLog>(totalRecords, pageNumber, pageSize)
|
||||
{
|
||||
Items = MapAvatarsAndNames(mapped)
|
||||
|
||||
@@ -8,10 +8,8 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Web.Http;
|
||||
using System.Web.SessionState;
|
||||
using AutoMapper;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Macros;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -49,11 +48,10 @@ namespace Umbraco.Web.Editors
|
||||
[MediaControllerControllerConfiguration]
|
||||
public class MediaController : ContentControllerBase
|
||||
{
|
||||
public MediaController(PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider)
|
||||
public MediaController(PropertyEditorCollection propertyEditors, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper)
|
||||
: base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper)
|
||||
{
|
||||
_propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors));
|
||||
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -237,7 +235,6 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
private int[] _userStartNodes;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
|
||||
|
||||
protected int[] UserStartNodes
|
||||
{
|
||||
@@ -489,16 +486,12 @@ namespace Umbraco.Web.Editors
|
||||
(save, property, v) => property.SetValue(v), //set prop val
|
||||
null); // media are all invariant
|
||||
|
||||
//We need to manually check the validation results here because:
|
||||
// * We still need to save the entity even if there are validation value errors
|
||||
// * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null)
|
||||
// then we cannot continue saving, we can only display errors
|
||||
// * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display
|
||||
// a message indicating this
|
||||
if (ModelState.IsValid == false)
|
||||
//we will continue to save if model state is invalid, however we cannot save if critical data is missing.
|
||||
//TODO: Allowing media to be saved when it is invalid is odd - media doesn't have a publish phase so suddenly invalid data is allowed to be 'live'
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem)
|
||||
&& (contentItem.Action == ContentSaveAction.SaveNew))
|
||||
//check for critical data validation issues, we can't continue saving if this data is invalid
|
||||
if (!RequiredForPersistenceAttribute.HasRequiredValuesForPersistence(contentItem))
|
||||
{
|
||||
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
|
||||
// add the model state to the outgoing object and throw validation response
|
||||
@@ -729,7 +722,7 @@ namespace Umbraco.Web.Editors
|
||||
if (fs == null) throw new InvalidOperationException("Could not acquire file stream");
|
||||
using (fs)
|
||||
{
|
||||
f.SetValue(_contentTypeBaseServiceProvider, Constants.Conventions.Media.File,fileName, fs);
|
||||
f.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File,fileName, fs);
|
||||
}
|
||||
|
||||
var saveResult = mediaService.Save(f, Security.CurrentUser.Id);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
@@ -12,7 +11,6 @@ using System.Net.Http;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Core.Services;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
|
||||
@@ -5,12 +5,9 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.ModelBinding;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -19,7 +16,6 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Xml;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -51,7 +49,7 @@ namespace Umbraco.Web.Editors
|
||||
? redirectUrlService.GetAllRedirectUrls(page, pageSize, out resultCount)
|
||||
: redirectUrlService.SearchRedirectUrls(searchTerm, page, pageSize, out resultCount);
|
||||
|
||||
searchResult.SearchResults = Mapper.Map<IEnumerable<ContentRedirectUrl>>(redirects).ToArray();
|
||||
searchResult.SearchResults = Mapper.MapEnumerable<IRedirectUrl, ContentRedirectUrl>(redirects);
|
||||
searchResult.TotalCount = resultCount;
|
||||
searchResult.CurrentPage = page;
|
||||
searchResult.PageCount = ((int)resultCount + pageSize - 1) / pageSize;
|
||||
@@ -73,9 +71,10 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
var redirectUrlService = Services.RedirectUrlService;
|
||||
var redirects = redirectUrlService.GetContentRedirectUrls(guidIdi.Guid);
|
||||
redirectsResult.SearchResults = Mapper.Map<IEnumerable<ContentRedirectUrl>>(redirects).ToArray();
|
||||
var mapped = Mapper.MapEnumerable<IRedirectUrl, ContentRedirectUrl>(redirects);
|
||||
redirectsResult.SearchResults = mapped;
|
||||
//not doing paging 'yet'
|
||||
redirectsResult.TotalCount = redirects.Count();
|
||||
redirectsResult.TotalCount = mapped.Count();
|
||||
redirectsResult.CurrentPage = 1;
|
||||
redirectsResult.PageCount = 1;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -35,11 +34,11 @@ namespace Umbraco.Web.Editors
|
||||
if (string.IsNullOrWhiteSpace(relationTypeAlias) == false)
|
||||
{
|
||||
return
|
||||
Mapper.Map<IEnumerable<IRelation>, IEnumerable<RelationDisplay>>(
|
||||
Mapper.MapEnumerable<IRelation, RelationDisplay>(
|
||||
relations.Where(x => x.RelationType.Alias.InvariantEquals(relationTypeAlias)));
|
||||
}
|
||||
|
||||
return Mapper.Map<IEnumerable<IRelation>, IEnumerable<RelationDisplay>>(relations);
|
||||
return Mapper.MapEnumerable<IRelation, RelationDisplay>(relations);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -49,7 +48,7 @@ namespace Umbraco.Web.Editors
|
||||
var relations = Services.RelationService.GetByRelationTypeId(relationType.Id);
|
||||
|
||||
var display = Mapper.Map<IRelationType, RelationTypeDisplay>(relationType);
|
||||
display.Relations = Mapper.Map<IEnumerable<IRelation>, IEnumerable<RelationDisplay>>(relations);
|
||||
display.Relations = Mapper.MapEnumerable<IRelation, RelationDisplay>(relations);
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using AutoMapper;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
@@ -103,8 +103,8 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
contents = sourceDocument == null
|
||||
? Enumerable.Empty<IPublishedContent>()
|
||||
: sourceDocument.Children(model.ContentType.Alias);
|
||||
queryExpression.AppendFormat(".Children(\"{0}\")", model.ContentType.Alias);
|
||||
: sourceDocument.ChildrenOfType(model.ContentType.Alias);
|
||||
queryExpression.AppendFormat(".ChildrenOfType(\"{0}\")", model.ContentType.Alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Filters;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -104,7 +102,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <returns></returns>
|
||||
public IEnumerable<UserGroupBasic> GetUserGroups(bool onlyCurrentUserGroups = true)
|
||||
{
|
||||
var allGroups = Mapper.Map<IEnumerable<IUserGroup>, IEnumerable<UserGroupBasic>>(Services.UserService.GetAllUserGroups())
|
||||
var allGroups = Mapper.MapEnumerable<IUserGroup, UserGroupBasic>(Services.UserService.GetAllUserGroups())
|
||||
.ToList();
|
||||
|
||||
var isAdmin = Security.CurrentUser.IsAdmin();
|
||||
|
||||
@@ -8,9 +8,7 @@ using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using System.Web.Mvc;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNet.Identity;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -24,7 +22,6 @@ using Umbraco.Core.Models.Identity;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Editors.Filters;
|
||||
@@ -32,7 +29,6 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using ActionFilterAttribute = System.Web.Http.Filters.ActionFilterAttribute;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
@@ -65,6 +61,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
[AppendUserModifiedHeader("id")]
|
||||
[FileUploadCleanupFilter(false)]
|
||||
[AdminUsersAuthorize]
|
||||
public async Task<HttpResponseMessage> PostSetAvatar(int id)
|
||||
{
|
||||
return await PostSetAvatarInternal(Request, Services.UserService, AppCaches.RuntimeCache, id);
|
||||
@@ -128,6 +125,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
[AppendUserModifiedHeader("id")]
|
||||
[AdminUsersAuthorize]
|
||||
public HttpResponseMessage PostClearAvatar(int id)
|
||||
{
|
||||
var found = Services.UserService.GetUserById(id);
|
||||
@@ -166,6 +164,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
[AdminUsersAuthorize]
|
||||
public UserDisplay GetById(int id)
|
||||
{
|
||||
var user = Services.UserService.GetUserById(id);
|
||||
@@ -239,7 +238,7 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var paged = new PagedUserResult(total, pageNumber, pageSize)
|
||||
{
|
||||
Items = Mapper.Map<IEnumerable<UserBasic>>(result),
|
||||
Items = Mapper.MapEnumerable<IUser, UserBasic>(result),
|
||||
UserStates = Services.UserService.GetUserStates()
|
||||
};
|
||||
|
||||
@@ -591,6 +590,7 @@ namespace Umbraco.Web.Editors
|
||||
/// Disables the users with the given user ids
|
||||
/// </summary>
|
||||
/// <param name="userIds"></param>
|
||||
[AdminUsersAuthorize("userIds")]
|
||||
public HttpResponseMessage PostDisableUsers([FromUri]int[] userIds)
|
||||
{
|
||||
var tryGetCurrentUserId = Security.GetUserId();
|
||||
@@ -622,6 +622,7 @@ namespace Umbraco.Web.Editors
|
||||
/// Enables the users with the given user ids
|
||||
/// </summary>
|
||||
/// <param name="userIds"></param>
|
||||
[AdminUsersAuthorize("userIds")]
|
||||
public HttpResponseMessage PostEnableUsers([FromUri]int[] userIds)
|
||||
{
|
||||
var users = Services.UserService.GetUsersById(userIds).ToArray();
|
||||
@@ -645,6 +646,7 @@ namespace Umbraco.Web.Editors
|
||||
/// Unlocks the users with the given user ids
|
||||
/// </summary>
|
||||
/// <param name="userIds"></param>
|
||||
[AdminUsersAuthorize("userIds")]
|
||||
public async Task<HttpResponseMessage> PostUnlockUsers([FromUri]int[] userIds)
|
||||
{
|
||||
if (userIds.Length <= 0)
|
||||
@@ -677,6 +679,7 @@ namespace Umbraco.Web.Editors
|
||||
Services.TextService.Localize("speechBubbles/unlockUsersSuccess", new[] { userIds.Length.ToString() }));
|
||||
}
|
||||
|
||||
[AdminUsersAuthorize("userIds")]
|
||||
public HttpResponseMessage PostSetUserGroupsOnUsers([FromUri]string[] userGroupAliases, [FromUri]int[] userIds)
|
||||
{
|
||||
var users = Services.UserService.GetUsersById(userIds).ToArray();
|
||||
@@ -702,7 +705,8 @@ namespace Umbraco.Web.Editors
|
||||
/// Limited to users that haven't logged in to avoid issues with related records constrained
|
||||
/// with a foreign key on the user Id
|
||||
/// </remarks>
|
||||
public async Task<HttpResponseMessage> PostDeleteNonLoggedInUser(int id)
|
||||
[AdminUsersAuthorize]
|
||||
public HttpResponseMessage PostDeleteNonLoggedInUser(int id)
|
||||
{
|
||||
var user = Services.UserService.GetUserById(id);
|
||||
if (user == null)
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security
|
||||
private HealthCheckStatus FixHttpsSetting()
|
||||
{
|
||||
var configFile = IOHelper.MapPath("~/Web.config");
|
||||
const string xPath = "/configuration/appSettings/add[@key='umbracoUseSSL']/@value";
|
||||
const string xPath = "/configuration/appSettings/add[@key='Umbraco.Core.UseHttps']/@value";
|
||||
var configurationService = new ConfigurationService(configFile, xPath, _textService);
|
||||
var updateConfigFile = configurationService.UpdateConfigFile("true");
|
||||
|
||||
|
||||
@@ -60,10 +60,10 @@ namespace Umbraco.Web.Install.Controllers
|
||||
}
|
||||
|
||||
// gen the install base url
|
||||
ViewBag.InstallApiBaseUrl = Url.GetUmbracoApiService("GetSetup", "InstallApi", "UmbracoInstall").TrimEnd("GetSetup");
|
||||
ViewData.SetInstallApiBaseUrl(Url.GetUmbracoApiService("GetSetup", "InstallApi", "UmbracoInstall").TrimEnd("GetSetup"));
|
||||
|
||||
// get the base umbraco folder
|
||||
ViewBag.UmbracoBaseFolder = IOHelper.ResolveUrl(SystemDirectories.Umbraco);
|
||||
ViewData.SetUmbracoBaseFolder(IOHelper.ResolveUrl(SystemDirectories.Umbraco));
|
||||
|
||||
_installHelper.InstallStatus(false, "");
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Routing;
|
||||
@@ -198,10 +199,10 @@ namespace Umbraco.Web.Macros
|
||||
Id = _inner.Id;
|
||||
Key = _inner.Key;
|
||||
|
||||
// TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current
|
||||
CreatorName = _inner.GetCreatorProfile()?.Name;
|
||||
WriterName = _inner.GetWriterProfile()?.Name;
|
||||
|
||||
// TODO: inject
|
||||
var contentType = Current.Services.ContentTypeBaseServices.GetContentTypeOf(_inner);
|
||||
ContentType = Current.PublishedContentTypeFactory.CreateContentType(contentType);
|
||||
|
||||
@@ -252,8 +253,9 @@ namespace Umbraco.Web.Macros
|
||||
if (_cultureInfos != null)
|
||||
return _cultureInfos;
|
||||
|
||||
var urlSegmentProviders = Current.UrlSegmentProviders; // TODO inject
|
||||
return _cultureInfos = _inner.PublishCultureInfos.Values
|
||||
.ToDictionary(x => x.Culture, x => new PublishedCultureInfo(x.Culture, x.Name, x.Date));
|
||||
.ToDictionary(x => x.Culture, x => new PublishedCultureInfo(x.Culture, x.Name, _inner.GetUrlSegment(urlSegmentProviders, x.Culture), x.Date));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
@@ -51,15 +53,36 @@ namespace Umbraco.Web
|
||||
{
|
||||
if (culture == null)
|
||||
culture = "";
|
||||
modelState.AddValidationError(result, "_Properties", propertyAlias, culture);
|
||||
modelState.AddValidationError(result, "_Properties", propertyAlias,
|
||||
//if the culture is null, we'll add the term 'invariant' as part of the key
|
||||
culture.IsNullOrWhiteSpace() ? "invariant" : culture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of cultures that have property errors
|
||||
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <returns></returns>
|
||||
internal static IReadOnlyList<string> GetCulturesWithPropertyErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState)
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="errMsg"></param>
|
||||
internal static void AddCultureValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
|
||||
string culture, string errMsg)
|
||||
{
|
||||
var key = "_content_variant_" + culture + "_";
|
||||
if (modelState.ContainsKey(key)) return;
|
||||
modelState.AddModelError(key, errMsg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of cultures that have property validation errors errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="localizationService"></param>
|
||||
/// <param name="cultureForInvariantErrors">The culture to affiliate invariant errors with</param>
|
||||
/// <returns>
|
||||
/// A list of cultures that have property validation errors. The default culture will be returned for any invariant property errors.
|
||||
/// </returns>
|
||||
internal static IReadOnlyList<string> GetCulturesWithPropertyErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
|
||||
ILocalizationService localizationService, string cultureForInvariantErrors)
|
||||
{
|
||||
//Add any culture specific errors here
|
||||
var cultureErrors = modelState.Keys
|
||||
@@ -67,12 +90,44 @@ namespace Umbraco.Web
|
||||
.Where(x => x.Length >= 3 && x[0] == "_Properties") //only choose _Properties errors
|
||||
.Select(x => x[2]) //select the culture part
|
||||
.Where(x => !x.IsNullOrWhiteSpace()) //if it has a value
|
||||
//if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language
|
||||
//so errors for those must show up under the default lang.
|
||||
.Select(x => x == "invariant" ? cultureForInvariantErrors : x)
|
||||
.WhereNotNull()
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
return cultureErrors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of cultures that have any validation errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="localizationService"></param>
|
||||
/// <param name="cultureForInvariantErrors">The culture to affiliate invariant errors with</param>
|
||||
/// <returns>
|
||||
/// A list of cultures that have validation errors. The default culture will be returned for any invariant errors.
|
||||
/// </returns>
|
||||
internal static IReadOnlyList<string> GetCulturesWithErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
|
||||
ILocalizationService localizationService, string cultureForInvariantErrors)
|
||||
{
|
||||
var propertyCultureErrors = modelState.GetCulturesWithPropertyErrors(localizationService, cultureForInvariantErrors);
|
||||
|
||||
//now check the other special culture errors that are
|
||||
var genericCultureErrors = modelState.Keys
|
||||
.Where(x => x.StartsWith("_content_variant_") && x.EndsWith("_"))
|
||||
.Select(x => x.TrimStart("_content_variant_").TrimEnd("_"))
|
||||
.Where(x => !x.IsNullOrWhiteSpace())
|
||||
//if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language
|
||||
//so errors for those must show up under the default lang.
|
||||
.Select(x => x == "invariant" ? cultureForInvariantErrors : x)
|
||||
.WhereNotNull()
|
||||
.Distinct();
|
||||
|
||||
return propertyCultureErrors.Union(genericCultureErrors).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the error to model state correctly for a property so we can use it on the client side.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Models.Validation;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
@@ -22,14 +16,6 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
|
||||
//name, alias, icon, thumb, desc, inherited from basic
|
||||
|
||||
//List view
|
||||
[DataMember(Name = "isContainer")]
|
||||
public bool IsContainer { get; set; }
|
||||
|
||||
//Element
|
||||
[DataMember(Name = "isElement")]
|
||||
public bool IsElement { get; set; }
|
||||
|
||||
[DataMember(Name = "listViewEditorName")]
|
||||
[ReadOnly(true)]
|
||||
public string ListViewEditorName { get; set; }
|
||||
@@ -84,6 +70,5 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
//Tabs
|
||||
[DataMember(Name = "groups")]
|
||||
public IEnumerable<PropertyGroupDisplay<TPropertyTypeDisplay>> Groups { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the list of action buttons allowed for this user - Publish, Send to publish, save, unpublish returned as the button's 'letter'
|
||||
/// </summary>
|
||||
internal class ActionButtonsResolver
|
||||
{
|
||||
public ActionButtonsResolver(IUserService userService, IContentService contentService)
|
||||
{
|
||||
UserService = userService;
|
||||
ContentService = contentService;
|
||||
}
|
||||
|
||||
private IUserService UserService { get; }
|
||||
private IContentService ContentService { get; }
|
||||
|
||||
public IEnumerable<string> Resolve(IContent source)
|
||||
{
|
||||
//cannot check permissions without a context
|
||||
if (Current.UmbracoContext == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
string path;
|
||||
if (source.HasIdentity)
|
||||
path = source.Path;
|
||||
else
|
||||
{
|
||||
var parent = ContentService.GetById(source.ParentId);
|
||||
path = parent == null ? "-1" : parent.Path;
|
||||
}
|
||||
|
||||
// TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is
|
||||
// with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null
|
||||
// reference exception :(
|
||||
return UserService.GetPermissionsForPath(Current.UmbracoContext.Security.CurrentUser, path).GetAllPermissions();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Umbraco.Web/Models/Mapping/AuditMapDefinition.cs
Normal file
26
src/Umbraco.Web/Models/Mapping/AuditMapDefinition.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class AuditMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IAuditItem, AuditLog>((source, context) => new AuditLog(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -UserAvatars -UserName
|
||||
private void Map(IAuditItem source, AuditLog target, MapperContext context)
|
||||
{
|
||||
target.UserId = source.UserId;
|
||||
target.NodeId = source.Id;
|
||||
target.Timestamp = source.CreateDate;
|
||||
target.LogType = source.AuditType.ToString();
|
||||
target.EntityType = source.EntityType;
|
||||
target.Comment = source.Comment;
|
||||
target.Parameters = source.Parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class AuditMapperProfile : Profile
|
||||
{
|
||||
public AuditMapperProfile()
|
||||
{
|
||||
CreateMap<IAuditItem, AuditLog>()
|
||||
.ForMember(log => log.UserAvatars, expression => expression.Ignore())
|
||||
.ForMember(log => log.UserName, expression => expression.Ignore())
|
||||
.ForMember(log => log.NodeId, expression => expression.MapFrom(item => item.Id))
|
||||
.ForMember(log => log.Timestamp, expression => expression.MapFrom(item => item.CreateDate))
|
||||
.ForMember(log => log.LogType, expression => expression.MapFrom(item => item.AuditType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class AvailablePropertyEditorsResolver
|
||||
{
|
||||
private readonly IContentSection _contentSection;
|
||||
|
||||
public AvailablePropertyEditorsResolver(IContentSection contentSection)
|
||||
{
|
||||
_contentSection = contentSection;
|
||||
}
|
||||
|
||||
public IEnumerable<PropertyEditorBasic> Resolve(IDataType source)
|
||||
{
|
||||
return Current.PropertyEditors
|
||||
.Where(x => !x.IsDeprecated || _contentSection.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias)
|
||||
.OrderBy(x => x.Name)
|
||||
.Select(Mapper.Map<PropertyEditorBasic>);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/Umbraco.Web/Models/Mapping/CodeFileMapDefinition.cs
Normal file
75
src/Umbraco.Web/Models/Mapping/CodeFileMapDefinition.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Stylesheet = Umbraco.Core.Models.Stylesheet;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class CodeFileMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<Stylesheet, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<IPartialView, CodeFileDisplay>((source, context) => new CodeFileDisplay(), Map);
|
||||
mapper.Define<Script, CodeFileDisplay>((source, context) => new CodeFileDisplay(), Map);
|
||||
mapper.Define<Stylesheet, CodeFileDisplay>((source, context) => new CodeFileDisplay(), Map);
|
||||
mapper.Define<CodeFileDisplay, IPartialView>(Map);
|
||||
mapper.Define<CodeFileDisplay, Script>(Map);
|
||||
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed -Udi -Icon
|
||||
private static void Map(Stylesheet source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = source.Path;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet
|
||||
private static void Map(IPartialView source, CodeFileDisplay target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.Id = source.Id.ToString();
|
||||
target.Name = source.Name;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet
|
||||
private static void Map(Script source, CodeFileDisplay target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.Id = source.Id.ToString();
|
||||
target.Name = source.Name;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet
|
||||
private static void Map(Stylesheet source, CodeFileDisplay target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.Id = source.Id.ToString();
|
||||
target.Name = source.Name;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
|
||||
// Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path
|
||||
private static void Map(CodeFileDisplay source, IPartialView target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate -GetFileContent
|
||||
// Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path
|
||||
private static void Map(CodeFileDisplay source, Script target, MapperContext context)
|
||||
{
|
||||
target.Content = source.Content;
|
||||
target.VirtualPath = source.VirtualPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Stylesheet = Umbraco.Core.Models.Stylesheet;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
public class CodeFileMapperProfile : Profile
|
||||
{
|
||||
public CodeFileMapperProfile()
|
||||
{
|
||||
CreateMap<Stylesheet, EntityBasic>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(sheet => sheet.Id))
|
||||
.ForMember(dest => dest.Alias, opt => opt.MapFrom(sheet => sheet.Alias))
|
||||
.ForMember(dest => dest.Key, opt => opt.MapFrom(sheet => sheet.Key))
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom(sheet => sheet.Name))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1))
|
||||
.ForMember(dest => dest.Path, opt => opt.MapFrom(sheet => sheet.Path))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.Ignore());
|
||||
|
||||
CreateMap<IPartialView, CodeFileDisplay>()
|
||||
.ForMember(dest => dest.FileType, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Snippet, opt => opt.Ignore());
|
||||
|
||||
CreateMap<Script, CodeFileDisplay>()
|
||||
.ForMember(dest => dest.FileType, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Snippet, opt => opt.Ignore());
|
||||
|
||||
CreateMap<Stylesheet, CodeFileDisplay>()
|
||||
.ForMember(dest => dest.FileType, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Snippet, opt => opt.Ignore());
|
||||
|
||||
CreateMap<CodeFileDisplay, IPartialView>()
|
||||
.IgnoreEntityCommonProperties()
|
||||
.ForMember(dest => dest.Id, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Key, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Name, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.OriginalPath, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.HasIdentity, opt => opt.Ignore());
|
||||
|
||||
CreateMap<CodeFileDisplay, Script>()
|
||||
.IgnoreEntityCommonProperties()
|
||||
.ForMember(dest => dest.Id, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Key, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Name, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.OriginalPath, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.HasIdentity, opt => opt.Ignore());
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/Umbraco.Web/Models/Mapping/CommonMapper.cs
Normal file
101
src/Umbraco.Web/Models/Mapping/CommonMapper.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Trees;
|
||||
using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class CommonMapper
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly ContentAppFactoryCollection _contentAppDefinitions;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_userService = userService;
|
||||
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_contentAppDefinitions = contentAppDefinitions;
|
||||
_localizedTextService = localizedTextService;
|
||||
}
|
||||
|
||||
public UserProfile GetOwner(IContentBase source, MapperContext context)
|
||||
{
|
||||
var profile = source.GetCreatorProfile(_userService);
|
||||
return profile == null ? null : context.Map<IProfile, UserProfile>(profile);
|
||||
}
|
||||
|
||||
public UserProfile GetCreator(IContent source, MapperContext context)
|
||||
{
|
||||
var profile = source.GetWriterProfile(_userService);
|
||||
return profile == null ? null : context.Map<IProfile, UserProfile>(profile);
|
||||
}
|
||||
|
||||
public ContentTypeBasic GetContentType(IContentBase source, MapperContext context)
|
||||
{
|
||||
// TODO: We can resolve the UmbracoContext from the IValueResolver options!
|
||||
// OMG
|
||||
if (HttpContext.Current != null && Composing.Current.UmbracoContext != null && Composing.Current.UmbracoContext.Security.CurrentUser != null
|
||||
&& Composing.Current.UmbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
|
||||
{
|
||||
var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source);
|
||||
var contentTypeBasic = context.Map<IContentTypeComposition, ContentTypeBasic>(contentType);
|
||||
|
||||
return contentTypeBasic;
|
||||
}
|
||||
//no access
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetTreeNodeUrl<TController>(IContentBase source)
|
||||
where TController : ContentTreeControllerBase
|
||||
{
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
if (umbracoContext == null) return null;
|
||||
|
||||
var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext);
|
||||
return urlHelper.GetUmbracoApiService<TController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
|
||||
}
|
||||
|
||||
public string GetMemberTreeNodeUrl(IContentBase source)
|
||||
{
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
if (umbracoContext == null) return null;
|
||||
|
||||
var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext);
|
||||
return urlHelper.GetUmbracoApiService<MemberTreeController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
|
||||
}
|
||||
|
||||
public IEnumerable<ContentApp> GetContentApps(IContentBase source)
|
||||
{
|
||||
var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray();
|
||||
|
||||
// localize content app names
|
||||
foreach (var app in apps)
|
||||
{
|
||||
var localizedAppName = _localizedTextService.Localize($"apps/{app.Alias}");
|
||||
if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false)
|
||||
{
|
||||
app.Name = localizedAppName;
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
// injected into ContentMapperProfile,
|
||||
// maps ContentApps when mapping IContent to ContentItemDisplay
|
||||
internal class ContentAppResolver : IValueResolver<IContent, ContentItemDisplay, IEnumerable<ContentApp>>
|
||||
{
|
||||
private readonly ContentAppFactoryCollection _contentAppDefinitions;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public ContentAppResolver(ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_contentAppDefinitions = contentAppDefinitions;
|
||||
_localizedTextService = localizedTextService;
|
||||
}
|
||||
|
||||
public IEnumerable<ContentApp> Resolve(IContent source, ContentItemDisplay destination, IEnumerable<ContentApp> destMember, ResolutionContext context)
|
||||
{
|
||||
var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray();
|
||||
|
||||
// localize content app names
|
||||
foreach (var app in apps)
|
||||
{
|
||||
var localizedAppName = _localizedTextService.Localize($"apps/{app.Alias}");
|
||||
if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false)
|
||||
{
|
||||
app.Name = localizedAppName;
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class ContentChildOfListViewResolver : IValueResolver<IContent, ContentItemDisplay, bool>
|
||||
{
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public ContentChildOfListViewResolver(IContentService contentService, IContentTypeService contentTypeService)
|
||||
{
|
||||
_contentService = contentService;
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
public bool Resolve(IContent source, ContentItemDisplay destination, bool destMember, ResolutionContext context)
|
||||
{
|
||||
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
|
||||
var parent = _contentService.GetParent(source);
|
||||
return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path));
|
||||
}
|
||||
}
|
||||
}
|
||||
254
src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs
Normal file
254
src/Umbraco.Web/Models/Mapping/ContentMapDefinition.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Declares how model mappings for content
|
||||
/// </summary>
|
||||
internal class ContentMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly CommonMapper _commonMapper;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IPublishedRouter _publishedRouter;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserService _userService;
|
||||
private readonly TabsAndPropertiesMapper<IContent> _tabsAndPropertiesMapper;
|
||||
private readonly ContentSavedStateMapper<ContentPropertyDisplay> _stateMapper;
|
||||
private readonly ContentBasicSavedStateMapper<ContentPropertyBasic> _basicStateMapper;
|
||||
private readonly ContentVariantMapper _contentVariantMapper;
|
||||
|
||||
public ContentMapDefinition(CommonMapper commonMapper, ILocalizedTextService localizedTextService, IContentService contentService, IContentTypeService contentTypeService,
|
||||
IFileService fileService, IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, ILocalizationService localizationService, ILogger logger,
|
||||
IUserService userService)
|
||||
{
|
||||
_commonMapper = commonMapper;
|
||||
_localizedTextService = localizedTextService;
|
||||
_contentService = contentService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_fileService = fileService;
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_publishedRouter = publishedRouter;
|
||||
_localizationService = localizationService;
|
||||
_logger = logger;
|
||||
_userService = userService;
|
||||
|
||||
_tabsAndPropertiesMapper = new TabsAndPropertiesMapper<IContent>(localizedTextService);
|
||||
_stateMapper = new ContentSavedStateMapper<ContentPropertyDisplay>();
|
||||
_basicStateMapper = new ContentBasicSavedStateMapper<ContentPropertyBasic>();
|
||||
_contentVariantMapper = new ContentVariantMapper(_localizationService);
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IContent, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
|
||||
mapper.Define<IContent, ContentItemDisplay>((source, context) => new ContentItemDisplay(), Map);
|
||||
mapper.Define<IContent, ContentVariantDisplay>((source, context) => new ContentVariantDisplay(), Map);
|
||||
mapper.Define<IContent, ContentItemBasic<ContentPropertyBasic>>((source, context) => new ContentItemBasic<ContentPropertyBasic>(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(IContent source, ContentPropertyCollectionDto target, MapperContext context)
|
||||
{
|
||||
target.Properties = context.MapEnumerable<Property, ContentPropertyDto>(source.Properties);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -AllowPreview -Errors -PersistedContent
|
||||
private void Map(IContent source, ContentItemDisplay target, MapperContext context)
|
||||
{
|
||||
target.AllowedActions = GetActions(source);
|
||||
target.AllowedTemplates = GetAllowedTemplates(source);
|
||||
target.ContentApps = _commonMapper.GetContentApps(source);
|
||||
target.ContentTypeAlias = source.ContentType.Alias;
|
||||
target.ContentTypeName = _localizedTextService.UmbracoDictionaryTranslate(source.ContentType.Name);
|
||||
target.DocumentType = _commonMapper.GetContentType(source, context);
|
||||
target.Icon = source.ContentType.Icon;
|
||||
target.Id = source.Id;
|
||||
target.IsBlueprint = source.Blueprint;
|
||||
target.IsChildOfListView = DermineIsChildOfListView(source);
|
||||
target.IsContainer = source.ContentType.IsContainer;
|
||||
target.IsElement = source.ContentType.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Owner = _commonMapper.GetOwner(source, context);
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.TemplateAlias = GetDefaultTemplate(source);
|
||||
target.TemplateId = source.TemplateId ?? default;
|
||||
target.Trashed = source.Trashed;
|
||||
target.TreeNodeUrl = _commonMapper.GetTreeNodeUrl<ContentTreeController>(source);
|
||||
target.Udi = Udi.Create(source.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, source.Key);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
target.Updater = _commonMapper.GetCreator(source, context);
|
||||
target.Urls = GetUrls(source);
|
||||
target.Variants = _contentVariantMapper.Map(source, context);
|
||||
|
||||
target.ContentDto = new ContentPropertyCollectionDto();
|
||||
target.ContentDto.Properties = context.MapEnumerable<Property, ContentPropertyDto>(source.Properties);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Segment -Language
|
||||
private void Map(IContent source, ContentVariantDisplay target, MapperContext context)
|
||||
{
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.ExpireDate = GetScheduledDate(source, ContentScheduleAction.Expire, context);
|
||||
target.Name = source.Name;
|
||||
target.PublishDate = source.PublishDate;
|
||||
target.ReleaseDate = GetScheduledDate(source, ContentScheduleAction.Release, context);
|
||||
target.State = _stateMapper.Map(source, context);
|
||||
target.Tabs = _tabsAndPropertiesMapper.Map(source, context);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Alias
|
||||
private void Map(IContent source, ContentItemBasic<ContentPropertyBasic> target, MapperContext context)
|
||||
{
|
||||
target.ContentTypeAlias = source.ContentType.Alias;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Edited = source.Edited;
|
||||
target.Icon = source.ContentType.Icon;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = GetName(source, context);
|
||||
target.Owner = _commonMapper.GetOwner(source, context);
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Properties = context.MapEnumerable<Property, ContentPropertyBasic>(source.Properties);
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.State = _basicStateMapper.Map(source, context);
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = Udi.Create(source.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, source.Key);
|
||||
target.UpdateDate = GetUpdateDate(source, context);
|
||||
target.Updater = _commonMapper.GetCreator(source, context);
|
||||
target.VariesByCulture = source.ContentType.VariesByCulture();
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetActions(IContent source)
|
||||
{
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
|
||||
//cannot check permissions without a context
|
||||
if (umbracoContext == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
string path;
|
||||
if (source.HasIdentity)
|
||||
path = source.Path;
|
||||
else
|
||||
{
|
||||
var parent = _contentService.GetById(source.ParentId);
|
||||
path = parent == null ? "-1" : parent.Path;
|
||||
}
|
||||
|
||||
// TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is
|
||||
// with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null
|
||||
// reference exception :(
|
||||
return _userService.GetPermissionsForPath(umbracoContext.Security.CurrentUser, path).GetAllPermissions();
|
||||
}
|
||||
|
||||
private UrlInfo[] GetUrls(IContent source)
|
||||
{
|
||||
if (source.ContentType.IsElement)
|
||||
return Array.Empty<UrlInfo>();
|
||||
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
|
||||
var urls = umbracoContext == null
|
||||
? new[] { UrlInfo.Message("Cannot generate urls without a current Umbraco Context") }
|
||||
: source.GetContentUrls(_publishedRouter, umbracoContext, _localizationService, _localizedTextService, _contentService, _logger).ToArray();
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
private DateTime GetUpdateDate(IContent source, MapperContext context)
|
||||
{
|
||||
// invariant = global date
|
||||
if (!source.ContentType.VariesByCulture()) return source.UpdateDate;
|
||||
|
||||
// variant = depends on culture
|
||||
var culture = context.GetCulture();
|
||||
|
||||
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
|
||||
if (culture == null)
|
||||
throw new InvalidOperationException("Missing culture in mapping options.");
|
||||
|
||||
// if we don't have a date for a culture, it means the culture is not available, and
|
||||
// hey we should probably not be mapping it, but it's too late, return a fallback date
|
||||
var date = source.GetUpdateDate(culture);
|
||||
return date ?? source.UpdateDate;
|
||||
}
|
||||
|
||||
private string GetName(IContent source, MapperContext context)
|
||||
{
|
||||
// invariant = only 1 name
|
||||
if (!source.ContentType.VariesByCulture()) return source.Name;
|
||||
|
||||
// variant = depends on culture
|
||||
var culture = context.GetCulture();
|
||||
|
||||
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
|
||||
if (culture == null)
|
||||
throw new InvalidOperationException("Missing culture in mapping options.");
|
||||
|
||||
// if we don't have a name for a culture, it means the culture is not available, and
|
||||
// hey we should probably not be mapping it, but it's too late, return a fallback name
|
||||
return source.CultureInfos.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"({source.Name})";
|
||||
}
|
||||
|
||||
private bool DermineIsChildOfListView(IContent source)
|
||||
{
|
||||
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
|
||||
var parent = _contentService.GetParent(source);
|
||||
return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path));
|
||||
}
|
||||
|
||||
private DateTime? GetScheduledDate(IContent source, ContentScheduleAction action, MapperContext context)
|
||||
{
|
||||
var culture = context.GetCulture() ?? string.Empty;
|
||||
var schedule = source.ContentSchedule.GetSchedule(culture, action);
|
||||
return schedule.FirstOrDefault()?.Date; // take the first, it's ordered by date
|
||||
}
|
||||
|
||||
private IDictionary<string, string> GetAllowedTemplates(IContent source)
|
||||
{
|
||||
var contentType = _contentTypeService.Get(source.ContentTypeId);
|
||||
|
||||
return contentType.AllowedTemplates
|
||||
.Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false)
|
||||
.ToDictionary(t => t.Alias, t => _localizedTextService.UmbracoDictionaryTranslate(t.Name));
|
||||
}
|
||||
|
||||
private string GetDefaultTemplate(IContent source)
|
||||
{
|
||||
if (source == null)
|
||||
return null;
|
||||
|
||||
// If no template id was set...
|
||||
if (!source.TemplateId.HasValue)
|
||||
{
|
||||
// ... and no default template is set, return null...
|
||||
// ... otherwise return the content type default template alias.
|
||||
return string.IsNullOrWhiteSpace(source.ContentType.DefaultTemplate?.Alias)
|
||||
? null
|
||||
: source.ContentType.DefaultTemplate?.Alias;
|
||||
}
|
||||
|
||||
var template = _fileService.GetTemplate(source.TemplateId.Value);
|
||||
return template.Alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Declares how model mappings for content
|
||||
/// </summary>
|
||||
internal class ContentMapperProfile : Profile
|
||||
{
|
||||
public ContentMapperProfile(
|
||||
ContentUrlResolver contentUrlResolver,
|
||||
ContentTreeNodeUrlResolver<IContent, ContentTreeController> contentTreeNodeUrlResolver,
|
||||
TabsAndPropertiesResolver<IContent, ContentVariantDisplay> tabsAndPropertiesResolver,
|
||||
ContentAppResolver contentAppResolver,
|
||||
IUserService userService,
|
||||
IContentService contentService,
|
||||
IContentTypeService contentTypeService,
|
||||
IContentTypeBaseServiceProvider contentTypeBaseServiceProvider,
|
||||
ILocalizationService localizationService,
|
||||
ILocalizedTextService localizedTextService)
|
||||
{
|
||||
// create, capture, cache
|
||||
var contentOwnerResolver = new OwnerResolver<IContent>(userService);
|
||||
var creatorResolver = new CreatorResolver(userService);
|
||||
var actionButtonsResolver = new ActionButtonsResolver(userService, contentService);
|
||||
var childOfListViewResolver = new ContentChildOfListViewResolver(contentService, contentTypeService);
|
||||
var contentTypeBasicResolver = new ContentTypeBasicResolver<IContent, ContentItemDisplay>(contentTypeBaseServiceProvider);
|
||||
var allowedTemplatesResolver = new AllowedTemplatesResolver(contentTypeService, localizedTextService);
|
||||
var defaultTemplateResolver = new DefaultTemplateResolver();
|
||||
var variantResolver = new ContentVariantResolver(localizationService);
|
||||
var schedPublishReleaseDateResolver = new ScheduledPublishDateResolver(ContentScheduleAction.Release);
|
||||
var schedPublishExpireDateResolver = new ScheduledPublishDateResolver(ContentScheduleAction.Expire);
|
||||
|
||||
//FROM IContent TO ContentItemDisplay
|
||||
CreateMap<IContent, ContentItemDisplay>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => contentOwnerResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Updater, opt => opt.MapFrom(src => creatorResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Variants, opt => opt.MapFrom(variantResolver))
|
||||
.ForMember(dest => dest.ContentApps, opt => opt.MapFrom(contentAppResolver))
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon))
|
||||
.ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias))
|
||||
.ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => localizedTextService.UmbracoDictionaryTranslate(src.ContentType.Name)))
|
||||
.ForMember(dest => dest.IsContainer, opt => opt.MapFrom(src => src.ContentType.IsContainer))
|
||||
.ForMember(dest => dest.IsElement, opt => opt.MapFrom(src => src.ContentType.IsElement))
|
||||
.ForMember(dest => dest.IsBlueprint, opt => opt.MapFrom(src => src.Blueprint))
|
||||
.ForMember(dest => dest.IsChildOfListView, opt => opt.MapFrom(childOfListViewResolver))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed))
|
||||
.ForMember(dest => dest.TemplateAlias, opt => opt.MapFrom(defaultTemplateResolver))
|
||||
.ForMember(dest => dest.Urls, opt => opt.MapFrom(contentUrlResolver))
|
||||
.ForMember(dest => dest.AllowPreview, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.TreeNodeUrl, opt => opt.MapFrom(contentTreeNodeUrlResolver))
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Errors, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.DocumentType, opt => opt.MapFrom(contentTypeBasicResolver))
|
||||
.ForMember(dest => dest.AllowedTemplates, opt => opt.MapFrom(allowedTemplatesResolver))
|
||||
.ForMember(dest => dest.AllowedActions, opt => opt.MapFrom(src => actionButtonsResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<IContent, ContentVariantDisplay>()
|
||||
.ForMember(dest => dest.PublishDate, opt => opt.MapFrom(src => src.PublishDate))
|
||||
.ForMember(dest => dest.ReleaseDate, opt => opt.MapFrom(schedPublishReleaseDateResolver))
|
||||
.ForMember(dest => dest.ExpireDate, opt => opt.MapFrom(schedPublishExpireDateResolver))
|
||||
.ForMember(dest => dest.Segment, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Language, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.State, opt => opt.MapFrom<ContentSavedStateResolver<ContentPropertyDisplay>>())
|
||||
.ForMember(dest => dest.Tabs, opt => opt.MapFrom(tabsAndPropertiesResolver));
|
||||
|
||||
//FROM IContent TO ContentItemBasic<ContentPropertyBasic, IContent>
|
||||
CreateMap<IContent, ContentItemBasic<ContentPropertyBasic>>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src =>
|
||||
Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key)))
|
||||
.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => contentOwnerResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Updater, opt => opt.MapFrom(src => creatorResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed))
|
||||
.ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias))
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.UpdateDate, opt => opt.MapFrom<UpdateDateResolver>())
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom<NameResolver>())
|
||||
.ForMember(dest => dest.State, opt => opt.MapFrom<ContentBasicSavedStateResolver<ContentPropertyBasic>>())
|
||||
.ForMember(dest => dest.VariesByCulture, opt => opt.MapFrom(src => src.ContentType.VariesByCulture()));
|
||||
|
||||
//FROM IContent TO ContentPropertyCollectionDto
|
||||
//NOTE: the property mapping for cultures relies on a culture being set in the mapping context
|
||||
CreateMap<IContent, ContentPropertyCollectionDto>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the update date for a content item/content variant
|
||||
/// </summary>
|
||||
private class UpdateDateResolver : IValueResolver<IContent, ContentItemBasic<ContentPropertyBasic>, DateTime>
|
||||
{
|
||||
public DateTime Resolve(IContent source, ContentItemBasic<ContentPropertyBasic> destination, DateTime destMember, ResolutionContext context)
|
||||
{
|
||||
// invariant = global date
|
||||
if (!source.ContentType.VariesByCulture()) return source.UpdateDate;
|
||||
|
||||
// variant = depends on culture
|
||||
var culture = context.Options.GetCulture();
|
||||
|
||||
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
|
||||
if (culture == null)
|
||||
throw new InvalidOperationException("Missing culture in mapping options.");
|
||||
|
||||
// if we don't have a date for a culture, it means the culture is not available, and
|
||||
// hey we should probably not be mapping it, but it's too late, return a fallback date
|
||||
var date = source.GetUpdateDate(culture);
|
||||
return date ?? source.UpdateDate;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the name for a content item/content variant
|
||||
/// </summary>
|
||||
private class NameResolver : IValueResolver<IContent, ContentItemBasic<ContentPropertyBasic>, string>
|
||||
{
|
||||
public string Resolve(IContent source, ContentItemBasic<ContentPropertyBasic> destination, string destMember, ResolutionContext context)
|
||||
{
|
||||
// invariant = only 1 name
|
||||
if (!source.ContentType.VariesByCulture()) return source.Name;
|
||||
|
||||
// variant = depends on culture
|
||||
var culture = context.Options.GetCulture();
|
||||
|
||||
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
|
||||
if (culture == null)
|
||||
throw new InvalidOperationException("Missing culture in mapping options.");
|
||||
|
||||
// if we don't have a name for a culture, it means the culture is not available, and
|
||||
// hey we should probably not be mapping it, but it's too late, return a fallback name
|
||||
return source.CultureInfos.TryGetValue(culture, out var name) && !name.Name.IsNullOrWhiteSpace() ? name.Name : $"({source.Name})";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class AllowedTemplatesResolver : IValueResolver<IContent, ContentItemDisplay, IDictionary<string, string>>
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public AllowedTemplatesResolver(IContentTypeService contentTypeService, ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_contentTypeService = contentTypeService;
|
||||
_localizedTextService = localizedTextService;
|
||||
}
|
||||
|
||||
public IDictionary<string, string> Resolve(IContent source, ContentItemDisplay destination, IDictionary<string, string> destMember, ResolutionContext context)
|
||||
{
|
||||
var contentType = _contentTypeService.Get(source.ContentTypeId);
|
||||
|
||||
return contentType.AllowedTemplates
|
||||
.Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false)
|
||||
.ToDictionary(t => t.Alias, t => _localizedTextService.UmbracoDictionaryTranslate(t.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a base generic ContentPropertyBasic from a Property
|
||||
/// </summary>
|
||||
internal class ContentPropertyBasicConverter<TDestination> : ITypeConverter<Property, TDestination>
|
||||
internal class ContentPropertyBasicMapper<TDestination>
|
||||
where TDestination : ContentPropertyBasic, new()
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
protected IDataTypeService DataTypeService { get; }
|
||||
|
||||
public ContentPropertyBasicConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
public ContentPropertyBasicMapper(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
_logger = logger;
|
||||
_propertyEditors = propertyEditors;
|
||||
@@ -34,12 +31,12 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// Assigns the PropertyEditor, Id, Alias and Value to the property
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual TDestination Convert(Property property, TDestination dest, ResolutionContext context)
|
||||
public virtual void Map(Property property, TDestination dest, MapperContext context)
|
||||
{
|
||||
var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias];
|
||||
if (editor == null)
|
||||
{
|
||||
_logger.Error<ContentPropertyBasicConverter<TDestination>>(
|
||||
_logger.Error<ContentPropertyBasicMapper<TDestination>>(
|
||||
new NullReferenceException("The property editor with alias " + property.PropertyType.PropertyEditorAlias + " does not exist"),
|
||||
"No property editor '{PropertyEditorAlias}' found, converting to a Label",
|
||||
property.PropertyType.PropertyEditorAlias);
|
||||
@@ -47,22 +44,19 @@ namespace Umbraco.Web.Models.Mapping
|
||||
editor = _propertyEditors[Constants.PropertyEditors.Aliases.Label];
|
||||
}
|
||||
|
||||
var result = new TDestination
|
||||
{
|
||||
Id = property.Id,
|
||||
Alias = property.Alias,
|
||||
PropertyEditor = editor,
|
||||
Editor = editor.Alias
|
||||
};
|
||||
dest.Id = property.Id;
|
||||
dest.Alias = property.Alias;
|
||||
dest.PropertyEditor = editor;
|
||||
dest.Editor = editor.Alias;
|
||||
|
||||
// if there's a set of property aliases specified, we will check if the current property's value should be mapped.
|
||||
// if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value.
|
||||
var includedProperties = context.Options.GetIncludedProperties();
|
||||
var includedProperties = context.GetIncludedProperties();
|
||||
if (includedProperties != null && !includedProperties.Contains(property.Alias))
|
||||
return result;
|
||||
return;
|
||||
|
||||
//Get the culture from the context which will be set during the mapping operation for each property
|
||||
var culture = context.Options.GetCulture();
|
||||
var culture = context.GetCulture();
|
||||
|
||||
//a culture needs to be in the context for a property type that can vary
|
||||
if (culture == null && property.PropertyType.VariesByCulture())
|
||||
@@ -71,11 +65,10 @@ namespace Umbraco.Web.Models.Mapping
|
||||
//set the culture to null if it's an invariant property type
|
||||
culture = !property.PropertyType.VariesByCulture() ? null : culture;
|
||||
|
||||
result.Culture = culture;
|
||||
dest.Culture = culture;
|
||||
|
||||
// if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return.
|
||||
result.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture);
|
||||
return result;
|
||||
dest.Value = editor.GetValueEditor().ToEditor(property, DataTypeService, culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -14,18 +10,18 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// <summary>
|
||||
/// Creates a ContentPropertyDisplay from a Property
|
||||
/// </summary>
|
||||
internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter<ContentPropertyDisplay>
|
||||
internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper<ContentPropertyDisplay>
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
|
||||
public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
public ContentPropertyDisplayMapper(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
: base(dataTypeService, logger, propertyEditors)
|
||||
{
|
||||
_textService = textService;
|
||||
}
|
||||
public override ContentPropertyDisplay Convert(Property originalProp, ContentPropertyDisplay dest, ResolutionContext context)
|
||||
public override void Map(Property originalProp, ContentPropertyDisplay dest, MapperContext context)
|
||||
{
|
||||
var display = base.Convert(originalProp, dest, context);
|
||||
base.Map(originalProp, dest, context);
|
||||
|
||||
var config = DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId).Configuration;
|
||||
|
||||
@@ -36,37 +32,35 @@ namespace Umbraco.Web.Models.Mapping
|
||||
// - does it make any sense to use a IDataValueEditor without configuring it?
|
||||
|
||||
// configure the editor for display with configuration
|
||||
var valEditor = display.PropertyEditor.GetValueEditor(config);
|
||||
var valEditor = dest.PropertyEditor.GetValueEditor(config);
|
||||
|
||||
//set the display properties after mapping
|
||||
display.Alias = originalProp.Alias;
|
||||
display.Description = originalProp.PropertyType.Description;
|
||||
display.Label = originalProp.PropertyType.Name;
|
||||
display.HideLabel = valEditor.HideLabel;
|
||||
dest.Alias = originalProp.Alias;
|
||||
dest.Description = originalProp.PropertyType.Description;
|
||||
dest.Label = originalProp.PropertyType.Name;
|
||||
dest.HideLabel = valEditor.HideLabel;
|
||||
|
||||
//add the validation information
|
||||
display.Validation.Mandatory = originalProp.PropertyType.Mandatory;
|
||||
display.Validation.Pattern = originalProp.PropertyType.ValidationRegExp;
|
||||
dest.Validation.Mandatory = originalProp.PropertyType.Mandatory;
|
||||
dest.Validation.Pattern = originalProp.PropertyType.ValidationRegExp;
|
||||
|
||||
if (display.PropertyEditor == null)
|
||||
if (dest.PropertyEditor == null)
|
||||
{
|
||||
//display.Config = PreValueCollection.AsDictionary(preVals);
|
||||
//if there is no property editor it means that it is a legacy data type
|
||||
// we cannot support editing with that so we'll just render the readonly value view.
|
||||
display.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html";
|
||||
dest.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html";
|
||||
}
|
||||
else
|
||||
{
|
||||
//let the property editor format the pre-values
|
||||
display.Config = display.PropertyEditor.GetConfigurationEditor().ToValueEditor(config);
|
||||
display.View = valEditor.View;
|
||||
dest.Config = dest.PropertyEditor.GetConfigurationEditor().ToValueEditor(config);
|
||||
dest.View = valEditor.View;
|
||||
}
|
||||
|
||||
//Translate
|
||||
display.Label = _textService.UmbracoDictionaryTranslate(display.Label);
|
||||
display.Description = _textService.UmbracoDictionaryTranslate(display.Description);
|
||||
|
||||
return display;
|
||||
dest.Label = _textService.UmbracoDictionaryTranslate(dest.Label);
|
||||
dest.Description = _textService.UmbracoDictionaryTranslate(dest.Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a ContentPropertyDto from a Property
|
||||
/// </summary>
|
||||
internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter<ContentPropertyDto>
|
||||
{
|
||||
public ContentPropertyDtoConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
: base(dataTypeService, logger, propertyEditors)
|
||||
{ }
|
||||
|
||||
public override ContentPropertyDto Convert(Property property, ContentPropertyDto dest, ResolutionContext context)
|
||||
{
|
||||
var propertyDto = base.Convert(property, dest, context);
|
||||
|
||||
propertyDto.IsRequired = property.PropertyType.Mandatory;
|
||||
propertyDto.ValidationRegExp = property.PropertyType.ValidationRegExp;
|
||||
propertyDto.Description = property.PropertyType.Description;
|
||||
propertyDto.Label = property.PropertyType.Name;
|
||||
propertyDto.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId);
|
||||
|
||||
return propertyDto;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs
Normal file
30
src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a ContentPropertyDto from a Property
|
||||
/// </summary>
|
||||
internal class ContentPropertyDtoMapper : ContentPropertyBasicMapper<ContentPropertyDto>
|
||||
{
|
||||
public ContentPropertyDtoMapper(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
: base(dataTypeService, logger, propertyEditors)
|
||||
{ }
|
||||
|
||||
public override void Map(Property property, ContentPropertyDto dest, MapperContext context)
|
||||
{
|
||||
base.Map(property, dest, context);
|
||||
|
||||
dest.IsRequired = property.PropertyType.Mandatory;
|
||||
dest.ValidationRegExp = property.PropertyType.ValidationRegExp;
|
||||
dest.Description = property.PropertyType.Description;
|
||||
dest.Label = property.PropertyType.Name;
|
||||
dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is
|
||||
/// why they are in their own mapper
|
||||
/// </summary>
|
||||
internal class ContentPropertyMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly ContentPropertyBasicMapper<ContentPropertyBasic> _contentPropertyBasicConverter;
|
||||
private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter;
|
||||
private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper;
|
||||
|
||||
public ContentPropertyMapDefinition(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
_contentPropertyBasicConverter = new ContentPropertyBasicMapper<ContentPropertyBasic>(dataTypeService, logger, propertyEditors);
|
||||
_contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, logger, propertyEditors);
|
||||
_contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(dataTypeService, textService, logger, propertyEditors);
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<PropertyGroup, Tab<ContentPropertyDisplay>>((source, context) => new Tab<ContentPropertyDisplay>(), Map);
|
||||
mapper.Define<Property, ContentPropertyBasic>((source, context) => new ContentPropertyBasic(), Map);
|
||||
mapper.Define<Property, ContentPropertyDto>((source, context) => new ContentPropertyDto(), Map);
|
||||
mapper.Define<Property, ContentPropertyDisplay>((source, context) => new ContentPropertyDisplay(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Properties -Alias -Expanded
|
||||
private void Map(PropertyGroup source, Tab<ContentPropertyDisplay> target, MapperContext mapper)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.IsActive = true;
|
||||
target.Label = source.Name;
|
||||
}
|
||||
|
||||
private void Map(Property source, ContentPropertyBasic target, MapperContext context)
|
||||
{
|
||||
// assume this is mapping everything and no MapAll is required
|
||||
_contentPropertyBasicConverter.Map(source, target, context);
|
||||
}
|
||||
|
||||
private void Map(Property source, ContentPropertyDto target, MapperContext context)
|
||||
{
|
||||
// assume this is mapping everything and no MapAll is required
|
||||
_contentPropertyDtoConverter.Map(source, target, context);
|
||||
}
|
||||
|
||||
private void Map(Property source, ContentPropertyDisplay target, MapperContext context)
|
||||
{
|
||||
// assume this is mapping everything and no MapAll is required
|
||||
_contentPropertyDisplayMapper.Map(source, target, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is
|
||||
/// why they are in their own mapper
|
||||
/// </summary>
|
||||
internal class ContentPropertyMapperProfile : Profile
|
||||
{
|
||||
public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
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>>()
|
||||
.ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name))
|
||||
.ForMember(tab => tab.IsActive, expression => expression.MapFrom(_ => true))
|
||||
.ForMember(tab => tab.Properties, expression => expression.Ignore())
|
||||
.ForMember(tab => tab.Alias, expression => expression.Ignore())
|
||||
.ForMember(tab => tab.Expanded, expression => expression.Ignore());
|
||||
|
||||
//FROM Property TO ContentPropertyBasic
|
||||
CreateMap<Property, ContentPropertyBasic>().ConvertUsing(contentPropertyBasicConverter);
|
||||
|
||||
//FROM Property TO ContentPropertyDto
|
||||
CreateMap<Property, ContentPropertyDto>().ConvertUsing(contentPropertyDtoConverter);
|
||||
|
||||
//FROM Property TO ContentPropertyDisplay
|
||||
CreateMap<Property, ContentPropertyDisplay>().ConvertUsing(contentPropertyDisplayConverter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ContentSavedState?"/> for an <see cref="IContent"/> item
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class ContentBasicSavedStateResolver<T> : IValueResolver<IContent, IContentProperties<T>, ContentSavedState?>
|
||||
internal class ContentBasicSavedStateMapper<T>
|
||||
where T : ContentPropertyBasic
|
||||
{
|
||||
private readonly ContentSavedStateResolver<T> _inner = new ContentSavedStateResolver<T>();
|
||||
private readonly ContentSavedStateMapper<T> _inner = new ContentSavedStateMapper<T>();
|
||||
|
||||
public ContentSavedState? Resolve(IContent source, IContentProperties<T> destination, ContentSavedState? destMember, ResolutionContext context)
|
||||
public ContentSavedState? Map(IContent source, MapperContext context)
|
||||
{
|
||||
return _inner.Resolve(source, destination, default, context);
|
||||
return _inner.Map(source, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +25,10 @@ namespace Umbraco.Web.Models.Mapping
|
||||
/// Returns the <see cref="ContentSavedState"/> for an <see cref="IContent"/> item
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class ContentSavedStateResolver<T> : IValueResolver<IContent, IContentProperties<T>, ContentSavedState>
|
||||
internal class ContentSavedStateMapper<T>
|
||||
where T : ContentPropertyBasic
|
||||
{
|
||||
public ContentSavedState Resolve(IContent source, IContentProperties<T> destination, ContentSavedState destMember, ResolutionContext context)
|
||||
public ContentSavedState Map(IContent source, MapperContext context)
|
||||
{
|
||||
PublishedState publishedState;
|
||||
bool isEdited;
|
||||
@@ -38,7 +37,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
if (source.ContentType.VariesByCulture())
|
||||
{
|
||||
//Get the culture from the context which will be set during the mapping operation for each variant
|
||||
var culture = context.Options.GetCulture();
|
||||
var culture = context.GetCulture();
|
||||
|
||||
//a culture needs to be in the context for a variant content item
|
||||
if (culture == null)
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Web.Mvc;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the tree node url for the content or media
|
||||
/// </summary>
|
||||
internal class ContentTreeNodeUrlResolver<TSource, TController> : IValueResolver<TSource, object, string>
|
||||
where TSource : IContentBase
|
||||
where TController : ContentTreeControllerBase
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
|
||||
public ContentTreeNodeUrlResolver(IUmbracoContextAccessor umbracoContextAccessor)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
}
|
||||
|
||||
public string Resolve(TSource source, object destination, string destMember, ResolutionContext context)
|
||||
{
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
if (umbracoContext == null) return null;
|
||||
|
||||
var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext);
|
||||
return urlHelper.GetUmbracoApiService<TController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a <see cref="ContentTypeBasic"/> from the <see cref="IContent"/> item and checks if the current user
|
||||
/// has access to see this data
|
||||
/// </summary>
|
||||
internal class ContentTypeBasicResolver<TSource, TDestination> : IValueResolver<TSource, TDestination, ContentTypeBasic>
|
||||
where TSource : IContentBase
|
||||
{
|
||||
private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
|
||||
|
||||
public ContentTypeBasicResolver(IContentTypeBaseServiceProvider contentTypeBaseServiceProvider)
|
||||
{
|
||||
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
|
||||
}
|
||||
|
||||
public ContentTypeBasic Resolve(TSource source, TDestination destination, ContentTypeBasic destMember, ResolutionContext context)
|
||||
{
|
||||
// TODO: We can resolve the UmbracoContext from the IValueResolver options!
|
||||
// OMG
|
||||
if (HttpContext.Current != null && Current.UmbracoContext != null && Current.UmbracoContext.Security.CurrentUser != null
|
||||
&& Current.UmbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
|
||||
{
|
||||
var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source);
|
||||
var contentTypeBasic = Mapper.Map<IContentTypeComposition, ContentTypeBasic>(contentType);
|
||||
|
||||
return contentTypeBasic;
|
||||
}
|
||||
//no access
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
676
src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs
Normal file
676
src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs
Normal file
@@ -0,0 +1,676 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines mappings for content/media/members type mappings
|
||||
/// </summary>
|
||||
internal class ContentTypeMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly IMemberTypeService _memberTypeService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ContentTypeMapDefinition(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService,
|
||||
IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService,
|
||||
ILogger logger)
|
||||
{
|
||||
_propertyEditors = propertyEditors;
|
||||
_dataTypeService = dataTypeService;
|
||||
_fileService = fileService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_mediaTypeService = mediaTypeService;
|
||||
_memberTypeService = memberTypeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<DocumentTypeSave, IContentType>((source, context) => new ContentType(source.ParentId), Map);
|
||||
mapper.Define<MediaTypeSave, IMediaType>((source, context) => new MediaType(source.ParentId), Map);
|
||||
mapper.Define<MemberTypeSave, IMemberType>((source, context) => new MemberType(source.ParentId), Map);
|
||||
|
||||
mapper.Define<IContentType, DocumentTypeDisplay>((source, context) => new DocumentTypeDisplay(), Map);
|
||||
mapper.Define<IMediaType, MediaTypeDisplay>((source, context) => new MediaTypeDisplay(), Map);
|
||||
mapper.Define<IMemberType, MemberTypeDisplay>((source, context) => new MemberTypeDisplay(), Map);
|
||||
|
||||
mapper.Define<PropertyTypeBasic, PropertyType>(
|
||||
(source, context) =>
|
||||
{
|
||||
var dataType = _dataTypeService.GetDataType(source.DataTypeId);
|
||||
if (dataType == null) throw new NullReferenceException("No data type found with id " + source.DataTypeId);
|
||||
return new PropertyType(dataType, source.Alias);
|
||||
}, Map);
|
||||
|
||||
// TODO: isPublishing in ctor?
|
||||
mapper.Define<PropertyGroupBasic<PropertyTypeBasic>, PropertyGroup>((source, context) => new PropertyGroup(false), Map);
|
||||
mapper.Define<PropertyGroupBasic<MemberPropertyTypeBasic>, PropertyGroup>((source, context) => new PropertyGroup(false), Map);
|
||||
|
||||
mapper.Define<IContentTypeComposition, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
mapper.Define<IContentType, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
mapper.Define<IMediaType, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
mapper.Define<IMemberType, ContentTypeBasic>((source, context) => new ContentTypeBasic(), Map);
|
||||
|
||||
mapper.Define<DocumentTypeSave, DocumentTypeDisplay>((source, context) => new DocumentTypeDisplay(), Map);
|
||||
mapper.Define<MediaTypeSave, MediaTypeDisplay>((source, context) => new MediaTypeDisplay(), Map);
|
||||
mapper.Define<MemberTypeSave, MemberTypeDisplay>((source, context) => new MemberTypeDisplay(), Map);
|
||||
|
||||
mapper.Define<PropertyGroupBasic<PropertyTypeBasic>, PropertyGroupDisplay<PropertyTypeDisplay>>((source, context) => new PropertyGroupDisplay<PropertyTypeDisplay>(), Map);
|
||||
mapper.Define<PropertyGroupBasic<MemberPropertyTypeBasic>, PropertyGroupDisplay<MemberPropertyTypeDisplay>>((source, context) => new PropertyGroupDisplay<MemberPropertyTypeDisplay>(), Map);
|
||||
|
||||
mapper.Define<PropertyTypeBasic, PropertyTypeDisplay>((source, context) => new PropertyTypeDisplay(), Map);
|
||||
mapper.Define<MemberPropertyTypeBasic, MemberPropertyTypeDisplay>((source, context) => new MemberPropertyTypeDisplay(), Map);
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(DocumentTypeSave source, IContentType target, MapperContext context)
|
||||
{
|
||||
MapSaveToTypeBase<DocumentTypeSave, PropertyTypeBasic>(source, target, context);
|
||||
MapComposition(source, target, alias => _contentTypeService.Get(alias));
|
||||
|
||||
target.AllowedTemplates = source.AllowedTemplates
|
||||
.Where(x => x != null)
|
||||
.Select(_fileService.GetTemplate)
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
target.SetDefaultTemplate(source.DefaultTemplate == null ? null : _fileService.GetTemplate(source.DefaultTemplate));
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MediaTypeSave source, IMediaType target, MapperContext context)
|
||||
{
|
||||
MapSaveToTypeBase<MediaTypeSave, PropertyTypeBasic>(source, target, context);
|
||||
MapComposition(source, target, alias => _mediaTypeService.Get(alias));
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MemberTypeSave source, IMemberType target, MapperContext context)
|
||||
{
|
||||
MapSaveToTypeBase<MemberTypeSave, MemberPropertyTypeBasic>(source, target, context);
|
||||
MapComposition(source, target, alias => _memberTypeService.Get(alias));
|
||||
|
||||
foreach (var propertyType in source.Groups.SelectMany(x => x.Properties))
|
||||
{
|
||||
var localCopy = propertyType;
|
||||
var destProp = target.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias));
|
||||
if (destProp == null) continue;
|
||||
target.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty);
|
||||
target.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty);
|
||||
target.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData);
|
||||
}
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(IContentType source, DocumentTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<DocumentTypeDisplay, PropertyTypeDisplay>(source, target);
|
||||
|
||||
target.AllowCultureVariant = source.VariesByCulture();
|
||||
|
||||
//sync templates
|
||||
target.AllowedTemplates = context.MapEnumerable<ITemplate, EntityBasic>(source.AllowedTemplates);
|
||||
|
||||
if (source.DefaultTemplate != null)
|
||||
target.DefaultTemplate = context.Map<EntityBasic>(source.DefaultTemplate);
|
||||
|
||||
//default listview
|
||||
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content";
|
||||
|
||||
if (string.IsNullOrEmpty(source.Alias)) return;
|
||||
|
||||
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias;
|
||||
if (_dataTypeService.GetDataType(name) != null)
|
||||
target.ListViewEditorName = name;
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(IMediaType source, MediaTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MediaTypeDisplay, PropertyTypeDisplay>(source, target);
|
||||
|
||||
//default listview
|
||||
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media";
|
||||
|
||||
if (string.IsNullOrEmpty(source.Name)) return;
|
||||
|
||||
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name;
|
||||
if (_dataTypeService.GetDataType(name) != null)
|
||||
target.ListViewEditorName = name;
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(IMemberType source, MemberTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MemberTypeDisplay, MemberPropertyTypeDisplay>(source, target);
|
||||
|
||||
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
|
||||
foreach (var propertyType in source.PropertyTypes)
|
||||
{
|
||||
var localCopy = propertyType;
|
||||
var displayProp = target.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias));
|
||||
if (displayProp == null) continue;
|
||||
displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias);
|
||||
displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias);
|
||||
displayProp.IsSensitiveData = source.IsSensitiveProperty(localCopy.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Blueprints
|
||||
private static void Map(IContentTypeBase source, ContentTypeBasic target, string entityType)
|
||||
{
|
||||
target.Udi = Udi.Create(entityType, source.Key);
|
||||
target.Alias = source.Alias;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.Id = source.Id;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
target.Trashed = source.Trashed;
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private static void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.MemberType);
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private static void Map(IContentType source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.DocumentType);
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private static void Map(IMediaType source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.MediaType);
|
||||
}
|
||||
|
||||
// no MapAll - uses the IContentTypeBase map method, which has MapAll
|
||||
private static void Map(IMemberType source, ContentTypeBasic target, MapperContext context)
|
||||
{
|
||||
Map(source, target, Constants.UdiEntityType.MemberType);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
|
||||
// Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType
|
||||
private static void Map(PropertyTypeBasic source, PropertyType target, MapperContext context)
|
||||
{
|
||||
target.Name = source.Label;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.Mandatory = source.Validation.Mandatory;
|
||||
target.ValidationRegExp = source.Validation.Pattern;
|
||||
target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing;
|
||||
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
|
||||
if (source.GroupId > 0)
|
||||
target.PropertyGroupId = new Lazy<int>(() => source.GroupId, false);
|
||||
|
||||
target.Alias = source.Alias;
|
||||
target.Description = source.Description;
|
||||
target.SortOrder = source.SortOrder;
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<DocumentTypeSave, PropertyTypeBasic, DocumentTypeDisplay, PropertyTypeDisplay>(source, target, context);
|
||||
|
||||
//sync templates
|
||||
var destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias);
|
||||
//if the dest is set and it's the same as the source, then don't change
|
||||
if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false)
|
||||
{
|
||||
var templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray());
|
||||
target.AllowedTemplates = source.AllowedTemplates
|
||||
.Select(x =>
|
||||
{
|
||||
var template = templates.SingleOrDefault(t => t.Alias == x);
|
||||
return template != null
|
||||
? context.Map<EntityBasic>(template)
|
||||
: null;
|
||||
})
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
if (source.DefaultTemplate.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//if the dest is set and it's the same as the source, then don't change
|
||||
if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias)
|
||||
{
|
||||
var template = _fileService.GetTemplate(source.DefaultTemplate);
|
||||
target.DefaultTemplate = template == null ? null : context.Map<EntityBasic>(template);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target.DefaultTemplate = null;
|
||||
}
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MediaTypeSave, PropertyTypeBasic, MediaTypeDisplay, PropertyTypeDisplay>(source, target, context);
|
||||
}
|
||||
|
||||
// no MapAll - take care
|
||||
private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context)
|
||||
{
|
||||
MapTypeToDisplayBase<MemberTypeSave, MemberPropertyTypeBasic, MemberTypeDisplay, MemberPropertyTypeDisplay>(source, target, context);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes
|
||||
private static void Map(PropertyGroupBasic<PropertyTypeBasic> source, PropertyGroup target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes
|
||||
private static void Map(PropertyGroupBasic<MemberPropertyTypeBasic> source, PropertyGroup target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames
|
||||
private static void Map(PropertyGroupBasic<PropertyTypeBasic> source, PropertyGroupDisplay<PropertyTypeDisplay> target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
|
||||
target.Inherited = source.Inherited;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
|
||||
target.Properties = context.MapEnumerable<PropertyTypeBasic, PropertyTypeDisplay>(source.Properties);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames
|
||||
private static void Map(PropertyGroupBasic<MemberPropertyTypeBasic> source, PropertyGroupDisplay<MemberPropertyTypeDisplay> target, MapperContext context)
|
||||
{
|
||||
if (source.Id > 0)
|
||||
target.Id = source.Id;
|
||||
|
||||
target.Inherited = source.Inherited;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
|
||||
target.Properties = context.MapEnumerable<MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(source.Properties);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked
|
||||
private static void Map(PropertyTypeBasic source, PropertyTypeDisplay target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
target.Inherited = source.Inherited;
|
||||
target.Label = source.Label;
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.Validation = source.Validation;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked
|
||||
private static void Map(MemberPropertyTypeBasic source, MemberPropertyTypeDisplay target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowCultureVariant = source.AllowCultureVariant;
|
||||
target.DataTypeId = source.DataTypeId;
|
||||
target.Description = source.Description;
|
||||
target.GroupId = source.GroupId;
|
||||
target.Id = source.Id;
|
||||
target.Inherited = source.Inherited;
|
||||
target.IsSensitiveData = source.IsSensitiveData;
|
||||
target.Label = source.Label;
|
||||
target.MemberCanEditProperty = source.MemberCanEditProperty;
|
||||
target.MemberCanViewProperty = source.MemberCanViewProperty;
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.Validation = source.Validation;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreatorId -Level -SortOrder
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate
|
||||
// Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType)
|
||||
private static void MapSaveToTypeBase<TSource, TSourcePropertyType>(TSource source, IContentTypeComposition target, MapperContext context)
|
||||
where TSource : ContentTypeSave<TSourcePropertyType>
|
||||
where TSourcePropertyType : PropertyTypeBasic
|
||||
{
|
||||
// TODO: not so clean really
|
||||
var isPublishing = target is IContentType;
|
||||
|
||||
var id = Convert.ToInt32(source.Id);
|
||||
if (id > 0)
|
||||
target.Id = id;
|
||||
|
||||
target.Alias = source.Alias;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
|
||||
target.AllowedAsRoot = source.AllowAsRoot;
|
||||
target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i));
|
||||
|
||||
if (!(target is IMemberType))
|
||||
{
|
||||
target.Variations = ContentVariation.Nothing;
|
||||
if (source.AllowCultureVariant)
|
||||
target.Variations |= ContentVariation.Culture;
|
||||
}
|
||||
|
||||
// handle property groups and property types
|
||||
// note that ContentTypeSave has
|
||||
// - all groups, inherited and local; only *one* occurrence per group *name*
|
||||
// - potentially including the generic properties group
|
||||
// - all properties, inherited and local
|
||||
//
|
||||
// also, see PropertyTypeGroupResolver.ResolveCore:
|
||||
// - if a group is local *and* inherited, then Inherited is true
|
||||
// and the identifier is the identifier of the *local* group
|
||||
//
|
||||
// IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some
|
||||
// unique-alias-checking, etc that is *not* compatible with re-mapping everything
|
||||
// the way we do it here, so we should exclusively do it by
|
||||
// - managing a property group's PropertyTypes collection
|
||||
// - managing the content type's PropertyTypes collection (for generic properties)
|
||||
|
||||
// handle actual groups (non-generic-properties)
|
||||
var destOrigGroups = target.PropertyGroups.ToArray(); // local groups
|
||||
var destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not
|
||||
var destGroups = new List<PropertyGroup>();
|
||||
var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray();
|
||||
foreach (var sourceGroup in sourceGroups)
|
||||
{
|
||||
// get the dest group
|
||||
var destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context);
|
||||
|
||||
// handle local properties
|
||||
var destProperties = sourceGroup.Properties
|
||||
.Where(x => x.Inherited == false)
|
||||
.Select(x => MapSaveProperty(x, destOrigProperties, context))
|
||||
.ToArray();
|
||||
|
||||
// if the group has no local properties, skip it, ie sort-of garbage-collect
|
||||
// local groups which would not have local properties anymore
|
||||
if (destProperties.Length == 0)
|
||||
continue;
|
||||
|
||||
// ensure no duplicate alias, then assign the group properties collection
|
||||
EnsureUniqueAliases(destProperties);
|
||||
destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
|
||||
destGroups.Add(destGroup);
|
||||
}
|
||||
|
||||
// ensure no duplicate name, then assign the groups collection
|
||||
EnsureUniqueNames(destGroups);
|
||||
target.PropertyGroups = new PropertyGroupCollection(destGroups);
|
||||
|
||||
// because the property groups collection was rebuilt, there is no need to remove
|
||||
// the old groups - they are just gone and will be cleared by the repository
|
||||
|
||||
// handle non-grouped (ie generic) properties
|
||||
var genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties);
|
||||
if (genericPropertiesGroup != null)
|
||||
{
|
||||
// handle local properties
|
||||
var destProperties = genericPropertiesGroup.Properties
|
||||
.Where(x => x.Inherited == false)
|
||||
.Select(x => MapSaveProperty(x, destOrigProperties, context))
|
||||
.ToArray();
|
||||
|
||||
// ensure no duplicate alias, then assign the generic properties collection
|
||||
EnsureUniqueAliases(destProperties);
|
||||
target.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
|
||||
}
|
||||
|
||||
// because all property collections were rebuilt, there is no need to remove
|
||||
// some old properties, they are just gone and will be cleared by the repository
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Blueprints -Errors -ListViewEditorName -Trashed
|
||||
private void MapTypeToDisplayBase(IContentTypeComposition source, ContentTypeCompositionDisplay target)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowAsRoot = source.AllowedAsRoot;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.Id = source.Id;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
target.Udi = MapContentTypeUdi(source);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
|
||||
target.AllowedContentTypes = source.AllowedContentTypes.Select(x => x.Id.Value);
|
||||
target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias);
|
||||
target.LockedCompositeContentTypes = MapLockedCompositions(source);
|
||||
}
|
||||
|
||||
// no MapAll - relies on the non-generic method
|
||||
private void MapTypeToDisplayBase<TTarget, TTargetPropertyType>(IContentTypeComposition source, TTarget target)
|
||||
where TTarget : ContentTypeCompositionDisplay<TTargetPropertyType>
|
||||
where TTargetPropertyType : PropertyTypeDisplay, new()
|
||||
{
|
||||
MapTypeToDisplayBase(source, target);
|
||||
|
||||
var groupsMapper = new PropertyTypeGroupMapper<TTargetPropertyType>(_propertyEditors, _dataTypeService, _logger);
|
||||
target.Groups = groupsMapper.Map(source);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -UpdateDate -ListViewEditorName -Errors -LockedCompositeContentTypes
|
||||
private void MapTypeToDisplayBase(ContentTypeSave source, ContentTypeCompositionDisplay target)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.AllowAsRoot = source.AllowAsRoot;
|
||||
target.AllowedContentTypes = source.AllowedContentTypes;
|
||||
target.Blueprints = source.Blueprints;
|
||||
target.CompositeContentTypes = source.CompositeContentTypes;
|
||||
target.Description = source.Description;
|
||||
target.Icon = source.Icon;
|
||||
target.Id = source.Id;
|
||||
target.IsContainer = source.IsContainer;
|
||||
target.IsElement = source.IsElement;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Thumbnail = source.Thumbnail;
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = source.Udi;
|
||||
}
|
||||
|
||||
// no MapAll - relies on the non-generic method
|
||||
private void MapTypeToDisplayBase<TSource, TSourcePropertyType, TTarget, TTargetPropertyType>(TSource source, TTarget target, MapperContext context)
|
||||
where TSource : ContentTypeSave<TSourcePropertyType>
|
||||
where TSourcePropertyType : PropertyTypeBasic
|
||||
where TTarget : ContentTypeCompositionDisplay<TTargetPropertyType>
|
||||
where TTargetPropertyType : PropertyTypeDisplay
|
||||
{
|
||||
MapTypeToDisplayBase(source, target);
|
||||
|
||||
target.Groups = context.MapEnumerable<PropertyGroupBasic<TSourcePropertyType>, PropertyGroupDisplay<TTargetPropertyType>>(source.Groups);
|
||||
}
|
||||
|
||||
private IEnumerable<string> MapLockedCompositions(IContentTypeComposition source)
|
||||
{
|
||||
// get ancestor ids from path of parent if not root
|
||||
if (source.ParentId == Constants.System.Root)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
var parent = _contentTypeService.Get(source.ParentId);
|
||||
if (parent == null)
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
var aliases = new List<string>();
|
||||
var ancestorIds = parent.Path.Split(',').Select(int.Parse);
|
||||
// loop through all content types and return ordered aliases of ancestors
|
||||
var allContentTypes = _contentTypeService.GetAll().ToArray();
|
||||
foreach (var ancestorId in ancestorIds)
|
||||
{
|
||||
var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId);
|
||||
if (ancestor != null)
|
||||
aliases.Add(ancestor.Alias);
|
||||
}
|
||||
return aliases.OrderBy(x => x);
|
||||
}
|
||||
|
||||
internal static Udi MapContentTypeUdi(IContentTypeComposition source)
|
||||
{
|
||||
if (source == null) return null;
|
||||
|
||||
string udiType;
|
||||
switch (source)
|
||||
{
|
||||
case IMemberType _:
|
||||
udiType = Constants.UdiEntityType.MemberType;
|
||||
break;
|
||||
case IMediaType _:
|
||||
udiType = Constants.UdiEntityType.MediaType;
|
||||
break;
|
||||
case IContentType _:
|
||||
udiType = Constants.UdiEntityType.DocumentType;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("panic");
|
||||
}
|
||||
|
||||
return Udi.Create(udiType, source.Key);
|
||||
}
|
||||
|
||||
private static PropertyGroup MapSaveGroup<TPropertyType>(PropertyGroupBasic<TPropertyType> sourceGroup, IEnumerable<PropertyGroup> destOrigGroups, MapperContext context)
|
||||
where TPropertyType : PropertyTypeBasic
|
||||
{
|
||||
PropertyGroup destGroup;
|
||||
if (sourceGroup.Id > 0)
|
||||
{
|
||||
// update an existing group
|
||||
// ensure it is still there, then map/update
|
||||
destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id);
|
||||
if (destGroup != null)
|
||||
{
|
||||
context.Map(sourceGroup, destGroup);
|
||||
return destGroup;
|
||||
}
|
||||
|
||||
// force-clear the ID as it does not match anything
|
||||
sourceGroup.Id = 0;
|
||||
}
|
||||
|
||||
// insert a new group, or update an existing group that has
|
||||
// been deleted in the meantime and we need to re-create
|
||||
// map/create
|
||||
destGroup = context.Map<PropertyGroup>(sourceGroup);
|
||||
return destGroup;
|
||||
}
|
||||
|
||||
private static PropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable<PropertyType> destOrigProperties, MapperContext context)
|
||||
{
|
||||
PropertyType destProperty;
|
||||
if (sourceProperty.Id > 0)
|
||||
{
|
||||
// updating an existing property
|
||||
// ensure it is still there, then map/update
|
||||
destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id);
|
||||
if (destProperty != null)
|
||||
{
|
||||
context.Map(sourceProperty, destProperty);
|
||||
return destProperty;
|
||||
}
|
||||
|
||||
// force-clear the ID as it does not match anything
|
||||
sourceProperty.Id = 0;
|
||||
}
|
||||
|
||||
// insert a new property, or update an existing property that has
|
||||
// been deleted in the meantime and we need to re-create
|
||||
// map/create
|
||||
destProperty = context.Map<PropertyType>(sourceProperty);
|
||||
return destProperty;
|
||||
}
|
||||
|
||||
private static void EnsureUniqueAliases(IEnumerable<PropertyType> properties)
|
||||
{
|
||||
var propertiesA = properties.ToArray();
|
||||
var distinctProperties = propertiesA
|
||||
.Select(x => x.Alias.ToUpperInvariant())
|
||||
.Distinct()
|
||||
.Count();
|
||||
if (distinctProperties != propertiesA.Length)
|
||||
throw new InvalidOperationException("Cannot map properties due to alias conflict.");
|
||||
}
|
||||
|
||||
private static void EnsureUniqueNames(IEnumerable<PropertyGroup> groups)
|
||||
{
|
||||
var groupsA = groups.ToArray();
|
||||
var distinctProperties = groupsA
|
||||
.Select(x => x.Name.ToUpperInvariant())
|
||||
.Distinct()
|
||||
.Count();
|
||||
if (distinctProperties != groupsA.Length)
|
||||
throw new InvalidOperationException("Cannot map groups due to name conflict.");
|
||||
}
|
||||
|
||||
private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, Func<string, IContentTypeComposition> getContentType)
|
||||
{
|
||||
var current = target.CompositionAliases().ToArray();
|
||||
var proposed = source.CompositeContentTypes;
|
||||
|
||||
var remove = current.Where(x => !proposed.Contains(x));
|
||||
var add = proposed.Where(x => !current.Contains(x));
|
||||
|
||||
foreach (var alias in remove)
|
||||
target.RemoveContentType(alias);
|
||||
|
||||
foreach (var alias in add)
|
||||
{
|
||||
// TODO: Remove N+1 lookup
|
||||
var contentType = getContentType(alias);
|
||||
if (contentType != null)
|
||||
target.AddContentType(contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core.Services;
|
||||
using ContentVariation = Umbraco.Core.Models.ContentVariation;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines mappings for content/media/members type mappings
|
||||
/// </summary>
|
||||
internal class ContentTypeMapperProfile : Profile
|
||||
{
|
||||
public ContentTypeMapperProfile(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService, IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, ILogger logger)
|
||||
{
|
||||
CreateMap<DocumentTypeSave, IContentType>()
|
||||
//do the base mapping
|
||||
.MapBaseContentTypeSaveToEntity<DocumentTypeSave, PropertyTypeBasic, IContentType>()
|
||||
.ConstructUsing((source) => new ContentType(source.ParentId))
|
||||
.ForMember(source => source.AllowedTemplates, opt => opt.Ignore())
|
||||
.ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore())
|
||||
.AfterMap((source, dest) =>
|
||||
{
|
||||
dest.AllowedTemplates = source.AllowedTemplates
|
||||
.Where(x => x != null)
|
||||
.Select(fileService.GetTemplate)
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
if (source.DefaultTemplate != null)
|
||||
dest.SetDefaultTemplate(fileService.GetTemplate(source.DefaultTemplate));
|
||||
else
|
||||
dest.SetDefaultTemplate(null);
|
||||
|
||||
ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService);
|
||||
});
|
||||
|
||||
CreateMap<MediaTypeSave, IMediaType>()
|
||||
//do the base mapping
|
||||
.MapBaseContentTypeSaveToEntity<MediaTypeSave, PropertyTypeBasic, IMediaType>()
|
||||
.ConstructUsing((source) => new MediaType(source.ParentId))
|
||||
.AfterMap((source, dest) =>
|
||||
{
|
||||
ContentTypeProfileExtensions.AfterMapMediaTypeSaveToEntity(source, dest, mediaTypeService);
|
||||
});
|
||||
|
||||
CreateMap<MemberTypeSave, IMemberType>()
|
||||
//do the base mapping
|
||||
.MapBaseContentTypeSaveToEntity<MemberTypeSave, MemberPropertyTypeBasic, IMemberType>()
|
||||
.ConstructUsing(source => new MemberType(source.ParentId))
|
||||
.AfterMap((source, dest) =>
|
||||
{
|
||||
ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService);
|
||||
|
||||
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
|
||||
foreach (var propertyType in source.Groups.SelectMany(x => x.Properties))
|
||||
{
|
||||
var localCopy = propertyType;
|
||||
var destProp = dest.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias));
|
||||
if (destProp != null)
|
||||
{
|
||||
dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty);
|
||||
dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty);
|
||||
dest.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CreateMap<IContentTypeComposition, string>().ConvertUsing(dest => dest.Alias);
|
||||
|
||||
CreateMap<IMemberType, MemberTypeDisplay>()
|
||||
//map base logic
|
||||
.MapBaseContentTypeEntityToDisplay<IMemberType, MemberTypeDisplay, MemberPropertyTypeDisplay>(propertyEditors, dataTypeService, contentTypeService, logger)
|
||||
.AfterMap((memberType, display) =>
|
||||
{
|
||||
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
|
||||
foreach (var propertyType in memberType.PropertyTypes)
|
||||
{
|
||||
var localCopy = propertyType;
|
||||
var displayProp = display.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias));
|
||||
if (displayProp != null)
|
||||
{
|
||||
displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias);
|
||||
displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias);
|
||||
displayProp.IsSensitiveData = memberType.IsSensitiveProperty(localCopy.Alias);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CreateMap<IMediaType, MediaTypeDisplay>()
|
||||
//map base logic
|
||||
.MapBaseContentTypeEntityToDisplay<IMediaType, MediaTypeDisplay, PropertyTypeDisplay>(propertyEditors, dataTypeService, contentTypeService, logger)
|
||||
.AfterMap((source, dest) =>
|
||||
{
|
||||
//default listview
|
||||
dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media";
|
||||
|
||||
if (string.IsNullOrEmpty(source.Name) == false)
|
||||
{
|
||||
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name;
|
||||
if (dataTypeService.GetDataType(name) != null)
|
||||
dest.ListViewEditorName = name;
|
||||
}
|
||||
});
|
||||
|
||||
CreateMap<IContentType, DocumentTypeDisplay>()
|
||||
//map base logic
|
||||
.MapBaseContentTypeEntityToDisplay<IContentType, DocumentTypeDisplay, PropertyTypeDisplay>(propertyEditors, dataTypeService, contentTypeService, logger)
|
||||
.ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore())
|
||||
.ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore())
|
||||
.ForMember(display => display.Notifications, opt => opt.Ignore())
|
||||
.ForMember(display => display.AllowCultureVariant, opt => opt.MapFrom(type => type.VariesByCulture()))
|
||||
.AfterMap((source, dest) =>
|
||||
{
|
||||
//sync templates
|
||||
dest.AllowedTemplates = source.AllowedTemplates.Select(Mapper.Map<EntityBasic>).ToArray();
|
||||
|
||||
if (source.DefaultTemplate != null)
|
||||
dest.DefaultTemplate = Mapper.Map<EntityBasic>(source.DefaultTemplate);
|
||||
|
||||
//default listview
|
||||
dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content";
|
||||
|
||||
if (string.IsNullOrEmpty(source.Alias) == false)
|
||||
{
|
||||
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias;
|
||||
if (dataTypeService.GetDataType(name) != null)
|
||||
dest.ListViewEditorName = name;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
CreateMap<IContentTypeComposition, ContentTypeBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MemberType, source.Key)))
|
||||
.ForMember(dest => dest.Blueprints, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
CreateMap<IMemberType, ContentTypeBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MemberType, source.Key)))
|
||||
.ForMember(dest => dest.Blueprints, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
CreateMap<IMediaType, ContentTypeBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MediaType, source.Key)))
|
||||
.ForMember(dest => dest.Blueprints, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
CreateMap<IContentType, ContentTypeBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.DocumentType, source.Key)))
|
||||
.ForMember(dest => dest.Blueprints, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<PropertyTypeBasic, PropertyType>()
|
||||
|
||||
.ConstructUsing((propertyTypeBasic, context) =>
|
||||
{
|
||||
var dataType = dataTypeService.GetDataType(propertyTypeBasic.DataTypeId);
|
||||
if (dataType == null) throw new NullReferenceException("No data type found with id " + propertyTypeBasic.DataTypeId);
|
||||
return new PropertyType(dataType, propertyTypeBasic.Alias);
|
||||
})
|
||||
|
||||
.IgnoreEntityCommonProperties()
|
||||
|
||||
.ForMember(dest => dest.SupportsPublishing, opt => opt.Ignore())
|
||||
|
||||
// see note above - have to do this here?
|
||||
.ForMember(dest => dest.PropertyEditorAlias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.DeleteDate, opt => opt.Ignore())
|
||||
|
||||
.ForMember(dto => dto.Variations, opt => opt.MapFrom<PropertyTypeVariationsResolver>())
|
||||
|
||||
//only map if it is actually set
|
||||
.ForMember(dest => dest.Id, opt => opt.Condition(source => source.Id > 0))
|
||||
//only map if it is actually set, if it's not set, it needs to be handled differently and will be taken care of in the
|
||||
// IContentType.AddPropertyType
|
||||
.ForMember(dest => dest.PropertyGroupId, opt => opt.Condition(source => source.GroupId > 0))
|
||||
.ForMember(dest => dest.PropertyGroupId, opt => opt.MapFrom(display => new Lazy<int>(() => display.GroupId, false)))
|
||||
.ForMember(dest => dest.Key, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.HasIdentity, opt => opt.Ignore())
|
||||
//ignore because this is set in the ctor NOT ON UPDATE, STUPID!
|
||||
//.ForMember(type => type.Alias, opt => opt.Ignore())
|
||||
//ignore because this is obsolete and shouldn't be used
|
||||
.ForMember(dest => dest.DataTypeId, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Mandatory, opt => opt.MapFrom(source => source.Validation.Mandatory))
|
||||
.ForMember(dest => dest.ValidationRegExp, opt => opt.MapFrom(source => source.Validation.Pattern))
|
||||
.ForMember(dest => dest.DataTypeId, opt => opt.MapFrom(source => source.DataTypeId))
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom(source => source.Label));
|
||||
|
||||
#region *** Used for mapping on top of an existing display object from a save object ***
|
||||
|
||||
CreateMap<MemberTypeSave, MemberTypeDisplay>()
|
||||
.MapBaseContentTypeSaveToDisplay<MemberTypeSave, MemberPropertyTypeBasic, MemberTypeDisplay, MemberPropertyTypeDisplay>();
|
||||
|
||||
CreateMap<MediaTypeSave, MediaTypeDisplay>()
|
||||
.MapBaseContentTypeSaveToDisplay<MediaTypeSave, PropertyTypeBasic, MediaTypeDisplay, PropertyTypeDisplay>();
|
||||
|
||||
CreateMap<DocumentTypeSave, DocumentTypeDisplay>()
|
||||
.MapBaseContentTypeSaveToDisplay<DocumentTypeSave, PropertyTypeBasic, DocumentTypeDisplay, PropertyTypeDisplay>()
|
||||
.ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore())
|
||||
.ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore())
|
||||
.AfterMap((source, dest) =>
|
||||
{
|
||||
//sync templates
|
||||
var destAllowedTemplateAliases = dest.AllowedTemplates.Select(x => x.Alias);
|
||||
//if the dest is set and it's the same as the source, then don't change
|
||||
if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false)
|
||||
{
|
||||
var templates = fileService.GetTemplates(source.AllowedTemplates.ToArray());
|
||||
dest.AllowedTemplates = source.AllowedTemplates
|
||||
.Select(x => Mapper.Map<EntityBasic>(templates.SingleOrDefault(t => t.Alias == x)))
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
if (source.DefaultTemplate.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//if the dest is set and it's the same as the source, then don't change
|
||||
if (dest.DefaultTemplate == null || source.DefaultTemplate != dest.DefaultTemplate.Alias)
|
||||
{
|
||||
var template = fileService.GetTemplate(source.DefaultTemplate);
|
||||
dest.DefaultTemplate = template == null ? null : Mapper.Map<EntityBasic>(template);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dest.DefaultTemplate = null;
|
||||
}
|
||||
});
|
||||
|
||||
//for doc types, media types
|
||||
CreateMap<PropertyGroupBasic<PropertyTypeBasic>, PropertyGroup>()
|
||||
.MapPropertyGroupBasicToPropertyGroupPersistence<PropertyGroupBasic<PropertyTypeBasic>, PropertyTypeBasic>();
|
||||
|
||||
//for members
|
||||
CreateMap<PropertyGroupBasic<MemberPropertyTypeBasic>, PropertyGroup>()
|
||||
.MapPropertyGroupBasicToPropertyGroupPersistence<PropertyGroupBasic<MemberPropertyTypeBasic>, MemberPropertyTypeBasic>();
|
||||
|
||||
//for doc types, media types
|
||||
CreateMap<PropertyGroupBasic<PropertyTypeBasic>, PropertyGroupDisplay<PropertyTypeDisplay>>()
|
||||
.MapPropertyGroupBasicToPropertyGroupDisplay<PropertyGroupBasic<PropertyTypeBasic>, PropertyTypeBasic, PropertyTypeDisplay>();
|
||||
|
||||
//for members
|
||||
CreateMap<PropertyGroupBasic<MemberPropertyTypeBasic>, PropertyGroupDisplay<MemberPropertyTypeDisplay>>()
|
||||
.MapPropertyGroupBasicToPropertyGroupDisplay<PropertyGroupBasic<MemberPropertyTypeBasic>, MemberPropertyTypeBasic, MemberPropertyTypeDisplay>();
|
||||
|
||||
CreateMap<PropertyTypeBasic, PropertyTypeDisplay>()
|
||||
.ForMember(g => g.Editor, opt => opt.Ignore())
|
||||
.ForMember(g => g.View, opt => opt.Ignore())
|
||||
.ForMember(g => g.Config, opt => opt.Ignore())
|
||||
.ForMember(g => g.ContentTypeId, opt => opt.Ignore())
|
||||
.ForMember(g => g.ContentTypeName, opt => opt.Ignore())
|
||||
.ForMember(g => g.Locked, exp => exp.Ignore());
|
||||
|
||||
CreateMap<MemberPropertyTypeBasic, MemberPropertyTypeDisplay>()
|
||||
.ForMember(g => g.Editor, opt => opt.Ignore())
|
||||
.ForMember(g => g.View, opt => opt.Ignore())
|
||||
.ForMember(g => g.Config, opt => opt.Ignore())
|
||||
.ForMember(g => g.ContentTypeId, opt => opt.Ignore())
|
||||
.ForMember(g => g.ContentTypeName, opt => opt.Ignore())
|
||||
.ForMember(g => g.Locked, exp => exp.Ignore());
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Used as a shared way to do the underlying mapping for content types base classes
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We used to use 'Include' Automapper inheritance functionality and although this works, the unit test
|
||||
/// to assert mappings fails which is an Automapper bug. So instead we will use an extension method for the mappings
|
||||
/// to re-use mappings.
|
||||
/// </remarks>
|
||||
internal static class ContentTypeProfileExtensions
|
||||
{
|
||||
public static IMappingExpression<TSource, PropertyGroup> MapPropertyGroupBasicToPropertyGroupPersistence<TSource, TPropertyTypeBasic>(
|
||||
this IMappingExpression<TSource, PropertyGroup> mapping)
|
||||
where TSource : PropertyGroupBasic<TPropertyTypeBasic>
|
||||
where TPropertyTypeBasic : PropertyTypeBasic
|
||||
{
|
||||
return mapping
|
||||
.ConstructUsing(x => new PropertyGroup(false)) // TODO: we have NO idea of isPublishing here = so what?
|
||||
.IgnoreEntityCommonProperties()
|
||||
.ForMember(dest => dest.Id, map => map.Condition(src => src.Id > 0))
|
||||
.ForMember(dest => dest.Key, map => map.Ignore())
|
||||
.ForMember(dest => dest.HasIdentity, map => map.Ignore())
|
||||
.ForMember(dest => dest.DeleteDate, map => map.Ignore())
|
||||
.ForMember(dest => dest.PropertyTypes, map => map.Ignore());
|
||||
}
|
||||
|
||||
public static IMappingExpression<TSource, PropertyGroupDisplay<TPropertyTypeDisplay>> MapPropertyGroupBasicToPropertyGroupDisplay<TSource, TPropertyTypeBasic, TPropertyTypeDisplay>(
|
||||
this IMappingExpression<TSource, PropertyGroupDisplay<TPropertyTypeDisplay>> mapping)
|
||||
where TSource : PropertyGroupBasic<TPropertyTypeBasic>
|
||||
where TPropertyTypeBasic : PropertyTypeBasic
|
||||
where TPropertyTypeDisplay : PropertyTypeDisplay
|
||||
{
|
||||
return mapping
|
||||
.ForMember(dest => dest.Id, opt => opt.Condition(src => src.Id > 0))
|
||||
.ForMember(dest => dest.ContentTypeId, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ParentTabContentTypes, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ParentTabContentTypeNames, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Properties, opt => opt.MapFrom(src => src.Properties.Select(Mapper.Map<TPropertyTypeDisplay>)));
|
||||
}
|
||||
|
||||
public static void AfterMapContentTypeSaveToEntity<TSource, TDestination>(TSource source, TDestination dest, IContentTypeService contentTypeService)
|
||||
where TSource : ContentTypeSave
|
||||
where TDestination : IContentTypeComposition
|
||||
{
|
||||
//sync compositions
|
||||
var current = dest.CompositionAliases().ToArray();
|
||||
var proposed = source.CompositeContentTypes;
|
||||
|
||||
var remove = current.Where(x => proposed.Contains(x) == false);
|
||||
var add = proposed.Where(x => current.Contains(x) == false);
|
||||
|
||||
foreach (var rem in remove)
|
||||
{
|
||||
dest.RemoveContentType(rem);
|
||||
}
|
||||
|
||||
foreach (var a in add)
|
||||
{
|
||||
// TODO: Remove N+1 lookup
|
||||
var addCt = contentTypeService.Get(a);
|
||||
if (addCt != null)
|
||||
dest.AddContentType(addCt);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AfterMapMediaTypeSaveToEntity<TSource, TDestination>(TSource source, TDestination dest, IMediaTypeService mediaTypeService)
|
||||
where TSource : MediaTypeSave
|
||||
where TDestination : IContentTypeComposition
|
||||
{
|
||||
//sync compositions
|
||||
var current = dest.CompositionAliases().ToArray();
|
||||
var proposed = source.CompositeContentTypes;
|
||||
|
||||
var remove = current.Where(x => proposed.Contains(x) == false);
|
||||
var add = proposed.Where(x => current.Contains(x) == false);
|
||||
|
||||
foreach (var rem in remove)
|
||||
{
|
||||
dest.RemoveContentType(rem);
|
||||
}
|
||||
|
||||
foreach (var a in add)
|
||||
{
|
||||
// TODO: Remove N+1 lookup
|
||||
var addCt = mediaTypeService.Get(a);
|
||||
if (addCt != null)
|
||||
dest.AddContentType(addCt);
|
||||
}
|
||||
}
|
||||
|
||||
public static IMappingExpression<TSource, TDestination> MapBaseContentTypeSaveToDisplay<TSource, TPropertyTypeSource, TDestination, TPropertyTypeDestination>(
|
||||
this IMappingExpression<TSource, TDestination> mapping)
|
||||
where TSource : ContentTypeSave<TPropertyTypeSource>
|
||||
where TDestination : ContentTypeCompositionDisplay<TPropertyTypeDestination>
|
||||
where TPropertyTypeDestination : PropertyTypeDisplay
|
||||
where TPropertyTypeSource : PropertyTypeBasic
|
||||
{
|
||||
var propertyGroupDisplayResolver = new PropertyGroupDisplayResolver<TSource, TPropertyTypeSource, TPropertyTypeDestination>();
|
||||
|
||||
return mapping
|
||||
.ForMember(dest => dest.CreateDate, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.UpdateDate, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ListViewEditorName, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Errors, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.LockedCompositeContentTypes, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Groups, opt => opt.MapFrom(src => propertyGroupDisplayResolver.Resolve(src)));
|
||||
}
|
||||
|
||||
public static IMappingExpression<TSource, TDestination> MapBaseContentTypeEntityToDisplay<TSource, TDestination, TPropertyTypeDisplay>(
|
||||
this IMappingExpression<TSource, TDestination> mapping, PropertyEditorCollection propertyEditors,
|
||||
IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILogger logger)
|
||||
where TSource : IContentTypeComposition
|
||||
where TDestination : ContentTypeCompositionDisplay<TPropertyTypeDisplay>
|
||||
where TPropertyTypeDisplay : PropertyTypeDisplay, new()
|
||||
{
|
||||
var contentTypeUdiResolver = new ContentTypeUdiResolver();
|
||||
var lockedCompositionsResolver = new LockedCompositionsResolver(contentTypeService);
|
||||
var propertyTypeGroupResolver = new PropertyTypeGroupResolver<TPropertyTypeDisplay>(propertyEditors, dataTypeService, logger);
|
||||
|
||||
return mapping
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => contentTypeUdiResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Blueprints, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Errors, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AllowAsRoot, opt => opt.MapFrom(src => src.AllowedAsRoot))
|
||||
.ForMember(dest => dest.ListViewEditorName, opt => opt.Ignore())
|
||||
//Ignore because this is not actually used for content types
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
|
||||
.ForMember(dest => dest.AllowedContentTypes, opt => opt.MapFrom(src => src.AllowedContentTypes.Select(x => x.Id.Value)))
|
||||
.ForMember(dest => dest.CompositeContentTypes, opt => opt.MapFrom(src => src.ContentTypeComposition))
|
||||
.ForMember(dest => dest.LockedCompositeContentTypes, opt => opt.MapFrom(src => lockedCompositionsResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Groups, opt => opt.MapFrom(src => propertyTypeGroupResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display -> Entity class base mapping logic
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource"></typeparam>
|
||||
/// <typeparam name="TDestination"></typeparam>
|
||||
/// <typeparam name="TSourcePropertyType"></typeparam>
|
||||
/// <param name="mapping"></param>
|
||||
/// <returns></returns>
|
||||
public static IMappingExpression<TSource, TDestination> MapBaseContentTypeSaveToEntity<TSource, TSourcePropertyType, TDestination>(
|
||||
this IMappingExpression<TSource, TDestination> mapping)
|
||||
//where TSource : ContentTypeCompositionDisplay
|
||||
where TSource : ContentTypeSave<TSourcePropertyType>
|
||||
where TDestination : IContentTypeComposition
|
||||
where TSourcePropertyType : PropertyTypeBasic
|
||||
{
|
||||
// TODO: not so clean really
|
||||
var isPublishing = typeof(IContentType).IsAssignableFrom(typeof(TDestination));
|
||||
|
||||
mapping = mapping
|
||||
//only map id if set to something higher then zero
|
||||
.ForMember(dest => dest.Id, opt => opt.Condition(src => (Convert.ToInt32(src.Id) > 0)))
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id)))
|
||||
|
||||
//These get persisted as part of the saving procedure, nothing to do with the display model
|
||||
.IgnoreEntityCommonProperties()
|
||||
|
||||
.ForMember(dest => dest.AllowedAsRoot, opt => opt.MapFrom(src => src.AllowAsRoot))
|
||||
.ForMember(dest => dest.CreatorId, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Level, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.SortOrder, opt => opt.Ignore())
|
||||
//ignore, we'll do this in after map
|
||||
.ForMember(dest => dest.PropertyGroups, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.NoGroupPropertyTypes, opt => opt.Ignore())
|
||||
// ignore, composition is managed in AfterMapContentTypeSaveToEntity
|
||||
.ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore());
|
||||
|
||||
// ignore for members
|
||||
mapping = typeof(TDestination) == typeof(IMemberType)
|
||||
? mapping.ForMember(dto => dto.Variations, opt => opt.Ignore())
|
||||
: mapping.ForMember(dto => dto.Variations, opt => opt.MapFrom<ContentTypeVariationsResolver<TSource, TSourcePropertyType, TDestination>>());
|
||||
|
||||
mapping = mapping
|
||||
.ForMember(
|
||||
dest => dest.AllowedContentTypes,
|
||||
opt => opt.MapFrom(src => src.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i))))
|
||||
|
||||
.AfterMap((src, dest) =>
|
||||
{
|
||||
// handle property groups and property types
|
||||
// note that ContentTypeSave has
|
||||
// - all groups, inherited and local; only *one* occurrence per group *name*
|
||||
// - potentially including the generic properties group
|
||||
// - all properties, inherited and local
|
||||
//
|
||||
// also, see PropertyTypeGroupResolver.ResolveCore:
|
||||
// - if a group is local *and* inherited, then Inherited is true
|
||||
// and the identifier is the identifier of the *local* group
|
||||
//
|
||||
// IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some
|
||||
// unique-alias-checking, etc that is *not* compatible with re-mapping everything
|
||||
// the way we do it here, so we should exclusively do it by
|
||||
// - managing a property group's PropertyTypes collection
|
||||
// - managing the content type's PropertyTypes collection (for generic properties)
|
||||
|
||||
// handle actual groups (non-generic-properties)
|
||||
var destOrigGroups = dest.PropertyGroups.ToArray(); // local groups
|
||||
var destOrigProperties = dest.PropertyTypes.ToArray(); // all properties, in groups or not
|
||||
var destGroups = new List<PropertyGroup>();
|
||||
var sourceGroups = src.Groups.Where(x => x.IsGenericProperties == false).ToArray();
|
||||
foreach (var sourceGroup in sourceGroups)
|
||||
{
|
||||
// get the dest group
|
||||
var destGroup = MapSaveGroup(sourceGroup, destOrigGroups);
|
||||
|
||||
// handle local properties
|
||||
var destProperties = sourceGroup.Properties
|
||||
.Where(x => x.Inherited == false)
|
||||
.Select(x => MapSaveProperty(x, destOrigProperties))
|
||||
.ToArray();
|
||||
|
||||
// if the group has no local properties, skip it, ie sort-of garbage-collect
|
||||
// local groups which would not have local properties anymore
|
||||
if (destProperties.Length == 0)
|
||||
continue;
|
||||
|
||||
// ensure no duplicate alias, then assign the group properties collection
|
||||
EnsureUniqueAliases(destProperties);
|
||||
destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
|
||||
destGroups.Add(destGroup);
|
||||
}
|
||||
|
||||
// ensure no duplicate name, then assign the groups collection
|
||||
EnsureUniqueNames(destGroups);
|
||||
dest.PropertyGroups = new PropertyGroupCollection(destGroups);
|
||||
|
||||
// because the property groups collection was rebuilt, there is no need to remove
|
||||
// the old groups - they are just gone and will be cleared by the repository
|
||||
|
||||
// handle non-grouped (ie generic) properties
|
||||
var genericPropertiesGroup = src.Groups.FirstOrDefault(x => x.IsGenericProperties);
|
||||
if (genericPropertiesGroup != null)
|
||||
{
|
||||
// handle local properties
|
||||
var destProperties = genericPropertiesGroup.Properties
|
||||
.Where(x => x.Inherited == false)
|
||||
.Select(x => MapSaveProperty(x, destOrigProperties))
|
||||
.ToArray();
|
||||
|
||||
// ensure no duplicate alias, then assign the generic properties collection
|
||||
EnsureUniqueAliases(destProperties);
|
||||
dest.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties);
|
||||
}
|
||||
|
||||
// because all property collections were rebuilt, there is no need to remove
|
||||
// some old properties, they are just gone and will be cleared by the repository
|
||||
});
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private static PropertyGroup MapSaveGroup<TPropertyType>(PropertyGroupBasic<TPropertyType> sourceGroup, IEnumerable<PropertyGroup> destOrigGroups)
|
||||
where TPropertyType : PropertyTypeBasic
|
||||
{
|
||||
PropertyGroup destGroup;
|
||||
if (sourceGroup.Id > 0)
|
||||
{
|
||||
// update an existing group
|
||||
// ensure it is still there, then map/update
|
||||
destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id);
|
||||
if (destGroup != null)
|
||||
{
|
||||
Mapper.Map(sourceGroup, destGroup);
|
||||
return destGroup;
|
||||
}
|
||||
|
||||
// force-clear the ID as it does not match anything
|
||||
sourceGroup.Id = 0;
|
||||
}
|
||||
|
||||
// insert a new group, or update an existing group that has
|
||||
// been deleted in the meantime and we need to re-create
|
||||
// map/create
|
||||
destGroup = Mapper.Map<PropertyGroup>(sourceGroup);
|
||||
return destGroup;
|
||||
}
|
||||
|
||||
private static PropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable<PropertyType> destOrigProperties)
|
||||
{
|
||||
PropertyType destProperty;
|
||||
if (sourceProperty.Id > 0)
|
||||
{
|
||||
// updating an existing property
|
||||
// ensure it is still there, then map/update
|
||||
destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id);
|
||||
if (destProperty != null)
|
||||
{
|
||||
Mapper.Map(sourceProperty, destProperty);
|
||||
return destProperty;
|
||||
}
|
||||
|
||||
// force-clear the ID as it does not match anything
|
||||
sourceProperty.Id = 0;
|
||||
}
|
||||
|
||||
// insert a new property, or update an existing property that has
|
||||
// been deleted in the meantime and we need to re-create
|
||||
// map/create
|
||||
destProperty = Mapper.Map<PropertyType>(sourceProperty);
|
||||
return destProperty;
|
||||
}
|
||||
|
||||
private static void EnsureUniqueAliases(IEnumerable<PropertyType> properties)
|
||||
{
|
||||
var propertiesA = properties.ToArray();
|
||||
var distinctProperties = propertiesA
|
||||
.Select(x => x.Alias.ToUpperInvariant())
|
||||
.Distinct()
|
||||
.Count();
|
||||
if (distinctProperties != propertiesA.Length)
|
||||
throw new InvalidOperationException("Cannot map properties due to alias conflict.");
|
||||
}
|
||||
|
||||
private static void EnsureUniqueNames(IEnumerable<PropertyGroup> groups)
|
||||
{
|
||||
var groupsA = groups.ToArray();
|
||||
var distinctProperties = groupsA
|
||||
.Select(x => x.Name.ToUpperInvariant())
|
||||
.Distinct()
|
||||
.Count();
|
||||
if (distinctProperties != groupsA.Length)
|
||||
throw new InvalidOperationException("Cannot map groups due to name conflict.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a UDI for a content type based on it's type
|
||||
/// </summary>
|
||||
internal class ContentTypeUdiResolver
|
||||
{
|
||||
public Udi Resolve(IContentTypeComposition source)
|
||||
{
|
||||
if (source == null) return null;
|
||||
|
||||
return Udi.Create(
|
||||
source.GetType() == typeof(IMemberType)
|
||||
? Constants.UdiEntityType.MemberType
|
||||
: source.GetType() == typeof(IMediaType)
|
||||
? Constants.UdiEntityType.MediaType : Constants.UdiEntityType.DocumentType, source.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using ContentVariation = Umbraco.Core.Models.ContentVariation;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class ContentTypeVariationsResolver<TSource, TSourcePropertyType, TDestination> : IValueResolver<TSource, TDestination, ContentVariation>
|
||||
where TSource : ContentTypeSave<TSourcePropertyType>
|
||||
where TDestination : IContentTypeComposition
|
||||
where TSourcePropertyType : PropertyTypeBasic
|
||||
{
|
||||
public ContentVariation Resolve(TSource source, TDestination destination, ContentVariation destMember, ResolutionContext context)
|
||||
{
|
||||
//this will always be the case, a content type will always be allowed to be invariant
|
||||
var result = ContentVariation.Nothing;
|
||||
|
||||
if (source.AllowCultureVariant)
|
||||
result |= ContentVariation.Culture;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class ContentUrlResolver : IValueResolver<IContent, ContentItemDisplay, UrlInfo[]>
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IPublishedRouter _publishedRouter;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ContentUrlResolver(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IPublishedRouter publishedRouter,
|
||||
ILocalizationService localizationService,
|
||||
ILocalizedTextService textService,
|
||||
IContentService contentService,
|
||||
ILogger logger)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_publishedRouter = publishedRouter ?? throw new System.ArgumentNullException(nameof(publishedRouter));
|
||||
_localizationService = localizationService ?? throw new System.ArgumentNullException(nameof(localizationService));
|
||||
_textService = textService ?? throw new System.ArgumentNullException(nameof(textService));
|
||||
_contentService = contentService ?? throw new System.ArgumentNullException(nameof(contentService));
|
||||
_logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public UrlInfo[] Resolve(IContent source, ContentItemDisplay destination, UrlInfo[] destMember, ResolutionContext context)
|
||||
{
|
||||
if (source.ContentType.IsElement)
|
||||
{
|
||||
return new UrlInfo[0];
|
||||
}
|
||||
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
|
||||
var urls = umbracoContext == null
|
||||
? new[] { UrlInfo.Message("Cannot generate urls without a current Umbraco Context") }
|
||||
: source.GetContentUrls(_publishedRouter, umbracoContext, _localizationService, _textService, _contentService, _logger).ToArray();
|
||||
|
||||
return urls;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
@@ -10,37 +10,37 @@ using Language = Umbraco.Web.Models.ContentEditing.Language;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class ContentVariantResolver : IValueResolver<IContent, ContentItemDisplay, IEnumerable<ContentVariantDisplay>>
|
||||
internal class ContentVariantMapper
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
public ContentVariantResolver(ILocalizationService localizationService)
|
||||
public ContentVariantMapper(ILocalizationService localizationService)
|
||||
{
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
}
|
||||
|
||||
public IEnumerable<ContentVariantDisplay> Resolve(IContent source, ContentItemDisplay destination, IEnumerable<ContentVariantDisplay> destMember, ResolutionContext context)
|
||||
public IEnumerable<ContentVariantDisplay> Map(IContent source, MapperContext context)
|
||||
{
|
||||
var result = new List<ContentVariantDisplay>();
|
||||
if (!source.ContentType.VariesByCulture())
|
||||
{
|
||||
//this is invariant so just map the IContent instance to ContentVariationDisplay
|
||||
result.Add(context.Mapper.Map<ContentVariantDisplay>(source));
|
||||
result.Add(context.Map<ContentVariantDisplay>(source));
|
||||
}
|
||||
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.Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(allLanguages, null, context).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 =>
|
||||
{
|
||||
//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.Options.SetCulture(x.IsoCode);
|
||||
return context.Mapper.Map<IContent, ContentVariantDisplay>(source, null, context);
|
||||
context.SetCulture(x.IsoCode);
|
||||
return context.Map<ContentVariantDisplay>(source);
|
||||
}).ToList();
|
||||
|
||||
for (int i = 0; i < langs.Count; i++)
|
||||
@@ -69,5 +69,4 @@ namespace Umbraco.Web.Models.Mapping
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps the Creator for content
|
||||
/// </summary>
|
||||
internal class CreatorResolver
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public CreatorResolver(IUserService userService)
|
||||
{
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public UserProfile Resolve(IContent source)
|
||||
{
|
||||
return Mapper.Map<IProfile, UserProfile>(source.GetWriterProfile(_userService));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class DataTypeConfigurationFieldDisplayResolver
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DataTypeConfigurationFieldDisplayResolver(ILogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps pre-values in the dictionary to the values for the fields
|
||||
/// </summary>
|
||||
internal static void MapConfigurationFields(ILogger logger, DataTypeConfigurationFieldDisplay[] fields, IDictionary<string, object> configuration)
|
||||
{
|
||||
if (fields == null) throw new ArgumentNullException(nameof(fields));
|
||||
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
// now we need to wire up the pre-values values with the actual fields defined
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (configuration.TryGetValue(field.Key, out var value))
|
||||
field.Value = value;
|
||||
else
|
||||
{
|
||||
// weird - just leave the field without a value - but warn
|
||||
logger.Warn<DataTypeConfigurationFieldDisplayResolver>("Could not find a value for configuration field '{ConfigField}'", field.Key);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a set of configuration fields for a data type.
|
||||
/// </summary>
|
||||
public IEnumerable<DataTypeConfigurationFieldDisplay> Resolve(IDataType dataType)
|
||||
{
|
||||
// in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto
|
||||
// an empty fields list, which made no sense since there would be nothing to map to - and besides,
|
||||
// a datatype without an editor alias is a serious issue - v8 wants an editor here
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !Current.PropertyEditors.TryGet(dataType.EditorAlias, out var editor))
|
||||
throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\".");
|
||||
|
||||
var configurationEditor = editor.GetConfigurationEditor();
|
||||
var fields = configurationEditor.Fields.Select(Mapper.Map<DataTypeConfigurationFieldDisplay>).ToArray();
|
||||
var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration);
|
||||
|
||||
MapConfigurationFields(_logger, fields, configurationDictionary);
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
202
src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs
Normal file
202
src/Umbraco.Web/Models/Mapping/DataTypeMapDefinition.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class DataTypeMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger logger)
|
||||
{
|
||||
_propertyEditors = propertyEditors;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static readonly int[] SystemIds =
|
||||
{
|
||||
Constants.DataTypes.DefaultContentListView,
|
||||
Constants.DataTypes.DefaultMediaListView,
|
||||
Constants.DataTypes.DefaultMembersListView
|
||||
};
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IDataEditor, PropertyEditorBasic>((source, context) => new PropertyEditorBasic(), Map);
|
||||
mapper.Define<ConfigurationField, DataTypeConfigurationFieldDisplay>((source, context) => new DataTypeConfigurationFieldDisplay(), Map);
|
||||
mapper.Define<IDataEditor, DataTypeBasic>((source, context) => new DataTypeBasic(), Map);
|
||||
mapper.Define<IDataType, DataTypeBasic>((source, context) => new DataTypeBasic(), Map);
|
||||
mapper.Define<IDataType, DataTypeDisplay>((source, context) => new DataTypeDisplay(), Map);
|
||||
mapper.Define<IDataType, IEnumerable<DataTypeConfigurationFieldDisplay>>(MapPreValues);
|
||||
mapper.Define<DataTypeSave, IDataType>((source, context) => new DataType(_propertyEditors[source.EditorAlias]) { CreateDate = DateTime.Now },Map);
|
||||
mapper.Define<IDataEditor, IEnumerable<DataTypeConfigurationFieldDisplay>>(MapPreValues);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(IDataEditor source, PropertyEditorBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = source.Icon;
|
||||
target.Name = source.Name;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Value
|
||||
private static void Map(ConfigurationField source, DataTypeConfigurationFieldDisplay target, MapperContext context)
|
||||
{
|
||||
target.Config = source.Config;
|
||||
target.Description = source.Description;
|
||||
target.HideLabel = source.HideLabel;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.View = source.View;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -HasPrevalues -IsSystemDataType -Id -Trashed -Key
|
||||
// Umbraco.Code.MapAll -ParentId -Path
|
||||
private static void Map(IDataEditor source, DataTypeBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Group = source.Group;
|
||||
target.Icon = source.Icon;
|
||||
target.Name = source.Name;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -HasPrevalues
|
||||
private void Map(IDataType source, DataTypeBasic target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.IsSystemDataType = SystemIds.Contains(source.Id);
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key);
|
||||
|
||||
if (!_propertyEditors.TryGet(source.EditorAlias, out var editor))
|
||||
return;
|
||||
|
||||
target.Alias = editor.Alias;
|
||||
target.Group = editor.Group;
|
||||
target.Icon = editor.Icon;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -HasPrevalues
|
||||
private void Map(IDataType source, DataTypeDisplay target, MapperContext context)
|
||||
{
|
||||
target.AvailableEditors = MapAvailableEditors(source, context);
|
||||
target.Id = source.Id;
|
||||
target.IsSystemDataType = SystemIds.Contains(source.Id);
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.PreValues = MapPreValues(source, context);
|
||||
target.SelectedEditor = source.EditorAlias.IsNullOrWhiteSpace() ? null : source.EditorAlias;
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key);
|
||||
|
||||
if (!_propertyEditors.TryGet(source.EditorAlias, out var editor))
|
||||
return;
|
||||
|
||||
target.Alias = editor.Alias;
|
||||
target.Group = editor.Group;
|
||||
target.Icon = editor.Icon;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate
|
||||
// Umbraco.Code.MapAll -Key -Path -CreatorId -Level -SortOrder -Configuration
|
||||
private void Map(DataTypeSave source, IDataType target, MapperContext context)
|
||||
{
|
||||
target.DatabaseType = MapDatabaseType(source);
|
||||
target.Editor = _propertyEditors[source.EditorAlias];
|
||||
target.Id = Convert.ToInt32(source.Id);
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
}
|
||||
|
||||
private IEnumerable<PropertyEditorBasic> MapAvailableEditors(IDataType source, MapperContext context)
|
||||
{
|
||||
var contentSection = Current.Configs.Settings().Content;
|
||||
var properties = _propertyEditors
|
||||
.Where(x => !x.IsDeprecated || contentSection.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias)
|
||||
.OrderBy(x => x.Name);
|
||||
return context.MapEnumerable<IDataEditor, PropertyEditorBasic>(properties);
|
||||
}
|
||||
|
||||
private IEnumerable<DataTypeConfigurationFieldDisplay> MapPreValues(IDataType dataType, MapperContext context)
|
||||
{
|
||||
// in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto
|
||||
// an empty fields list, which made no sense since there would be nothing to map to - and besides,
|
||||
// a datatype without an editor alias is a serious issue - v8 wants an editor here
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !Current.PropertyEditors.TryGet(dataType.EditorAlias, out var editor))
|
||||
throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\".");
|
||||
|
||||
var configurationEditor = editor.GetConfigurationEditor();
|
||||
var fields = context.MapEnumerable<ConfigurationField,DataTypeConfigurationFieldDisplay>(configurationEditor.Fields);
|
||||
var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration);
|
||||
|
||||
MapConfigurationFields(fields, configurationDictionary);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private void MapConfigurationFields(List<DataTypeConfigurationFieldDisplay> fields, IDictionary<string, object> configuration)
|
||||
{
|
||||
if (fields == null) throw new ArgumentNullException(nameof(fields));
|
||||
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
// now we need to wire up the pre-values values with the actual fields defined
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (configuration.TryGetValue(field.Key, out var value))
|
||||
{
|
||||
field.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// weird - just leave the field without a value - but warn
|
||||
_logger.Warn<DataTypeMapDefinition>("Could not find a value for configuration field '{ConfigField}'", field.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ValueStorageType MapDatabaseType(DataTypeSave source)
|
||||
{
|
||||
if (!_propertyEditors.TryGet(source.EditorAlias, out var editor))
|
||||
throw new InvalidOperationException($"Could not find property editor \"{source.EditorAlias}\".");
|
||||
|
||||
// TODO: what about source.PropertyEditor? can we get the configuration here? 'cos it may change the storage type?!
|
||||
var valueType = editor.GetValueEditor().ValueType;
|
||||
return ValueTypes.ToStorageType(valueType);
|
||||
}
|
||||
|
||||
private IEnumerable<DataTypeConfigurationFieldDisplay> MapPreValues(IDataEditor source, MapperContext context)
|
||||
{
|
||||
// this is a new data type, initialize default configuration
|
||||
// get the configuration editor,
|
||||
// get the configuration fields and map to UI,
|
||||
// get the configuration default values and map to UI
|
||||
|
||||
var configurationEditor = source.GetConfigurationEditor();
|
||||
|
||||
var fields = context.MapEnumerable<ConfigurationField, DataTypeConfigurationFieldDisplay>(configurationEditor.Fields);
|
||||
|
||||
var defaultConfiguration = configurationEditor.DefaultConfiguration;
|
||||
if (defaultConfiguration != null)
|
||||
MapConfigurationFields(fields, defaultConfiguration);
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures model mappings for datatypes.
|
||||
/// </summary>
|
||||
internal class DataTypeMapperProfile : Profile
|
||||
{
|
||||
public DataTypeMapperProfile(PropertyEditorCollection propertyEditors, ILogger logger)
|
||||
{
|
||||
// create, capture, cache
|
||||
var availablePropertyEditorsResolver = new AvailablePropertyEditorsResolver(Current.Configs.Settings().Content);
|
||||
var configurationDisplayResolver = new DataTypeConfigurationFieldDisplayResolver(logger);
|
||||
var databaseTypeResolver = new DatabaseTypeResolver();
|
||||
|
||||
CreateMap<IDataEditor, PropertyEditorBasic>();
|
||||
|
||||
// map the standard properties, not the values
|
||||
CreateMap<ConfigurationField, DataTypeConfigurationFieldDisplay>()
|
||||
.ForMember(dest => dest.Value, opt => opt.Ignore());
|
||||
|
||||
var systemIds = new[]
|
||||
{
|
||||
Constants.DataTypes.DefaultContentListView,
|
||||
Constants.DataTypes.DefaultMediaListView,
|
||||
Constants.DataTypes.DefaultMembersListView
|
||||
};
|
||||
|
||||
CreateMap<IDataEditor, DataTypeBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.HasPrevalues, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.IsSystemDataType, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Id, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Key, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ParentId, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<IDataType, DataTypeBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.DataType, src.Key)))
|
||||
.ForMember(dest => dest.HasPrevalues, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Group, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.IsSystemDataType, opt => opt.MapFrom(src => systemIds.Contains(src.Id)))
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.AfterMap((src, dest) =>
|
||||
{
|
||||
if (Current.PropertyEditors.TryGet(src.EditorAlias, out var editor))
|
||||
{
|
||||
dest.Alias = editor.Alias;
|
||||
dest.Group = editor.Group;
|
||||
dest.Icon = editor.Icon;
|
||||
}
|
||||
});
|
||||
|
||||
CreateMap<IDataType, DataTypeDisplay>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.DataType, src.Key)))
|
||||
.ForMember(dest => dest.AvailableEditors, opt => opt.MapFrom(src => availablePropertyEditorsResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.PreValues, opt => opt.MapFrom(src => configurationDisplayResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.SelectedEditor, opt => opt.MapFrom(src => src.EditorAlias.IsNullOrWhiteSpace() ? null : src.EditorAlias))
|
||||
.ForMember(dest => dest.HasPrevalues, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Group, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.IsSystemDataType, opt => opt.MapFrom(src => systemIds.Contains(src.Id)))
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.AfterMap((src, dest) =>
|
||||
{
|
||||
if (Current.PropertyEditors.TryGet(src.EditorAlias, out var editor))
|
||||
{
|
||||
dest.Group = editor.Group;
|
||||
dest.Icon = editor.Icon;
|
||||
}
|
||||
});
|
||||
|
||||
//gets a list of PreValueFieldDisplay objects from the data type definition
|
||||
CreateMap<IDataType, IEnumerable<DataTypeConfigurationFieldDisplay>>()
|
||||
.ConvertUsing(src => configurationDisplayResolver.Resolve(src));
|
||||
|
||||
CreateMap<DataTypeSave, IDataType>()
|
||||
.ConstructUsing(src => new DataType(propertyEditors[src.EditorAlias], -1) {CreateDate = DateTime.Now})
|
||||
.IgnoreEntityCommonProperties()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id)))
|
||||
.ForMember(dest => dest.Key, opt => opt.Ignore()) // ignore key, else resets UniqueId - U4-3911
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.EditorAlias, opt => opt.MapFrom(src => src.EditorAlias))
|
||||
.ForMember(dest => dest.DatabaseType, opt => opt.MapFrom(src => databaseTypeResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.CreatorId, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Level, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.SortOrder, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Configuration, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Editor, opt => opt.MapFrom(src => propertyEditors[src.EditorAlias]));
|
||||
|
||||
//Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals
|
||||
CreateMap<IDataEditor, IEnumerable<DataTypeConfigurationFieldDisplay>>()
|
||||
.ConvertUsing((dataEditor, configurationFieldDisplays) =>
|
||||
{
|
||||
// this is a new data type, initialize default configuration
|
||||
// get the configuration editor,
|
||||
// get the configuration fields and map to UI,
|
||||
// get the configuration default values and map to UI
|
||||
|
||||
var configurationEditor = dataEditor.GetConfigurationEditor();
|
||||
|
||||
var fields = configurationEditor.Fields.Select(Mapper.Map<DataTypeConfigurationFieldDisplay>).ToArray();
|
||||
|
||||
var defaultConfiguration = configurationEditor.DefaultConfiguration;
|
||||
if (defaultConfiguration != null)
|
||||
DataTypeConfigurationFieldDisplayResolver.MapConfigurationFields(logger, fields, defaultConfiguration);
|
||||
|
||||
return fields;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the DataTypeDatabaseType from the selected property editor for the data type
|
||||
/// </summary>
|
||||
internal class DatabaseTypeResolver
|
||||
{
|
||||
public ValueStorageType Resolve(DataTypeSave source)
|
||||
{
|
||||
if (!Current.PropertyEditors.TryGet(source.EditorAlias, out var editor))
|
||||
throw new InvalidOperationException($"Could not find property editor \"{source.EditorAlias}\".");
|
||||
|
||||
// TODO: what about source.PropertyEditor? can we get the configuration here? 'cos it may change the storage type?!
|
||||
var valueType = editor.GetValueEditor().ValueType;
|
||||
return ValueTypes.ToStorageType(valueType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using AutoMapper;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class DefaultTemplateResolver : IValueResolver<IContent, ContentItemDisplay, string>
|
||||
{
|
||||
public string Resolve(IContent source, ContentItemDisplay destination, string destMember, ResolutionContext context)
|
||||
{
|
||||
if (source == null)
|
||||
return null;
|
||||
|
||||
// If no template id was set...
|
||||
if (!source.TemplateId.HasValue)
|
||||
{
|
||||
// ... and no default template is set, return null...
|
||||
if (string.IsNullOrWhiteSpace(source.ContentType.DefaultTemplate?.Alias))
|
||||
return null;
|
||||
|
||||
// ... otherwise return the content type default template alias.
|
||||
return source.ContentType.DefaultTemplate?.Alias;
|
||||
}
|
||||
|
||||
var fileService = DependencyResolver.Current.GetService<IFileService>();
|
||||
var template = fileService.GetTemplate(source.TemplateId.Value);
|
||||
|
||||
return template.Alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
116
src/Umbraco.Web/Models/Mapping/DictionaryMapDefinition.cs
Normal file
116
src/Umbraco.Web/Models/Mapping/DictionaryMapDefinition.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The dictionary model mapper.
|
||||
/// </summary>
|
||||
internal class DictionaryMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
public DictionaryMapDefinition(ILocalizationService localizationService)
|
||||
{
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IDictionaryItem, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<IDictionaryItem, DictionaryDisplay>((source, context) => new DictionaryDisplay(), Map);
|
||||
mapper.Define<IDictionaryItem, DictionaryOverviewDisplay>((source, context) => new DictionaryOverviewDisplay(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -ParentId -Path -Trashed -Udi -Icon
|
||||
private static void Map(IDictionaryItem source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.ItemKey;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.ItemKey;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Icon -Trashed -Alias
|
||||
private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.ItemKey;
|
||||
target.ParentId = source.ParentId ?? Guid.Empty;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.DictionaryItem, source.Key);
|
||||
|
||||
// build up the path to make it possible to set active item in tree
|
||||
// TODO: check if there is a better way
|
||||
if (source.ParentId.HasValue)
|
||||
{
|
||||
var ids = new List<int> { -1 };
|
||||
var parentIds = new List<int>();
|
||||
GetParentId(source.ParentId.Value, _localizationService, parentIds);
|
||||
parentIds.Reverse();
|
||||
ids.AddRange(parentIds);
|
||||
ids.Add(source.Id);
|
||||
target.Path = string.Join(",", ids);
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Path = "-1," + source.Id;
|
||||
}
|
||||
|
||||
// add all languages and the translations
|
||||
foreach (var lang in _localizationService.GetAllLanguages())
|
||||
{
|
||||
var langId = lang.Id;
|
||||
var translation = source.Translations.FirstOrDefault(x => x.LanguageId == langId);
|
||||
|
||||
target.Translations.Add(new DictionaryTranslationDisplay
|
||||
{
|
||||
IsoCode = lang.IsoCode,
|
||||
DisplayName = lang.CultureInfo.DisplayName,
|
||||
Translation = (translation != null) ? translation.Value : string.Empty,
|
||||
LanguageId = lang.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Level -Translations
|
||||
private void Map(IDictionaryItem source, DictionaryOverviewDisplay target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Name = source.ItemKey;
|
||||
|
||||
// add all languages and the translations
|
||||
foreach (var lang in _localizationService.GetAllLanguages())
|
||||
{
|
||||
var langId = lang.Id;
|
||||
var translation = source.Translations.FirstOrDefault(x => x.LanguageId == langId);
|
||||
|
||||
target.Translations.Add(
|
||||
new DictionaryOverviewTranslationDisplay
|
||||
{
|
||||
DisplayName = lang.CultureInfo.DisplayName,
|
||||
HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetParentId(Guid parentId, ILocalizationService localizationService, List<int> ids)
|
||||
{
|
||||
var dictionary = localizationService.GetDictionaryItemById(parentId);
|
||||
if (dictionary == null)
|
||||
return;
|
||||
|
||||
ids.Add(dictionary.Id);
|
||||
|
||||
if (dictionary.ParentId.HasValue)
|
||||
GetParentId(dictionary.ParentId.Value, localizationService, ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
using AutoMapper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The dictionary model mapper.
|
||||
/// </summary>
|
||||
internal class DictionaryMapperProfile : Profile
|
||||
{
|
||||
public DictionaryMapperProfile(ILocalizationService localizationService)
|
||||
{
|
||||
CreateMap<IDictionaryItem, EntityBasic>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(sheet => sheet.Id))
|
||||
.ForMember(dest => dest.Alias, opt => opt.MapFrom(sheet => sheet.ItemKey))
|
||||
.ForMember(dest => dest.Key, opt => opt.MapFrom(sheet => sheet.Key))
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom(sheet => sheet.ItemKey))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.Ignore());
|
||||
|
||||
CreateMap<IDictionaryItem, DictionaryDisplay>()
|
||||
.ForMember(x => x.Translations, expression => expression.Ignore())
|
||||
.ForMember(x => x.Notifications, expression => expression.Ignore())
|
||||
.ForMember(x => x.Icon, expression => expression.Ignore())
|
||||
.ForMember(x => x.Trashed, expression => expression.Ignore())
|
||||
.ForMember(x => x.Alias, expression => expression.Ignore())
|
||||
.ForMember(x => x.Path, expression => expression.Ignore())
|
||||
.ForMember(x => x.AdditionalData, expression => expression.Ignore())
|
||||
.ForMember(
|
||||
x => x.Udi,
|
||||
expression => expression.MapFrom(
|
||||
content => Udi.Create(Constants.UdiEntityType.DictionaryItem, content.Key))).ForMember(
|
||||
x => x.Name,
|
||||
expression => expression.MapFrom(content => content.ItemKey))
|
||||
.AfterMap(
|
||||
(src, dest) =>
|
||||
{
|
||||
// build up the path to make it possible to set active item in tree
|
||||
// TODO: check if there is a better way
|
||||
if (src.ParentId.HasValue)
|
||||
{
|
||||
var ids = new List<int> { -1 };
|
||||
|
||||
|
||||
var parentIds = new List<int>();
|
||||
|
||||
this.GetParentId(src.ParentId.Value, localizationService, parentIds);
|
||||
|
||||
parentIds.Reverse();
|
||||
|
||||
ids.AddRange(parentIds);
|
||||
|
||||
ids.Add(src.Id);
|
||||
|
||||
dest.Path = string.Join(",", ids);
|
||||
}
|
||||
else
|
||||
{
|
||||
dest.Path = "-1," + src.Id;
|
||||
}
|
||||
|
||||
// add all languages and the translations
|
||||
foreach (var lang in localizationService.GetAllLanguages())
|
||||
{
|
||||
var langId = lang.Id;
|
||||
var translation = src.Translations.FirstOrDefault(x => x.LanguageId == langId);
|
||||
|
||||
dest.Translations.Add(new DictionaryTranslationDisplay
|
||||
{
|
||||
IsoCode = lang.IsoCode,
|
||||
DisplayName = lang.CultureInfo.DisplayName,
|
||||
Translation = (translation != null) ? translation.Value : string.Empty,
|
||||
LanguageId = lang.Id
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
CreateMap<IDictionaryItem, DictionaryOverviewDisplay>()
|
||||
.ForMember(dest => dest.Level, expression => expression.Ignore())
|
||||
.ForMember(dest => dest.Translations, expression => expression.Ignore())
|
||||
.ForMember(
|
||||
x => x.Name,
|
||||
expression => expression.MapFrom(content => content.ItemKey))
|
||||
.AfterMap(
|
||||
(src, dest) =>
|
||||
{
|
||||
// add all languages and the translations
|
||||
foreach (var lang in localizationService.GetAllLanguages())
|
||||
{
|
||||
var langId = lang.Id;
|
||||
var translation = src.Translations.FirstOrDefault(x => x.LanguageId == langId);
|
||||
|
||||
dest.Translations.Add(
|
||||
new DictionaryOverviewTranslationDisplay
|
||||
{
|
||||
DisplayName = lang.CultureInfo.DisplayName,
|
||||
HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Goes up the dictionary tree to get all parent ids
|
||||
/// </summary>
|
||||
/// <param name="parentId">
|
||||
/// The parent id.
|
||||
/// </param>
|
||||
/// <param name="localizationService">
|
||||
/// The localization service.
|
||||
/// </param>
|
||||
/// <param name="ids">
|
||||
/// The ids.
|
||||
/// </param>
|
||||
private void GetParentId(Guid parentId, ILocalizationService localizationService, List<int> ids)
|
||||
{
|
||||
var dictionary = localizationService.GetDictionaryItemById(parentId);
|
||||
|
||||
if (dictionary == null)
|
||||
return;
|
||||
|
||||
ids.Add(dictionary.Id);
|
||||
|
||||
if (dictionary.ParentId.HasValue)
|
||||
GetParentId(dictionary.ParentId.Value, localizationService, ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
238
src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs
Normal file
238
src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Examine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Examine;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class EntityMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IEntitySlim, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<PropertyType, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<PropertyGroup, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<IUser, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<ITemplate, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<EntityBasic, ContentTypeSort>((source, context) => new ContentTypeSort(), Map);
|
||||
mapper.Define<IContentTypeComposition, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<EntitySlim, SearchResultEntity>((source, context) => new SearchResultEntity(), Map);
|
||||
mapper.Define<ISearchResult, SearchResultEntity>((source, context) => new SearchResultEntity(), Map);
|
||||
mapper.Define<ISearchResults, IEnumerable<SearchResultEntity>>((source, context) => context.MapEnumerable<ISearchResult, SearchResultEntity>(source));
|
||||
mapper.Define<IEnumerable<ISearchResult>, IEnumerable<SearchResultEntity>>((source, context) => context.MapEnumerable<ISearchResult, SearchResultEntity>(source));
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Alias
|
||||
private static void Map(IEntitySlim source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Icon = MapContentTypeIcon(source);
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = MapName(source, context);
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key);
|
||||
|
||||
if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace())
|
||||
target.Icon = "icon-user";
|
||||
|
||||
// NOTE: we're mapping the objects in AdditionalData by object reference here.
|
||||
// it works fine for now, but it's something to keep in mind in the future
|
||||
foreach(var kvp in source.AdditionalData)
|
||||
{
|
||||
target.AdditionalData[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
target.AdditionalData.Add("IsContainer", source.IsContainer);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -Trashed
|
||||
private static void Map(PropertyType source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = "icon-box";
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "";
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -Trashed
|
||||
private static void Map(PropertyGroup source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Name.ToLowerInvariant();
|
||||
target.Icon = "icon-tab";
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "";
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -Trashed
|
||||
private static void Map(IUser source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Username;
|
||||
target.Icon = "icon-user";
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "";
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed
|
||||
private static void Map(ITemplate source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = "icon-layout";
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = source.Path;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.Template, source.Key);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -SortOrder
|
||||
private static void Map(EntityBasic source, ContentTypeSort target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Id = new Lazy<int>(() => Convert.ToInt32(source.Id));
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed
|
||||
private static void Map(IContentTypeComposition source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = source.Icon;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Udi = ContentTypeMapDefinition.MapContentTypeUdi(source);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed -Alias -Score
|
||||
private static void Map(EntitySlim source, SearchResultEntity target, MapperContext context)
|
||||
{
|
||||
target.Icon = MapContentTypeIcon(source);
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key);
|
||||
|
||||
if (target.Icon.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (source.NodeObjectType == Constants.ObjectTypes.Member)
|
||||
target.Icon = "icon-user";
|
||||
else if (source.NodeObjectType == Constants.ObjectTypes.DataType)
|
||||
target.Icon = "icon-autofill";
|
||||
else if (source.NodeObjectType == Constants.ObjectTypes.DocumentType)
|
||||
target.Icon = "icon-item-arrangement";
|
||||
else if (source.NodeObjectType == Constants.ObjectTypes.MediaType)
|
||||
target.Icon = "icon-thumbnails";
|
||||
else if (source.NodeObjectType == Constants.ObjectTypes.TemplateType)
|
||||
target.Icon = "icon-newspaper-alt";
|
||||
}
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Alias -Trashed
|
||||
private static void Map(ISearchResult source, SearchResultEntity target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.Score = source.Score;
|
||||
|
||||
// TODO: Properly map this (not aftermap)
|
||||
|
||||
//get the icon if there is one
|
||||
target.Icon = source.Values.ContainsKey(UmbracoExamineIndex.IconFieldName)
|
||||
? source.Values[UmbracoExamineIndex.IconFieldName]
|
||||
: "icon-document";
|
||||
|
||||
target.Name = source.Values.ContainsKey("nodeName") ? source.Values["nodeName"] : "[no name]";
|
||||
|
||||
if (source.Values.ContainsKey(UmbracoExamineIndex.NodeKeyFieldName))
|
||||
{
|
||||
if (Guid.TryParse(source.Values[UmbracoExamineIndex.NodeKeyFieldName], out var key))
|
||||
{
|
||||
target.Key = key;
|
||||
|
||||
//need to set the UDI
|
||||
if (source.Values.ContainsKey(LuceneIndex.CategoryFieldName))
|
||||
{
|
||||
switch (source.Values[LuceneIndex.CategoryFieldName])
|
||||
{
|
||||
case IndexTypes.Member:
|
||||
target.Udi = new GuidUdi(Constants.UdiEntityType.Member, target.Key);
|
||||
break;
|
||||
case IndexTypes.Content:
|
||||
target.Udi = new GuidUdi(Constants.UdiEntityType.Document, target.Key);
|
||||
break;
|
||||
case IndexTypes.Media:
|
||||
target.Udi = new GuidUdi(Constants.UdiEntityType.Media, target.Key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (source.Values.ContainsKey("parentID"))
|
||||
{
|
||||
if (int.TryParse(source.Values["parentID"], out var parentId))
|
||||
{
|
||||
target.ParentId = parentId;
|
||||
}
|
||||
else
|
||||
{
|
||||
target.ParentId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
target.Path = source.Values.ContainsKey(UmbracoExamineIndex.IndexPathFieldName) ? source.Values[UmbracoExamineIndex.IndexPathFieldName] : "";
|
||||
|
||||
if (source.Values.ContainsKey(LuceneIndex.ItemTypeFieldName))
|
||||
{
|
||||
target.AdditionalData.Add("contentType", source.Values[LuceneIndex.ItemTypeFieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string MapContentTypeIcon(IEntitySlim entity)
|
||||
=> entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null;
|
||||
|
||||
private static string MapName(IEntitySlim source, MapperContext context)
|
||||
{
|
||||
if (!(source is DocumentEntitySlim doc))
|
||||
return source.Name;
|
||||
|
||||
// invariant = only 1 name
|
||||
if (!doc.Variations.VariesByCulture()) return source.Name;
|
||||
|
||||
// variant = depends on culture
|
||||
var culture = context.GetCulture();
|
||||
|
||||
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
|
||||
if (culture == null)
|
||||
//throw new InvalidOperationException("Missing culture in mapping options.");
|
||||
// TODO: we should throw, but this is used in various places that won't set a culture yet
|
||||
return source.Name;
|
||||
|
||||
// if we don't have a name for a culture, it means the culture is not available, and
|
||||
// hey we should probably not be mapping it, but it's too late, return a fallback name
|
||||
return doc.CultureNames.TryGetValue(culture, out var name) && !name.IsNullOrWhiteSpace() ? name : $"({source.Name})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Examine;
|
||||
using Examine.LuceneEngine.Providers;
|
||||
using Examine.LuceneEngine;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Examine;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class EntityMapperProfile : Profile
|
||||
{
|
||||
private static string GetContentTypeIcon(IEntitySlim entity)
|
||||
=> entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null;
|
||||
|
||||
public EntityMapperProfile()
|
||||
{
|
||||
// create, capture, cache
|
||||
var contentTypeUdiResolver = new ContentTypeUdiResolver();
|
||||
|
||||
CreateMap<IEntitySlim, EntityBasic>()
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom<NameResolver>())
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(ObjectTypes.GetUdiType(src.NodeObjectType), src.Key)))
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => GetContentTypeIcon(src)))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed))
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.AfterMap((src, dest) =>
|
||||
{
|
||||
if (src.NodeObjectType == Constants.ObjectTypes.Member && dest.Icon.IsNullOrWhiteSpace())
|
||||
{
|
||||
dest.Icon = "icon-user";
|
||||
}
|
||||
|
||||
dest.AdditionalData.Add("IsContainer", src.IsContainer);
|
||||
});
|
||||
|
||||
CreateMap<PropertyType, EntityBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-box"))
|
||||
.ForMember(dest => dest.Path, opt => opt.MapFrom(_ => ""))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<PropertyGroup, EntityBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-tab"))
|
||||
.ForMember(dest => dest.Path, opt => opt.MapFrom(_ => ""))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1))
|
||||
//in v6 the 'alias' is it's lower cased name so we'll stick to that.
|
||||
.ForMember(dest => dest.Alias, opt => opt.MapFrom(src => src.Name.ToLowerInvariant()))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<IUser, EntityBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-user"))
|
||||
.ForMember(dest => dest.Path, opt => opt.MapFrom(_ => ""))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1))
|
||||
.ForMember(dest => dest.Alias, opt => opt.MapFrom(src => src.Username))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<ITemplate, EntityBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Template, src.Key)))
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-layout"))
|
||||
.ForMember(dest => dest.Path, opt => opt.MapFrom(src => src.Path))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<EntityBasic, ContentTypeSort>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => new Lazy<int>(() => Convert.ToInt32(src.Id))))
|
||||
.ForMember(dest => dest.SortOrder, opt => opt.Ignore());
|
||||
|
||||
CreateMap<IContentTypeComposition, EntityBasic>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => contentTypeUdiResolver.Resolve(src)))
|
||||
.ForMember(dest => dest.Path, opt => opt.MapFrom(src => src.Path))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentId))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
|
||||
|
||||
CreateMap<EntitySlim, SearchResultEntity>()
|
||||
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(ObjectTypes.GetUdiType(src.NodeObjectType), src.Key)))
|
||||
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src=> GetContentTypeIcon(src)))
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Score, opt => opt.Ignore())
|
||||
.AfterMap((entity, basic) =>
|
||||
{
|
||||
if (basic.Icon.IsNullOrWhiteSpace())
|
||||
{
|
||||
if (entity.NodeObjectType == Constants.ObjectTypes.Member)
|
||||
basic.Icon = "icon-user";
|
||||
else if (entity.NodeObjectType == Constants.ObjectTypes.DataType)
|
||||
basic.Icon = "icon-autofill";
|
||||
else if (entity.NodeObjectType == Constants.ObjectTypes.DocumentType)
|
||||
basic.Icon = "icon-item-arrangement";
|
||||
else if (entity.NodeObjectType == Constants.ObjectTypes.MediaType)
|
||||
basic.Icon = "icon-thumbnails";
|
||||
else if (entity.NodeObjectType == Constants.ObjectTypes.TemplateType)
|
||||
basic.Icon = "icon-newspaper-alt";
|
||||
}
|
||||
});
|
||||
|
||||
CreateMap<ISearchResult, SearchResultEntity>()
|
||||
//default to document icon
|
||||
.ForMember(dest => dest.Score, opt => opt.MapFrom(result => result.Score))
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
|
||||
.ForMember(dest => dest.Name, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Key, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ParentId, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Alias, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.AfterMap((src, dest) =>
|
||||
{
|
||||
// TODO: Properly map this (not aftermap)
|
||||
|
||||
//get the icon if there is one
|
||||
dest.Icon = src.Values.ContainsKey(UmbracoExamineIndex.IconFieldName)
|
||||
? src.Values[UmbracoExamineIndex.IconFieldName]
|
||||
: "icon-document";
|
||||
|
||||
dest.Name = src.Values.ContainsKey("nodeName") ? src.Values["nodeName"] : "[no name]";
|
||||
if (src.Values.ContainsKey(UmbracoExamineIndex.NodeKeyFieldName))
|
||||
{
|
||||
Guid key;
|
||||
if (Guid.TryParse(src.Values[UmbracoExamineIndex.NodeKeyFieldName], out key))
|
||||
{
|
||||
dest.Key = key;
|
||||
|
||||
//need to set the UDI
|
||||
if (src.Values.ContainsKey(LuceneIndex.CategoryFieldName))
|
||||
{
|
||||
switch (src.Values[LuceneIndex.CategoryFieldName])
|
||||
{
|
||||
case IndexTypes.Member:
|
||||
dest.Udi = new GuidUdi(Constants.UdiEntityType.Member, dest.Key);
|
||||
break;
|
||||
case IndexTypes.Content:
|
||||
dest.Udi = new GuidUdi(Constants.UdiEntityType.Document, dest.Key);
|
||||
break;
|
||||
case IndexTypes.Media:
|
||||
dest.Udi = new GuidUdi(Constants.UdiEntityType.Media, dest.Key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (src.Values.ContainsKey("parentID"))
|
||||
{
|
||||
int parentId;
|
||||
if (int.TryParse(src.Values["parentID"], out parentId))
|
||||
{
|
||||
dest.ParentId = parentId;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest.ParentId = -1;
|
||||
}
|
||||
}
|
||||
dest.Path = src.Values.ContainsKey(UmbracoExamineIndex.IndexPathFieldName) ? src.Values[UmbracoExamineIndex.IndexPathFieldName] : "";
|
||||
|
||||
if (src.Values.ContainsKey(LuceneIndex.ItemTypeFieldName))
|
||||
{
|
||||
dest.AdditionalData.Add("contentType", src.Values[LuceneIndex.ItemTypeFieldName]);
|
||||
}
|
||||
});
|
||||
|
||||
CreateMap<ISearchResults, IEnumerable<SearchResultEntity>>()
|
||||
.ConvertUsing(results => results.Select(Mapper.Map<SearchResultEntity>).ToList());
|
||||
|
||||
CreateMap<IEnumerable<ISearchResult>, IEnumerable<SearchResultEntity>>()
|
||||
.ConvertUsing(results => results.Select(Mapper.Map<SearchResultEntity>).ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the name for a content item/content variant
|
||||
/// </summary>
|
||||
private class NameResolver : IValueResolver<IEntitySlim, EntityBasic, string>
|
||||
{
|
||||
public string Resolve(IEntitySlim source, EntityBasic destination, string destMember, ResolutionContext context)
|
||||
{
|
||||
if (!(source is DocumentEntitySlim doc))
|
||||
return source.Name;
|
||||
|
||||
// invariant = only 1 name
|
||||
if (!doc.Variations.VariesByCulture()) return source.Name;
|
||||
|
||||
// variant = depends on culture
|
||||
var culture = context.Options.GetCulture();
|
||||
|
||||
// if there's no culture here, the issue is somewhere else (UI, whatever) - throw!
|
||||
if (culture == null)
|
||||
//throw new InvalidOperationException("Missing culture in mapping options.");
|
||||
// TODO: we should throw, but this is used in various places that won't set a culture yet
|
||||
return source.Name;
|
||||
|
||||
// if we don't have a name for a culture, it means the culture is not available, and
|
||||
// hey we should probably not be mapping it, but it's too late, return a fallback name
|
||||
return doc.CultureNames.TryGetValue(culture, out var name) && !name.IsNullOrWhiteSpace() ? name : $"({source.Name})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Mapping extension methods for re-use with other mappers (saves code duplication)
|
||||
/// </summary>
|
||||
internal static class EntityProfileExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ignores readonly properties and the date values
|
||||
/// </summary>
|
||||
/// <param name="mapping"></param>
|
||||
/// <returns></returns>
|
||||
public static IMappingExpression<TSource, TDest> IgnoreEntityCommonProperties<TSource, TDest>(this IMappingExpression<TSource, TDest> mapping)
|
||||
where TDest : IEntity
|
||||
{
|
||||
return mapping
|
||||
.IgnoreAllPropertiesWithAnInaccessibleSetter()
|
||||
.ForMember(dest => dest.CreateDate, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.UpdateDate, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.DeleteDate, opt => opt.Ignore());
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/Umbraco.Web/Models/Mapping/LanguageMapDefinition.cs
Normal file
58
src/Umbraco.Web/Models/Mapping/LanguageMapDefinition.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Language = Umbraco.Web.Models.ContentEditing.Language;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class LanguageMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<ILanguage, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<ILanguage, Language>((source, context) => new Language(), Map);
|
||||
mapper.Define<IEnumerable<ILanguage>, IEnumerable<Language>>((source, context) => new List<Language>(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Udi -Path -Trashed -AdditionalData -Icon
|
||||
private static void Map(ILanguage source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Name = source.CultureName;
|
||||
target.Key = source.Key;
|
||||
target.ParentId = -1;
|
||||
target.Alias = source.IsoCode;
|
||||
target.Id = source.Id;
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(ILanguage source, Language target, MapperContext context)
|
||||
{
|
||||
target.Id = source.Id;
|
||||
target.IsoCode = source.IsoCode;
|
||||
target.Name = source.CultureInfo.DisplayName;
|
||||
target.IsDefault = source.IsDefault;
|
||||
target.IsMandatory = source.IsMandatory;
|
||||
target.FallbackLanguageId = source.FallbackLanguageId;
|
||||
}
|
||||
|
||||
private static void Map(IEnumerable<ILanguage> source, IEnumerable<Language> target, MapperContext context)
|
||||
{
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
if (!(target is List<Language> list))
|
||||
throw new NotSupportedException($"{nameof(target)} must be a List<Language>.");
|
||||
|
||||
var temp = context.MapEnumerable<ILanguage, Language>(source);
|
||||
|
||||
//Put the default language first in the list & then sort rest by a-z
|
||||
var defaultLang = temp.SingleOrDefault(x => x.IsDefault);
|
||||
|
||||
// insert default lang first, then remaining language a-z
|
||||
list.Add(defaultLang);
|
||||
list.AddRange(temp.Where(x => x != defaultLang).OrderBy(x => x.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Language = Umbraco.Web.Models.ContentEditing.Language;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class LanguageMapperProfile : Profile
|
||||
{
|
||||
public LanguageMapperProfile()
|
||||
{
|
||||
CreateMap<ILanguage, EntityBasic>()
|
||||
.ForMember(dest => dest.Id, opt => opt.MapFrom(x => x.Id))
|
||||
.ForMember(dest => dest.Name, opt => opt.MapFrom(x => x.CultureName))
|
||||
.ForMember(dest => dest.Key, opt => opt.MapFrom(x => x.Key))
|
||||
.ForMember(dest => dest.Alias, opt => opt.MapFrom(x => x.IsoCode))
|
||||
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1))
|
||||
.ForMember(dest => dest.Path, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Udi, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Icon, opt => opt.Ignore());
|
||||
|
||||
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 langs = source.Select(x => context.Mapper.Map<ILanguage, Language>(x, null, context)).ToList();
|
||||
|
||||
//Put the default language first in the list & then sort rest by a-z
|
||||
var defaultLang = langs.SingleOrDefault(x => x.IsDefault);
|
||||
|
||||
//Remove the default language from the list for now
|
||||
langs.Remove(defaultLang);
|
||||
|
||||
//Sort the remaining languages a-z
|
||||
langs = langs.OrderBy(x => x.Name).ToList();
|
||||
|
||||
//Insert the default language as the first item
|
||||
langs.Insert(0, defaultLang);
|
||||
|
||||
return langs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using AutoMapper;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class LockedCompositionsResolver
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public LockedCompositionsResolver(IContentTypeService contentTypeService)
|
||||
{
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
public IEnumerable<string> Resolve(IContentTypeComposition source)
|
||||
{
|
||||
var aliases = new List<string>();
|
||||
// get ancestor ids from path of parent if not root
|
||||
if (source.ParentId != Constants.System.Root)
|
||||
{
|
||||
var parent = _contentTypeService.Get(source.ParentId);
|
||||
if (parent != null)
|
||||
{
|
||||
var ancestorIds = parent.Path.Split(',').Select(int.Parse);
|
||||
// loop through all content types and return ordered aliases of ancestors
|
||||
var allContentTypes = _contentTypeService.GetAll().ToArray();
|
||||
foreach (var ancestorId in ancestorIds)
|
||||
{
|
||||
var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId);
|
||||
if (ancestor != null)
|
||||
{
|
||||
aliases.Add(ancestor.Alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return aliases.OrderBy(x => x);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs
Normal file
72
src/Umbraco.Web/Models/Mapping/MacroMapDefinition.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class MacroMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly ParameterEditorCollection _parameterEditors;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MacroMapDefinition(ParameterEditorCollection parameterEditors, ILogger logger)
|
||||
{
|
||||
_parameterEditors = parameterEditors;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IMacro, EntityBasic>((source, context) => new EntityBasic(), Map);
|
||||
mapper.Define<IMacro, IEnumerable<MacroParameter>>((source, context) => context.MapEnumerable<IMacroProperty, MacroParameter>(source.Properties.Values));
|
||||
mapper.Define<IMacroProperty, MacroParameter>((source, context) => new MacroParameter(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Trashed -AdditionalData
|
||||
private static void Map(IMacro source, EntityBasic target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Icon = "icon-settings-alt";
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.ParentId = -1;
|
||||
target.Path = "-1," + source.Id;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Value
|
||||
private void Map(IMacroProperty source, MacroParameter target, MapperContext context)
|
||||
{
|
||||
target.Alias = source.Alias;
|
||||
target.Name = source.Name;
|
||||
target.SortOrder = source.SortOrder;
|
||||
|
||||
//map the view and the config
|
||||
// we need to show the deprecated ones for backwards compatibility
|
||||
var paramEditor = _parameterEditors[source.EditorAlias]; // TODO: include/filter deprecated?!
|
||||
if (paramEditor == null)
|
||||
{
|
||||
//we'll just map this to a text box
|
||||
paramEditor = _parameterEditors[Constants.PropertyEditors.Aliases.TextBox];
|
||||
_logger.Warn<MacroMapDefinition>("Could not resolve a parameter editor with alias {PropertyEditorAlias}, a textbox will be rendered in it's place", source.EditorAlias);
|
||||
}
|
||||
|
||||
target.View = paramEditor.GetValueEditor().View;
|
||||
|
||||
// sets the parameter configuration to be the default configuration editor's configuration,
|
||||
// ie configurationEditor.DefaultConfigurationObject, prepared for the value editor, ie
|
||||
// after ToValueEditor - important to use DefaultConfigurationObject here, because depending
|
||||
// on editors, ToValueEditor expects the actual strongly typed configuration - not the
|
||||
// dictionary thing returned by DefaultConfiguration
|
||||
|
||||
var configurationEditor = paramEditor.GetConfigurationEditor();
|
||||
target.Configuration = configurationEditor.ToValueEditor(configurationEditor.DefaultConfigurationObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Declares model mappings for macros.
|
||||
/// </summary>
|
||||
internal class MacroMapperProfile : Profile
|
||||
{
|
||||
public MacroMapperProfile()
|
||||
{
|
||||
//FROM IMacro TO EntityBasic
|
||||
CreateMap<IMacro, EntityBasic>()
|
||||
.ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.Macro, content.Key)))
|
||||
.ForMember(entityBasic => entityBasic.Icon, expression => expression.MapFrom(_ => "icon-settings-alt"))
|
||||
.ForMember(dto => dto.ParentId, expression => expression.MapFrom(_ => -1))
|
||||
.ForMember(dto => dto.Path, expression => expression.MapFrom(macro => "-1," + macro.Id))
|
||||
.ForMember(dto => dto.Trashed, expression => expression.Ignore())
|
||||
.ForMember(dto => dto.AdditionalData, expression => expression.Ignore());
|
||||
|
||||
CreateMap<IMacro, IEnumerable<MacroParameter>>()
|
||||
.ConvertUsing(macro => macro.Properties.Values.Select(Mapper.Map<MacroParameter>).ToList());
|
||||
|
||||
CreateMap<IMacroProperty, MacroParameter>()
|
||||
.ForMember(x => x.View, expression => expression.Ignore())
|
||||
.ForMember(x => x.Configuration, expression => expression.Ignore())
|
||||
.ForMember(x => x.Value, expression => expression.Ignore())
|
||||
.AfterMap((property, parameter) =>
|
||||
{
|
||||
//map the view and the config
|
||||
// we need to show the deprecated ones for backwards compatibility
|
||||
var paramEditor = Current.ParameterEditors[property.EditorAlias]; // TODO: include/filter deprecated?!
|
||||
if (paramEditor == null)
|
||||
{
|
||||
//we'll just map this to a text box
|
||||
paramEditor = Current.ParameterEditors[Constants.PropertyEditors.Aliases.TextBox];
|
||||
Current.Logger.Warn<MacroMapperProfile>("Could not resolve a parameter editor with alias {PropertyEditorAlias}, a textbox will be rendered in it's place", property.EditorAlias);
|
||||
}
|
||||
|
||||
parameter.View = paramEditor.GetValueEditor().View;
|
||||
|
||||
// sets the parameter configuration to be the default configuration editor's configuration,
|
||||
// ie configurationEditor.DefaultConfigurationObject, prepared for the value editor, ie
|
||||
// after ToValueEditor - important to use DefaultConfigurationObject here, because depending
|
||||
// on editors, ToValueEditor expects the actual strongly typed configuration - not the
|
||||
// dictionary thing returned by DefaultConfiguration
|
||||
|
||||
var configurationEditor = paramEditor.GetConfigurationEditor();
|
||||
parameter.Configuration = configurationEditor.ToValueEditor(configurationEditor.DefaultConfigurationObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs
Normal file
45
src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Umbraco.Core.Mapping;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="MapperContext"/> class.
|
||||
/// </summary>
|
||||
internal static class MapperContextExtensions
|
||||
{
|
||||
private const string CultureKey = "Map.Culture";
|
||||
private const string IncludedPropertiesKey = "Map.IncludedProperties";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the context culture.
|
||||
/// </summary>
|
||||
public static string GetCulture(this MapperContext context)
|
||||
{
|
||||
return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a context culture.
|
||||
/// </summary>
|
||||
public static void SetCulture(this MapperContext context, string culture)
|
||||
{
|
||||
context.Items[CultureKey] = culture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get included properties.
|
||||
/// </summary>
|
||||
public static string[] GetIncludedProperties(this MapperContext context)
|
||||
{
|
||||
return context.HasItems && context.Items.TryGetValue(IncludedPropertiesKey, out var obj) && obj is string[] s ? s : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets included properties.
|
||||
/// </summary>
|
||||
public static void SetIncludedProperties(this MapperContext context, string[] properties)
|
||||
{
|
||||
context.Items[IncludedPropertiesKey] = properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using AutoMapper;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for AutoMapper's <see cref="IMappingOperationOptions"/>.
|
||||
/// </summary>
|
||||
internal static class MappingOperationOptionsExtensions
|
||||
{
|
||||
private const string CultureKey = "MappingOperationOptions.Culture";
|
||||
private const string IncludedPropertiesKey = "MappingOperationOptions.IncludeProperties";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the context culture.
|
||||
/// </summary>
|
||||
public static string GetCulture(this IMappingOperationOptions options)
|
||||
{
|
||||
return options.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a context culture.
|
||||
/// </summary>
|
||||
public static void SetCulture(this IMappingOperationOptions options, string culture)
|
||||
{
|
||||
options.Items[CultureKey] = culture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get included properties.
|
||||
/// </summary>
|
||||
public static string[] GetIncludedProperties(this IMappingOperationOptions options)
|
||||
{
|
||||
return options.Items.TryGetValue(IncludedPropertiesKey, out var obj) && obj is string[] s ? s : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets included properties.
|
||||
/// </summary>
|
||||
public static void SetIncludedProperties(this IMappingOperationOptions options, string[] properties)
|
||||
{
|
||||
options.Items[IncludedPropertiesKey] = properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.ContentEditing;
|
||||
using Umbraco.Web.ContentApps;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
// injected into ContentMapperProfile,
|
||||
// maps ContentApps when mapping IMedia to MediaItemDisplay
|
||||
internal class MediaAppResolver : IValueResolver<IMedia, MediaItemDisplay, IEnumerable<ContentApp>>
|
||||
{
|
||||
private readonly ContentAppFactoryCollection _contentAppDefinitions;
|
||||
|
||||
public MediaAppResolver(ContentAppFactoryCollection contentAppDefinitions)
|
||||
{
|
||||
_contentAppDefinitions = contentAppDefinitions;
|
||||
}
|
||||
|
||||
public IEnumerable<ContentApp> Resolve(IMedia source, MediaItemDisplay destination, IEnumerable<ContentApp> destMember, ResolutionContext context)
|
||||
{
|
||||
return _contentAppDefinitions.GetContentAppsFor(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using AutoMapper;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
internal class MediaChildOfListViewResolver : IValueResolver<IMedia, MediaItemDisplay, bool>
|
||||
{
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
|
||||
public MediaChildOfListViewResolver(IMediaService mediaService, IMediaTypeService mediaTypeService)
|
||||
{
|
||||
_mediaService = mediaService;
|
||||
_mediaTypeService = mediaTypeService;
|
||||
}
|
||||
|
||||
public bool Resolve(IMedia source, MediaItemDisplay destination, bool destMember, ResolutionContext context)
|
||||
{
|
||||
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
|
||||
var parent = _mediaService.GetParent(source);
|
||||
return parent != null && (parent.ContentType.IsContainer || _mediaTypeService.HasContainerInPath(parent.Path));
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs
Normal file
103
src/Umbraco.Web/Models/Mapping/MediaMapDefinition.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Web.Models.Mapping
|
||||
{
|
||||
/// <summary>
|
||||
/// Declares model mappings for media.
|
||||
/// </summary>
|
||||
internal class MediaMapDefinition : IMapDefinition
|
||||
{
|
||||
private readonly CommonMapper _commonMapper;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly TabsAndPropertiesMapper<IMedia> _tabsAndPropertiesMapper;
|
||||
|
||||
public MediaMapDefinition(ILogger logger, CommonMapper commonMapper, IMediaService mediaService, IMediaTypeService mediaTypeService,
|
||||
ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_logger = logger;
|
||||
_commonMapper = commonMapper;
|
||||
_mediaService = mediaService;
|
||||
_mediaTypeService = mediaTypeService;
|
||||
|
||||
_tabsAndPropertiesMapper = new TabsAndPropertiesMapper<IMedia>(localizedTextService);
|
||||
}
|
||||
|
||||
public void DefineMaps(UmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IMedia, ContentPropertyCollectionDto>((source, context) => new ContentPropertyCollectionDto(), Map);
|
||||
mapper.Define<IMedia, MediaItemDisplay>((source, context) => new MediaItemDisplay(), Map);
|
||||
mapper.Define<IMedia, ContentItemBasic<ContentPropertyBasic>>((source, context) => new ContentItemBasic<ContentPropertyBasic>(), Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(IMedia source, ContentPropertyCollectionDto target, MapperContext context)
|
||||
{
|
||||
target.Properties = context.MapEnumerable<Property, ContentPropertyDto>(source.Properties);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsContainer
|
||||
private void Map(IMedia source, MediaItemDisplay target, MapperContext context)
|
||||
{
|
||||
target.ContentApps = _commonMapper.GetContentApps(source);
|
||||
target.ContentType = _commonMapper.GetContentType(source, context);
|
||||
target.ContentTypeAlias = source.ContentType.Alias;
|
||||
target.ContentTypeName = source.ContentType.Name;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Icon = source.ContentType.Icon;
|
||||
target.Id = source.Id;
|
||||
target.IsChildOfListView = DermineIsChildOfListView(source);
|
||||
target.Key = source.Key;
|
||||
target.MediaLink = string.Join(",", source.GetUrls(Current.Configs.Settings().Content, _logger));
|
||||
target.Name = source.Name;
|
||||
target.Owner = _commonMapper.GetOwner(source, context);
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.State = null;
|
||||
target.Tabs = _tabsAndPropertiesMapper.Map(source, context);
|
||||
target.Trashed = source.Trashed;
|
||||
target.TreeNodeUrl = _commonMapper.GetTreeNodeUrl<MediaTreeController>(source);
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.Media, source.Key);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
target.VariesByCulture = source.ContentType.VariesByCulture();
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll -Edited -Updater -Alias
|
||||
private void Map(IMedia source, ContentItemBasic<ContentPropertyBasic> target, MapperContext context)
|
||||
{
|
||||
target.ContentTypeAlias = source.ContentType.Alias;
|
||||
target.CreateDate = source.CreateDate;
|
||||
target.Icon = source.ContentType.Icon;
|
||||
target.Id = source.Id;
|
||||
target.Key = source.Key;
|
||||
target.Name = source.Name;
|
||||
target.Owner = _commonMapper.GetOwner(source, context);
|
||||
target.ParentId = source.ParentId;
|
||||
target.Path = source.Path;
|
||||
target.Properties = context.MapEnumerable<Property, ContentPropertyBasic>(source.Properties);
|
||||
target.SortOrder = source.SortOrder;
|
||||
target.State = null;
|
||||
target.Trashed = source.Trashed;
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.Media, source.Key);
|
||||
target.UpdateDate = source.UpdateDate;
|
||||
target.VariesByCulture = source.ContentType.VariesByCulture();
|
||||
}
|
||||
|
||||
private bool DermineIsChildOfListView(IMedia source)
|
||||
{
|
||||
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
|
||||
var parent = _mediaService.GetParent(source);
|
||||
return parent != null && (parent.ContentType.IsContainer || _mediaTypeService.HasContainerInPath(parent.Path));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user