diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
index 640db2163a..4c9b19f1a9 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -673,19 +673,6 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
edited = true;
}
- // To establish the new value of "edited" we compare all properties publishedValue to editedValue and look
- // for differences.
- //
- // If we SaveAndPublish but the publish fails (e.g. already scheduled for release)
- // we have lost the publishedValue on IContent (in memory vs database) so we cannot correctly make that comparison.
- //
- // This is a slight change to behaviour, historically a publish, followed by change & save, followed by undo change & save
- // would change edited back to false.
- if (!publishing && editedSnapshot)
- {
- edited = true;
- }
-
if (entity.ContentType.VariesByCulture())
{
// names also impact 'edited'
diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
index 74e2aba112..0190d5d400 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs
@@ -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
{
///
- /// 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
///
[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; }
///
- /// Returns the available composite content types for a given content type
+ /// Returns the available composite content types for a given content type
///
///
///
- /// 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
///
///
- /// 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.
///
///
/// Whether the composite content types should be applicable for an element type
///
- protected ActionResult>> PerformGetAvailableCompositeContentTypes(int contentTypeId,
+ protected ActionResult>> 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().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().ToArray();
+
+ allContentTypes = MediaTypeService.GetAll().Cast().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().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 ancestors = availableCompositions.Ancestors.Select(x => x.Alias);
return availableCompositions.Results
- .Select(x => new Tuple(UmbracoMapper.Map(x.Composition), x.Allowed))
+ .Select(x =>
+ new Tuple(UmbracoMapper.Map(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 GetEntityContainers(IContentTypeComposition contentType, UmbracoObjectTypes type)
+ private IEnumerable GetEntityContainers(IContentTypeComposition contentType,
+ UmbracoObjectTypes type)
{
if (contentType == null)
{
@@ -170,12 +194,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
///
- /// 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
///
/// Type of content Type, eg documentType or mediaType
/// Id of composition content type
///
- protected ActionResult> PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId, UmbracoObjectTypes type)
+ protected ActionResult> 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 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(ctId, contentTypeSave, ct);
+ TContentTypeDisplay err =
+ CreateModelStateValidationEror(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 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(ex, contentTypeSave, ct, ctId);
- if (responseEx != null) return ValidationProblem(responseEx);
+ TContentTypeDisplay responseEx =
+ CreateInvalidCompositionResponseException(
+ ex, contentTypeSave, ct, ctId);
+ if (responseEx != null)
+ {
+ return ValidationProblem(responseEx);
+ }
}
- var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, ct);
- if (exResult != null) return ValidationProblem(exResult);
+ TContentTypeDisplay exResult =
+ CreateCompositionValidationExceptionIfInvalid(
+ 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(ex, contentTypeSave, ct, ctId);
+ TContentTypeDisplay responseEx =
+ CreateInvalidCompositionResponseException(
+ ex, contentTypeSave, ct, ctId);
if (responseEx is null)
+ {
throw ex;
+ }
return ValidationProblem(responseEx);
}
- var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, newCt);
- if (exResult != null) return ValidationProblem(exResult);
+ TContentTypeDisplay exResult =
+ CreateCompositionValidationExceptionIfInvalid(
+ 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 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);
+ }
}
///
- /// Move
+ /// Move
///
///
///
@@ -398,13 +457,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
Func getContentType,
Func>> doMove)
{
- var toMove = getContentType(move.Id);
+ TContentType toMove = getContentType(move.Id);
if (toMove == null)
{
return NotFound();
}
- var result = doMove(toMove, move.ParentId);
+ Attempt> 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
}
///
- /// Move
+ /// Move
///
///
///
@@ -435,13 +494,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
Func getContentType,
Func>> doCopy)
{
- var toMove = getContentType(move.Id);
+ TContentType toMove = getContentType(move.Id);
if (toMove == null)
{
return NotFound();
}
- var result = doCopy(toMove, move.ParentId);
+ Attempt> 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
}
///
- /// 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
///
///
///
///
- private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition)
+ private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition)
where TContentTypeSave : ContentTypeSave
where TPropertyType : PropertyTypeBasic
where TContentTypeDisplay : ContentTypeCompositionDisplay
{
- var service = GetContentTypeService();
- var validateAttempt = service.ValidateComposition(composition);
+ IContentTypeBaseService service = GetContentTypeService();
+ Attempt 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();
+ IEnumerable duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
+ var invalidPropertyGroupAliases =
+ (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ??
+ Array.Empty();
- AddCompositionValidationErrors(contentTypeSave, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
+ AddCompositionValidationErrors(contentTypeSave,
+ duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
- var display = UmbracoMapper.Map(composition);
+ TContentTypeDisplay display = UmbracoMapper.Map(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())
+ {
return ContentTypeService as IContentTypeBaseService;
+ }
+
if (typeof(T).Implements())
+ {
return MediaTypeService as IContentTypeBaseService;
+ }
+
if (typeof(T).Implements())
+ {
return MemberTypeService as IContentTypeBaseService;
+ }
+
throw new ArgumentException("Type " + typeof(T).FullName + " does not have a service.");
}
///
- /// 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
///
///
///
///
///
- private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave, IEnumerable duplicatePropertyTypeAliases, IEnumerable invalidPropertyGroupAliases)
+ private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave,
+ IEnumerable duplicatePropertyTypeAliases, IEnumerable invalidPropertyGroupAliases)
where TContentTypeSave : ContentTypeSave
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 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 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
}
///
- /// 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
///
///
///
@@ -547,7 +625,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
///
///
- private TContentTypeDisplay CreateInvalidCompositionResponseException(
+ private TContentTypeDisplay CreateInvalidCompositionResponseException(
Exception ex, TContentTypeSave contentTypeSave, TContentType ct, int ctId)
where TContentTypeDisplay : ContentTypeCompositionDisplay
where TContentTypeSave : ContentTypeSave
@@ -562,23 +641,27 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
invalidCompositionException = (InvalidCompositionException)ex.InnerException;
}
+
if (invalidCompositionException != null)
{
- AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
+ AddCompositionValidationErrors(contentTypeSave,
+ invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
return CreateModelStateValidationEror(ctId, contentTypeSave, ct);
}
+
return null;
}
///
- /// Used to throw the ModelState validation results when the ModelState is invalid
+ /// Used to throw the ModelState validation results when the ModelState is invalid
///
///
///
///
///
///
- private TContentTypeDisplay CreateModelStateValidationEror(int ctId, TContentTypeSave contentTypeSave, TContentType ct)
+ private TContentTypeDisplay CreateModelStateValidationEror(int ctId,
+ TContentTypeSave contentTypeSave, TContentType ct)
where TContentTypeDisplay : ContentTypeCompositionDisplay
where TContentTypeSave : ContentTypeSave
{
diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
index 2add065d70..91af372434 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using System.Dynamic;
using System.Globalization;
using System.Linq;
+using System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -13,6 +15,7 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
+using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Models.TemplateQuery;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Security;
@@ -26,20 +29,22 @@ using Umbraco.Cms.Web.BackOffice.ModelBinders;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.ModelBinders;
using Umbraco.Extensions;
-using Constants = Umbraco.Cms.Core.Constants;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
///
- /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode
+ /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are
+ /// based on CMSNode
///
///
- ///
- /// This controller allows resolving basic entity data for various entities without placing the hard restrictions on users that may not have access
- /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all users. In some cases such as accessing
- /// Members, more explicit security checks are done.
- ///
- /// Some objects such as macros are not based on CMSNode
+ ///
+ /// This controller allows resolving basic entity data for various entities without placing the hard restrictions
+ /// on users that may not have access
+ /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all
+ /// users. In some cases such as accessing
+ /// Members, more explicit security checks are done.
+ ///
+ /// Some objects such as macros are not based on CMSNode
///
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[ParameterSwapControllerActionSelector(nameof(GetAncestors), "id", typeof(int), typeof(Guid))]
@@ -51,26 +56,28 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[ParameterSwapControllerActionSelector(nameof(GetUrl), "id", typeof(int), typeof(Udi))]
public class EntityController : UmbracoAuthorizedJsonController
{
- private readonly ITreeService _treeService;
- private readonly UmbracoTreeSearcher _treeSearcher;
- private readonly SearchableTreeCollection _searchableTreeCollection;
- private readonly IPublishedContentQuery _publishedContentQuery;
- private readonly IShortStringHelper _shortStringHelper;
- private readonly IEntityService _entityService;
- private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
- private readonly IPublishedUrlProvider _publishedUrlProvider;
- private readonly IContentService _contentService;
- private readonly IUmbracoMapper _umbracoMapper;
- private readonly IDataTypeService _dataTypeService;
- private readonly ISqlContext _sqlContext;
- private readonly ILocalizedTextService _localizedTextService;
- private readonly IFileService _fileService;
- private readonly IContentTypeService _contentTypeService;
- private readonly IMediaTypeService _mediaTypeService;
- private readonly IMacroService _macroService;
- private readonly IUserService _userService;
- private readonly ILocalizationService _localizationService;
+ private static readonly string[] _postFilterSplitStrings = { "=", "==", "!=", "<>", ">", "<", ">=", "<=" };
+
private readonly AppCaches _appCaches;
+ private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
+ private readonly IContentService _contentService;
+ private readonly IContentTypeService _contentTypeService;
+ private readonly IDataTypeService _dataTypeService;
+ private readonly IEntityService _entityService;
+ private readonly IFileService _fileService;
+ private readonly ILocalizationService _localizationService;
+ private readonly ILocalizedTextService _localizedTextService;
+ private readonly IMacroService _macroService;
+ private readonly IMediaTypeService _mediaTypeService;
+ private readonly IPublishedContentQuery _publishedContentQuery;
+ private readonly IPublishedUrlProvider _publishedUrlProvider;
+ private readonly SearchableTreeCollection _searchableTreeCollection;
+ private readonly IShortStringHelper _shortStringHelper;
+ private readonly ISqlContext _sqlContext;
+ private readonly UmbracoTreeSearcher _treeSearcher;
+ private readonly ITreeService _treeService;
+ private readonly IUmbracoMapper _umbracoMapper;
+ private readonly IUserService _userService;
public EntityController(
ITreeService treeService,
@@ -102,7 +109,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
publishedContentQuery ?? throw new ArgumentNullException(nameof(publishedContentQuery));
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
- _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
+ _backofficeSecurityAccessor = backofficeSecurityAccessor ??
+ throw new ArgumentNullException(nameof(backofficeSecurityAccessor));
_publishedUrlProvider =
publishedUrlProvider ?? throw new ArgumentNullException(nameof(publishedUrlProvider));
_contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
@@ -121,17 +129,18 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
-
///
- /// Returns an Umbraco alias given a string
+ /// Returns an Umbraco alias given a string
///
///
///
///
public dynamic GetSafeAlias(string value, bool camelCase = true)
{
- var returnValue = string.IsNullOrWhiteSpace(value) ? string.Empty : value.ToSafeAlias(_shortStringHelper, camelCase);
- dynamic returnObj = new System.Dynamic.ExpandoObject();
+ var returnValue = string.IsNullOrWhiteSpace(value)
+ ? string.Empty
+ : value.ToSafeAlias(_shortStringHelper, camelCase);
+ dynamic returnObj = new ExpandoObject();
returnObj.alias = returnValue;
returnObj.original = value;
returnObj.camelCase = camelCase;
@@ -140,41 +149,47 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
///
- /// Searches for results based on the entity type
+ /// Searches for results based on the entity type
///
///
///
///
- /// A starting point for the search, generally a node id, but for members this is a member type alias
+ /// A starting point for the search, generally a node id, but for members this is a member type alias
///
/// If set used to look up whether user and group start node permissions will be ignored.
///
[HttpGet]
- public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeKey = null)
+ public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null,
+ Guid? dataTypeKey = null)
{
// NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that
if (string.IsNullOrEmpty(query))
+ {
return Enumerable.Empty();
+ }
//TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type
- var ignoreUserStartNodes = dataTypeKey.HasValue && _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
+ var ignoreUserStartNodes = dataTypeKey.HasValue &&
+ _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes);
}
///
- /// Searches for all content that the user is allowed to see (based on their allowed sections)
+ /// Searches for all content that the user is allowed to see (based on their allowed sections)
///
///
///
///
- /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need
- /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search
- /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result.
- ///
- /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search
- /// methods might be used in things like pickers in the content editor.
+ /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to
+ /// edit, we need
+ /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the
+ /// global search
+ /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result.
+ /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those
+ /// search
+ /// methods might be used in things like pickers in the content editor.
///
[HttpGet]
public IDictionary SearchAll(string query)
@@ -182,16 +197,22 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var result = new Dictionary();
if (string.IsNullOrEmpty(query))
+ {
return result;
+ }
var allowedSections = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.AllowedSections.ToArray();
- foreach (var searchableTree in _searchableTreeCollection.SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder))
+ foreach (KeyValuePair searchableTree in _searchableTreeCollection
+ .SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder))
{
if (allowedSections.Contains(searchableTree.Value.AppAlias))
{
- var tree = _treeService.GetByAlias(searchableTree.Key);
- if (tree == null) continue; //shouldn't occur
+ Tree tree = _treeService.GetByAlias(searchableTree.Key);
+ if (tree == null)
+ {
+ continue; //shouldn't occur
+ }
result[Tree.GetRootNodeDisplayName(tree, _localizedTextService)] = new TreeSearchResult
{
@@ -203,49 +224,52 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
};
}
}
+
return result;
}
///
- /// Gets the path for a given node ID
+ /// Gets the path for a given node ID
///
///
///
///
public IConvertToActionResult GetPath(int id, UmbracoEntityTypes type)
{
- var foundContentResult = GetResultForId(id, type);
- var foundContent = foundContentResult.Value;
+ ActionResult foundContentResult = GetResultForId(id, type);
+ EntityBasic foundContent = foundContentResult.Value;
if (foundContent is null)
{
return foundContentResult;
}
- return new ActionResult>(foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
- s => int.Parse(s, CultureInfo.InvariantCulture)));
+ return new ActionResult>(foundContent.Path
+ .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
+ s => int.Parse(s, CultureInfo.InvariantCulture)));
}
///
- /// Gets the path for a given node ID
+ /// Gets the path for a given node ID
///
///
///
///
public IConvertToActionResult GetPath(Guid id, UmbracoEntityTypes type)
{
- var foundContentResult = GetResultForKey(id, type);
- var foundContent = foundContentResult.Value;
+ ActionResult foundContentResult = GetResultForKey(id, type);
+ EntityBasic foundContent = foundContentResult.Value;
if (foundContent is null)
{
return foundContentResult;
}
- return new ActionResult>(foundContent.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
- s => int.Parse(s, CultureInfo.InvariantCulture)));
+ return new ActionResult>(foundContent.Path
+ .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).Select(
+ s => int.Parse(s, CultureInfo.InvariantCulture)));
}
///
- /// Gets the path for a given node ID
+ /// Gets the path for a given node ID
///
///
///
@@ -262,16 +286,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
///
- /// Gets the URL of an entity
+ /// Gets the URL of an entity
///
/// UDI of the entity to fetch URL for
/// The culture to fetch the URL for
/// The URL or path to the item
public IActionResult GetUrl(Udi id, string culture = "*")
{
- var intId = _entityService.GetId(id);
+ Attempt intId = _entityService.GetId(id);
if (!intId.Success)
+ {
return NotFound();
+ }
+
UmbracoEntityTypes entityType;
switch (id.EntityType)
{
@@ -287,19 +314,20 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
default:
return NotFound();
}
+
return GetUrl(intId.Result, entityType, culture);
}
///
- /// Get entity URLs by UDIs
+ /// Get entity URLs by UDIs
///
///
- /// A list of UDIs to lookup items by
+ /// A list of UDIs to lookup items by
///
/// The culture to fetch the URL for
/// Dictionary mapping Udi -> Url
///
- /// We allow for POST because there could be quite a lot of Ids.
+ /// We allow for POST because there could be quite a lot of Ids.
///
[HttpGet]
[HttpPost]
@@ -320,7 +348,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return guidUdi.EntityType switch
{
- Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(guidUdi.Guid, culture: culture ?? ClientCulture()),
+ Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(guidUdi.Guid,
+ culture: culture ?? ClientCulture()),
// NOTE: If culture is passed here we get an empty string rather than a media item URL WAT
Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(guidUdi.Guid, culture: null),
_ => null
@@ -328,21 +357,18 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
return udis
- .Select(udi => new {
- Udi = udi,
- Url = MediaOrDocumentUrl(udi)
- }).ToDictionary(x => x.Udi, x => x.Url);
+ .Select(udi => new { Udi = udi, Url = MediaOrDocumentUrl(udi) }).ToDictionary(x => x.Udi, x => x.Url);
}
///
- /// Gets the URL of an entity
+ /// Gets the URL of an entity
///
/// Int id of the entity to fetch URL for
/// The type of entity such as Document, Media, Member
/// The culture to fetch the URL for
/// The URL or path to the item
///
- /// We are not restricting this with security because there is no sensitive data
+ /// We are not restricting this with security because there is no sensitive data
///
public IActionResult GetUrl(int id, UmbracoEntityTypes type, string culture = null)
{
@@ -361,7 +387,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
}
- var ancestors = GetResultForAncestors(id, type);
+ IEnumerable ancestors = GetResultForAncestors(id, type);
//if content, skip the first node for replicating NiceUrl defaults
if (type == UmbracoEntityTypes.Document)
@@ -376,7 +402,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
///
- /// Gets an entity by a xpath query
+ /// Gets an entity by a xpath query
///
///
///
@@ -388,48 +414,53 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
if (type != UmbracoEntityTypes.Document)
+ {
throw new ArgumentException("Get by query is only compatible with entities of type Document");
+ }
var q = ParseXPathQuery(query, nodeContextId);
- var node = _publishedContentQuery.ContentSingleAtXPath(q);
+ IPublishedContent node = _publishedContentQuery.ContentSingleAtXPath(q);
if (node == null)
+ {
return null;
+ }
return GetById(node.Id, type);
}
// PP: Work in progress on the query parser
- private string ParseXPathQuery(string query, int id)
- {
- return UmbracoXPathPathSyntaxParser.ParseXPathQuery(
- xpathExpression: query,
- nodeContextId: id,
- getPath: nodeid =>
+ private string ParseXPathQuery(string query, int id) =>
+ UmbracoXPathPathSyntaxParser.ParseXPathQuery(
+ query,
+ id,
+ nodeid =>
{
- var ent = _entityService.Get(nodeid);
+ IEntitySlim ent = _entityService.Get(nodeid);
return ent.Path.Split(Constants.CharArrays.Comma).Reverse();
},
- publishedContentExists: i => _publishedContentQuery.Content(i) != null);
- }
+ i => _publishedContentQuery.Content(i) != null);
[HttpGet]
public ActionResult GetUrlAndAnchors(Udi id, string culture = "*")
{
- var intId = _entityService.GetId(id);
+ Attempt intId = _entityService.GetId(id);
if (!intId.Success)
+ {
return NotFound();
+ }
return GetUrlAndAnchors(intId.Result, culture);
}
+
[HttpGet]
public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*")
{
culture = culture ?? ClientCulture();
var url = _publishedUrlProvider.GetUrl(id, culture: culture);
- var anchorValues = _contentService.GetAnchorValuesFromRTEs(id, culture);
+ IEnumerable anchorValues = _contentService.GetAnchorValuesFromRTEs(id, culture);
return new UrlAndAnchors(url, anchorValues);
}
@@ -437,137 +468,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
[HttpPost]
public IEnumerable GetAnchors(AnchorsModel model)
{
- var anchorValues = _contentService.GetAnchorValuesFromRTEContent(model.RteContent);
+ IEnumerable anchorValues = _contentService.GetAnchorValuesFromRTEContent(model.RteContent);
return anchorValues;
}
-
- #region GetById
-
- ///
- /// Gets an entity by it's id
- ///
- ///
- ///
- ///
- public ActionResult GetById(int id, UmbracoEntityTypes type)
- {
- return GetResultForId(id, type);
- }
-
- ///
- /// Gets an entity by it's key
- ///
- ///
- ///
- ///
- public ActionResult GetById(Guid id, UmbracoEntityTypes type)
- {
- return GetResultForKey(id, type);
- }
-
- ///
- /// Gets an entity by it's UDI
- ///
- ///
- ///
- ///
- public ActionResult GetById(Udi id, UmbracoEntityTypes type)
- {
- var guidUdi = id as GuidUdi;
- if (guidUdi != null)
- {
- return GetResultForKey(guidUdi.Guid, type);
- }
-
- return NotFound();
- }
- #endregion
-
- #region GetByIds
- ///
- /// Get entities by integer ids
- ///
- ///
- ///
- ///
- ///
- /// We allow for POST because there could be quite a lot of Ids
- ///
- [HttpGet]
- [HttpPost]
- public ActionResult> GetByIds([FromJsonPath]int[] ids, [FromQuery]UmbracoEntityTypes type)
- {
- if (ids == null)
- {
- return NotFound();
- }
-
- return new ActionResult>(GetResultForIds(ids, type));
- }
-
- ///
- /// Get entities by GUID ids
- ///
- ///
- ///
- ///
- ///
- /// We allow for POST because there could be quite a lot of Ids
- ///
- [HttpGet]
- [HttpPost]
- public ActionResult> GetByIds([FromJsonPath]Guid[] ids, [FromQuery]UmbracoEntityTypes type)
- {
- if (ids == null)
- {
- return NotFound();
- }
-
- return new ActionResult>(GetResultForKeys(ids, type));
- }
-
- ///
- /// Get entities by UDIs
- ///
- ///
- /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type!
- ///
- ///
- ///
- ///
- /// We allow for POST because there could be quite a lot of Ids.
- ///
- [HttpGet]
- [HttpPost]
- public ActionResult> GetByIds([FromJsonPath]Udi[] ids, [FromQuery]UmbracoEntityTypes type)
- {
- if (ids == null)
- {
- return NotFound();
- }
-
- if (ids.Length == 0)
- {
- return Enumerable.Empty().ToList();
- }
-
- //all udi types will need to be the same in this list so we'll determine by the first
- //currently we only support GuidUdi for this method
-
- var guidUdi = ids[0] as GuidUdi;
- if (guidUdi != null)
- {
- return new ActionResult>(GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type));
- }
-
- return NotFound();
- }
- #endregion
-
public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeKey = null)
{
- var objectType = ConvertToObjectType(type);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{
//TODO: Need to check for Object types that support hierarchy here, some might not.
@@ -577,11 +484,15 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
- if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
+ if (id == Constants.System.Root && startNodes.Length > 0 &&
+ startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
{
- var nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
+ IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
if (nodes.Length == 0)
+ {
return Enumerable.Empty();
+ }
+
var pr = new List(nodes.Select(_umbracoMapper.Map));
return pr;
}
@@ -592,6 +503,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
.WhereNotNull()
.Select(_umbracoMapper.Map);
}
+
//now we need to convert the unknown ones
switch (type)
{
@@ -599,12 +511,13 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + type);
}
}
///
- /// Get paged child entities by id
+ /// Get paged child entities by id
///
///
///
@@ -648,7 +561,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//the EntityService can search paged members from the root
intId = -1;
- return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeKey);
+ return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter,
+ dataTypeKey);
}
//the EntityService cannot search members of a certain type, this is currently not supported and would require
@@ -657,16 +571,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
//TODO: We should really fix this in the EntityService but if we don't we should allow the ISearchableTree for the members controller
// to be used for this search instead of the built in/internal searcher
- var searchResult = _treeSearcher.ExamineSearch(filter ?? "", type, pageSize, pageNumber - 1, out long total, null, id);
+ IEnumerable searchResult = _treeSearcher.ExamineSearch(filter ?? "", type, pageSize,
+ pageNumber - 1, out var total, null, id);
- return new PagedResult(total, pageNumber, pageSize)
- {
- Items = searchResult
- };
+ return new PagedResult(total, pageNumber, pageSize) { Items = searchResult };
}
///
- /// Get paged child entities by id
+ /// Get paged child entities by id
///
///
///
@@ -687,11 +599,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
Guid? dataTypeKey = null)
{
if (pageNumber <= 0)
+ {
return NotFound();
- if (pageSize <= 0)
- return NotFound();
+ }
- var objectType = ConvertToObjectType(type);
+ if (pageSize <= 0)
+ {
+ return NotFound();
+ }
+
+ UmbracoObjectTypes? objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{
IEnumerable entities;
@@ -702,14 +619,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
// root is special: we reduce it to start nodes if the user's start node is not the default, then we need to return their start nodes
- if (id == Constants.System.Root && startNodes.Length > 0 && startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
+ if (id == Constants.System.Root && startNodes.Length > 0 &&
+ startNodes.Contains(Constants.System.Root) == false && !ignoreUserStartNodes)
{
if (pageNumber > 0)
+ {
return new PagedResult(0, 0, 0);
- var nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
+ }
+
+ IEntitySlim[] nodes = _entityService.GetAll(objectType.Value, startNodes).ToArray();
if (nodes.Length == 0)
+ {
return new PagedResult(0, 0, 0);
- if (pageSize < nodes.Length) pageSize = nodes.Length; // bah
+ }
+
+ if (pageSize < nodes.Length)
+ {
+ pageSize = nodes.Length; // bah
+ }
+
var pr = new PagedResult(nodes.Length, pageNumber, pageSize)
{
Items = nodes.Select(_umbracoMapper.Map)
@@ -718,7 +646,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
// else proceed as usual
- entities = _entityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ entities = _entityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
filter.IsNullOrWhiteSpace()
? null
: _sqlContext.Query().Where(x => x.Name.Contains(filter)),
@@ -735,7 +664,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
Items = entities.Select(source =>
{
- var target = _umbracoMapper.Map(source, context =>
+ EntityBasic target = _umbracoMapper.Map(source, context =>
{
context.SetCulture(culture);
context.SetCulture(culture);
@@ -758,7 +687,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + type);
}
}
@@ -767,9 +697,11 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
switch (type)
{
case UmbracoEntityTypes.Document:
- return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches);
+ return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(
+ _entityService, _appCaches);
case UmbracoEntityTypes.Media:
- return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches);
+ return _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(
+ _entityService, _appCaches);
default:
return Array.Empty();
}
@@ -786,14 +718,19 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
Guid? dataTypeKey = null)
{
if (pageNumber <= 0)
+ {
return NotFound();
+ }
+
if (pageSize <= 0)
+ {
return NotFound();
+ }
// re-normalize since NULL can be passed in
filter = filter ?? string.Empty;
- var objectType = ConvertToObjectType(type);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(type);
if (objectType.HasValue)
{
IEnumerable entities;
@@ -803,20 +740,23 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{
// root is special: we reduce it to start nodes
- int[] aids = GetStartNodes(type);
+ var aids = GetStartNodes(type);
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey);
entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes
- ? _entityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ ? _entityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
_sqlContext.Query().Where(x => x.Name.Contains(filter)),
- Ordering.By(orderBy, orderDirection), includeTrashed: false)
- : _entityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ Ordering.By(orderBy, orderDirection), false)
+ : _entityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
_sqlContext.Query().Where(x => x.Name.Contains(filter)),
Ordering.By(orderBy, orderDirection));
}
else
{
- entities = _entityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords,
+ entities = _entityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize,
+ out totalRecords,
_sqlContext.Query().Where(x => x.Name.Contains(filter)),
Ordering.By(orderBy, orderDirection));
}
@@ -843,20 +783,26 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + type);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + type);
}
}
- private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && _dataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value);
+ private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue &&
+ _dataTypeService
+ .IsDataTypeIgnoringUserStartNodes(
+ dataTypeKey.Value);
- public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings)
- {
- return GetResultForAncestors(id, type, queryStrings);
- }
+ public IEnumerable GetAncestors(int id, UmbracoEntityTypes type,
+ [ModelBinder(typeof(HttpQueryStringModelBinder))]
+ FormCollection queryStrings) =>
+ GetResultForAncestors(id, type, queryStrings);
- public ActionResult> GetAncestors(Guid id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings)
+ public ActionResult> GetAncestors(Guid id, UmbracoEntityTypes type,
+ [ModelBinder(typeof(HttpQueryStringModelBinder))]
+ FormCollection queryStrings)
{
- var entity = _entityService.Get(id);
+ IEntitySlim entity = _entityService.Get(id);
if (entity is null)
{
return NotFound();
@@ -866,22 +812,24 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
///
- /// Searches for results based on the entity type
+ /// Searches for results based on the entity type
///
///
///
///
/// If set to true, user and group start node permissions will be ignored.
///
- private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false)
+ private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType,
+ string searchFrom = null, bool ignoreUserStartNodes = false)
{
var culture = ClientCulture();
- return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, culture, searchFrom, ignoreUserStartNodes);
+ return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, culture, searchFrom,
+ ignoreUserStartNodes);
}
private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
// TODO: Need to check for Object types that support hierarchic here, some might not.
@@ -890,6 +838,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
.WhereNotNull()
.Select(MapEntities());
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -897,30 +846,37 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
- private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormCollection queryStrings = null)
+ private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType,
+ FormCollection queryStrings = null)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
// TODO: Need to check for Object types that support hierarchic here, some might not.
- var ids = _entityService.Get(id).Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture)).Distinct().ToArray();
+ var ids = _entityService.Get(id).Path.Split(Constants.CharArrays.Comma)
+ .Select(s => int.Parse(s, CultureInfo.InvariantCulture)).Distinct().ToArray();
- var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId"));
+ var ignoreUserStartNodes =
+ IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId"));
if (ignoreUserStartNodes == false)
{
int[] aids = null;
switch (entityType)
{
case UmbracoEntityTypes.Document:
- aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateContentStartNodeIds(_entityService, _appCaches);
+ aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser
+ .CalculateContentStartNodeIds(_entityService, _appCaches);
break;
case UmbracoEntityTypes.Media:
- aids = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(_entityService, _appCaches);
+ aids =
+ _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.CalculateMediaStartNodeIds(
+ _entityService, _appCaches);
break;
}
@@ -935,12 +891,14 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
lids.Add(i);
continue;
}
+
if (aids.Contains(i))
{
lids.Add(i);
ok = true;
}
}
+
ids = lids.ToArray();
}
}
@@ -954,6 +912,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
.OrderBy(x => x.Level)
.Select(MapEntities(culture));
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -963,28 +922,33 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType)
{
if (keys.Length == 0)
+ {
return Enumerable.Empty();
+ }
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var entities = _entityService.GetAll(objectType.Value, keys)
+ IEnumerable entities = _entityService.GetAll(objectType.Value, keys)
.WhereNotNull()
.Select(MapEntities());
// entities are in "some" order, put them back in order
var xref = entities.ToDictionary(x => x.Key);
- var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null);
+ IEnumerable result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null)
+ .Where(x => x != null);
return result;
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -994,28 +958,33 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType)
{
if (ids.Length == 0)
+ {
return Enumerable.Empty();
+ }
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var entities = _entityService.GetAll(objectType.Value, ids)
+ IEnumerable entities = _entityService.GetAll(objectType.Value, ids)
.WhereNotNull()
.Select(MapEntities());
// entities are in "some" order, put them back in order
var xref = entities.ToDictionary(x => x.Id);
- var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null);
+ IEnumerable result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null)
+ .Where(x => x != null);
return result;
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -1025,22 +994,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.User:
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private ActionResult GetResultForKey(Guid key, UmbracoEntityTypes entityType)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var found = _entityService.Get(key, objectType.Value);
+ IEntitySlim found = _entityService.Get(key, objectType.Value);
if (found == null)
{
return NotFound();
}
+
return _umbracoMapper.Map(found);
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -1055,22 +1027,25 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private ActionResult GetResultForId(int id, UmbracoEntityTypes entityType)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
- var found = _entityService.Get(id, objectType.Value);
+ IEntitySlim found = _entityService.Get(id, objectType.Value);
if (found == null)
{
return NotFound();
}
+
return MapEntity(found);
}
+
//now we need to convert the unknown ones
switch (entityType)
{
@@ -1085,7 +1060,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
case UmbracoEntityTypes.Macro:
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
@@ -1116,145 +1092,151 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
///
- ///
///
/// The type of entity.
- /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are ignored.
+ ///
+ /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are
+ /// ignored.
+ ///
///
- public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter)
- {
- return GetResultForAll(type, postFilter);
- }
+ public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter) =>
+ GetResultForAll(type, postFilter);
///
- /// Gets the result for the entity list based on the type
+ /// Gets the result for the entity list based on the type
///
///
/// A string where filter that will filter the results dynamically with linq - optional
///
private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null)
{
- var objectType = ConvertToObjectType(entityType);
+ UmbracoObjectTypes? objectType = ConvertToObjectType(entityType);
if (objectType.HasValue)
{
// TODO: Should we order this by something ?
- var entities = _entityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities());
+ IEnumerable entities =
+ _entityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities());
return ExecutePostFilter(entities, postFilter);
}
+
//now we need to convert the unknown ones
switch (entityType)
{
case UmbracoEntityTypes.Template:
- var templates = _fileService.GetTemplates();
- var filteredTemplates = ExecutePostFilter(templates, postFilter);
+ IEnumerable templates = _fileService.GetTemplates();
+ IEnumerable filteredTemplates = ExecutePostFilter(templates, postFilter);
return filteredTemplates.Select(MapEntities());
case UmbracoEntityTypes.Macro:
//Get all macros from the macro service
- var macros = _macroService.GetAll().WhereNotNull().OrderBy(x => x.Name);
- var filteredMacros = ExecutePostFilter(macros, postFilter);
+ IOrderedEnumerable macros = _macroService.GetAll().WhereNotNull().OrderBy(x => x.Name);
+ IEnumerable filteredMacros = ExecutePostFilter(macros, postFilter);
return filteredMacros.Select(MapEntities());
case UmbracoEntityTypes.PropertyType:
//get all document types, then combine all property types into one list
- var propertyTypes = _contentTypeService.GetAll().Cast()
- .Concat(_mediaTypeService.GetAll())
- .ToArray()
- .SelectMany(x => x.PropertyTypes)
- .DistinctBy(composition => composition.Alias);
- var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter);
+ IEnumerable propertyTypes = _contentTypeService.GetAll()
+ .Cast()
+ .Concat(_mediaTypeService.GetAll())
+ .ToArray()
+ .SelectMany(x => x.PropertyTypes)
+ .DistinctBy(composition => composition.Alias);
+ IEnumerable filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter);
return _umbracoMapper.MapEnumerable(filteredPropertyTypes);
case UmbracoEntityTypes.PropertyGroup:
//get all document types, then combine all property types into one list
- var propertyGroups = _contentTypeService.GetAll().Cast()
- .Concat(_mediaTypeService.GetAll())
- .ToArray()
- .SelectMany(x => x.PropertyGroups)
- .DistinctBy(composition => composition.Name);
- var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter);
+ IEnumerable propertyGroups = _contentTypeService.GetAll()
+ .Cast()
+ .Concat(_mediaTypeService.GetAll())
+ .ToArray()
+ .SelectMany(x => x.PropertyGroups)
+ .DistinctBy(composition => composition.Name);
+ IEnumerable filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter);
return _umbracoMapper.MapEnumerable(filteredpropertyGroups);
case UmbracoEntityTypes.User:
- var users = _userService.GetAll(0, int.MaxValue, out _);
- var filteredUsers = ExecutePostFilter(users, postFilter);
+ IEnumerable users = _userService.GetAll(0, int.MaxValue, out _);
+ IEnumerable filteredUsers = ExecutePostFilter(users, postFilter);
return _umbracoMapper.MapEnumerable(filteredUsers);
case UmbracoEntityTypes.Stylesheet:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on stylesheets is not currently supported");
+ }
return _fileService.GetStylesheets().Select(MapEntities());
case UmbracoEntityTypes.Script:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on scripts is not currently supported");
+ }
return _fileService.GetScripts().Select(MapEntities());
case UmbracoEntityTypes.PartialView:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on partial views is not currently supported");
+ }
return _fileService.GetPartialViews().Select(MapEntities());
case UmbracoEntityTypes.Language:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on languages is not currently supported");
+ }
return _localizationService.GetAllLanguages().Select(MapEntities());
case UmbracoEntityTypes.DictionaryItem:
if (!postFilter.IsNullOrWhiteSpace())
+ {
throw new NotSupportedException("Filtering on dictionary items is not currently supported");
+ }
return GetAllDictionaryItems();
default:
- throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType);
+ throw new NotSupportedException("The " + typeof(EntityController) +
+ " does not currently support data for the type " + entityType);
}
}
private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter)
{
- if (postFilter.IsNullOrWhiteSpace()) return entities;
+ if (postFilter.IsNullOrWhiteSpace())
+ {
+ return entities;
+ }
var postFilterConditions = postFilter.Split(Constants.CharArrays.Ampersand);
foreach (var postFilterCondition in postFilterConditions)
{
- var queryCondition = BuildQueryCondition(postFilterCondition);
+ QueryCondition queryCondition = BuildQueryCondition(postFilterCondition);
if (queryCondition != null)
{
- var whereClauseExpression = queryCondition.BuildCondition("x");
+ Expression> whereClauseExpression = queryCondition.BuildCondition("x");
entities = entities.Where(whereClauseExpression.Compile());
}
-
}
+
return entities;
}
- private static readonly string[] _postFilterSplitStrings = new[]
- {
- "=",
- "==",
- "!=",
- "<>",
- ">",
- "<",
- ">=",
- "<="
- };
-
private static QueryCondition BuildQueryCondition(string postFilter)
{
var postFilterParts = postFilter.Split(_postFilterSplitStrings, 2, StringSplitOptions.RemoveEmptyEntries);
@@ -1280,26 +1262,21 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
return null;
}
- var type = typeof(T);
- var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
+ Type type = typeof(T);
+ PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
if (property == null)
{
return null;
}
- var queryCondition = new QueryCondition()
+ var queryCondition = new QueryCondition
{
- Term = new OperatorTerm()
- {
- Operator = binaryOperator
- },
+ Term = new OperatorTerm { Operator = binaryOperator },
ConstraintValue = constraintValue,
- Property = new PropertyModel()
+ Property = new PropertyModel
{
- Alias = propertyName,
- Name = propertyName,
- Type = property.PropertyType.Name
+ Alias = propertyName, Name = propertyName, Type = property.PropertyType.Name
}
};
@@ -1320,14 +1297,141 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
private string ClientCulture() => Request.ClientCulture();
+
+ #region GetById
+
+ ///
+ /// Gets an entity by it's id
+ ///
+ ///
+ ///
+ ///
+ public ActionResult GetById(int id, UmbracoEntityTypes type) => GetResultForId(id, type);
+
+ ///
+ /// Gets an entity by it's key
+ ///
+ ///
+ ///
+ ///
+ public ActionResult GetById(Guid id, UmbracoEntityTypes type) => GetResultForKey(id, type);
+
+ ///
+ /// Gets an entity by it's UDI
+ ///
+ ///
+ ///
+ ///
+ public ActionResult GetById(Udi id, UmbracoEntityTypes type)
+ {
+ var guidUdi = id as GuidUdi;
+ if (guidUdi != null)
+ {
+ return GetResultForKey(guidUdi.Guid, type);
+ }
+
+ return NotFound();
+ }
+
+ #endregion
+
+ #region GetByIds
+
+ ///
+ /// Get entities by integer ids
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// We allow for POST because there could be quite a lot of Ids
+ ///
+ [HttpGet]
+ [HttpPost]
+ public ActionResult> GetByIds([FromJsonPath] int[] ids,
+ [FromQuery] UmbracoEntityTypes type)
+ {
+ if (ids == null)
+ {
+ return NotFound();
+ }
+
+ return new ActionResult>(GetResultForIds(ids, type));
+ }
+
+ ///
+ /// Get entities by GUID ids
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// We allow for POST because there could be quite a lot of Ids
+ ///
+ [HttpGet]
+ [HttpPost]
+ public ActionResult> GetByIds([FromJsonPath] Guid[] ids,
+ [FromQuery] UmbracoEntityTypes type)
+ {
+ if (ids == null)
+ {
+ return NotFound();
+ }
+
+ return new ActionResult>(GetResultForKeys(ids, type));
+ }
+
+ ///
+ /// Get entities by UDIs
+ ///
+ ///
+ /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type!
+ ///
+ ///
+ ///
+ ///
+ /// We allow for POST because there could be quite a lot of Ids.
+ ///
+ [HttpGet]
+ [HttpPost]
+ public ActionResult> GetByIds([FromJsonPath] Udi[] ids,
+ [FromQuery] UmbracoEntityTypes type)
+ {
+ if (ids == null)
+ {
+ return NotFound();
+ }
+
+ if (ids.Length == 0)
+ {
+ return Enumerable.Empty().ToList();
+ }
+
+ //all udi types will need to be the same in this list so we'll determine by the first
+ //currently we only support GuidUdi for this method
+
+ var guidUdi = ids[0] as GuidUdi;
+ if (guidUdi != null)
+ {
+ return new ActionResult>(
+ GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type));
+ }
+
+ return NotFound();
+ }
+
+ #endregion
+
#region Methods to get all dictionary items
+
private IEnumerable GetAllDictionaryItems()
{
var list = new List();
- foreach (var dictionaryItem in _localizationService.GetRootDictionaryItems().OrderBy(DictionaryItemSort()))
+ foreach (IDictionaryItem dictionaryItem in _localizationService.GetRootDictionaryItems()
+ .OrderBy(DictionaryItemSort()))
{
- var item = _umbracoMapper.Map(dictionaryItem);
+ EntityBasic item = _umbracoMapper.Map(dictionaryItem);
list.Add(item);
GetChildItemsForList(dictionaryItem, list);
}
@@ -1339,15 +1443,16 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
private void GetChildItemsForList(IDictionaryItem dictionaryItem, ICollection list)
{
- foreach (var childItem in _localizationService.GetDictionaryItemChildren(dictionaryItem.Key).OrderBy(DictionaryItemSort()))
+ foreach (IDictionaryItem childItem in _localizationService.GetDictionaryItemChildren(dictionaryItem.Key)
+ .OrderBy(DictionaryItemSort()))
{
- var item = _umbracoMapper.Map(childItem);
+ EntityBasic item = _umbracoMapper.Map(childItem);
list.Add(item);
GetChildItemsForList(childItem, list);
}
}
- #endregion
+ #endregion
}
}
diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs
index e28d67a5dd..24f128e410 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs
@@ -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
{
///
- /// An API controller used for dealing with member groups
+ /// An API controller used for dealing with member groups
///
[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));
}
///
- /// Gets the member group json for the member group id
+ /// Gets the member group json for the member group id
///
///
///
@@ -56,7 +56,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
///
- /// Gets the member group json for the member group guid
+ /// Gets the member group json for the member group guid
///
///
///
@@ -72,7 +72,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
}
///
- /// Gets the member group json for the member group udi
+ /// Gets the member group json for the member group udi
///
///
///
@@ -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 GetAllGroups()
=> _memberGroupService.GetAll()
- .Select(_umbracoMapper.Map);
+ .Select(_umbracoMapper.Map);
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 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(memberGroup);
+ MemberGroupDisplay display = _umbracoMapper.Map(memberGroup);
display.AddErrorNotification(
_localizedTextService.Localize("speechBubbles", "memberGroupNameDuplicate"),
string.Empty);
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js
index e1639dde26..9bd1909960 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbfocuslock.directive.js
@@ -35,9 +35,6 @@
// List of elements that can be focusable within the focus lock
var focusableElementsSelector = '[role="button"], a[href]:not([disabled]):not(.ng-hide), button:not([disabled]):not(.ng-hide), textarea:not([disabled]):not(.ng-hide), input:not([disabled]):not(.ng-hide), select:not([disabled]):not(.ng-hide)';
- // Grab the body element so we can add the tabbing class on it when needed
- var bodyElement = document.querySelector('body');
-
function getDomNodes(){
infiniteEditorsWrapper = document.querySelector('.umb-editors');
if(infiniteEditorsWrapper) {
@@ -47,7 +44,10 @@
function getFocusableElements(targetElm) {
var elm = targetElm ? targetElm : target;
- focusableElements = elm.querySelectorAll(focusableElementsSelector);
+
+ // Filter out elements that are children of parents with the .ng-hide class
+ focusableElements = [...elm.querySelectorAll(focusableElementsSelector)].filter(elm => !elm.closest('.ng-hide'));
+
// Set first and last focusable elements
firstFocusableElement = focusableElements[0];
lastFocusableElement = focusableElements[focusableElements.length - 1];
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
index 47441326d7..7870267995 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
@@ -118,7 +118,7 @@ Use this directive to render a ui component for selecting child items to a paren
(function() {
'use strict';
- function ChildSelectorDirective() {
+ function ChildSelectorDirective(overlayService, localizationService) {
function link(scope, el, attr, ctrl) {
@@ -126,10 +126,30 @@ Use this directive to render a ui component for selecting child items to a paren
scope.dialogModel = {};
scope.showDialog = false;
- scope.removeChild = (selectedChild, $index) => {
- if (scope.onRemove) {
- scope.onRemove(selectedChild, $index);
- }
+ scope.removeChild = (selectedChild, $index, event) => {
+ const dialog = {
+ view: "views/components/overlays/umb-template-remove-confirm.html",
+ layout: selectedChild,
+ submitButtonLabelKey: "defaultdialogs_yesRemove",
+ submitButtonStyle: "danger",
+ submit: function () {
+ if(scope.onRemove) {
+ scope.onRemove(selectedChild, $index);
+ overlayService.close();
+ }
+ },
+ close: function () {
+ overlayService.close();
+ }
+ };
+
+ localizationService.localize("general_delete").then(value => {
+ dialog.title = value;
+ overlayService.open(dialog);
+ });
+
+ event.preventDefault();
+ event.stopPropagation();
};
scope.addChild = $event => {
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
index b7b19dd771..fe802a8a28 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js
@@ -214,13 +214,14 @@ When building a custom infinite editor view you can use the same components as a
* Method to tell editors that they are begin blurred.
*/
function blur() {
-
- /* keyboard shortcuts will be overwritten by the new infinite editor
+ if (isEnabled === true) {
+ /* keyboard shortcuts will be overwritten by the new infinite editor
so we need to store the shortcuts for the current editor so they can be rebound
when the infinite editor closes
*/
- unbindKeyboardShortcuts();
- isEnabled = false;
+ unbindKeyboardShortcuts();
+ isEnabled = false;
+ }
}
/**
* @ngdoc method
diff --git a/src/Umbraco.Web.UI.Client/src/less/gridview.less b/src/Umbraco.Web.UI.Client/src/less/gridview.less
index 80f13fbf1f..83071a0d93 100644
--- a/src/Umbraco.Web.UI.Client/src/less/gridview.less
+++ b/src/Umbraco.Web.UI.Client/src/less/gridview.less
@@ -621,7 +621,6 @@
}
.preview-rows {
-
display: inline-block;
position: relative;
box-sizing: border-box;
@@ -675,9 +674,9 @@
}
.preview-rows.columns {
- min-height: 18px;
+ min-height: 16px;
line-height: 11px;
- padding: 1px;
+ padding: 0 1px 1px 1px;
&.prevalues-rows {
min-height: 30px;
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-template-remove-confirm.html b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-template-remove-confirm.html
new file mode 100644
index 0000000000..17b853fcbc
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/views/components/overlays/umb-template-remove-confirm.html
@@ -0,0 +1,15 @@
+
+
+
+ You are removing the child node{{model.layout.name}}.
+
+
+
+
+ Removing a child node will limit the editors options to create different content types beneath a node.
+
+