2022-05-04 07:13:27 +12:00
using System.Collections.Concurrent ;
2021-10-22 08:51:01 +02:00
using System.Dynamic ;
2021-09-14 22:13:39 +02:00
using System.Globalization ;
2023-02-06 17:27:36 +05:30
using System.IO ;
2021-10-22 08:51:01 +02:00
using System.Linq.Expressions ;
2019-01-23 10:22:29 +01:00
using System.Reflection ;
2023-02-06 17:27:36 +05:30
using System.Security.Cryptography ;
using Examine.Search ;
2020-06-15 13:07:49 +02:00
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.Mvc ;
2021-01-14 19:41:32 +01:00
using Microsoft.AspNetCore.Mvc.Infrastructure ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core ;
2021-03-05 15:36:27 +01:00
using Umbraco.Cms.Core.Cache ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core.Mapping ;
using Umbraco.Cms.Core.Models ;
using Umbraco.Cms.Core.Models.ContentEditing ;
using Umbraco.Cms.Core.Models.Entities ;
using Umbraco.Cms.Core.Models.Membership ;
2021-10-22 08:51:01 +02:00
using Umbraco.Cms.Core.Models.PublishedContent ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core.Models.TemplateQuery ;
2023-02-06 17:27:36 +05:30
using Umbraco.Cms.Core.Persistence ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core.Routing ;
using Umbraco.Cms.Core.Security ;
using Umbraco.Cms.Core.Services ;
using Umbraco.Cms.Core.Strings ;
using Umbraco.Cms.Core.Trees ;
using Umbraco.Cms.Core.Xml ;
2021-02-12 13:36:50 +01:00
using Umbraco.Cms.Infrastructure.Persistence ;
2021-02-15 11:45:27 +01:00
using Umbraco.Cms.Infrastructure.Search ;
2021-02-10 11:11:18 +01:00
using Umbraco.Cms.Web.BackOffice.ModelBinders ;
2021-02-10 11:42:04 +01:00
using Umbraco.Cms.Web.Common.Attributes ;
using Umbraco.Cms.Web.Common.ModelBinders ;
2020-06-15 13:07:49 +02:00
using Umbraco.Extensions ;
2019-11-05 13:45:42 +01:00
2022-06-20 08:37:17 +02:00
namespace Umbraco.Cms.Web.BackOffice.Controllers ;
/// <summary>
/// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are
/// based on CMSNode
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>Some objects such as macros are not based on CMSNode</para>
/// </remarks>
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
[ParameterSwapControllerActionSelector(nameof(GetAncestors), "id", typeof(int), typeof(Guid))]
[ParameterSwapControllerActionSelector(nameof(GetPagedChildren), "id", typeof(int), typeof(string))]
[ParameterSwapControllerActionSelector(nameof(GetPath), "id", typeof(int), typeof(Guid), typeof(Udi))]
[ParameterSwapControllerActionSelector(nameof(GetUrlAndAnchors), "id", typeof(int), typeof(Guid), typeof(Udi))]
[ParameterSwapControllerActionSelector(nameof(GetById), "id", typeof(int), typeof(Guid), typeof(Udi))]
[ParameterSwapControllerActionSelector(nameof(GetByIds), "ids", typeof(int[] ) , typeof ( Guid [ ] ) , typeof ( Udi [ ] ) ) ]
[ParameterSwapControllerActionSelector(nameof(GetUrl), "id", typeof(int), typeof(Udi))]
[ParameterSwapControllerActionSelector(nameof(GetUrlsByIds), "ids", typeof(int[] ) , typeof ( Guid [ ] ) , typeof ( Udi [ ] ) ) ]
public class EntityController : UmbracoAuthorizedJsonController
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
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 ,
UmbracoTreeSearcher treeSearcher ,
SearchableTreeCollection searchableTreeCollection ,
IPublishedContentQuery publishedContentQuery ,
IShortStringHelper shortStringHelper ,
IEntityService entityService ,
IBackOfficeSecurityAccessor backofficeSecurityAccessor ,
IPublishedUrlProvider publishedUrlProvider ,
IContentService contentService ,
IUmbracoMapper umbracoMapper ,
IDataTypeService dataTypeService ,
ISqlContext sqlContext ,
ILocalizedTextService localizedTextService ,
IFileService fileService ,
IContentTypeService contentTypeService ,
IMediaTypeService mediaTypeService ,
IMacroService macroService ,
IUserService userService ,
ILocalizationService localizationService ,
AppCaches appCaches )
{
_treeService = treeService ? ? throw new ArgumentNullException ( nameof ( treeService ) ) ;
_treeSearcher = treeSearcher ? ? throw new ArgumentNullException ( nameof ( treeSearcher ) ) ;
_searchableTreeCollection = searchableTreeCollection ? ?
throw new ArgumentNullException ( nameof ( searchableTreeCollection ) ) ;
_publishedContentQuery =
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 ) ) ;
_publishedUrlProvider =
publishedUrlProvider ? ? throw new ArgumentNullException ( nameof ( publishedUrlProvider ) ) ;
_contentService = contentService ? ? throw new ArgumentNullException ( nameof ( contentService ) ) ;
_umbracoMapper = umbracoMapper ? ? throw new ArgumentNullException ( nameof ( umbracoMapper ) ) ;
_dataTypeService = dataTypeService ? ? throw new ArgumentNullException ( nameof ( dataTypeService ) ) ;
_sqlContext = sqlContext ? ? throw new ArgumentNullException ( nameof ( sqlContext ) ) ;
_localizedTextService =
localizedTextService ? ? throw new ArgumentNullException ( nameof ( localizedTextService ) ) ;
_fileService = fileService ? ? throw new ArgumentNullException ( nameof ( fileService ) ) ;
_contentTypeService = contentTypeService ? ? throw new ArgumentNullException ( nameof ( contentTypeService ) ) ;
_mediaTypeService = mediaTypeService ? ? throw new ArgumentNullException ( nameof ( mediaTypeService ) ) ;
_macroService = macroService ? ? throw new ArgumentNullException ( nameof ( macroService ) ) ;
_userService = userService ? ? throw new ArgumentNullException ( nameof ( userService ) ) ;
_localizationService = localizationService ? ? throw new ArgumentNullException ( nameof ( localizationService ) ) ;
_appCaches = appCaches ? ? throw new ArgumentNullException ( nameof ( appCaches ) ) ;
}
/// <summary>
/// Returns an Umbraco alias given a string
/// </summary>
/// <param name="value"></param>
/// <param name="camelCase"></param>
/// <returns></returns>
public dynamic GetSafeAlias ( string value , bool camelCase = true )
{
var returnValue = string . IsNullOrWhiteSpace ( value )
? string . Empty
: value . ToSafeAlias ( _shortStringHelper , camelCase ) ;
dynamic returnObj = new ExpandoObject ( ) ;
returnObj . alias = returnValue ;
returnObj . original = value ;
returnObj . camelCase = camelCase ;
return returnObj ;
}
2018-06-29 19:52:40 +02:00
/// <summary>
2022-06-20 08:37:17 +02:00
/// Searches for results based on the entity type
2018-06-29 19:52:40 +02:00
/// </summary>
2022-06-20 08:37:17 +02:00
/// <param name="query"></param>
/// <param name="type"></param>
/// <param name="searchFrom">
/// A starting point for the search, generally a node id, but for members this is a member type alias
/// </param>
/// <param name="dataTypeKey">If set used to look up whether user and group start node permissions will be ignored.</param>
/// <returns></returns>
[HttpGet]
public IEnumerable < EntityBasic > 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 < EntityBasic > ( ) ;
}
//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 ) ;
return ExamineSearch ( query , type , searchFrom , ignoreUserStartNodes ) ;
}
2022-07-25 11:07:56 +01:00
/// <summary>
/// Searches for all content that the user is allowed to see (based on their allowed sections)
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
[HttpGet]
public async Task < IDictionary < string , TreeSearchResult > > SearchAll ( string query )
2022-06-20 08:37:17 +02:00
{
2022-07-25 11:07:56 +01:00
if ( string . IsNullOrEmpty ( query ) )
{
return new Dictionary < string , TreeSearchResult > ( ) ;
}
2018-06-29 19:52:40 +02:00
2022-07-25 11:07:56 +01:00
var culture = ClientCulture ( ) ;
var allowedSections = _backofficeSecurityAccessor . BackOfficeSecurity ? . CurrentUser ? . AllowedSections . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
2022-07-25 11:07:56 +01:00
var searchTasks = new List < Task < KeyValuePair < string , TreeSearchResult > > > ( ) ;
foreach ( KeyValuePair < string , SearchableApplicationTree > searchableTree in _searchableTreeCollection
. SearchableApplicationTrees . OrderBy ( t = > t . Value . SortOrder ) )
2018-06-29 19:52:40 +02:00
{
2022-07-25 11:07:56 +01:00
if ( allowedSections ? . Contains ( searchableTree . Value . AppAlias ) ? ? false )
2018-06-29 19:52:40 +02:00
{
2022-07-25 11:07:56 +01:00
Tree ? tree = _treeService . GetByAlias ( searchableTree . Key ) ;
if ( tree = = null )
{
continue ; //shouldn't occur
}
2018-06-29 19:52:40 +02:00
2022-07-25 11:07:56 +01:00
var rootNodeDisplayName = Tree . GetRootNodeDisplayName ( tree , _localizedTextService ) ;
if ( rootNodeDisplayName is not null )
{
searchTasks . Add ( ExecuteSearchAsync ( query , culture , searchableTree , rootNodeDisplayName ) ) ;
}
2018-06-29 19:52:40 +02:00
}
}
2022-07-25 11:07:56 +01:00
var taskResults = await Task . WhenAll ( searchTasks ) ;
2022-05-04 07:13:27 +12:00
2022-07-25 11:07:56 +01:00
return new Dictionary < string , TreeSearchResult > ( taskResults ) ;
}
2022-07-11 14:07:08 +02:00
2022-07-25 11:07:56 +01:00
private static async Task < KeyValuePair < string , TreeSearchResult > > ExecuteSearchAsync (
string query ,
string? culture ,
KeyValuePair < string , SearchableApplicationTree > searchableTree ,
string rootNodeDisplayName )
2018-06-29 19:52:40 +02:00
{
2022-07-25 11:07:56 +01:00
ISearchableTree searcher = searchableTree . Value . SearchableTree ;
const int pageSize = 200 ;
IEnumerable < SearchResultEntity > results = (
searcher is ISearchableTreeWithCulture searcherWithCulture
? await searcherWithCulture . SearchAsync ( query , pageSize , 0 , culture : culture )
: await searcher . SearchAsync ( query , pageSize , 0 ) )
. WhereNotNull ( ) ;
2022-06-20 08:37:17 +02:00
2022-07-25 11:07:56 +01:00
var searchResult = new TreeSearchResult
{
Results = results ,
TreeAlias = searchableTree . Key ,
AppAlias = searchableTree . Value . AppAlias ,
JsFormatterService = searchableTree . Value . FormatterService ,
JsFormatterMethod = searchableTree . Value . FormatterMethod
} ;
2018-06-29 19:52:40 +02:00
2022-07-25 11:07:56 +01:00
return new KeyValuePair < string , TreeSearchResult > ( rootNodeDisplayName , searchResult ) ;
2018-06-29 19:52:40 +02:00
}
2022-07-25 11:07:56 +01:00
/// <summary>
/// Gets the path for a given node ID
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <returns></returns>
public IConvertToActionResult GetPath ( int id , UmbracoEntityTypes type )
{
ActionResult < EntityBasic ? > foundContentResult = GetResultForId ( id , type ) ;
EntityBasic ? foundContent = foundContentResult . Value ;
if ( foundContent is null )
{
return foundContentResult ;
}
2022-06-20 08:37:17 +02:00
return new ActionResult < IEnumerable < int > > ( foundContent . Path
. Split ( Constants . CharArrays . Comma , StringSplitOptions . RemoveEmptyEntries ) . Select (
s = > int . Parse ( s , CultureInfo . InvariantCulture ) ) ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Gets the path for a given node ID
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <returns></returns>
public IConvertToActionResult GetPath ( Guid id , UmbracoEntityTypes type )
{
ActionResult < EntityBasic ? > foundContentResult = GetResultForKey ( id , type ) ;
EntityBasic ? foundContent = foundContentResult . Value ;
if ( foundContent is null )
{
return foundContentResult ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
return new ActionResult < IEnumerable < int > > ( foundContent . Path
. Split ( Constants . CharArrays . Comma , StringSplitOptions . RemoveEmptyEntries ) . Select (
s = > int . Parse ( s , CultureInfo . InvariantCulture ) ) ) ;
}
/// <summary>
/// Gets the path for a given node ID
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <returns></returns>
public IActionResult GetPath ( Udi id , UmbracoEntityTypes type )
{
var guidUdi = id as GuidUdi ;
if ( guidUdi ! = null )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return GetPath ( guidUdi . Guid , type ) . Convert ( ) ;
}
return NotFound ( ) ;
}
2021-01-12 14:00:14 +01:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Gets the URL of an entity
/// </summary>
/// <param name="id">UDI of the entity to fetch URL for</param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>The URL or path to the item</returns>
public IActionResult GetUrl ( Udi id , string culture = "*" )
{
Attempt < int > intId = _entityService . GetId ( id ) ;
if ( ! intId . Success )
{
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
UmbracoEntityTypes entityType ;
switch ( id . EntityType )
2020-01-17 13:58:18 +01:00
{
2022-06-20 08:37:17 +02:00
case Constants . UdiEntityType . Document :
entityType = UmbracoEntityTypes . Document ;
break ;
case Constants . UdiEntityType . Media :
entityType = UmbracoEntityTypes . Media ;
break ;
case Constants . UdiEntityType . Member :
entityType = UmbracoEntityTypes . Member ;
break ;
default :
2021-01-12 14:00:14 +01:00
return NotFound ( ) ;
2022-06-20 08:37:17 +02:00
}
return GetUrl ( intId . Result , entityType , culture ) ;
}
/// <summary>
/// Get entity URLs by IDs
/// </summary>
/// <param name="ids">
/// A list of IDs to lookup items by
/// </param>
/// <param name="type">The entity type to look for.</param>
/// <param name="culture">The culture to fetch the URL for.</param>
/// <returns>Dictionary mapping Udi -> Url</returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids.
/// </remarks>
[HttpGet]
[HttpPost]
public IDictionary < int , string? > GetUrlsByIds ( [ FromJsonPath ] int [ ] ids , [ FromQuery ] UmbracoEntityTypes type , [ FromQuery ] string? culture = null )
{
if ( ids = = null | | ! ids . Any ( ) )
{
return new Dictionary < int , string? > ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
string? MediaOrDocumentUrl ( int id )
{
switch ( type )
2020-01-17 13:58:18 +01:00
{
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Document :
return _publishedUrlProvider . GetUrl ( id , culture : culture ? ? ClientCulture ( ) ) ;
case UmbracoEntityTypes . Media :
{
IPublishedContent ? media = _publishedContentQuery . Media ( id ) ;
// NOTE: If culture is passed here we get an empty string rather than a media item URL.
return _publishedUrlProvider . GetMediaUrl ( media , culture : null ) ;
}
2020-01-17 13:58:18 +01:00
default :
2022-06-20 08:37:17 +02:00
return null ;
2020-01-17 13:58:18 +01:00
}
2022-06-20 08:37:17 +02:00
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
return ids
. Distinct ( )
. Select ( id = > new { Id = id , Url = MediaOrDocumentUrl ( id ) } ) . ToDictionary ( x = > x . Id , x = > x . Url ) ;
}
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Get entity URLs by IDs
/// </summary>
/// <param name="ids">
/// A list of IDs to lookup items by
/// </param>
/// <param name="type">The entity type to look for.</param>
/// <param name="culture">The culture to fetch the URL for.</param>
/// <returns>Dictionary mapping Udi -> Url</returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids.
/// </remarks>
[HttpGet]
[HttpPost]
public IDictionary < Guid , string? > GetUrlsByIds ( [ FromJsonPath ] Guid [ ] ids , [ FromQuery ] UmbracoEntityTypes type , [ FromQuery ] string? culture = null )
{
if ( ids = = null | | ! ids . Any ( ) )
{
return new Dictionary < Guid , string? > ( ) ;
}
string? MediaOrDocumentUrl ( Guid id )
{
return type switch
2021-11-25 12:47:59 +00:00
{
2022-06-20 08:37:17 +02:00
UmbracoEntityTypes . Document = > _publishedUrlProvider . GetUrl ( id , culture : culture ? ? ClientCulture ( ) ) ,
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
// NOTE: If culture is passed here we get an empty string rather than a media item URL.
UmbracoEntityTypes . Media = > _publishedUrlProvider . GetMediaUrl ( id , culture : null ) ,
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
_ = > null
} ;
}
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
return ids
. Distinct ( )
. Select ( id = > new { Id = id , Url = MediaOrDocumentUrl ( id ) } ) . ToDictionary ( x = > x . Id , x = > x . Url ) ;
}
/// <summary>
/// Get entity URLs by IDs
/// </summary>
/// <param name="ids">
/// A list of IDs to lookup items by
/// </param>
/// <param name="type">The entity type to look for.</param>
/// <param name="culture">The culture to fetch the URL for.</param>
/// <returns>Dictionary mapping Udi -> Url</returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids.
/// </remarks>
[HttpGet]
[HttpPost]
public IDictionary < Udi , string? > GetUrlsByIds ( [ FromJsonPath ] Udi [ ] ids , [ FromQuery ] UmbracoEntityTypes type , [ FromQuery ] string? culture = null )
{
if ( ids = = null | | ! ids . Any ( ) )
{
return new Dictionary < Udi , string? > ( ) ;
}
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
// TODO: PMJ 2021-09-27 - Should GetUrl(Udi) exist as an extension method on UrlProvider/IUrlProvider (in v9)
string? MediaOrDocumentUrl ( Udi id )
{
if ( id is not GuidUdi guidUdi )
2021-11-25 12:47:59 +00:00
{
2022-06-20 08:37:17 +02:00
return null ;
2021-11-25 12:47:59 +00:00
}
2022-06-20 08:37:17 +02:00
return type switch
2021-11-25 12:47:59 +00:00
{
2022-06-20 08:37:17 +02:00
UmbracoEntityTypes . Document = > _publishedUrlProvider . GetUrl ( guidUdi . Guid , culture : culture ? ? ClientCulture ( ) ) ,
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
// NOTE: If culture is passed here we get an empty string rather than a media item URL.
UmbracoEntityTypes . Media = > _publishedUrlProvider . GetMediaUrl ( guidUdi . Guid , culture : null ) ,
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
_ = > null
} ;
}
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
return ids
. Distinct ( )
. Select ( id = > new { Id = id , Url = MediaOrDocumentUrl ( id ) } ) . ToDictionary ( x = > x . Id , x = > x . Url ) ;
}
Merge v8/dev 20-10-2021 (#11426)
* Adjust icon in umb-checkbox and ensure icon is centered
* Missing nl translation for blockEditor_addBlock
* Implement icon parameter for doctype editor (#11008)
* fix: implement icon parameter for doctype editor
issue #10108
* fix: move color from icon to class attribute
* fix: removed defined colors, defaulting to the standard dark grey (ie "no color picked" in icon picker)
* cleaned up unused dependencies, double quotes to single, removed unused 'color' param from the create methods, and use shorthand object creation in createDocType (if the key has the same name as the variable passed as a prop, we only need to pass the key name)
* fix comment
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Align sortable handle vertically in multivalues prevalue editor
* 10341: Use different picker for content types (#10896)
* 10341: Use different picker for content types
* use es6 where possible (inc removing underscore for teeny tiny performance improvement)
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Falling back to contentTypeName when Block List label is empty (#10963)
* Falling back to contentTypeName when Block List label is empty
* Adding $contentTypeName variable for Block List labels
* Fix incorrect attribute
* Grid: Add button styling fix (#10978)
* Add missing focus styling
* Ensure add button is perfectly rounded and remove unused / uneeded CSS.
* Remove redundant border-color property
* Revert removal of unused css
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
* Create content template localization (#10945)
* Don't use self-closing element for custom HTML elements
* Use button element for close/cancel in copy dialog
* Update localization of "createBlueprintFrom"
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Cleanup examine search results, and adds ability to toggle fields (#9141)
* Cleanup examine search results, and adds ability to toggle fields
* update table to use joinarray filter with one-time binding to avoid recalculating filter values, updated filter to not explode when array arg is null
* fix failing tests - improve filter to not fail on non-array params, update tests accordingly
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Add EntityController GetUrlsByUdis
Enables loading multiple URLs in a single request for Media & Documents
* Update content picker to use GetUrlsByUdis
* Allows replacing MainDom with alternate DB
There are some cases where there is a complex hosting strategy and folks want a readonly database and are hosting on Azure. In that case, it is not entirely possible to have a readonly Umbraco database because SqlMainDom is required and part of that requirement is to have read/write access to the umbraco key value table.
This PR allows for the default MainDom to be replaced and to allow for an SqlMainDomLock to use an alternate connection string so that a separate read/write database can be used.
* Remove inherited property group id/key when local properties are added (#11231)
* Remove inherited property group id/key when local properties are added
* Rebind saved content type values
* Remove inherited from save group
* Rename parameter for clarity
* Removes annoying wait text, which causes layout jank
* v8: Backoffice Welsh language translation updates (#11240)
* Updated the Welsh language file to include newly added keys (based on the en us language file)
* Updated the searchInputDescription key
* Updated the endTitle key
* Use medium sized overlay
* Use umb-icon component for icons in content type groups and tabs
* fixes wrong reference to enterSubmitFolder method in ng-keydown
* 11251: Don't add default dashboard to url
* Fix preview of SVG when height and width not are set
* If caching a published document, make sure you use the published Name… (#11313)
* If caching a published document, make sure you use the published Name. Closes #11074.
* Fix case of new node
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
* Added missing Italian translations (#11197)
* Resolve incorrect ContentSavedState for failed publish
Closes #11290 (for v8)
* add modelValue validation for server to correctly update validation errors
* 11048: Bugfix for groups and properties that get replaced (#11257)
(cherry picked from commit 1605dc10bd91caa46d4bec1946a392f1d47c993d)
* Icon fallback to `icon-document` for existing document types (#11283)
* Align create buttons styling (#11352)
* Added button for cancelling dictionary create action
* Use hideMenu
* Align dictionary create with the other creates
* Align import documenttype
* Align for data type folder create
* Align document type create buttons
* Forgot small ng-show
* Align create media folder buttons
* Align create macro buttons
* Align create relation buttons
* Align create partial view macro folder buttons
* Align partial view folder create buttons
* Align create scripts folder buttons
* Align create scripts folder buttons
* Use primary instead of success
* V8: Duplicate MemberGroup names cause MemberGroup mixup (#11291)
* Prevented duplicate member group names
* Added English lang
* Updated 'Exist' typo
* add labels in FR and NL
* Adding property group aliases to ex.message
* Adding invalid prop group aliases as ModelState errors, so we don't introduce breaking changes
* Pointing the actual reason for invalidating composition
* Validate all content type dependencies and throw a single InvalidCompositionException
* Rename based on review comments
* Update composition validation error messages
* Update InvalidCompositionException message
* Allow switching property editor from numeric to slider (#11287)
* Make it possible to change from numeric/decimal property editor to slider without breaking editor
* Formatting
* Enables friendly pasting in multipletextbox
* UI API docs: Added reset rules for .close class
* UI API docs: Fixed incorrect method name
* 11331: Check property on instance if id is not set yet
* Fixed cypress tests
Co-authored-by: Bjarne Fyrstenborg <bjarne_fyrstenborg@hotmail.com>
Co-authored-by: Corné Strijkert <cornestrijkert@hotmail.com>
Co-authored-by: Søren Gregersen <soreng@gmail.com>
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
Co-authored-by: patrickdemooij9 <patrickdemooij98@hotmail.com>
Co-authored-by: Callum Whyte <hey@callumwhyte.com>
Co-authored-by: Jan Skovgaard <1932158+BatJan@users.noreply.github.com>
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
Co-authored-by: Søren Kottal <sk@ecreo.dk>
Co-authored-by: Paul Johnson <pmj@umbraco.com>
Co-authored-by: Shannon <sdeminick@gmail.com>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
Co-authored-by: Ronald Barendse <ronald@barend.se>
Co-authored-by: Owain Jones <owain.jones@method4.co.uk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Doug Moore <lordscarlet@idledreams.net>
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
Co-authored-by: Martino Gabrielli <39855999+ZioTino@users.noreply.github.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Jamie Townsend <jamie.townsend@unitstack.co.uk>
Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
Co-authored-by: Anders Bjerner <abjerner@skybrud.dk>
2021-10-20 13:12:46 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Get entity URLs by UDIs
/// </summary>
/// <param name="udis">
/// A list of UDIs to lookup items by
/// </param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>Dictionary mapping Udi -> Url</returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids.
/// </remarks>
[HttpGet]
[HttpPost]
[Obsolete("Use GetUrlsByIds instead.")]
public IDictionary < Udi , string? > GetUrlsByUdis ( [ FromJsonPath ] Udi [ ] udis , string? culture = null )
{
if ( udis = = null | | ! udis . Any ( ) )
{
return new Dictionary < Udi , string? > ( ) ;
}
Merge v8/dev 20-10-2021 (#11426)
* Adjust icon in umb-checkbox and ensure icon is centered
* Missing nl translation for blockEditor_addBlock
* Implement icon parameter for doctype editor (#11008)
* fix: implement icon parameter for doctype editor
issue #10108
* fix: move color from icon to class attribute
* fix: removed defined colors, defaulting to the standard dark grey (ie "no color picked" in icon picker)
* cleaned up unused dependencies, double quotes to single, removed unused 'color' param from the create methods, and use shorthand object creation in createDocType (if the key has the same name as the variable passed as a prop, we only need to pass the key name)
* fix comment
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Align sortable handle vertically in multivalues prevalue editor
* 10341: Use different picker for content types (#10896)
* 10341: Use different picker for content types
* use es6 where possible (inc removing underscore for teeny tiny performance improvement)
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Falling back to contentTypeName when Block List label is empty (#10963)
* Falling back to contentTypeName when Block List label is empty
* Adding $contentTypeName variable for Block List labels
* Fix incorrect attribute
* Grid: Add button styling fix (#10978)
* Add missing focus styling
* Ensure add button is perfectly rounded and remove unused / uneeded CSS.
* Remove redundant border-color property
* Revert removal of unused css
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
* Create content template localization (#10945)
* Don't use self-closing element for custom HTML elements
* Use button element for close/cancel in copy dialog
* Update localization of "createBlueprintFrom"
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Cleanup examine search results, and adds ability to toggle fields (#9141)
* Cleanup examine search results, and adds ability to toggle fields
* update table to use joinarray filter with one-time binding to avoid recalculating filter values, updated filter to not explode when array arg is null
* fix failing tests - improve filter to not fail on non-array params, update tests accordingly
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Add EntityController GetUrlsByUdis
Enables loading multiple URLs in a single request for Media & Documents
* Update content picker to use GetUrlsByUdis
* Allows replacing MainDom with alternate DB
There are some cases where there is a complex hosting strategy and folks want a readonly database and are hosting on Azure. In that case, it is not entirely possible to have a readonly Umbraco database because SqlMainDom is required and part of that requirement is to have read/write access to the umbraco key value table.
This PR allows for the default MainDom to be replaced and to allow for an SqlMainDomLock to use an alternate connection string so that a separate read/write database can be used.
* Remove inherited property group id/key when local properties are added (#11231)
* Remove inherited property group id/key when local properties are added
* Rebind saved content type values
* Remove inherited from save group
* Rename parameter for clarity
* Removes annoying wait text, which causes layout jank
* v8: Backoffice Welsh language translation updates (#11240)
* Updated the Welsh language file to include newly added keys (based on the en us language file)
* Updated the searchInputDescription key
* Updated the endTitle key
* Use medium sized overlay
* Use umb-icon component for icons in content type groups and tabs
* fixes wrong reference to enterSubmitFolder method in ng-keydown
* 11251: Don't add default dashboard to url
* Fix preview of SVG when height and width not are set
* If caching a published document, make sure you use the published Name… (#11313)
* If caching a published document, make sure you use the published Name. Closes #11074.
* Fix case of new node
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
* Added missing Italian translations (#11197)
* Resolve incorrect ContentSavedState for failed publish
Closes #11290 (for v8)
* add modelValue validation for server to correctly update validation errors
* 11048: Bugfix for groups and properties that get replaced (#11257)
(cherry picked from commit 1605dc10bd91caa46d4bec1946a392f1d47c993d)
* Icon fallback to `icon-document` for existing document types (#11283)
* Align create buttons styling (#11352)
* Added button for cancelling dictionary create action
* Use hideMenu
* Align dictionary create with the other creates
* Align import documenttype
* Align for data type folder create
* Align document type create buttons
* Forgot small ng-show
* Align create media folder buttons
* Align create macro buttons
* Align create relation buttons
* Align create partial view macro folder buttons
* Align partial view folder create buttons
* Align create scripts folder buttons
* Align create scripts folder buttons
* Use primary instead of success
* V8: Duplicate MemberGroup names cause MemberGroup mixup (#11291)
* Prevented duplicate member group names
* Added English lang
* Updated 'Exist' typo
* add labels in FR and NL
* Adding property group aliases to ex.message
* Adding invalid prop group aliases as ModelState errors, so we don't introduce breaking changes
* Pointing the actual reason for invalidating composition
* Validate all content type dependencies and throw a single InvalidCompositionException
* Rename based on review comments
* Update composition validation error messages
* Update InvalidCompositionException message
* Allow switching property editor from numeric to slider (#11287)
* Make it possible to change from numeric/decimal property editor to slider without breaking editor
* Formatting
* Enables friendly pasting in multipletextbox
* UI API docs: Added reset rules for .close class
* UI API docs: Fixed incorrect method name
* 11331: Check property on instance if id is not set yet
* Fixed cypress tests
Co-authored-by: Bjarne Fyrstenborg <bjarne_fyrstenborg@hotmail.com>
Co-authored-by: Corné Strijkert <cornestrijkert@hotmail.com>
Co-authored-by: Søren Gregersen <soreng@gmail.com>
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
Co-authored-by: patrickdemooij9 <patrickdemooij98@hotmail.com>
Co-authored-by: Callum Whyte <hey@callumwhyte.com>
Co-authored-by: Jan Skovgaard <1932158+BatJan@users.noreply.github.com>
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
Co-authored-by: Søren Kottal <sk@ecreo.dk>
Co-authored-by: Paul Johnson <pmj@umbraco.com>
Co-authored-by: Shannon <sdeminick@gmail.com>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
Co-authored-by: Ronald Barendse <ronald@barend.se>
Co-authored-by: Owain Jones <owain.jones@method4.co.uk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Doug Moore <lordscarlet@idledreams.net>
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
Co-authored-by: Martino Gabrielli <39855999+ZioTino@users.noreply.github.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Jamie Townsend <jamie.townsend@unitstack.co.uk>
Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
Co-authored-by: Anders Bjerner <abjerner@skybrud.dk>
2021-10-20 13:12:46 +02:00
2022-06-20 08:37:17 +02:00
var udiEntityType = udis . First ( ) . EntityType ;
UmbracoEntityTypes entityType ;
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
switch ( udiEntityType )
{
case Constants . UdiEntityType . Document :
entityType = UmbracoEntityTypes . Document ;
break ;
case Constants . UdiEntityType . Media :
entityType = UmbracoEntityTypes . Media ;
break ;
default :
entityType = ( UmbracoEntityTypes ) ( - 1 ) ;
break ;
}
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
return GetUrlsByIds ( udis , entityType , culture ) ;
}
Merge v8/dev 20-10-2021 (#11426)
* Adjust icon in umb-checkbox and ensure icon is centered
* Missing nl translation for blockEditor_addBlock
* Implement icon parameter for doctype editor (#11008)
* fix: implement icon parameter for doctype editor
issue #10108
* fix: move color from icon to class attribute
* fix: removed defined colors, defaulting to the standard dark grey (ie "no color picked" in icon picker)
* cleaned up unused dependencies, double quotes to single, removed unused 'color' param from the create methods, and use shorthand object creation in createDocType (if the key has the same name as the variable passed as a prop, we only need to pass the key name)
* fix comment
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Align sortable handle vertically in multivalues prevalue editor
* 10341: Use different picker for content types (#10896)
* 10341: Use different picker for content types
* use es6 where possible (inc removing underscore for teeny tiny performance improvement)
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Falling back to contentTypeName when Block List label is empty (#10963)
* Falling back to contentTypeName when Block List label is empty
* Adding $contentTypeName variable for Block List labels
* Fix incorrect attribute
* Grid: Add button styling fix (#10978)
* Add missing focus styling
* Ensure add button is perfectly rounded and remove unused / uneeded CSS.
* Remove redundant border-color property
* Revert removal of unused css
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
* Create content template localization (#10945)
* Don't use self-closing element for custom HTML elements
* Use button element for close/cancel in copy dialog
* Update localization of "createBlueprintFrom"
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Cleanup examine search results, and adds ability to toggle fields (#9141)
* Cleanup examine search results, and adds ability to toggle fields
* update table to use joinarray filter with one-time binding to avoid recalculating filter values, updated filter to not explode when array arg is null
* fix failing tests - improve filter to not fail on non-array params, update tests accordingly
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Add EntityController GetUrlsByUdis
Enables loading multiple URLs in a single request for Media & Documents
* Update content picker to use GetUrlsByUdis
* Allows replacing MainDom with alternate DB
There are some cases where there is a complex hosting strategy and folks want a readonly database and are hosting on Azure. In that case, it is not entirely possible to have a readonly Umbraco database because SqlMainDom is required and part of that requirement is to have read/write access to the umbraco key value table.
This PR allows for the default MainDom to be replaced and to allow for an SqlMainDomLock to use an alternate connection string so that a separate read/write database can be used.
* Remove inherited property group id/key when local properties are added (#11231)
* Remove inherited property group id/key when local properties are added
* Rebind saved content type values
* Remove inherited from save group
* Rename parameter for clarity
* Removes annoying wait text, which causes layout jank
* v8: Backoffice Welsh language translation updates (#11240)
* Updated the Welsh language file to include newly added keys (based on the en us language file)
* Updated the searchInputDescription key
* Updated the endTitle key
* Use medium sized overlay
* Use umb-icon component for icons in content type groups and tabs
* fixes wrong reference to enterSubmitFolder method in ng-keydown
* 11251: Don't add default dashboard to url
* Fix preview of SVG when height and width not are set
* If caching a published document, make sure you use the published Name… (#11313)
* If caching a published document, make sure you use the published Name. Closes #11074.
* Fix case of new node
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
* Added missing Italian translations (#11197)
* Resolve incorrect ContentSavedState for failed publish
Closes #11290 (for v8)
* add modelValue validation for server to correctly update validation errors
* 11048: Bugfix for groups and properties that get replaced (#11257)
(cherry picked from commit 1605dc10bd91caa46d4bec1946a392f1d47c993d)
* Icon fallback to `icon-document` for existing document types (#11283)
* Align create buttons styling (#11352)
* Added button for cancelling dictionary create action
* Use hideMenu
* Align dictionary create with the other creates
* Align import documenttype
* Align for data type folder create
* Align document type create buttons
* Forgot small ng-show
* Align create media folder buttons
* Align create macro buttons
* Align create relation buttons
* Align create partial view macro folder buttons
* Align partial view folder create buttons
* Align create scripts folder buttons
* Align create scripts folder buttons
* Use primary instead of success
* V8: Duplicate MemberGroup names cause MemberGroup mixup (#11291)
* Prevented duplicate member group names
* Added English lang
* Updated 'Exist' typo
* add labels in FR and NL
* Adding property group aliases to ex.message
* Adding invalid prop group aliases as ModelState errors, so we don't introduce breaking changes
* Pointing the actual reason for invalidating composition
* Validate all content type dependencies and throw a single InvalidCompositionException
* Rename based on review comments
* Update composition validation error messages
* Update InvalidCompositionException message
* Allow switching property editor from numeric to slider (#11287)
* Make it possible to change from numeric/decimal property editor to slider without breaking editor
* Formatting
* Enables friendly pasting in multipletextbox
* UI API docs: Added reset rules for .close class
* UI API docs: Fixed incorrect method name
* 11331: Check property on instance if id is not set yet
* Fixed cypress tests
Co-authored-by: Bjarne Fyrstenborg <bjarne_fyrstenborg@hotmail.com>
Co-authored-by: Corné Strijkert <cornestrijkert@hotmail.com>
Co-authored-by: Søren Gregersen <soreng@gmail.com>
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
Co-authored-by: patrickdemooij9 <patrickdemooij98@hotmail.com>
Co-authored-by: Callum Whyte <hey@callumwhyte.com>
Co-authored-by: Jan Skovgaard <1932158+BatJan@users.noreply.github.com>
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
Co-authored-by: Søren Kottal <sk@ecreo.dk>
Co-authored-by: Paul Johnson <pmj@umbraco.com>
Co-authored-by: Shannon <sdeminick@gmail.com>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
Co-authored-by: Ronald Barendse <ronald@barend.se>
Co-authored-by: Owain Jones <owain.jones@method4.co.uk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Doug Moore <lordscarlet@idledreams.net>
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
Co-authored-by: Martino Gabrielli <39855999+ZioTino@users.noreply.github.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Jamie Townsend <jamie.townsend@unitstack.co.uk>
Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
Co-authored-by: Anders Bjerner <abjerner@skybrud.dk>
2021-10-20 13:12:46 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Gets the URL of an entity
/// </summary>
/// <param name="id">Int id of the entity to fetch URL for</param>
/// <param name="type">The type of entity such as Document, Media, Member</param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>The URL or path to the item</returns>
/// <remarks>
/// We are not restricting this with security because there is no sensitive data
/// </remarks>
public IActionResult GetUrl ( int id , UmbracoEntityTypes type , string? culture = null )
{
culture ? ? = ClientCulture ( ) ;
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
var returnUrl = string . Empty ;
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
if ( type = = UmbracoEntityTypes . Document )
{
var foundUrl = _publishedUrlProvider . GetUrl ( id , culture : culture ) ;
if ( string . IsNullOrEmpty ( foundUrl ) = = false & & foundUrl ! = "#" )
2021-11-25 12:47:59 +00:00
{
2022-06-20 08:37:17 +02:00
returnUrl = foundUrl ;
2021-11-25 12:47:59 +00:00
2022-06-20 08:37:17 +02:00
return Ok ( returnUrl ) ;
}
Merge v8/dev 20-10-2021 (#11426)
* Adjust icon in umb-checkbox and ensure icon is centered
* Missing nl translation for blockEditor_addBlock
* Implement icon parameter for doctype editor (#11008)
* fix: implement icon parameter for doctype editor
issue #10108
* fix: move color from icon to class attribute
* fix: removed defined colors, defaulting to the standard dark grey (ie "no color picked" in icon picker)
* cleaned up unused dependencies, double quotes to single, removed unused 'color' param from the create methods, and use shorthand object creation in createDocType (if the key has the same name as the variable passed as a prop, we only need to pass the key name)
* fix comment
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Align sortable handle vertically in multivalues prevalue editor
* 10341: Use different picker for content types (#10896)
* 10341: Use different picker for content types
* use es6 where possible (inc removing underscore for teeny tiny performance improvement)
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Falling back to contentTypeName when Block List label is empty (#10963)
* Falling back to contentTypeName when Block List label is empty
* Adding $contentTypeName variable for Block List labels
* Fix incorrect attribute
* Grid: Add button styling fix (#10978)
* Add missing focus styling
* Ensure add button is perfectly rounded and remove unused / uneeded CSS.
* Remove redundant border-color property
* Revert removal of unused css
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
* Create content template localization (#10945)
* Don't use self-closing element for custom HTML elements
* Use button element for close/cancel in copy dialog
* Update localization of "createBlueprintFrom"
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Cleanup examine search results, and adds ability to toggle fields (#9141)
* Cleanup examine search results, and adds ability to toggle fields
* update table to use joinarray filter with one-time binding to avoid recalculating filter values, updated filter to not explode when array arg is null
* fix failing tests - improve filter to not fail on non-array params, update tests accordingly
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
* Add EntityController GetUrlsByUdis
Enables loading multiple URLs in a single request for Media & Documents
* Update content picker to use GetUrlsByUdis
* Allows replacing MainDom with alternate DB
There are some cases where there is a complex hosting strategy and folks want a readonly database and are hosting on Azure. In that case, it is not entirely possible to have a readonly Umbraco database because SqlMainDom is required and part of that requirement is to have read/write access to the umbraco key value table.
This PR allows for the default MainDom to be replaced and to allow for an SqlMainDomLock to use an alternate connection string so that a separate read/write database can be used.
* Remove inherited property group id/key when local properties are added (#11231)
* Remove inherited property group id/key when local properties are added
* Rebind saved content type values
* Remove inherited from save group
* Rename parameter for clarity
* Removes annoying wait text, which causes layout jank
* v8: Backoffice Welsh language translation updates (#11240)
* Updated the Welsh language file to include newly added keys (based on the en us language file)
* Updated the searchInputDescription key
* Updated the endTitle key
* Use medium sized overlay
* Use umb-icon component for icons in content type groups and tabs
* fixes wrong reference to enterSubmitFolder method in ng-keydown
* 11251: Don't add default dashboard to url
* Fix preview of SVG when height and width not are set
* If caching a published document, make sure you use the published Name… (#11313)
* If caching a published document, make sure you use the published Name. Closes #11074.
* Fix case of new node
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
* Added missing Italian translations (#11197)
* Resolve incorrect ContentSavedState for failed publish
Closes #11290 (for v8)
* add modelValue validation for server to correctly update validation errors
* 11048: Bugfix for groups and properties that get replaced (#11257)
(cherry picked from commit 1605dc10bd91caa46d4bec1946a392f1d47c993d)
* Icon fallback to `icon-document` for existing document types (#11283)
* Align create buttons styling (#11352)
* Added button for cancelling dictionary create action
* Use hideMenu
* Align dictionary create with the other creates
* Align import documenttype
* Align for data type folder create
* Align document type create buttons
* Forgot small ng-show
* Align create media folder buttons
* Align create macro buttons
* Align create relation buttons
* Align create partial view macro folder buttons
* Align partial view folder create buttons
* Align create scripts folder buttons
* Align create scripts folder buttons
* Use primary instead of success
* V8: Duplicate MemberGroup names cause MemberGroup mixup (#11291)
* Prevented duplicate member group names
* Added English lang
* Updated 'Exist' typo
* add labels in FR and NL
* Adding property group aliases to ex.message
* Adding invalid prop group aliases as ModelState errors, so we don't introduce breaking changes
* Pointing the actual reason for invalidating composition
* Validate all content type dependencies and throw a single InvalidCompositionException
* Rename based on review comments
* Update composition validation error messages
* Update InvalidCompositionException message
* Allow switching property editor from numeric to slider (#11287)
* Make it possible to change from numeric/decimal property editor to slider without breaking editor
* Formatting
* Enables friendly pasting in multipletextbox
* UI API docs: Added reset rules for .close class
* UI API docs: Fixed incorrect method name
* 11331: Check property on instance if id is not set yet
* Fixed cypress tests
Co-authored-by: Bjarne Fyrstenborg <bjarne_fyrstenborg@hotmail.com>
Co-authored-by: Corné Strijkert <cornestrijkert@hotmail.com>
Co-authored-by: Søren Gregersen <soreng@gmail.com>
Co-authored-by: Nathan Woulfe <nathan@nathanw.com.au>
Co-authored-by: patrickdemooij9 <patrickdemooij98@hotmail.com>
Co-authored-by: Callum Whyte <hey@callumwhyte.com>
Co-authored-by: Jan Skovgaard <1932158+BatJan@users.noreply.github.com>
Co-authored-by: BatJan <jaskov@gmail.com>
Co-authored-by: Jan Skovgaard Olsen <jso@co3.dk>
Co-authored-by: Søren Kottal <sk@ecreo.dk>
Co-authored-by: Paul Johnson <pmj@umbraco.com>
Co-authored-by: Shannon <sdeminick@gmail.com>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
Co-authored-by: Ronald Barendse <ronald@barend.se>
Co-authored-by: Owain Jones <owain.jones@method4.co.uk>
Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
Co-authored-by: Doug Moore <lordscarlet@idledreams.net>
Co-authored-by: Moore, Douglas S <Douglas.Moore@opm.gov>
Co-authored-by: Martino Gabrielli <39855999+ZioTino@users.noreply.github.com>
Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Jamie Townsend <jamie.townsend@unitstack.co.uk>
Co-authored-by: Elitsa Marinovska <elm@umbraco.dk>
Co-authored-by: Anders Bjerner <abjerner@skybrud.dk>
2021-10-20 13:12:46 +02:00
}
2022-06-20 08:37:17 +02:00
IEnumerable < EntityBasic > ancestors = GetResultForAncestors ( id , type ) ;
2019-08-12 21:52:00 +02:00
2022-06-20 08:37:17 +02:00
//if content, skip the first node for replicating NiceUrl defaults
if ( type = = UmbracoEntityTypes . Document )
{
ancestors = ancestors . Skip ( 1 ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
returnUrl = "/" + string . Join ( "/" , ancestors . Select ( x = > x . Name ) ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
return Ok ( returnUrl ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Gets an entity by a xpath query
/// </summary>
/// <param name="query"></param>
/// <param name="nodeContextId"></param>
/// <param name="type"></param>
/// <returns></returns>
public ActionResult < EntityBasic ? > ? GetByQuery ( string query , int nodeContextId , UmbracoEntityTypes type )
{
// TODO: Rename this!!! It's misleading, it should be GetByXPath
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
if ( type ! = UmbracoEntityTypes . Document )
{
throw new ArgumentException ( "Get by query is only compatible with entities of type Document" ) ;
2018-06-29 19:52:40 +02:00
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
var q = ParseXPathQuery ( query , nodeContextId ) ;
IPublishedContent ? node = _publishedContentQuery . ContentSingleAtXPath ( q ) ;
if ( node = = null )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return null ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
return GetById ( node . Id , type ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
// PP: Work in progress on the query parser
private string ParseXPathQuery ( string query , int id ) = >
UmbracoXPathPathSyntaxParser . ParseXPathQuery (
query ,
id ,
nodeid = >
2021-10-22 08:51:01 +02:00
{
2022-06-20 08:37:17 +02:00
IEntitySlim ? ent = _entityService . Get ( nodeid ) ;
return ent ? . Path . Split ( Constants . CharArrays . Comma ) . Reverse ( ) ;
} ,
i = > _publishedContentQuery . Content ( i ) ! = null ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
[HttpGet]
public ActionResult < UrlAndAnchors > GetUrlAndAnchors ( Udi id , string culture = "*" )
{
Attempt < int > intId = _entityService . GetId ( id ) ;
if ( ! intId . Success )
{
return NotFound ( ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
return GetUrlAndAnchors ( intId . Result , culture ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
[HttpGet]
public UrlAndAnchors GetUrlAndAnchors ( int id , string? culture = "*" )
{
culture ? ? = ClientCulture ( ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
var url = _publishedUrlProvider . GetUrl ( id , culture : culture ) ;
IEnumerable < string > anchorValues = _contentService . GetAnchorValuesFromRTEs ( id , culture ) ;
return new UrlAndAnchors ( url , anchorValues ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
[HttpGet]
[HttpPost]
public IEnumerable < string > GetAnchors ( AnchorsModel model )
{
if ( model . RteContent is null )
{
return Enumerable . Empty < string > ( ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
IEnumerable < string > anchorValues = _contentService . GetAnchorValuesFromRTEContent ( model . RteContent ) ;
return anchorValues ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
public IEnumerable < EntityBasic > GetChildren ( int id , UmbracoEntityTypes type , Guid ? dataTypeKey = null )
{
UmbracoObjectTypes ? objectType = ConvertToObjectType ( type ) ;
if ( objectType . HasValue )
2019-07-13 14:49:05 +02:00
{
2022-06-20 08:37:17 +02:00
//TODO: Need to check for Object types that support hierarchy here, some might not.
var startNodes = GetStartNodes ( type ) ;
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 )
2021-10-22 08:51:01 +02:00
{
2022-06-20 08:37:17 +02:00
IEntitySlim [ ] nodes = _entityService . GetAll ( objectType . Value , startNodes ) . ToArray ( ) ;
if ( nodes . Length = = 0 )
{
return Enumerable . Empty < EntityBasic > ( ) ;
}
var pr = new List < EntityBasic > ( nodes . Select ( _umbracoMapper . Map < EntityBasic > ) . WhereNotNull ( ) ) ;
return pr ;
2021-10-22 08:51:01 +02:00
}
2019-06-24 10:52:35 +02:00
2022-06-20 08:37:17 +02:00
// else proceed as usual
return _entityService . GetChildren ( id , objectType . Value )
. Select ( _umbracoMapper . Map < EntityBasic > )
. WhereNotNull ( ) ;
2019-07-15 16:18:04 +10:00
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( type )
2019-06-28 13:03:36 +10:00
{
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Language :
case UmbracoEntityTypes . User :
case UmbracoEntityTypes . Macro :
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + type ) ;
}
}
2020-01-17 13:58:18 +01:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Get paged child entities by id
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <param name="pageNumber"></param>
/// <param name="pageSize"></param>
/// <param name="orderBy"></param>
/// <param name="orderDirection"></param>
/// <param name="filter"></param>
/// <param name="dataTypeKey"></param>
/// <returns></returns>
public ActionResult < PagedResult < EntityBasic > > GetPagedChildren (
string id ,
UmbracoEntityTypes type ,
int pageNumber ,
int pageSize ,
string orderBy = "SortOrder" ,
Direction orderDirection = Direction . Ascending ,
string filter = "" ,
Guid ? dataTypeKey = null )
{
if ( int . TryParse ( id , NumberStyles . Integer , CultureInfo . InvariantCulture , out var intId ) )
{
return GetPagedChildren ( intId , type , pageNumber , pageSize , orderBy , orderDirection , filter ) ;
2019-06-28 13:03:36 +10:00
}
2022-06-20 08:37:17 +02:00
if ( Guid . TryParse ( id , out _ ) )
2019-07-02 11:17:23 +02:00
{
2022-06-20 08:37:17 +02:00
//Not supported currently
return NotFound ( ) ;
}
2022-04-01 14:35:18 +02:00
2022-06-20 08:37:17 +02:00
if ( UdiParser . TryParse ( id , out _ ) )
{
//Not supported currently
return NotFound ( ) ;
2019-07-02 11:17:23 +02:00
}
2022-06-20 08:37:17 +02:00
//so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type
if ( id = = Constants . Conventions . MemberTypes . AllMembersListId )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
//the EntityService can search paged members from the root
2019-06-28 13:03:36 +10:00
2022-06-20 08:37:17 +02:00
intId = - 1 ;
return GetPagedChildren ( intId , type , pageNumber , pageSize , orderBy , orderDirection , filter , dataTypeKey ) ;
}
2019-06-28 13:03:36 +10:00
2022-06-20 08:37:17 +02:00
//the EntityService cannot search members of a certain type, this is currently not supported and would require
//quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search
2019-06-28 13:03:36 +10:00
2022-06-20 08:37:17 +02:00
//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
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
IEnumerable < SearchResultEntity > searchResult = _treeSearcher . ExamineSearch (
filter ? ? string . Empty ,
type ,
pageSize ,
pageNumber - 1 ,
out var total ,
null ,
id ) ;
2019-06-28 13:03:36 +10:00
2022-06-20 08:37:17 +02:00
return new PagedResult < EntityBasic > ( total , pageNumber , pageSize ) { Items = searchResult } ;
}
2019-06-28 13:03:36 +10:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Get paged child entities by id
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <param name="pageNumber"></param>
/// <param name="pageSize"></param>
/// <param name="orderBy"></param>
/// <param name="orderDirection"></param>
/// <param name="filter"></param>
/// <param name="dataTypeKey"></param>
/// <returns></returns>
public ActionResult < PagedResult < EntityBasic > > GetPagedChildren (
int id ,
UmbracoEntityTypes type ,
int pageNumber ,
int pageSize ,
string orderBy = "SortOrder" ,
Direction orderDirection = Direction . Ascending ,
string filter = "" ,
Guid ? dataTypeKey = null )
{
if ( pageNumber < = 0 )
{
return NotFound ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
if ( pageSize < = 0 )
{
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
UmbracoObjectTypes ? objectType = ConvertToObjectType ( type ) ;
if ( objectType . HasValue )
{
IEnumerable < IEntitySlim > entities ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
var startNodes = GetStartNodes ( type ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes ( dataTypeKey ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
// 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 )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return new PagedResult < EntityBasic > ( 0 , 0 , 0 ) ;
2018-06-29 19:52:40 +02:00
}
2023-02-06 17:27:36 +05:30
//adding multiple conditions ,considering id,key & name as filter param
//for id as int
int . TryParse ( filter , out int filterAsIntId ) ;
//for key as Guid
Guid . TryParse ( filter , out Guid filterAsGuid ) ;
2022-06-20 08:37:17 +02:00
// else proceed as usual
entities = _entityService . GetPagedChildren (
id ,
objectType . Value ,
pageNumber - 1 ,
pageSize ,
out long totalRecords ,
filter . IsNullOrWhiteSpace ( )
? null
2023-02-06 17:27:36 +05:30
: _sqlContext . Query < IUmbracoEntity > ( ) . Where ( x = > x . Name ! . Contains ( filter )
| | x . Id = = filterAsIntId
| | x . Key = = filterAsGuid ) ,
2022-06-20 08:37:17 +02:00
Ordering . By ( orderBy , orderDirection ) ) ;
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
if ( totalRecords = = 0 )
2021-10-22 08:51:01 +02:00
{
2022-06-20 08:37:17 +02:00
return new PagedResult < EntityBasic > ( 0 , 0 , 0 ) ;
2021-10-22 08:51:01 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
var culture = ClientCulture ( ) ;
var pagedResult = new PagedResult < EntityBasic > ( totalRecords , pageNumber , pageSize )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
Items = entities . Select ( source = >
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
EntityBasic ? target = _umbracoMapper . Map < IEntitySlim , EntityBasic > ( source , context = >
{
context . SetCulture ( culture ) ;
context . SetCulture ( culture ) ;
} ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
if ( target is not null )
2019-03-26 10:39:50 +01:00
{
2022-06-20 08:37:17 +02:00
//TODO: Why is this here and not in the mapping?
target . AdditionalData [ "hasChildren" ] = source . HasChildren ;
}
2022-04-01 14:35:18 +02:00
2022-06-20 08:37:17 +02:00
return target ;
} ) . WhereNotNull ( )
} ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
return pagedResult ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( type )
{
case UmbracoEntityTypes . PropertyType :
case UmbracoEntityTypes . PropertyGroup :
case UmbracoEntityTypes . Language :
case UmbracoEntityTypes . User :
case UmbracoEntityTypes . Macro :
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + type ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
private int [ ] GetStartNodes ( UmbracoEntityTypes type )
{
switch ( type )
2019-07-02 10:56:44 +02:00
{
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Document :
return _backofficeSecurityAccessor . BackOfficeSecurity ? . CurrentUser ? . CalculateContentStartNodeIds (
_entityService , _appCaches ) ? ? Array . Empty < int > ( ) ;
case UmbracoEntityTypes . Media :
return _backofficeSecurityAccessor . BackOfficeSecurity ? . CurrentUser ? . CalculateMediaStartNodeIds (
_entityService , _appCaches ) ? ? Array . Empty < int > ( ) ;
default :
return Array . Empty < int > ( ) ;
2019-07-02 10:56:44 +02:00
}
2022-06-20 08:37:17 +02:00
}
2019-07-02 10:56:44 +02:00
2022-06-20 08:37:17 +02:00
public ActionResult < PagedResult < EntityBasic > > GetPagedDescendants (
int id ,
UmbracoEntityTypes type ,
int pageNumber ,
int pageSize ,
string orderBy = "SortOrder" ,
Direction orderDirection = Direction . Ascending ,
string filter = "" ,
Guid ? dataTypeKey = null )
{
if ( pageNumber < = 0 )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return NotFound ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
if ( pageSize < = 0 )
{
return NotFound ( ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
// re-normalize since NULL can be passed in
filter ? ? = string . Empty ;
2019-11-05 15:05:51 +11:00
2022-06-20 08:37:17 +02:00
UmbracoObjectTypes ? objectType = ConvertToObjectType ( type ) ;
if ( objectType . HasValue )
{
IEnumerable < IUmbracoEntity > entities ;
long totalRecords ;
2019-11-05 15:05:51 +11:00
2022-06-20 08:37:17 +02:00
if ( id = = Constants . System . Root )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
// root is special: we reduce it to start nodes
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
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 ,
_sqlContext . Query < IUmbracoEntity > ( )
. Where ( x = > x . Name ! . Contains ( filter ) ) ,
Ordering . By ( orderBy , orderDirection ) ,
false )
: _entityService . GetPagedDescendants (
aids ,
objectType . Value ,
pageNumber - 1 ,
pageSize ,
2021-10-22 08:51:01 +02:00
out totalRecords ,
2022-04-01 14:35:18 +02:00
_sqlContext . Query < IUmbracoEntity > ( ) . Where ( x = > x . Name ! . Contains ( filter ) ) ,
2018-12-21 13:15:46 +11:00
Ordering . By ( orderBy , orderDirection ) ) ;
2022-06-20 08:37:17 +02:00
}
else
{
entities = _entityService . GetPagedDescendants (
id ,
objectType . Value ,
pageNumber - 1 ,
pageSize ,
out totalRecords ,
_sqlContext . Query < IUmbracoEntity > ( )
. Where ( x = > x . Name ! . Contains ( filter ) ) ,
Ordering . By ( orderBy , orderDirection ) ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
if ( totalRecords = = 0 )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return new PagedResult < EntityBasic > ( 0 , 0 , 0 ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
var pagedResult = new PagedResult < EntityBasic > ( totalRecords , pageNumber , pageSize )
{
Items = entities . Select ( MapEntities ( ) ) . WhereNotNull ( )
} ;
2019-06-28 13:03:36 +10:00
2022-06-20 08:37:17 +02:00
return pagedResult ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( type )
2021-09-08 14:59:52 +02:00
{
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . PropertyType :
case UmbracoEntityTypes . PropertyGroup :
case UmbracoEntityTypes . Language :
case UmbracoEntityTypes . User :
case UmbracoEntityTypes . Macro :
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + type ) ;
2021-09-08 14:59:52 +02:00
}
2022-06-20 08:37:17 +02:00
}
private bool IsDataTypeIgnoringUserStartNodes ( Guid ? dataTypeKey ) = > dataTypeKey . HasValue & &
_dataTypeService
. IsDataTypeIgnoringUserStartNodes (
dataTypeKey . Value ) ;
2021-09-08 14:59:52 +02:00
2022-06-20 08:37:17 +02:00
public IEnumerable < EntityBasic > GetAncestors ( int id , UmbracoEntityTypes type , [ ModelBinder ( typeof ( HttpQueryStringModelBinder ) ) ] FormCollection queryStrings ) = >
GetResultForAncestors ( id , type , queryStrings ) ;
public ActionResult < IEnumerable < EntityBasic > > GetAncestors ( Guid id , UmbracoEntityTypes type , [ ModelBinder ( typeof ( HttpQueryStringModelBinder ) ) ] FormCollection queryStrings )
{
IEntitySlim ? entity = _entityService . Get ( id ) ;
if ( entity is null )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
return Ok ( GetResultForAncestors ( entity . Id , type , queryStrings ) ) ;
}
/// <summary>
/// Searches for results based on the entity type
/// </summary>
/// <param name="query"></param>
/// <param name="entityType"></param>
/// <param name="searchFrom"></param>
/// <param name="ignoreUserStartNodes">If set to true, user and group start node permissions will be ignored.</param>
/// <returns></returns>
private IEnumerable < SearchResultEntity > 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 ) ;
}
private IEnumerable < EntityBasic > GetResultForChildren ( int id , UmbracoEntityTypes entityType )
{
UmbracoObjectTypes ? objectType = ConvertToObjectType ( entityType ) ;
if ( objectType . HasValue )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
// TODO: Need to check for Object types that support hierarchic here, some might not.
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
return _entityService . GetChildren ( id , objectType . Value )
. Select ( MapEntities ( ) )
. WhereNotNull ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( entityType )
{
case UmbracoEntityTypes . Language :
case UmbracoEntityTypes . User :
case UmbracoEntityTypes . Macro :
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + entityType ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
private IEnumerable < EntityBasic > GetResultForAncestors ( int id , UmbracoEntityTypes entityType , FormCollection ? queryStrings = null )
{
UmbracoObjectTypes ? objectType = ConvertToObjectType ( entityType ) ;
if ( objectType . HasValue )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
// TODO: Need to check for Object types that support hierarchic here, some might not.
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
var ids = _entityService . Get ( id ) ? . Path . Split ( Constants . CharArrays . Comma )
. Select ( s = > int . Parse ( s , CultureInfo . InvariantCulture ) ) . Distinct ( ) . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
var ignoreUserStartNodes =
IsDataTypeIgnoringUserStartNodes ( queryStrings ? . GetValue < Guid ? > ( "dataTypeId" ) ) ;
if ( ignoreUserStartNodes = = false )
{
int [ ] ? aids = null ;
switch ( entityType )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Document :
aids = _backofficeSecurityAccessor . BackOfficeSecurity ? . CurrentUser ?
. CalculateContentStartNodeIds ( _entityService , _appCaches ) ;
break ;
case UmbracoEntityTypes . Media :
aids =
_backofficeSecurityAccessor . BackOfficeSecurity ? . CurrentUser ? . CalculateMediaStartNodeIds (
_entityService , _appCaches ) ;
break ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
if ( aids ! = null )
{
var lids = new List < int > ( ) ;
var ok = false ;
if ( ids is not null )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
foreach ( var i in ids )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
if ( ok )
2019-06-28 13:03:36 +10:00
{
2022-06-20 08:37:17 +02:00
lids . Add ( i ) ;
continue ;
2019-06-28 13:03:36 +10:00
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
if ( aids . Contains ( i ) )
{
lids . Add ( i ) ;
ok = true ;
}
}
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
ids = lids . ToArray ( ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
var culture = queryStrings ? . GetValue < string > ( "culture" ) ;
2018-09-26 16:27:34 +02:00
2022-06-20 08:37:17 +02:00
return ids is null | | ids . Length = = 0
? Enumerable . Empty < EntityBasic > ( )
: _entityService . GetAll ( objectType . Value , ids )
. OrderBy ( x = > x . Level )
. Select ( MapEntities ( culture ) )
. WhereNotNull ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( entityType )
{
case UmbracoEntityTypes . PropertyType :
case UmbracoEntityTypes . PropertyGroup :
case UmbracoEntityTypes . Language :
case UmbracoEntityTypes . User :
case UmbracoEntityTypes . Macro :
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + entityType ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
private IEnumerable < EntityBasic > GetResultForKeys ( Guid [ ] keys , UmbracoEntityTypes entityType )
{
if ( keys . Length = = 0 )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return Enumerable . Empty < EntityBasic > ( ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
UmbracoObjectTypes ? objectType = ConvertToObjectType ( entityType ) ;
if ( objectType . HasValue )
{
IEnumerable < EntityBasic > entities = _entityService . GetAll ( objectType . Value , keys )
. Select ( MapEntities ( ) )
. WhereNotNull ( ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
// entities are in "some" order, put them back in order
var xref = entities . ToDictionary ( x = > x . Key ) ;
IEnumerable < EntityBasic > result = keys . Select ( x = > xref . ContainsKey ( x ) ? xref [ x ] : null )
. WhereNotNull ( ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
return result ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( entityType )
{
case UmbracoEntityTypes . PropertyType :
case UmbracoEntityTypes . PropertyGroup :
case UmbracoEntityTypes . Language :
case UmbracoEntityTypes . User :
case UmbracoEntityTypes . Macro :
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + entityType ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
private IEnumerable < EntityBasic > GetResultForIds ( int [ ] ids , UmbracoEntityTypes entityType )
{
if ( ids . Length = = 0 )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return Enumerable . Empty < EntityBasic > ( ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
UmbracoObjectTypes ? objectType = ConvertToObjectType ( entityType ) ;
if ( objectType . HasValue )
{
IEnumerable < EntityBasic > entities = _entityService . GetAll ( objectType . Value , ids )
. Select ( MapEntities ( ) )
. WhereNotNull ( ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
// entities are in "some" order, put them back in order
var xref = entities . Where ( x = > x . Id ! = null ) . ToDictionary ( x = > x . Id ! ) ;
IEnumerable < EntityBasic > result = ids . Select ( x = > xref . ContainsKey ( x ) ? xref [ x ] : null )
. WhereNotNull ( ) ;
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
return result ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( entityType )
{
case UmbracoEntityTypes . PropertyType :
case UmbracoEntityTypes . PropertyGroup :
case UmbracoEntityTypes . Language :
case UmbracoEntityTypes . User :
case UmbracoEntityTypes . Macro :
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + entityType ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
private ActionResult < EntityBasic ? > GetResultForKey ( Guid key , UmbracoEntityTypes entityType )
{
UmbracoObjectTypes ? objectType = ConvertToObjectType ( entityType ) ;
if ( objectType . HasValue )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
IEntitySlim ? found = _entityService . Get ( key , objectType . Value ) ;
if ( found = = null )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
return _umbracoMapper . Map < IEntitySlim , EntityBasic > ( found ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( entityType )
{
case UmbracoEntityTypes . PropertyType :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . PropertyGroup :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Language :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . User :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Macro :
case UmbracoEntityTypes . Template :
ITemplate ? template = _fileService . GetTemplate ( key ) ;
if ( template is null )
{
return NotFound ( ) ;
}
2021-10-21 02:09:08 +02:00
2022-06-20 08:37:17 +02:00
return _umbracoMapper . Map < ITemplate , EntityBasic > ( template ) ;
2021-10-21 02:09:08 +02:00
2022-06-20 08:37:17 +02:00
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + entityType ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
private ActionResult < EntityBasic ? > GetResultForId ( int id , UmbracoEntityTypes entityType )
{
UmbracoObjectTypes ? objectType = ConvertToObjectType ( entityType ) ;
if ( objectType . HasValue )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
IEntitySlim ? found = _entityService . Get ( id , objectType . Value ) ;
if ( found = = null )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
return NotFound ( ) ;
2018-06-29 19:52:40 +02:00
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
return MapEntity ( found ) ;
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
//now we need to convert the unknown ones
switch ( entityType )
{
case UmbracoEntityTypes . PropertyType :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . PropertyGroup :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Language :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . User :
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Macro :
2021-10-21 02:09:08 +02:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Template :
ITemplate ? template = _fileService . GetTemplate ( id ) ;
if ( template is null )
{
return NotFound ( ) ;
}
2021-10-21 02:09:08 +02:00
2022-06-20 08:37:17 +02:00
return _umbracoMapper . Map < ITemplate , EntityBasic > ( template ) ;
default :
throw new NotSupportedException ( "The " + typeof ( EntityController ) +
" does not currently support data for the type " + entityType ) ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
private static UmbracoObjectTypes ? ConvertToObjectType ( UmbracoEntityTypes entityType )
{
switch ( entityType )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Document :
return UmbracoObjectTypes . Document ;
case UmbracoEntityTypes . Media :
return UmbracoObjectTypes . Media ;
case UmbracoEntityTypes . MemberType :
return UmbracoObjectTypes . MemberType ;
case UmbracoEntityTypes . MemberGroup :
return UmbracoObjectTypes . MemberGroup ;
case UmbracoEntityTypes . MediaType :
return UmbracoObjectTypes . MediaType ;
case UmbracoEntityTypes . DocumentType :
return UmbracoObjectTypes . DocumentType ;
case UmbracoEntityTypes . Member :
return UmbracoObjectTypes . Member ;
case UmbracoEntityTypes . DataType :
return UmbracoObjectTypes . DataType ;
default :
//There is no UmbracoEntity conversion (things like Macros, Users, etc...)
return null ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
}
2018-06-29 19:52:40 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// </summary>
/// <param name="type">The type of entity.</param>
/// <param name="postFilter">
/// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are
/// ignored.
/// </param>
/// <returns></returns>
public IEnumerable < EntityBasic > ? GetAll ( UmbracoEntityTypes type , string postFilter ) = >
GetResultForAll ( type , postFilter ) ;
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Gets the result for the entity list based on the type
/// </summary>
/// <param name="entityType"></param>
/// <param name="postFilter">A string where filter that will filter the results dynamically with linq - optional</param>
/// <returns></returns>
private IEnumerable < EntityBasic > ? GetResultForAll ( UmbracoEntityTypes entityType , string? postFilter = null )
{
UmbracoObjectTypes ? objectType = ConvertToObjectType ( entityType ) ;
if ( objectType . HasValue )
{
// TODO: Should we order this by something ?
IEnumerable < EntityBasic > entities =
_entityService . GetAll ( objectType . Value ) . Select ( MapEntities ( ) ) . WhereNotNull ( ) ;
return ExecutePostFilter ( entities , postFilter ) ;
}
//now we need to convert the unknown ones
switch ( entityType )
{
case UmbracoEntityTypes . Template :
IEnumerable < ITemplate > ? templates = _fileService . GetTemplates ( ) ;
IEnumerable < ITemplate > ? filteredTemplates = ExecutePostFilter ( templates , postFilter ) ;
return filteredTemplates ? . Select ( MapEntities ( ) ) . WhereNotNull ( ) ;
case UmbracoEntityTypes . Macro :
//Get all macros from the macro service
IOrderedEnumerable < IMacro > macros = _macroService . GetAll ( ) . WhereNotNull ( ) . OrderBy ( x = > x . Name ) ;
IEnumerable < IMacro > ? filteredMacros = ExecutePostFilter ( macros , postFilter ) ;
return filteredMacros ? . Select ( MapEntities ( ) ) . WhereNotNull ( ) ;
case UmbracoEntityTypes . PropertyType :
//get all document types, then combine all property types into one list
IEnumerable < IPropertyType > propertyTypes = _contentTypeService . GetAll ( )
. Cast < IContentTypeComposition > ( )
. Concat ( _mediaTypeService . GetAll ( ) )
. ToArray ( )
. SelectMany ( x = > x . PropertyTypes )
. DistinctBy ( composition = > composition . Alias ) ;
IEnumerable < IPropertyType > ? filteredPropertyTypes = ExecutePostFilter ( propertyTypes , postFilter ) ;
return _umbracoMapper
. MapEnumerable < IPropertyType , EntityBasic > ( filteredPropertyTypes ? ?
Enumerable . Empty < IPropertyType > ( ) ) . WhereNotNull ( ) ;
case UmbracoEntityTypes . PropertyGroup :
//get all document types, then combine all property types into one list
IEnumerable < PropertyGroup > propertyGroups = _contentTypeService . GetAll ( )
. Cast < IContentTypeComposition > ( )
. Concat ( _mediaTypeService . GetAll ( ) )
. ToArray ( )
. SelectMany ( x = > x . PropertyGroups )
. DistinctBy ( composition = > composition . Name ) ;
IEnumerable < PropertyGroup > ? filteredpropertyGroups = ExecutePostFilter ( propertyGroups , postFilter ) ;
return _umbracoMapper
. MapEnumerable < PropertyGroup , EntityBasic > ( filteredpropertyGroups ? ?
Enumerable . Empty < PropertyGroup > ( ) ) . WhereNotNull ( ) ;
case UmbracoEntityTypes . User :
IEnumerable < IUser > users = _userService . GetAll ( 0 , int . MaxValue , out _ ) ;
IEnumerable < IUser > ? filteredUsers = ExecutePostFilter ( users , postFilter ) ;
return _umbracoMapper . MapEnumerable < IUser , EntityBasic > ( filteredUsers ? ? Enumerable . Empty < IUser > ( ) )
. WhereNotNull ( ) ;
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Stylesheet :
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
if ( ! postFilter . IsNullOrWhiteSpace ( ) )
{
throw new NotSupportedException ( "Filtering on stylesheets is not currently supported" ) ;
}
2021-07-08 13:11:39 -06:00
2022-06-20 08:37:17 +02:00
return _fileService . GetStylesheets ( ) . Select ( MapEntities ( ) ) . WhereNotNull ( ) ;
2021-07-08 13:11:39 -06:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Script :
2021-07-08 13:11:39 -06:00
2022-06-20 08:37:17 +02:00
if ( ! postFilter . IsNullOrWhiteSpace ( ) )
{
throw new NotSupportedException ( "Filtering on scripts is not currently supported" ) ;
}
2021-07-08 13:11:39 -06:00
2022-06-20 08:37:17 +02:00
return _fileService . GetScripts ( ) . Select ( MapEntities ( ) ) . WhereNotNull ( ) ;
2021-07-08 13:11:39 -06:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . PartialView :
2021-07-08 13:11:39 -06:00
2022-06-20 08:37:17 +02:00
if ( ! postFilter . IsNullOrWhiteSpace ( ) )
{
throw new NotSupportedException ( "Filtering on partial views is not currently supported" ) ;
}
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
return _fileService . GetPartialViews ( ) . Select ( MapEntities ( ) ) . WhereNotNull ( ) ;
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
case UmbracoEntityTypes . Language :
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
if ( ! postFilter . IsNullOrWhiteSpace ( ) )
{
throw new NotSupportedException ( "Filtering on languages is not currently supported" ) ;
}
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
return _localizationService . GetAllLanguages ( ) . Select ( MapEntities ( ) ) . WhereNotNull ( ) ;
case UmbracoEntityTypes . DictionaryItem :
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
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 ) ;
}
}
private IEnumerable < T > ? ExecutePostFilter < T > ( IEnumerable < T > ? entities , string? postFilter )
{
if ( postFilter . IsNullOrWhiteSpace ( ) )
{
return entities ;
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
var postFilterConditions = postFilter ! . Split ( Constants . CharArrays . Ampersand ) ;
foreach ( var postFilterCondition in postFilterConditions )
2018-06-29 19:52:40 +02:00
{
2022-06-20 08:37:17 +02:00
QueryCondition ? queryCondition = BuildQueryCondition < T > ( postFilterCondition ) ;
if ( queryCondition ! = null )
2021-10-22 08:51:01 +02:00
{
2022-06-20 08:37:17 +02:00
Expression < Func < T , bool > > whereClauseExpression = queryCondition . BuildCondition < T > ( "x" ) ;
entities = entities ? . Where ( whereClauseExpression . Compile ( ) ) ;
2021-10-22 08:51:01 +02:00
}
2022-06-20 08:37:17 +02:00
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
return entities ;
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
private static QueryCondition ? BuildQueryCondition < T > ( string postFilter )
{
var postFilterParts = postFilter . Split ( _postFilterSplitStrings , 2 , StringSplitOptions . RemoveEmptyEntries ) ;
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
if ( postFilterParts . Length ! = 2 )
{
return null ;
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
var propertyName = postFilterParts [ 0 ] ;
var constraintValue = postFilterParts [ 1 ] ;
var stringOperator = postFilter . Substring ( propertyName . Length , postFilter . Length - propertyName . Length - constraintValue . Length ) ;
Operator binaryOperator ;
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
try
{
binaryOperator = OperatorFactory . FromString ( stringOperator ) ;
}
catch ( ArgumentException )
{
// unsupported operators are ignored
return null ;
2018-06-29 19:52:40 +02:00
}
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
Type type = typeof ( T ) ;
PropertyInfo ? property = type . GetProperty ( propertyName , BindingFlags . Public | BindingFlags . Instance ) ;
if ( property = = null )
2021-01-22 15:02:25 +13:00
{
2022-06-20 08:37:17 +02:00
return null ;
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
var queryCondition = new QueryCondition
{
Term = new OperatorTerm { Operator = binaryOperator } ,
ConstraintValue = constraintValue ,
Property = new PropertyModel
2019-01-23 10:22:29 +01:00
{
2022-06-20 08:37:17 +02:00
Alias = propertyName ,
Name = propertyName ,
Type = property . PropertyType . Name
2019-01-23 10:22:29 +01:00
}
2022-06-20 08:37:17 +02:00
} ;
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
return queryCondition ;
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
private Func < object , EntityBasic ? > MapEntities ( string? culture = null )
{
culture ? ? = ClientCulture ( ) ;
return x = > MapEntity ( x , culture ) ;
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
private EntityBasic ? MapEntity ( object entity , string? culture = null )
{
culture ? ? = ClientCulture ( ) ;
return _umbracoMapper . Map < EntityBasic > ( entity , context = > { context . SetCulture ( culture ) ; } ) ;
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
private string? ClientCulture ( ) = > Request . ClientCulture ( ) ;
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
#region GetById
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Gets an entity by it's id
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <returns></returns>
public ActionResult < EntityBasic ? > GetById ( int id , UmbracoEntityTypes type ) = > GetResultForId ( id , type ) ;
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Gets an entity by it's key
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <returns></returns>
public ActionResult < EntityBasic ? > GetById ( Guid id , UmbracoEntityTypes type ) = > GetResultForKey ( id , type ) ;
/// <summary>
/// Gets an entity by it's UDI
/// </summary>
/// <param name="id"></param>
/// <param name="type"></param>
/// <returns></returns>
public ActionResult < EntityBasic ? > GetById ( Udi id , UmbracoEntityTypes type )
{
var guidUdi = id as GuidUdi ;
if ( guidUdi ! = null )
2019-02-09 15:41:30 +01:00
{
2022-06-20 08:37:17 +02:00
return GetResultForKey ( guidUdi . Guid , type ) ;
2019-02-09 15:41:30 +01:00
}
2019-01-23 10:22:29 +01:00
2022-06-20 08:37:17 +02:00
return NotFound ( ) ;
}
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
#endregion
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
#region GetByIds
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Get entities by integer ids
/// </summary>
/// <param name="ids"></param>
/// <param name="type"></param>
/// <returns></returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids
/// </remarks>
[HttpGet]
[HttpPost]
public ActionResult < IEnumerable < EntityBasic > > GetByIds ( [ FromJsonPath ] int [ ] ids , [ FromQuery ] UmbracoEntityTypes type )
{
if ( ids = = null )
{
return NotFound ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
return new ActionResult < IEnumerable < EntityBasic > > ( GetResultForIds ( ids , type ) ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Get entities by GUID ids
/// </summary>
/// <param name="ids"></param>
/// <param name="type"></param>
/// <returns></returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids
/// </remarks>
[HttpGet]
[HttpPost]
public ActionResult < IEnumerable < EntityBasic > > GetByIds ( [ FromJsonPath ] Guid [ ] ids , [ FromQuery ] UmbracoEntityTypes type )
{
if ( ids = = null )
2021-10-22 08:51:01 +02:00
{
return NotFound ( ) ;
}
2022-06-20 08:37:17 +02:00
return new ActionResult < IEnumerable < EntityBasic > > ( GetResultForKeys ( ids , type ) ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
/// <summary>
/// Get entities by UDIs
/// </summary>
/// <param name="ids">
/// A list of UDIs to lookup items by, all UDIs must be of the same UDI type!
/// </param>
/// <param name="type"></param>
/// <returns></returns>
/// <remarks>
/// We allow for POST because there could be quite a lot of Ids.
/// </remarks>
[HttpGet]
[HttpPost]
public ActionResult < IEnumerable < EntityBasic > > GetByIds ( [ FromJsonPath ] Udi [ ] ids , [ FromQuery ] UmbracoEntityTypes type )
{
if ( ids = = null )
2021-10-22 08:51:01 +02:00
{
2022-06-20 08:37:17 +02:00
return NotFound ( ) ;
2021-10-22 08:51:01 +02:00
}
2022-06-20 08:37:17 +02:00
if ( ids . Length = = 0 )
2021-10-22 08:51:01 +02:00
{
2022-06-20 08:37:17 +02:00
return Enumerable . Empty < EntityBasic > ( ) . ToList ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
//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
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
var guidUdi = ids [ 0 ] as GuidUdi ;
if ( guidUdi ! = null )
{
return new ActionResult < IEnumerable < EntityBasic > > (
GetResultForKeys ( ids . Select ( x = > ( ( GuidUdi ) x ) . Guid ) . ToArray ( ) , type ) ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
return NotFound ( ) ;
}
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
#endregion
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
#region Methods to get all dictionary items
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
private IEnumerable < EntityBasic > GetAllDictionaryItems ( )
{
var list = new List < EntityBasic > ( ) ;
2021-10-22 08:51:01 +02:00
2022-06-20 08:37:17 +02:00
IEnumerable < IDictionaryItem > ? rootDictionaryItems = _localizationService . GetRootDictionaryItems ( ) ;
if ( rootDictionaryItems is not null )
2019-01-17 00:29:43 +11:00
{
2022-06-20 08:37:17 +02:00
foreach ( IDictionaryItem dictionaryItem in rootDictionaryItems
. OrderBy ( DictionaryItemSort ( ) ) )
2019-01-17 00:29:43 +11:00
{
2022-06-20 08:37:17 +02:00
EntityBasic ? item = _umbracoMapper . Map < IDictionaryItem , EntityBasic > ( dictionaryItem ) ;
if ( item is not null )
2022-04-01 14:35:18 +02:00
{
2022-06-20 08:37:17 +02:00
list . Add ( item ) ;
2022-04-01 14:35:18 +02:00
}
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
GetChildItemsForList ( dictionaryItem , list ) ;
}
2019-01-17 00:29:43 +11:00
}
2022-06-20 08:37:17 +02:00
return list ;
}
2019-01-17 00:29:43 +11:00
2022-06-20 08:37:17 +02:00
private static Func < IDictionaryItem , string > DictionaryItemSort ( ) = > item = > item . ItemKey ;
private void GetChildItemsForList ( IDictionaryItem dictionaryItem , ICollection < EntityBasic > list )
{
IEnumerable < IDictionaryItem > ? itemChildren = _localizationService . GetDictionaryItemChildren ( dictionaryItem . Key ) ;
2022-04-01 14:35:18 +02:00
2022-06-20 08:37:17 +02:00
if ( itemChildren is not null )
{
foreach ( IDictionaryItem childItem in itemChildren
. OrderBy ( DictionaryItemSort ( ) ) )
2019-01-17 00:29:43 +11:00
{
2022-06-20 08:37:17 +02:00
EntityBasic ? item = _umbracoMapper . Map < IDictionaryItem , EntityBasic > ( childItem ) ;
if ( item is not null )
2022-04-01 14:35:18 +02:00
{
2022-06-20 08:37:17 +02:00
list . Add ( item ) ;
2022-04-01 14:35:18 +02:00
}
2022-06-20 08:37:17 +02:00
GetChildItemsForList ( childItem , list ) ;
}
2019-01-23 10:22:29 +01:00
}
2018-06-29 19:52:40 +02:00
}
2022-06-20 08:37:17 +02:00
#endregion
2018-06-29 19:52:40 +02:00
}