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:
Bjarke Berg
2021-10-22 08:51:01 +02:00
38 changed files with 4404 additions and 2196 deletions

View File

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

View File

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