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:
elitsa
2019-04-12 11:27:43 +02:00
575 changed files with 12608 additions and 10691 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -209,7 +209,7 @@ namespace Umbraco.Web.Editors
Alias = y.Alias,
View = y.View
})
});
}).ToList();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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)
{
}
}
}

View File

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

View File

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

View 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)
{
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using AutoMapper;
using Umbraco.Web.Mvc;
using System.Linq;
using Umbraco.Core;

View File

@@ -1,5 +1,4 @@
using AutoMapper;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View 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