Merge remote-tracking branch 'origin/v8/dev' into v9/feature/v8_merge_22_10_2021
# Conflicts: # src/Umbraco.Core/Exceptions/InvalidCompositionException.cs # src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs # src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs # src/Umbraco.Tests/Umbraco.Tests.csproj # src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs # src/Umbraco.Web.BackOffice/Controllers/EntityController.cs # src/Umbraco.Web.UI.Client/src/views/datatypes/create.html # src/Umbraco.Web.UI.Client/src/views/documentTypes/create.controller.js # src/Umbraco.Web.UI.Client/src/views/documenttypes/importdocumenttype.html # src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html # src/Umbraco.Web.UI.Client/src/views/partialviewmacros/create.html # src/Umbraco.Web.UI.Client/src/views/partialviews/create.html # src/Umbraco.Web.UI.Client/src/views/relationtypes/create.html # src/Umbraco.Web.UI/Umbraco/config/lang/cy.xml # src/Umbraco.Web.UI/Umbraco/config/lang/it.xml # src/Umbraco.Web.UI/umbraco/config/lang/da.xml # src/Umbraco.Web.UI/umbraco/config/lang/en.xml # src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/umbraco/config/lang/nl.xml # src/Umbraco.Web/Editors/MemberGroupController.cs # tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs # tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/NuCacheRebuildTests.cs
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
@@ -16,12 +17,11 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.BackOffice.Filters;
|
||||
using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Am abstract API controller providing functionality used for dealing with content and media types
|
||||
/// Am abstract API controller providing functionality used for dealing with content and media types
|
||||
/// </summary>
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[PrefixlessBodyModelValidator]
|
||||
@@ -39,13 +39,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
IUmbracoMapper umbracoMapper,
|
||||
ILocalizedTextService localizedTextService)
|
||||
{
|
||||
_editorValidatorCollection = editorValidatorCollection ?? throw new ArgumentNullException(nameof(editorValidatorCollection));
|
||||
_editorValidatorCollection = editorValidatorCollection ??
|
||||
throw new ArgumentNullException(nameof(editorValidatorCollection));
|
||||
CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary));
|
||||
ContentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService));
|
||||
MediaTypeService = mediaTypeService ?? throw new ArgumentNullException(nameof(mediaTypeService));
|
||||
MemberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService));
|
||||
UmbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
|
||||
LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
LocalizedTextService =
|
||||
localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
}
|
||||
|
||||
protected ICultureDictionary CultureDictionary { get; }
|
||||
@@ -56,22 +58,26 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
public ILocalizedTextService LocalizedTextService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the available composite content types for a given content type
|
||||
/// Returns the available composite content types for a given content type
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="filterContentTypes">
|
||||
/// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out
|
||||
/// along with any content types that have matching property types that are included in the filtered content types
|
||||
/// This is normally an empty list but if additional content type aliases are passed in, any content types containing
|
||||
/// those aliases will be filtered out
|
||||
/// along with any content types that have matching property types that are included in the filtered content types
|
||||
/// </param>
|
||||
/// <param name="filterPropertyTypes">
|
||||
/// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out.
|
||||
/// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
|
||||
/// be looked up via the db, they need to be passed in.
|
||||
/// This is normally an empty list but if additional property type aliases are passed in, any content types that have
|
||||
/// these aliases will be filtered out.
|
||||
/// This is required because in the case of creating/modifying a content type because new property types being added to
|
||||
/// it are not yet persisted so cannot
|
||||
/// be looked up via the db, they need to be passed in.
|
||||
/// </param>
|
||||
/// <param name="contentTypeId"></param>
|
||||
/// <param name="isElement">Whether the composite content types should be applicable for an element type</param>
|
||||
/// <returns></returns>
|
||||
protected ActionResult<IEnumerable<Tuple<EntityBasic, bool>>> PerformGetAvailableCompositeContentTypes(int contentTypeId,
|
||||
protected ActionResult<IEnumerable<Tuple<EntityBasic, bool>>> PerformGetAvailableCompositeContentTypes(
|
||||
int contentTypeId,
|
||||
UmbracoObjectTypes type,
|
||||
string[] filterContentTypes,
|
||||
string[] filterPropertyTypes,
|
||||
@@ -89,26 +95,38 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
if (contentTypeId > 0)
|
||||
{
|
||||
source = ContentTypeService.Get(contentTypeId);
|
||||
if (source == null) return NotFound();
|
||||
if (source == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
allContentTypes = ContentTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
if (contentTypeId > 0)
|
||||
{
|
||||
source =MediaTypeService.Get(contentTypeId);
|
||||
if (source == null) return NotFound();
|
||||
source = MediaTypeService.Get(contentTypeId);
|
||||
if (source == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
allContentTypes =MediaTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
|
||||
|
||||
allContentTypes = MediaTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
if (contentTypeId > 0)
|
||||
{
|
||||
source = MemberTypeService.Get(contentTypeId);
|
||||
if (source == null) return NotFound();
|
||||
if (source == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
allContentTypes = MemberTypeService.GetAll().Cast<IContentTypeComposition>().ToArray();
|
||||
break;
|
||||
|
||||
@@ -116,16 +134,20 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
||||
}
|
||||
|
||||
var availableCompositions = ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes, isElement);
|
||||
ContentTypeAvailableCompositionsResults availableCompositions =
|
||||
ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes,
|
||||
filterPropertyTypes, isElement);
|
||||
|
||||
|
||||
|
||||
var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray();
|
||||
IContentTypeComposition[] currCompositions =
|
||||
source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray();
|
||||
var compAliases = currCompositions.Select(x => x.Alias).ToArray();
|
||||
var ancestors = availableCompositions.Ancestors.Select(x => x.Alias);
|
||||
IEnumerable<string> ancestors = availableCompositions.Ancestors.Select(x => x.Alias);
|
||||
|
||||
return availableCompositions.Results
|
||||
.Select(x => new Tuple<EntityBasic, bool>(UmbracoMapper.Map<IContentTypeComposition, EntityBasic>(x.Composition), x.Allowed))
|
||||
.Select(x =>
|
||||
new Tuple<EntityBasic, bool>(UmbracoMapper.Map<IContentTypeComposition, EntityBasic>(x.Composition),
|
||||
x.Allowed))
|
||||
.Select(x =>
|
||||
{
|
||||
//we need to ensure that the item is enabled if it is already selected
|
||||
@@ -139,9 +161,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
//translate the name
|
||||
x.Item1.Name = TranslateItem(x.Item1.Name);
|
||||
|
||||
var contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key);
|
||||
var containers = GetEntityContainers(contentType, type)?.ToArray();
|
||||
var containerPath = $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}";
|
||||
IContentTypeComposition contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key);
|
||||
EntityContainer[] containers = GetEntityContainers(contentType, type)?.ToArray();
|
||||
var containerPath =
|
||||
$"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}";
|
||||
x.Item1.AdditionalData["containerPath"] = containerPath;
|
||||
|
||||
return x;
|
||||
@@ -149,7 +172,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<EntityContainer> GetEntityContainers(IContentTypeComposition contentType, UmbracoObjectTypes type)
|
||||
private IEnumerable<EntityContainer> GetEntityContainers(IContentTypeComposition contentType,
|
||||
UmbracoObjectTypes type)
|
||||
{
|
||||
if (contentType == null)
|
||||
{
|
||||
@@ -170,12 +194,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of content types where a particular composition content type is used
|
||||
/// Returns a list of content types where a particular composition content type is used
|
||||
/// </summary>
|
||||
/// <param name="type">Type of content Type, eg documentType or mediaType</param>
|
||||
/// <param name="contentTypeId">Id of composition content type</param>
|
||||
/// <returns></returns>
|
||||
protected ActionResult<IEnumerable<EntityBasic>> PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId, UmbracoObjectTypes type)
|
||||
protected ActionResult<IEnumerable<EntityBasic>> PerformGetWhereCompositionIsUsedInContentTypes(
|
||||
int contentTypeId, UmbracoObjectTypes type)
|
||||
{
|
||||
var id = 0;
|
||||
|
||||
@@ -190,7 +215,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
source =MediaTypeService.Get(contentTypeId);
|
||||
source = MediaTypeService.Get(contentTypeId);
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
@@ -202,7 +227,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
id = source.Id;
|
||||
}
|
||||
@@ -216,7 +243,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
composedOf =MediaTypeService.GetComposedOf(id);
|
||||
composedOf = MediaTypeService.GetComposedOf(id);
|
||||
break;
|
||||
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
@@ -242,10 +269,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
protected string TranslateItem(string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (text.StartsWith("#") == false)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
text = text.Substring(1);
|
||||
return CultureDictionary[text].IfNullOrWhiteSpace(text);
|
||||
@@ -261,18 +292,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
where TPropertyType : PropertyTypeBasic
|
||||
{
|
||||
var ctId = Convert.ToInt32(contentTypeSave.Id);
|
||||
var ct = ctId > 0 ? getContentType(ctId) : null;
|
||||
if (ctId > 0 && ct == null) return NotFound();
|
||||
TContentType ct = ctId > 0 ? getContentType(ctId) : null;
|
||||
if (ctId > 0 && ct == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
//Validate that there's no other ct with the same alias
|
||||
// it in fact cannot be the same as any content type alias (member, content or media) because
|
||||
// this would interfere with how ModelsBuilder works and also how many of the published caches
|
||||
// works since that is based on aliases.
|
||||
var allAliases = ContentTypeService.GetAllContentTypeAliases();
|
||||
IEnumerable<string> allAliases = ContentTypeService.GetAllContentTypeAliases();
|
||||
var exists = allAliases.InvariantContains(contentTypeSave.Alias);
|
||||
if (exists && (ctId == 0 || !ct.Alias.InvariantEquals(contentTypeSave.Alias)))
|
||||
{
|
||||
ModelState.AddModelError("Alias", LocalizedTextService.Localize("editcontenttype", "aliasAlreadyExists"));
|
||||
ModelState.AddModelError("Alias",
|
||||
LocalizedTextService.Localize("editcontenttype", "aliasAlreadyExists"));
|
||||
}
|
||||
|
||||
// execute the external validators
|
||||
@@ -280,13 +315,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
|
||||
if (ModelState.IsValid == false)
|
||||
{
|
||||
var err = CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(ctId, contentTypeSave, ct);
|
||||
TContentTypeDisplay err =
|
||||
CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(ctId, contentTypeSave, ct);
|
||||
return ValidationProblem(err);
|
||||
}
|
||||
|
||||
//filter out empty properties
|
||||
contentTypeSave.Groups = contentTypeSave.Groups.Where(x => x.Name.IsNullOrWhiteSpace() == false).ToList();
|
||||
foreach (var group in contentTypeSave.Groups)
|
||||
foreach (PropertyGroupBasic<TPropertyType> group in contentTypeSave.Groups)
|
||||
{
|
||||
group.Properties = group.Properties.Where(x => x.Alias.IsNullOrWhiteSpace() == false).ToList();
|
||||
}
|
||||
@@ -302,12 +338,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var responseEx = CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentTypeSave, TPropertyType>(ex, contentTypeSave, ct, ctId);
|
||||
if (responseEx != null) return ValidationProblem(responseEx);
|
||||
TContentTypeDisplay responseEx =
|
||||
CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentTypeSave, TPropertyType>(
|
||||
ex, contentTypeSave, ct, ctId);
|
||||
if (responseEx != null)
|
||||
{
|
||||
return ValidationProblem(responseEx);
|
||||
}
|
||||
}
|
||||
|
||||
var exResult = CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(contentTypeSave, ct);
|
||||
if (exResult != null) return ValidationProblem(exResult);
|
||||
TContentTypeDisplay exResult =
|
||||
CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(
|
||||
contentTypeSave, ct);
|
||||
if (exResult != null)
|
||||
{
|
||||
return ValidationProblem(exResult);
|
||||
}
|
||||
|
||||
saveContentType(ct);
|
||||
|
||||
@@ -329,7 +375,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
allowIfselfAsChildSortOrder = contentTypeSave.AllowedContentTypes.IndexOf(0);
|
||||
allowItselfAsChild = contentTypeSave.AllowedContentTypes.Any(x => x == 0);
|
||||
|
||||
contentTypeSave.AllowedContentTypes = contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList();
|
||||
contentTypeSave.AllowedContentTypes =
|
||||
contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList();
|
||||
}
|
||||
|
||||
//save as new
|
||||
@@ -342,15 +389,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var responseEx = CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentTypeSave, TPropertyType>(ex, contentTypeSave, ct, ctId);
|
||||
TContentTypeDisplay responseEx =
|
||||
CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentTypeSave, TPropertyType>(
|
||||
ex, contentTypeSave, ct, ctId);
|
||||
if (responseEx is null)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return ValidationProblem(responseEx);
|
||||
}
|
||||
|
||||
var exResult = CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(contentTypeSave, newCt);
|
||||
if (exResult != null) return ValidationProblem(exResult);
|
||||
TContentTypeDisplay exResult =
|
||||
CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(
|
||||
contentTypeSave, newCt);
|
||||
if (exResult != null)
|
||||
{
|
||||
return ValidationProblem(exResult);
|
||||
}
|
||||
|
||||
//set id to null to ensure its handled as a new type
|
||||
contentTypeSave.Id = null;
|
||||
@@ -364,30 +420,33 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
newCt.AllowedContentTypes =
|
||||
newCt.AllowedContentTypes.Union(
|
||||
new []{ new ContentTypeSort(newCt.Id, allowIfselfAsChildSortOrder) }
|
||||
new[] { new ContentTypeSort(newCt.Id, allowIfselfAsChildSortOrder) }
|
||||
);
|
||||
saveContentType(newCt);
|
||||
}
|
||||
|
||||
return newCt;
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateExternalValidators(ModelStateDictionary modelState, object model)
|
||||
{
|
||||
var modelType = model.GetType();
|
||||
Type modelType = model.GetType();
|
||||
|
||||
var validationResults = _editorValidatorCollection
|
||||
.Where(x => x.ModelType == modelType)
|
||||
.SelectMany(x => x.Validate(model))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any());
|
||||
IEnumerable<ValidationResult> validationResults = _editorValidatorCollection
|
||||
.Where(x => x.ModelType == modelType)
|
||||
.SelectMany(x => x.Validate(model))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.ErrorMessage) && x.MemberNames.Any());
|
||||
|
||||
foreach (var r in validationResults)
|
||||
foreach (var m in r.MemberNames)
|
||||
modelState.AddModelError(m, r.ErrorMessage);
|
||||
foreach (ValidationResult r in validationResults)
|
||||
foreach (var m in r.MemberNames)
|
||||
{
|
||||
modelState.AddModelError(m, r.ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move
|
||||
/// Move
|
||||
/// </summary>
|
||||
/// <param name="move"></param>
|
||||
/// <param name="getContentType"></param>
|
||||
@@ -398,13 +457,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
Func<int, TContentType> getContentType,
|
||||
Func<TContentType, int, Attempt<OperationResult<MoveOperationStatusType>>> doMove)
|
||||
{
|
||||
var toMove = getContentType(move.Id);
|
||||
TContentType toMove = getContentType(move.Id);
|
||||
if (toMove == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var result = doMove(toMove, move.ParentId);
|
||||
Attempt<OperationResult<MoveOperationStatusType>> result = doMove(toMove, move.ParentId);
|
||||
if (result.Success)
|
||||
{
|
||||
return Content(toMove.Path, MediaTypeNames.Text.Plain, Encoding.UTF8);
|
||||
@@ -424,7 +483,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move
|
||||
/// Move
|
||||
/// </summary>
|
||||
/// <param name="move"></param>
|
||||
/// <param name="getContentType"></param>
|
||||
@@ -435,13 +494,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
Func<int, TContentType> getContentType,
|
||||
Func<TContentType, int, Attempt<OperationResult<MoveOperationStatusType, TContentType>>> doCopy)
|
||||
{
|
||||
var toMove = getContentType(move.Id);
|
||||
TContentType toMove = getContentType(move.Id);
|
||||
if (toMove == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var result = doCopy(toMove, move.ParentId);
|
||||
Attempt<OperationResult<MoveOperationStatusType, TContentType>> result = doCopy(toMove, move.ParentId);
|
||||
if (result.Success)
|
||||
{
|
||||
return Content(toMove.Path, MediaTypeNames.Text.Plain, Encoding.UTF8);
|
||||
@@ -461,33 +520,39 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
|
||||
/// Validates the composition and adds errors to the model state if any are found then throws an error response if
|
||||
/// there are errors
|
||||
/// </summary>
|
||||
/// <param name="contentTypeSave"></param>
|
||||
/// <param name="composition"></param>
|
||||
/// <returns></returns>
|
||||
private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType, TContentTypeDisplay>(TContentTypeSave contentTypeSave, TContentType composition)
|
||||
private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid<TContentTypeSave, TPropertyType,
|
||||
TContentTypeDisplay>(TContentTypeSave contentTypeSave, TContentType composition)
|
||||
where TContentTypeSave : ContentTypeSave<TPropertyType>
|
||||
where TPropertyType : PropertyTypeBasic
|
||||
where TContentTypeDisplay : ContentTypeCompositionDisplay
|
||||
{
|
||||
var service = GetContentTypeService<TContentType>();
|
||||
var validateAttempt = service.ValidateComposition(composition);
|
||||
IContentTypeBaseService<TContentType> service = GetContentTypeService<TContentType>();
|
||||
Attempt<string[]> validateAttempt = service.ValidateComposition(composition);
|
||||
if (validateAttempt == false)
|
||||
{
|
||||
// if it's not successful then we need to return some model state for the property type and property group
|
||||
// aliases that are duplicated
|
||||
var duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
|
||||
var invalidPropertyGroupAliases = (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ?? Array.Empty<string>();
|
||||
IEnumerable<string> duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
|
||||
var invalidPropertyGroupAliases =
|
||||
(validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ??
|
||||
Array.Empty<string>();
|
||||
|
||||
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
|
||||
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave,
|
||||
duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
|
||||
|
||||
var display = UmbracoMapper.Map<TContentTypeDisplay>(composition);
|
||||
TContentTypeDisplay display = UmbracoMapper.Map<TContentTypeDisplay>(composition);
|
||||
//map the 'save' data on top
|
||||
display = UmbracoMapper.Map(contentTypeSave, display);
|
||||
display.Errors = ModelState.ToErrorDictionary();
|
||||
return display;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -495,30 +560,42 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
where T : IContentTypeComposition
|
||||
{
|
||||
if (typeof(T).Implements<IContentType>())
|
||||
{
|
||||
return ContentTypeService as IContentTypeBaseService<T>;
|
||||
}
|
||||
|
||||
if (typeof(T).Implements<IMediaType>())
|
||||
{
|
||||
return MediaTypeService as IContentTypeBaseService<T>;
|
||||
}
|
||||
|
||||
if (typeof(T).Implements<IMemberType>())
|
||||
{
|
||||
return MemberTypeService as IContentTypeBaseService<T>;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Type " + typeof(T).FullName + " does not have a service.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors
|
||||
/// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors
|
||||
/// </summary>
|
||||
/// <param name="contentTypeSave"></param>
|
||||
/// <param name="duplicatePropertyTypeAliases"></param>
|
||||
/// <param name="invalidPropertyGroupAliases"></param>
|
||||
/// <returns></returns>
|
||||
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave, IEnumerable<string> duplicatePropertyTypeAliases, IEnumerable<string> invalidPropertyGroupAliases)
|
||||
private void AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(TContentTypeSave contentTypeSave,
|
||||
IEnumerable<string> duplicatePropertyTypeAliases, IEnumerable<string> invalidPropertyGroupAliases)
|
||||
where TContentTypeSave : ContentTypeSave<TPropertyType>
|
||||
where TPropertyType : PropertyTypeBasic
|
||||
{
|
||||
foreach (var propertyTypeAlias in duplicatePropertyTypeAliases)
|
||||
{
|
||||
// Find the property type relating to these
|
||||
var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyTypeAlias);
|
||||
var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(property));
|
||||
TPropertyType property = contentTypeSave.Groups.SelectMany(x => x.Properties)
|
||||
.Single(x => x.Alias == propertyTypeAlias);
|
||||
PropertyGroupBasic<TPropertyType> group =
|
||||
contentTypeSave.Groups.Single(x => x.Properties.Contains(property));
|
||||
var propertyIndex = group.Properties.IndexOf(property);
|
||||
var groupIndex = contentTypeSave.Groups.IndexOf(group);
|
||||
|
||||
@@ -529,7 +606,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
foreach (var propertyGroupAlias in invalidPropertyGroupAliases)
|
||||
{
|
||||
// Find the property group relating to these
|
||||
var group = contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias);
|
||||
PropertyGroupBasic<TPropertyType> group =
|
||||
contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias);
|
||||
var groupIndex = contentTypeSave.Groups.IndexOf(group);
|
||||
var key = $"Groups[{groupIndex}].Name";
|
||||
ModelState.AddModelError(key, "Different group types aren't allowed between compositions");
|
||||
@@ -537,7 +615,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the exception is an InvalidCompositionException create a response exception to be thrown for validation errors
|
||||
/// If the exception is an InvalidCompositionException create a response exception to be thrown for validation errors
|
||||
/// </summary>
|
||||
/// <typeparam name="TContentTypeDisplay"></typeparam>
|
||||
/// <typeparam name="TContentTypeSave"></typeparam>
|
||||
@@ -547,7 +625,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
/// <param name="ct"></param>
|
||||
/// <param name="ctId"></param>
|
||||
/// <returns></returns>
|
||||
private TContentTypeDisplay CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentTypeSave, TPropertyType>(
|
||||
private TContentTypeDisplay CreateInvalidCompositionResponseException<TContentTypeDisplay, TContentTypeSave,
|
||||
TPropertyType>(
|
||||
Exception ex, TContentTypeSave contentTypeSave, TContentType ct, int ctId)
|
||||
where TContentTypeDisplay : ContentTypeCompositionDisplay
|
||||
where TContentTypeSave : ContentTypeSave<TPropertyType>
|
||||
@@ -562,23 +641,27 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
invalidCompositionException = (InvalidCompositionException)ex.InnerException;
|
||||
}
|
||||
|
||||
if (invalidCompositionException != null)
|
||||
{
|
||||
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave, invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
|
||||
AddCompositionValidationErrors<TContentTypeSave, TPropertyType>(contentTypeSave,
|
||||
invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
|
||||
return CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(ctId, contentTypeSave, ct);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to throw the ModelState validation results when the ModelState is invalid
|
||||
/// Used to throw the ModelState validation results when the ModelState is invalid
|
||||
/// </summary>
|
||||
/// <typeparam name="TContentTypeDisplay"></typeparam>
|
||||
/// <typeparam name="TContentTypeSave"></typeparam>
|
||||
/// <param name="ctId"></param>
|
||||
/// <param name="contentTypeSave"></param>
|
||||
/// <param name="ct"></param>
|
||||
private TContentTypeDisplay CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(int ctId, TContentTypeSave contentTypeSave, TContentType ct)
|
||||
private TContentTypeDisplay CreateModelStateValidationEror<TContentTypeSave, TContentTypeDisplay>(int ctId,
|
||||
TContentTypeSave contentTypeSave, TContentType ct)
|
||||
where TContentTypeDisplay : ContentTypeCompositionDisplay
|
||||
where TContentTypeSave : ContentTypeSave
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,21 +12,20 @@ using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
using Constants = Umbraco.Cms.Core.Constants;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// An API controller used for dealing with member groups
|
||||
/// An API controller used for dealing with member groups
|
||||
/// </summary>
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberGroups)]
|
||||
[ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))]
|
||||
public class MemberGroupController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
private readonly IMemberGroupService _memberGroupService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
public MemberGroupController(
|
||||
IMemberGroupService memberGroupService,
|
||||
@@ -35,11 +34,12 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
{
|
||||
_memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
|
||||
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
|
||||
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
_localizedTextService =
|
||||
localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the member group json for the member group id
|
||||
/// Gets the member group json for the member group id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
@@ -56,7 +56,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the member group json for the member group guid
|
||||
/// Gets the member group json for the member group guid
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
@@ -72,7 +72,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the member group json for the member group udi
|
||||
/// Gets the member group json for the member group udi
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
@@ -100,7 +100,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
[HttpPost]
|
||||
public IActionResult DeleteById(int id)
|
||||
{
|
||||
var memberGroup = _memberGroupService.GetById(id);
|
||||
IMemberGroup memberGroup = _memberGroupService.GetById(id);
|
||||
if (memberGroup == null)
|
||||
{
|
||||
return NotFound();
|
||||
@@ -112,7 +112,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
|
||||
public IEnumerable<MemberGroupDisplay> GetAllGroups()
|
||||
=> _memberGroupService.GetAll()
|
||||
.Select(_umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>);
|
||||
.Select(_umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>);
|
||||
|
||||
public MemberGroupDisplay GetEmpty()
|
||||
{
|
||||
@@ -123,18 +123,21 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
public bool IsMemberGroupNameUnique(int id, string oldName, string newName)
|
||||
{
|
||||
if (newName == oldName)
|
||||
{
|
||||
return true; // name hasn't changed
|
||||
}
|
||||
|
||||
var memberGroup = _memberGroupService.GetByName(newName);
|
||||
IMemberGroup memberGroup = _memberGroupService.GetByName(newName);
|
||||
if (memberGroup == null)
|
||||
{
|
||||
return true; // no member group found
|
||||
}
|
||||
|
||||
return memberGroup.Id == id;
|
||||
}
|
||||
|
||||
public ActionResult<MemberGroupDisplay> PostSave(MemberGroupSave saveModel)
|
||||
{
|
||||
|
||||
var id = int.Parse(saveModel.Id.ToString(), CultureInfo.InvariantCulture);
|
||||
IMemberGroup memberGroup = id > 0 ? _memberGroupService.GetById(id) : new MemberGroup();
|
||||
if (memberGroup == null)
|
||||
@@ -157,7 +160,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
var display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
|
||||
MemberGroupDisplay display = _umbracoMapper.Map<IMemberGroup, MemberGroupDisplay>(memberGroup);
|
||||
display.AddErrorNotification(
|
||||
_localizedTextService.Localize("speechBubbles", "memberGroupNameDuplicate"),
|
||||
string.Empty);
|
||||
|
||||
Reference in New Issue
Block a user