using System; using System.Collections.Generic; using System.Net; using System.Web.Http; using Umbraco.Core; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; using System.Reflection; using Umbraco.Core.Models; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; using System.Web.Http.ModelBinding; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Models; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Models.TemplateQuery; using Umbraco.Web.Search; using Umbraco.Web.Services; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { /// /// The API controller used for getting entity objects, basic name, icon, id representation of umbraco objects that are based on CMSNode /// /// /// /// This controller allows resolving basic entity data for various entities without placing the hard restrictions on users that may not have access /// to the sections these entities entities exist in. This is to allow pickers, etc... of data to work for all users. In some cases such as accessing /// Members, more explicit security checks are done. /// /// Some objects such as macros are not based on CMSNode /// [EntityControllerConfiguration] [PluginController("UmbracoApi")] public class EntityController : UmbracoAuthorizedJsonController { private readonly ITreeService _treeService; private readonly UmbracoTreeSearcher _treeSearcher; private readonly SearchableTreeCollection _searchableTreeCollection; public EntityController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, ITreeService treeService, UmbracoHelper umbracoHelper, SearchableTreeCollection searchableTreeCollection, UmbracoTreeSearcher treeSearcher) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _treeService = treeService; _searchableTreeCollection = searchableTreeCollection; _treeSearcher = treeSearcher; } /// /// Configures this controller with a custom action selector /// private class EntityControllerConfigurationAttribute : Attribute, IControllerConfiguration { public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) { controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector( //This is a special case, we'll accept a String here so that we can get page members when the special "all-members" //id is passed in eventually we'll probably want to support GUID + Udi too new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPagedChildren", "id", typeof(int), typeof(string)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetPath", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetUrlAndAnchors", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi)), new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetByIds", "ids", typeof(int[]), typeof(Guid[]), typeof(Udi[])))); } } /// /// Returns an Umbraco alias given a string /// /// /// /// public dynamic GetSafeAlias(string value, bool camelCase = true) { var returnValue = string.IsNullOrWhiteSpace(value) ? string.Empty : value.ToSafeAlias(camelCase); dynamic returnObj = new System.Dynamic.ExpandoObject(); returnObj.alias = returnValue; returnObj.original = value; returnObj.camelCase = camelCase; return returnObj; } /// /// Searches for results based on the entity type /// /// /// /// /// A starting point for the search, generally a node id, but for members this is a member type alias /// /// If set used to look up whether user and group start node permissions will be ignored. /// [HttpGet] public IEnumerable Search(string query, UmbracoEntityTypes type, string searchFrom = null, Guid? dataTypeKey = null) { // NOTE: Theoretically you shouldn't be able to see member data if you don't have access to members right? ... but there is a member picker, so can't really do that if (string.IsNullOrEmpty(query)) return Enumerable.Empty(); //TODO: This uses the internal UmbracoTreeSearcher, this instead should delgate to the ISearchableTree implementation for the type var ignoreUserStartNodes = dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); return ExamineSearch(query, type, searchFrom, ignoreUserStartNodes); } /// /// Searches for all content that the user is allowed to see (based on their allowed sections) /// /// /// /// /// Even though a normal entity search will allow any user to search on a entity type that they may not have access to edit, we need /// to filter these results to the sections they are allowed to edit since this search function is explicitly for the global search /// so if we showed entities that they weren't allowed to edit they would get errors when clicking on the result. /// /// The reason a user is allowed to search individual entity types that they are not allowed to edit is because those search /// methods might be used in things like pickers in the content editor. /// [HttpGet] public IDictionary SearchAll(string query) { var result = new Dictionary(); if (string.IsNullOrEmpty(query)) return result; var allowedSections = Security.CurrentUser.AllowedSections.ToArray(); foreach (var searchableTree in _searchableTreeCollection.SearchableApplicationTrees.OrderBy(t => t.Value.SortOrder)) { if (allowedSections.Contains(searchableTree.Value.AppAlias)) { var tree = _treeService.GetByAlias(searchableTree.Key); if (tree == null) continue; //shouldn't occur result[Tree.GetRootNodeDisplayName(tree, Services.TextService)] = new TreeSearchResult { Results = searchableTree.Value.SearchableTree.Search(query, 200, 0, out var total), TreeAlias = searchableTree.Key, AppAlias = searchableTree.Value.AppAlias, JsFormatterService = searchableTree.Value.FormatterService, JsFormatterMethod = searchableTree.Value.FormatterMethod }; } } return result; } /// /// Gets the path for a given node ID /// /// /// /// public IEnumerable GetPath(int id, UmbracoEntityTypes type) { var foundContent = GetResultForId(id, type); return foundContent.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } /// /// Gets the path for a given node ID /// /// /// /// public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) { var foundContent = GetResultForKey(id, type); return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } /// /// Gets the path for a given node ID /// /// /// /// public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) { var guidUdi = id as GuidUdi; if (guidUdi != null) { return GetPath(guidUdi.Guid, type); } throw new HttpResponseException(HttpStatusCode.NotFound); } /// /// Gets the url of an entity /// /// Int id of the entity to fetch URL for /// The type of entity such as Document, Media, Member /// The culture to fetch the URL for /// The URL or path to the item /// /// We are not restricting this with security because there is no sensitive data /// public HttpResponseMessage GetUrl(int id, UmbracoEntityTypes type, string culture = null) { culture = culture ?? ClientCulture(); var returnUrl = string.Empty; if (type == UmbracoEntityTypes.Document) { var foundUrl = UmbracoContext.Url(id, culture); if (string.IsNullOrEmpty(foundUrl) == false && foundUrl != "#") { returnUrl = foundUrl; return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(returnUrl) }; } } var ancestors = GetResultForAncestors(id, type); //if content, skip the first node for replicating NiceUrl defaults if(type == UmbracoEntityTypes.Document) { ancestors = ancestors.Skip(1); } returnUrl = "/" + string.Join("/", ancestors.Select(x => x.Name)); return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(returnUrl) }; } /// /// Gets an entity by a xpath query /// /// /// /// /// public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) { // TODO: Rename this!!! It's misleading, it should be GetByXPath if (type != UmbracoEntityTypes.Document) throw new ArgumentException("Get by query is only compatible with entities of type Document"); var q = ParseXPathQuery(query, nodeContextId); var node = Umbraco.ContentSingleAtXPath(q); if (node == null) return null; return GetById(node.Id, type); } // PP: Work in progress on the query parser private string ParseXPathQuery(string query, int id) { return UmbracoXPathPathSyntaxParser.ParseXPathQuery( xpathExpression: query, nodeContextId: id, getPath: nodeid => { var ent = Services.EntityService.Get(nodeid); return ent.Path.Split(',').Reverse(); }, publishedContentExists: i => Umbraco.Content(i) != null); } [HttpGet] public UrlAndAnchors GetUrlAndAnchors(Udi id, string culture = "*") { var intId = Services.EntityService.GetId(id); if (!intId.Success) throw new HttpResponseException(HttpStatusCode.NotFound); return GetUrlAndAnchors(intId.Result, culture); } [HttpGet] public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*") { var url = UmbracoContext.UrlProvider.GetUrl(id); var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id, culture); return new UrlAndAnchors(url, anchorValues); } [HttpGet] [HttpPost] public IEnumerable GetAnchors(AnchorsModel model) { var anchorValues = Services.ContentService.GetAnchorValuesFromRTEContent(model.RteContent); return anchorValues; } #region GetById /// /// Gets an entity by it's id /// /// /// /// public EntityBasic GetById(int id, UmbracoEntityTypes type) { return GetResultForId(id, type); } /// /// Gets an entity by it's key /// /// /// /// public EntityBasic GetById(Guid id, UmbracoEntityTypes type) { return GetResultForKey(id, type); } /// /// Gets an entity by it's UDI /// /// /// /// public EntityBasic GetById(Udi id, UmbracoEntityTypes type) { var guidUdi = id as GuidUdi; if (guidUdi != null) { return GetResultForKey(guidUdi.Guid, type); } throw new HttpResponseException(HttpStatusCode.NotFound); } #endregion #region GetByIds /// /// Get entities by integer ids /// /// /// /// /// /// We allow for POST because there could be quite a lot of Ids /// [HttpGet] [HttpPost] public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) { if (ids == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return GetResultForIds(ids, type); } /// /// Get entities by GUID ids /// /// /// /// /// /// We allow for POST because there could be quite a lot of Ids /// [HttpGet] [HttpPost] public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) { if (ids == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return GetResultForKeys(ids, type); } /// /// Get entities by UDIs /// /// /// A list of UDIs to lookup items by, all UDIs must be of the same UDI type! /// /// /// /// /// We allow for POST because there could be quite a lot of Ids. /// [HttpGet] [HttpPost] public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromUri]UmbracoEntityTypes type) { if (ids == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } if (ids.Length == 0) { return Enumerable.Empty(); } //all udi types will need to be the same in this list so we'll determine by the first //currently we only support GuidIdi for this method var guidUdi = ids[0] as GuidUdi; if (guidUdi != null) { return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); } throw new HttpResponseException(HttpStatusCode.NotFound); } #endregion public IEnumerable GetChildren(int id, UmbracoEntityTypes type, Guid? dataTypeKey = null) { var objectType = ConvertToObjectType(type); if (objectType.HasValue) { //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) { var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); if (nodes.Length == 0) return Enumerable.Empty(); var pr = new List(nodes.Select(Mapper.Map)); return pr; } // else proceed as usual return Services.EntityService.GetChildren(id, objectType.Value) .WhereNotNull() .Select(Mapper.Map); } //now we need to convert the unknown ones switch (type) { case UmbracoEntityTypes.Domain: 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); } } /// /// Get paged child entities by id /// /// /// /// /// /// /// /// /// /// public PagedResult 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, out var intId)) { return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter); } if (Guid.TryParse(id, out _)) { //Not supported currently throw new HttpResponseException(HttpStatusCode.NotFound); } if (UdiParser.TryParse(id, out _)) { //Not supported currently throw new HttpResponseException(HttpStatusCode.NotFound); } //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) { //the EntityService can search paged members from the root intId = -1; return GetPagedChildren(intId, type, pageNumber, pageSize, orderBy, orderDirection, filter, dataTypeKey); } //the EntityService cannot search members of a certain type, this is currently not supported and would require //quite a bit of plumbing to do in the Services/Repository, we'll revert to a paged search //TODO: We should really fix this in the EntityService but if we don't we should allow the ISearchableTree for the members controller // to be used for this search instead of the built in/internal searcher var searchResult = _treeSearcher.ExamineSearch(filter ?? "", type, pageSize, pageNumber - 1, out long total, id); return new PagedResult(total, pageNumber, pageSize) { Items = searchResult }; } /// /// Get paged child entities by id /// /// /// /// /// /// /// /// /// public PagedResult GetPagedChildren( int id, UmbracoEntityTypes type, int pageNumber, int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "", Guid? dataTypeKey = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); if (pageSize <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); var objectType = ConvertToObjectType(type); if (objectType.HasValue) { IEnumerable entities; long totalRecords; 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) { if (pageNumber > 0) return new PagedResult(0, 0, 0); var nodes = Services.EntityService.GetAll(objectType.Value, startNodes).ToArray(); if (nodes.Length == 0) return new PagedResult(0, 0, 0); if (pageSize < nodes.Length) pageSize = nodes.Length; // bah var pr = new PagedResult(nodes.Length, pageNumber, pageSize) { Items = nodes.Select(Mapper.Map) }; return pr; } // else proceed as usual entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, filter.IsNullOrWhiteSpace() ? null : SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection)); if (totalRecords == 0) { return new PagedResult(0, 0, 0); } var culture = ClientCulture(); var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) { Items = entities.Select(source => { var target = Mapper.Map(source, context => { context.SetCulture(culture); }); //TODO: Why is this here and not in the mapping? target.AdditionalData["hasChildren"] = source.HasChildren; return target; }) }; return pagedResult; } //now we need to convert the unknown ones switch (type) { case UmbracoEntityTypes.PropertyType: case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: 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); } } private int[] GetStartNodes(UmbracoEntityTypes type) { switch (type) { case UmbracoEntityTypes.Document: return Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); case UmbracoEntityTypes.Media: return Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); default: return Array.Empty(); } } public PagedResult GetPagedDescendants( int id, UmbracoEntityTypes type, int pageNumber, int pageSize, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "", Guid? dataTypeKey = null) { if (pageNumber <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); if (pageSize <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); var objectType = ConvertToObjectType(type); if (objectType.HasValue) { IEnumerable entities; long totalRecords; if (id == Constants.System.Root) { // root is special: we reduce it to start nodes int[] aids = GetStartNodes(type); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(dataTypeKey); entities = aids == null || aids.Contains(Constants.System.Root) || ignoreUserStartNodes ? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection), includeTrashed: false) : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection)); } else { entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, SqlContext.Query().Where(x => x.Name.Contains(filter)), Ordering.By(orderBy, orderDirection)); } if (totalRecords == 0) { return new PagedResult(0, 0, 0); } var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) { Items = entities.Select(MapEntities()) }; return pagedResult; } //now we need to convert the unknown ones switch (type) { case UmbracoEntityTypes.PropertyType: case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: 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); } } private bool IsDataTypeIgnoringUserStartNodes(Guid? dataTypeKey) => dataTypeKey.HasValue && Services.DataTypeService.IsDataTypeIgnoringUserStartNodes(dataTypeKey.Value); public IEnumerable GetAncestors(int id, UmbracoEntityTypes type, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormDataCollection queryStrings) { return GetResultForAncestors(id, type, queryStrings); } /// /// Searches for results based on the entity type /// /// /// /// /// If set to true, user and group start node permissions will be ignored. /// private IEnumerable ExamineSearch(string query, UmbracoEntityTypes entityType, string searchFrom = null, bool ignoreUserStartNodes = false) { return _treeSearcher.ExamineSearch(query, entityType, 200, 0, out _, searchFrom, ignoreUserStartNodes); } private IEnumerable GetResultForChildren(int id, UmbracoEntityTypes entityType) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { // TODO: Need to check for Object types that support hierarchic here, some might not. return Services.EntityService.GetChildren(id, objectType.Value) .WhereNotNull() .Select(MapEntities()); } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.Domain: 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); } } private IEnumerable GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { // TODO: Need to check for Object types that support hierarchic here, some might not. var ids = Services.EntityService.Get(id).Path.Split(',').Select(int.Parse).Distinct().ToArray(); var ignoreUserStartNodes = IsDataTypeIgnoringUserStartNodes(queryStrings?.GetValue("dataTypeId")); if (ignoreUserStartNodes == false) { int[] aids = null; switch (entityType) { case UmbracoEntityTypes.Document: aids = Security.CurrentUser.CalculateContentStartNodeIds(Services.EntityService); break; case UmbracoEntityTypes.Media: aids = Security.CurrentUser.CalculateMediaStartNodeIds(Services.EntityService); break; } if (aids != null) { var lids = new List(); var ok = false; foreach (var i in ids) { if (ok) { lids.Add(i); continue; } if (aids.Contains(i)) { lids.Add(i); ok = true; } } ids = lids.ToArray(); } } var culture = queryStrings?.GetValue("culture"); return ids.Length == 0 ? Enumerable.Empty() : Services.EntityService.GetAll(objectType.Value, ids) .WhereNotNull() .OrderBy(x => x.Level) .Select(MapEntities(culture)); } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.PropertyType: case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: 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); } } private IEnumerable GetResultForKeys(Guid[] keys, UmbracoEntityTypes entityType) { if (keys.Length == 0) return Enumerable.Empty(); var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { var entities = Services.EntityService.GetAll(objectType.Value, keys) .WhereNotNull() .Select(MapEntities()); // entities are in "some" order, put them back in order var xref = entities.ToDictionary(x => x.Key); var result = keys.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); return result; } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.PropertyType: case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: 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); } } private IEnumerable GetResultForIds(int[] ids, UmbracoEntityTypes entityType) { if (ids.Length == 0) return Enumerable.Empty(); var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { var entities = Services.EntityService.GetAll(objectType.Value, ids) .WhereNotNull() .Select(MapEntities()); // entities are in "some" order, put them back in order var xref = entities.ToDictionary(x => x.Id); var result = ids.Select(x => xref.ContainsKey(x) ? xref[x] : null).Where(x => x != null); return result; } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.PropertyType: case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: 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); } } private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { var found = Services.EntityService.Get(key, objectType.Value); if (found == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return Mapper.Map(found); } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.PropertyType: case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: 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); } } private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { var found = Services.EntityService.Get(id, objectType.Value); if (found == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return MapEntity(found); } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.PropertyType: case UmbracoEntityTypes.PropertyGroup: case UmbracoEntityTypes.Domain: 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); } } private static UmbracoObjectTypes? ConvertToObjectType(UmbracoEntityTypes entityType) { switch (entityType) { 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; } } /// /// /// /// The type of entity. /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are ignored. /// public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter) { return GetResultForAll(type, postFilter); } /// /// Gets the result for the entity list based on the type /// /// /// A string where filter that will filter the results dynamically with linq - optional /// private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { // TODO: Should we order this by something ? var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities()); return ExecutePostFilter(entities, postFilter); } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.Template: var templates = Services.FileService.GetTemplates(); var filteredTemplates = ExecutePostFilter(templates, postFilter); return filteredTemplates.Select(MapEntities()); case UmbracoEntityTypes.Macro: //Get all macros from the macro service var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); var filteredMacros = ExecutePostFilter(macros, postFilter); return filteredMacros.Select(MapEntities()); case UmbracoEntityTypes.PropertyType: //get all document types, then combine all property types into one list var propertyTypes = Services.ContentTypeService.GetAll().Cast() .Concat(Services.MediaTypeService.GetAll()) .ToArray() .SelectMany(x => x.PropertyTypes) .DistinctBy(composition => composition.Alias); var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter); return Mapper.MapEnumerable(filteredPropertyTypes); case UmbracoEntityTypes.PropertyGroup: //get all document types, then combine all property types into one list var propertyGroups = Services.ContentTypeService.GetAll().Cast() .Concat(Services.MediaTypeService.GetAll()) .ToArray() .SelectMany(x => x.PropertyGroups) .DistinctBy(composition => composition.Name); var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter); return Mapper.MapEnumerable(filteredpropertyGroups); case UmbracoEntityTypes.User: var users = Services.UserService.GetAll(0, int.MaxValue, out _); var filteredUsers = ExecutePostFilter(users, postFilter); return Mapper.MapEnumerable(filteredUsers); case UmbracoEntityTypes.Stylesheet: if (!postFilter.IsNullOrWhiteSpace()) throw new NotSupportedException("Filtering on stylesheets is not currently supported"); return Services.FileService.GetStylesheets().Select(MapEntities()); case UmbracoEntityTypes.Language: if (!postFilter.IsNullOrWhiteSpace() ) throw new NotSupportedException("Filtering on languages is not currently supported"); return Services.LocalizationService.GetAllLanguages().Select(MapEntities()); case UmbracoEntityTypes.DictionaryItem: if (!postFilter.IsNullOrWhiteSpace()) throw new NotSupportedException("Filtering on dictionary items is not currently supported"); return GetAllDictionaryItems(); case UmbracoEntityTypes.Domain: default: throw new NotSupportedException("The " + typeof(EntityController) + " does not currently support data for the type " + entityType); } } private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter) { if (postFilter.IsNullOrWhiteSpace()) return entities; var postFilterConditions = postFilter.Split('&'); foreach (var postFilterCondition in postFilterConditions) { var queryCondition = BuildQueryCondition(postFilterCondition); if (queryCondition != null) { var whereClauseExpression = queryCondition.BuildCondition("x"); entities = entities.Where(whereClauseExpression.Compile()); } } return entities; } private static QueryCondition BuildQueryCondition(string postFilter) { var postFilterParts = postFilter.Split(new[] { "=", "==", "!=", "<>", ">", "<", ">=", "<=" }, 2, StringSplitOptions.RemoveEmptyEntries); if (postFilterParts.Length != 2) { return null; } var propertyName = postFilterParts[0]; var constraintValue = postFilterParts[1]; var stringOperator = postFilter.Substring(propertyName.Length, postFilter.Length - propertyName.Length - constraintValue.Length); Operator binaryOperator; try { binaryOperator = OperatorFactory.FromString(stringOperator); } catch (ArgumentException) { // unsupported operators are ignored return null; } var type = typeof(T); var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); if (property == null) { return null; } var queryCondition = new QueryCondition() { Term = new OperatorTerm() { Operator = binaryOperator }, ConstraintValue = constraintValue, Property = new PropertyModel() { Alias = propertyName, Name = propertyName, Type = property.PropertyType.Name } }; return queryCondition; } private Func MapEntities(string culture = null) { culture = culture ?? ClientCulture(); return x => MapEntity(x, culture); } private EntityBasic MapEntity(object entity, string culture = null) { culture = culture ?? ClientCulture(); return Mapper.Map(entity, context => { context.SetCulture(culture); }); } private string ClientCulture() => Request.ClientCulture(); #region Methods to get all dictionary items private IEnumerable GetAllDictionaryItems() { var list = new List(); foreach (var dictionaryItem in Services.LocalizationService.GetRootDictionaryItems().OrderBy(DictionaryItemSort())) { var item = Mapper.Map(dictionaryItem); list.Add(item); GetChildItemsForList(dictionaryItem, list); } return list; } private static Func DictionaryItemSort() => item => item.ItemKey; private void GetChildItemsForList(IDictionaryItem dictionaryItem, ICollection list) { foreach (var childItem in Services.LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).OrderBy(DictionaryItemSort())) { var item = Mapper.Map(childItem); list.Add(item); GetChildItemsForList(childItem, list); } } #endregion } }