diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs index 39fafe590b59..c6628d49f013 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/SearchDocumentItemController.cs @@ -1,11 +1,14 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.Document.Item; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Controllers.Document.Item; @@ -14,13 +17,52 @@ public class SearchDocumentItemController : DocumentItemControllerBase { private readonly IIndexedEntitySearchService _indexedEntitySearchService; private readonly IDocumentPresentationFactory _documentPresentationFactory; + private readonly IDataTypeService _dataTypeService; - public SearchDocumentItemController(IIndexedEntitySearchService indexedEntitySearchService, IDocumentPresentationFactory documentPresentationFactory) + [ActivatorUtilitiesConstructor] + public SearchDocumentItemController( + IIndexedEntitySearchService indexedEntitySearchService, + IDocumentPresentationFactory documentPresentationFactory, + IDataTypeService dataTypeService) { _indexedEntitySearchService = indexedEntitySearchService; _documentPresentationFactory = documentPresentationFactory; + _dataTypeService = dataTypeService; } + [Obsolete("Use the non-obsolete constructor instead, will be removed in v18")] + public SearchDocumentItemController( + IIndexedEntitySearchService indexedEntitySearchService, + IDocumentPresentationFactory documentPresentationFactory) + : this( + indexedEntitySearchService, + documentPresentationFactory, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 18.")] + [ApiExplorerSettings(IgnoreApi = true)] + public async Task SearchWithTrashed( + CancellationToken cancellationToken, + string query, + bool? trashed = null, + string? culture = null, + int skip = 0, + int take = 100, + Guid? parentId = null, + [FromQuery] IEnumerable? allowedDocumentTypes = null) + => await SearchWithTrashed( + cancellationToken, + query, + trashed, + culture, + skip, + take, + parentId, + allowedDocumentTypes, + null); + [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] @@ -32,9 +74,21 @@ public async Task SearchWithTrashed( int skip = 0, int take = 100, Guid? parentId = null, - [FromQuery] IEnumerable? allowedDocumentTypes = null) + [FromQuery] IEnumerable? allowedDocumentTypes = null, + Guid? dataTypeId = null) { - PagedModel searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Document, query, parentId, allowedDocumentTypes, trashed, culture, skip, take); + var ignoreUserStartNodes = await IgnoreUserStartNodes(dataTypeId); + PagedModel searchResult = await _indexedEntitySearchService.SearchAsync( + UmbracoObjectTypes.Document, + query, + parentId, + allowedDocumentTypes, + trashed, + culture, + skip, + take, + ignoreUserStartNodes); + var result = new PagedModel { Items = searchResult.Items.OfType().Select(_documentPresentationFactory.CreateItemResponseModel), @@ -43,4 +97,7 @@ public async Task SearchWithTrashed( return Ok(result); } + + private async Task IgnoreUserStartNodes(Guid? dataTypeKey) => + dataTypeKey is not null && await _dataTypeService.IsDataTypeIgnoringUserStartNodesAsync(dataTypeKey.Value); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs index 5c6b428424a3..e0eb3df87e36 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Tree/ChildrenDocumentTreeController.cs @@ -36,7 +36,12 @@ public ChildrenDocumentTreeController( [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> Children(CancellationToken cancellationToken, Guid parentId, int skip = 0, int take = 100, Guid? dataTypeId = null) + public async Task>> Children( + CancellationToken cancellationToken, + Guid parentId, + int skip = 0, + int take = 100, + Guid? dataTypeId = null) { IgnoreUserStartNodesForDataType(dataTypeId); return await GetChildren(parentId, skip, take); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs index d3202e1df177..2e346b52edc5 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/SearchMediaItemController.cs @@ -1,11 +1,14 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.Media.Item; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Controllers.Media.Item; @@ -14,13 +17,52 @@ public class SearchMediaItemController : MediaItemControllerBase { private readonly IIndexedEntitySearchService _indexedEntitySearchService; private readonly IMediaPresentationFactory _mediaPresentationFactory; + private readonly IDataTypeService _dataTypeService; - public SearchMediaItemController(IIndexedEntitySearchService indexedEntitySearchService, IMediaPresentationFactory mediaPresentationFactory) + [ActivatorUtilitiesConstructor] + public SearchMediaItemController( + IIndexedEntitySearchService indexedEntitySearchService, + IMediaPresentationFactory mediaPresentationFactory, + IDataTypeService dataTypeService) { _indexedEntitySearchService = indexedEntitySearchService; _mediaPresentationFactory = mediaPresentationFactory; + _dataTypeService = dataTypeService; } + [Obsolete("Use the non-obsolete constructor instead, will be removed in Umbraco 18.")] + public SearchMediaItemController( + IIndexedEntitySearchService indexedEntitySearchService, + IMediaPresentationFactory mediaPresentationFactory) + : this( + indexedEntitySearchService, + mediaPresentationFactory, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + [Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 18.")] + [ApiExplorerSettings(IgnoreApi = true)] + public async Task SearchFromParentWithAllowedTypes( + CancellationToken cancellationToken, + string query, + bool? trashed = null, + string? culture = null, + int skip = 0, + int take = 100, + Guid? parentId = null, + [FromQuery] IEnumerable? allowedMediaTypes = null) + => await SearchFromParentWithAllowedTypes( + cancellationToken, + query, + trashed, + culture, + skip, + take, + parentId, + allowedMediaTypes, + null); + [HttpGet("search")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)] @@ -32,9 +74,20 @@ public async Task SearchFromParentWithAllowedTypes( int skip = 0, int take = 100, Guid? parentId = null, - [FromQuery]IEnumerable? allowedMediaTypes = null) + [FromQuery] IEnumerable? allowedMediaTypes = null, + Guid? dataTypeId = null) { - PagedModel searchResult = await _indexedEntitySearchService.SearchAsync(UmbracoObjectTypes.Media, query, parentId, allowedMediaTypes, trashed, culture, skip, take); + var ignoreUserStartNodes = await IgnoreUserStartNodes(dataTypeId); + PagedModel searchResult = await _indexedEntitySearchService.SearchAsync( + UmbracoObjectTypes.Media, + query, + parentId, + allowedMediaTypes, + trashed, + culture, + skip, + take, + ignoreUserStartNodes); var result = new PagedModel { Items = searchResult.Items.OfType().Select(_mediaPresentationFactory.CreateItemResponseModel), @@ -43,4 +96,7 @@ public async Task SearchFromParentWithAllowedTypes( return Ok(result); } + + private async Task IgnoreUserStartNodes(Guid? dataTypeKey) => + dataTypeKey is not null && await _dataTypeService.IsDataTypeIgnoringUserStartNodesAsync(dataTypeKey.Value); } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 2f0dde606a79..94379b0c3e37 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -10385,6 +10385,14 @@ "format": "uuid" } } + }, + { + "name": "dataTypeId", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } } ], "responses": { @@ -16281,6 +16289,14 @@ "format": "uuid" } } + }, + { + "name": "dataTypeId", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } } ], "responses": { diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs index b0cd6af6dcb9..11e178808736 100644 --- a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -7,14 +7,11 @@ namespace Umbraco.Extensions; public static class DateTypeServiceExtensions { public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key) - { - IDataType? dataType = dataTypeService.GetAsync(key).GetAwaiter().GetResult(); - - if (dataType != null && dataType.ConfigurationObject is IIgnoreUserStartNodesConfig ignoreStartNodesConfig) - { - return ignoreStartNodesConfig.IgnoreUserStartNodes; - } + => dataTypeService.IsDataTypeIgnoringUserStartNodesAsync(key).GetAwaiter().GetResult(); - return false; + public static async Task IsDataTypeIgnoringUserStartNodesAsync(this IDataTypeService dataTypeService, Guid key) + { + IDataType? dataType = await dataTypeService.GetAsync(key); + return dataType is { ConfigurationObject: IIgnoreUserStartNodesConfig { IgnoreUserStartNodes: true } }; } } diff --git a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs index 86fab12b7400..02ac583f8c9f 100644 --- a/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs +++ b/src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs @@ -1,15 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; using System.Text.RegularExpressions; using Examine; using Examine.Search; using Lucene.Net.QueryParsers.Classic; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; @@ -28,20 +29,16 @@ public class BackOfficeExamineSearcher : IBackOfficeExamineSearcher private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IEntityService _entityService; private readonly IExamineManager _examineManager; - private readonly ILocalizationService _languageService; - private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly ILanguageService _languageService; private readonly IUmbracoTreeSearcherFields _treeSearcherFields; - private readonly IUmbracoMapper _umbracoMapper; public BackOfficeExamineSearcher( IExamineManager examineManager, - ILocalizationService languageService, + ILanguageService languageService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IEntityService entityService, IUmbracoTreeSearcherFields treeSearcherFields, - AppCaches appCaches, - IUmbracoMapper umbracoMapper, - IPublishedUrlProvider publishedUrlProvider) + AppCaches appCaches) { _examineManager = examineManager; _languageService = languageService; @@ -49,8 +46,47 @@ public BackOfficeExamineSearcher( _entityService = entityService; _treeSearcherFields = treeSearcherFields; _appCaches = appCaches; - _umbracoMapper = umbracoMapper; - _publishedUrlProvider = publishedUrlProvider; + } + + [Obsolete("Please use the non-obsolete constructor. Will be removed in V18.")] + public BackOfficeExamineSearcher( + IExamineManager examineManager, + ILocalizationService languageService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IEntityService entityService, + IUmbracoTreeSearcherFields treeSearcherFields, + AppCaches appCaches, + IUmbracoMapper umbracoMapper, + IPublishedUrlProvider publishedUrlProvider) + : this( + examineManager, + StaticServiceProvider.Instance.GetRequiredService(), + backOfficeSecurityAccessor, + entityService, + treeSearcherFields, + appCaches) + { + } + + [Obsolete("Please use the non-obsolete constructor. Will be removed in V18.")] + public BackOfficeExamineSearcher( + IExamineManager examineManager, + ILocalizationService localizationService, + ILanguageService languageService, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IEntityService entityService, + IUmbracoTreeSearcherFields treeSearcherFields, + AppCaches appCaches, + IUmbracoMapper umbracoMapper, + IPublishedUrlProvider publishedUrlProvider) + : this( + examineManager, + languageService, + backOfficeSecurityAccessor, + entityService, + treeSearcherFields, + appCaches) + { } [Obsolete("Please use the method that accepts all parameters. Will be removed in V17.")] @@ -113,8 +149,6 @@ public IEnumerable Search( fields.Remove(UmbracoExamineFieldNames.NodeKeyFieldName); } - IUser? currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - switch (entityType) { case UmbracoEntityTypes.Member: @@ -127,7 +161,7 @@ public IEnumerable Search( } if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && - searchFrom.Trim() != "-1") + searchFrom.Trim() != Constants.System.RootString) { sb.Append("+__NodeTypeAlias:"); sb.Append(searchFrom); @@ -143,10 +177,12 @@ public IEnumerable Search( fieldsToLoad.Add(field); } - var allMediaStartNodes = currentUser != null - ? currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches) - : Array.Empty(); - AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + AppendPath(sb, UmbracoObjectTypes.Media, searchFrom, ignoreUserStartNodes, out var abortMediaQuery); + if (abortMediaQuery) + { + totalFound = 0; + return []; + } if (trashed.HasValue) { @@ -162,10 +198,12 @@ public IEnumerable Search( fieldsToLoad.Add(field); } - var allContentStartNodes = currentUser != null - ? currentUser.CalculateContentStartNodeIds(_entityService, _appCaches) - : Array.Empty(); - AppendPath(sb, UmbracoObjectTypes.Document, allContentStartNodes, searchFrom, ignoreUserStartNodes, _entityService); + AppendPath(sb, UmbracoObjectTypes.Document, searchFrom, ignoreUserStartNodes, out var abortContentQuery); + if (abortContentQuery) + { + totalFound = 0; + return []; + } if (trashed.HasValue) { @@ -192,7 +230,7 @@ public IEnumerable Search( if (!BuildQuery(sb, query, searchFrom, fields, type)) { totalFound = 0; - return Enumerable.Empty(); + return []; } ISearchResults? result = index.Searcher @@ -222,7 +260,9 @@ private bool BuildQuery(StringBuilder sb, string query, string? searchFrom, List // then nodeName will be matched normally with wildcards // the rest will be normal without wildcards - var allLangs = _languageService.GetAllLanguages().Select(x => x.IsoCode.ToLowerInvariant()).ToList(); + var allLangs = _languageService.GetAllAsync().GetAwaiter().GetResult() + .Select(x => x.IsoCode.ToLowerInvariant()) + .ToList(); // the chars [*-_] in the query will mess everything up so let's remove those // However we cannot just remove - and _ since these signify a space, so we instead replace them with that. @@ -400,76 +440,89 @@ private static void AppendNodeNameWithWildcards(StringBuilder sb, string[] query } } - private static void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, int[]? startNodeIds, string? searchFrom, bool ignoreUserStartNodes, IEntityService entityService) + private void AppendPath(StringBuilder sb, UmbracoObjectTypes objectType, string? searchFrom, bool ignoreUserStartNodes, out bool abortQuery) { - if (sb == null) + ArgumentNullException.ThrowIfNull(sb); + + abortQuery = false; + + if (searchFrom is Constants.System.RootString) { - throw new ArgumentNullException(nameof(sb)); + searchFrom = null; } - if (entityService == null) + var userStartNodes = ignoreUserStartNodes ? [Constants.System.Root] : GetUserStartNodes(objectType); + if (searchFrom is null && userStartNodes.Contains(Constants.System.Root)) { - throw new ArgumentNullException(nameof(entityService)); + // If we have no searchFrom and the user either has access to the root node or we are ignoring user + // start nodes, we don't need to filter by path. + return; } - if (Guid.TryParse(searchFrom, out Guid guid)) + string[] pathsToFilter; + if (searchFrom is null) { - searchFrom = entityService.GetId(guid, objectType).Result.ToString(); + // If we don't want to filter by a specific entity, we can simply use the user start nodes. + pathsToFilter = GetEntityPaths(objectType, userStartNodes); } else { - // fallback to Udi for legacy reasons as the calling methods take string? - UdiParser.TryParse(searchFrom, true, out Udi? udi); - searchFrom = udi == null ? searchFrom : entityService.GetId(udi).Result.ToString(); - } + TreeEntityPath? searchFromPath = GetEntityPath(searchFrom, objectType); + if (searchFromPath is null) + { + // If the searchFrom cannot be found, return no results. + // This is to prevent showing entities outside the intended filter. + abortQuery = true; + return; + } - TreeEntityPath? entityPath = - int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var searchFromId) && - searchFromId > 0 - ? entityService.GetAllPaths(objectType, searchFromId).FirstOrDefault() - : null; + var userStartNodePaths = GetEntityPaths(objectType, userStartNodes); - if (entityPath != null) - { - // find... only what's underneath - sb.Append("+__Path:"); - AppendPath(sb, entityPath.Path, false); - sb.Append(' '); + // If the user has access to the entity, we can simply filter by the entity path. + if (userStartNodePaths.Any(userStartNodePath => StartsWithPath(searchFromPath.Path, userStartNodePath))) + { + sb.Append("+__Path:"); + AppendPath(sb, searchFromPath.Path, false); + sb.Append(' '); + return; + } + + // If the user does not have access to the entity, let's filter the paths by the ones that start with the + // entity path (are descendants of the entity). + pathsToFilter = userStartNodePaths.Where(ep => StartsWithPath(ep, searchFromPath.Path)).ToArray(); } - else if (startNodeIds?.Length == 0) + + // If we have no paths left, no need to perform the query at all, just return no results. + if (pathsToFilter.Length == 0) { - // make sure we don't find anything - sb.Append("+__Path:none "); + abortQuery = true; + return; } - else if (startNodeIds?.Contains(-1) == false && ignoreUserStartNodes == false) // -1 = no restriction - { - IEnumerable entityPaths = entityService.GetAllPaths(objectType, startNodeIds); - // for each start node, find the start node, and what's underneath - // +__Path:(-1*,1234 -1*,1234,* -1*,5678 -1*,5678,* ...) - sb.Append("+__Path:("); - var first = true; - foreach (TreeEntityPath ep in entityPaths) + // For each start node, find the start node, and what's underneath + // +__Path:(-1*,1234 -1*,1234,* -1*,5678 -1*,5678,* ...) + sb.Append("+__Path:("); + var first = true; + foreach (string pathToFilter in pathsToFilter) + { + if (first) { - if (first) - { - first = false; - } - else - { - sb.Append(' '); - } - - AppendPath(sb, ep.Path, true); + first = false; + } + else + { + sb.Append(' '); } - sb.Append(") "); + AppendPath(sb, pathToFilter, true); } + + sb.Append(") "); } private static void AppendPath(StringBuilder sb, string path, bool includeThisNode) { - path = path.Replace("-", "\\-").Replace(",", "\\,"); + path = path.Replace("-", "\\-"); if (includeThisNode) { sb.Append(path); @@ -477,6 +530,68 @@ private static void AppendPath(StringBuilder sb, string path, bool includeThisNo } sb.Append(path); - sb.Append("\\,*"); + sb.Append(",*"); + } + + private static bool StartsWithPath(string path1, string path2) + { + if (path1.StartsWith(path2) == false) + { + return false; + } + + return path1.Length == path2.Length || path1[path2.Length] == ','; + } + + private int[] GetUserStartNodes(UmbracoObjectTypes objectType) + { + IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + if (currentUser is null) + { + return []; + } + + var startNodes = objectType switch + { + UmbracoObjectTypes.Document => currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), + UmbracoObjectTypes.Media => currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), + _ => throw new NotSupportedException($"The object type {objectType} is not supported for start nodes."), + }; + + return startNodes ?? [Constants.System.Root]; // If no start nodes are defined, we assume the user has access to the root node (-1). + } + + private string[] GetEntityPaths(UmbracoObjectTypes objectType, int[] entityIds) => + entityIds switch + { + [] => [], + _ when entityIds.Contains(Constants.System.Root) => [Constants.System.RootString], + _ => _entityService.GetAllPaths(objectType, entityIds).Select(x => x.Path).ToArray(), + }; + + private TreeEntityPath? GetEntityPath(string? searchFrom, UmbracoObjectTypes objectType) + { + if (searchFrom is null) + { + return null; + } + + Guid? entityKey = null; + if (Guid.TryParse(searchFrom, out Guid entityGuid)) + { + entityKey = entityGuid; + } // fallback to Udi for legacy reasons as the calling methods take string? + else if (UdiParser.TryParse(searchFrom, true, out Udi? udi) && udi is GuidUdi guidUdi) + { + entityKey = guidUdi.Guid; + } + else if (int.TryParse(searchFrom, NumberStyles.Integer, CultureInfo.InvariantCulture, out var entityId) + && entityId > 0 + && _entityService.GetKey(entityId, objectType) is { Success: true } attempt) + { + entityKey = attempt.Result; + } + + return entityKey is null ? null : _entityService.GetAllPaths(objectType, entityKey.Value).FirstOrDefault(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts index 8cc5b2ace817..95169bef0bb4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/backend-api/types.gen.ts @@ -6802,6 +6802,7 @@ export type GetItemDocumentSearchData = { take?: number; parentId?: string; allowedDocumentTypes?: Array; + dataTypeId?: string; }; url: '/umbraco/management/api/v1/item/document/search'; }; @@ -9156,6 +9157,7 @@ export type GetItemMediaSearchData = { take?: number; parentId?: string; allowedMediaTypes?: Array; + dataTypeId?: string; }; url: '/umbraco/management/api/v1/item/media/search'; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker/picker.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker/picker.context.ts index 52594864e89d..e606e79b0ffc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker/picker.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker/picker.context.ts @@ -1,14 +1,22 @@ import { UMB_PICKER_CONTEXT } from './picker.context.token.js'; import { UmbPickerSearchManager } from './search/manager/picker-search.manager.js'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/content'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; export class UmbPickerContext extends UmbContextBase { public readonly selection = new UmbSelectionManager(this); public readonly search = new UmbPickerSearchManager(this); + public dataType?: { unique: string }; constructor(host: UmbControllerHost) { super(host, UMB_PICKER_CONTEXT); + + this.consumeContext(UMB_PROPERTY_TYPE_BASED_PROPERTY_CONTEXT, (context) => { + this.observe(context?.dataType, (dataType) => { + this.dataType = dataType; + }); + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/picker-search.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/picker-search.manager.ts index 290f596a2572..9d73c0fdba2a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/picker-search.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/picker-search.manager.ts @@ -187,6 +187,7 @@ export class UmbPickerSearchManager< // ensure that config params are always included ...this.#config?.queryParams, searchFrom: this.#config?.searchFrom, + dataTypeUnique: this.#config?.dataTypeUnique, }; const { data } = await this.#searchProvider.search(args); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/types.ts index 452fe3a538ce..c20e2ca02e8f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker/search/manager/types.ts @@ -4,4 +4,5 @@ export interface UmbPickerSearchManagerConfig; includeTrashed?: boolean; culture?: string | null; + dataTypeUnique?: string; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts index cebd5b473813..c5fbf002e2d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/media-picker-modal.element.ts @@ -213,6 +213,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement; includeTrashed?: boolean; culture?: string | null; + dataTypeUnique?: string; } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs index bb4f099ef02d..e19a185c1228 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Examine.Lucene/UmbracoExamine/BackOfficeExamineSearcherTests.cs @@ -71,16 +71,17 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) builder.Services.AddHostedService(); } - private IEnumerable BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0) => + private IEnumerable BackOfficeExamineSearch(string query, int pageSize = 20, int pageIndex = 0, bool ignoreUserStartNodes = false) => BackOfficeExamineSearcher.Search( query, UmbracoEntityTypes.Document, pageSize, pageIndex, - out _, - null, - null, - ignoreUserStartNodes: true); + totalFound: out _, + contentTypeAliases: null, + trashed: null, + searchFrom: null, + ignoreUserStartNodes: ignoreUserStartNodes); private async Task SetupUserIdentity(string userId) {