aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2026-06-17 20:45:52 +0200
committerGitHub <noreply@github.com>2026-06-17 20:45:52 +0200
commitada11f56921e25c20fcb6760231b4c3fef0c7fdb (patch)
treea541fd90d5cbf33357e94c1a0f51dd7b86e33b12
parent5036bf7db0a44e7917f8930e9a894df3f6934d18 (diff)
parenta9dc8f6f742e71720aad2c402a08087f7a7d5368 (diff)
Always apply recursive when filters are requested (#17088)
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs439
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs96
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs8
3 files changed, 322 insertions, 221 deletions
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 5f23f2fcee..c52a6cd7dc 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -321,24 +321,21 @@ public class ItemsController : BaseJellyfinApiController
recursive = true;
includeItemTypes = [BaseItemKind.Playlist];
}
- else if (folder is ICollectionFolder)
+ else if (folder is ICollectionFolder && includeItemTypes.Length == 0)
{
- if (includeItemTypes.Length == 0)
+ includeItemTypes = collectionType switch
{
- includeItemTypes = collectionType switch
- {
- CollectionType.boxsets => [BaseItemKind.BoxSet],
- null => [BaseItemKind.Movie, BaseItemKind.Series],
- _ => []
- };
- }
+ CollectionType.boxsets => [BaseItemKind.BoxSet],
+ null => [BaseItemKind.Movie, BaseItemKind.Series],
+ _ => []
+ };
+ }
- // When the client doesn't specify recursive/includeItemTypes, force the query
- // through the database path where all filters (IsHD, genres, etc.) are applied.
- if (includeItemTypes.Length > 0)
- {
- recursive ??= true;
- }
+ // includeItemTypes on a library lists its contents recursively rather than just its
+ // immediate children, so default to a recursive query when the client didn't choose.
+ if (folder is ICollectionFolder && includeItemTypes.Length > 0)
+ {
+ recursive ??= true;
}
if (item is not UserRootFolder
@@ -351,246 +348,248 @@ public class ItemsController : BaseJellyfinApiController
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
}
- if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder)
- {
- // Use search providers when searchTerm is provided. Providers return only IDs and scores;
- // items are loaded server-side via folder.GetItems below, which applies user-access filtering.
- Dictionary<Guid, float>? searchResultScores = null;
- Guid[] itemIds = ids;
-
- if (!string.IsNullOrWhiteSpace(searchTerm))
- {
- var searchProviderQuery = new SearchProviderQuery
- {
- SearchTerm = searchTerm,
- UserId = userId,
- IncludeItemTypes = includeItemTypes,
- ExcludeItemTypes = excludeItemTypes,
- MediaTypes = mediaTypes,
- Limit = limit.HasValue ? limit.Value * 3 : null,
- ParentId = parentId
- };
+ // Build the query up front so the dispatch below can decide the path from it.
+ // Use search providers when searchTerm is provided. Providers return only IDs and scores;
+ // items are loaded server-side via folder.GetItems below, which applies user-access filtering.
+ Dictionary<Guid, float>? searchResultScores = null;
+ Guid[] itemIds = ids;
- var searchResults = await _searchManager.GetSearchResultsAsync(searchProviderQuery, HttpContext.RequestAborted).ConfigureAwait(false);
- if (searchResults.Count > 0)
- {
- searchResultScores = searchResults.ToDictionary(r => r.ItemId, r => r.Score);
- itemIds = ids.Length > 0
- ? ids.Concat(searchResultScores.Keys).Distinct().ToArray()
- : searchResultScores.Keys.ToArray();
- }
- }
-
- var query = new InternalItemsQuery(user)
+ if (!string.IsNullOrWhiteSpace(searchTerm))
+ {
+ var searchProviderQuery = new SearchProviderQuery
{
- IsPlayed = isPlayed,
- MediaTypes = mediaTypes,
+ SearchTerm = searchTerm,
+ UserId = userId,
IncludeItemTypes = includeItemTypes,
ExcludeItemTypes = excludeItemTypes,
- Recursive = recursive ?? false,
- OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
- IsFavorite = isFavorite,
- Limit = searchResultScores is null ? limit : null,
- StartIndex = searchResultScores is null ? startIndex : null,
- IsMissing = isMissing,
- IsUnaired = isUnaired,
- CollapseBoxSetItems = collapseBoxSetItems,
- NameLessThan = nameLessThan,
- NameStartsWith = nameStartsWith,
- NameStartsWithOrGreater = nameStartsWithOrGreater,
- HasImdbId = hasImdbId,
- IsPlaceHolder = isPlaceHolder,
- IsLocked = isLocked,
- MinWidth = minWidth,
- MinHeight = minHeight,
- MaxWidth = maxWidth,
- MaxHeight = maxHeight,
- Is3D = is3D,
- HasTvdbId = hasTvdbId,
- HasTmdbId = hasTmdbId,
- IsMovie = isMovie,
- IsSeries = isSeries,
- IsNews = isNews,
- IsKids = isKids,
- IsSports = isSports,
- HasOverview = hasOverview,
- HasOfficialRating = hasOfficialRating,
- HasParentalRating = hasParentalRating,
- HasSpecialFeature = hasSpecialFeature,
- HasSubtitles = hasSubtitles,
- HasThemeSong = hasThemeSong,
- HasThemeVideo = hasThemeVideo,
- HasTrailer = hasTrailer,
- IsHD = isHd,
- Is4K = is4K,
- Tags = tags,
- OfficialRatings = officialRatings,
- Genres = genres,
- ArtistIds = artistIds,
- AlbumArtistIds = albumArtistIds,
- ContributingArtistIds = contributingArtistIds,
- GenreIds = genreIds,
- StudioIds = studioIds,
- Person = person,
- PersonIds = personIds,
- PersonTypes = personTypes,
- Years = years,
- ImageTypes = imageTypes,
- VideoTypes = videoTypes,
- AdjacentTo = adjacentTo,
- ItemIds = itemIds,
- MinCommunityRating = minCommunityRating,
- MinCriticRating = minCriticRating,
- ParentId = parentId ?? Guid.Empty,
- IndexNumber = indexNumber,
- ParentIndexNumber = parentIndexNumber,
- EnableTotalRecordCount = enableTotalRecordCount,
- ExcludeItemIds = excludeItemIds,
- DtoOptions = dtoOptions,
- SearchTerm = searchResultScores is null ? searchTerm : null,
- MinDateLastSaved = minDateLastSaved?.ToUniversalTime(),
- MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(),
- MinPremiereDate = minPremiereDate?.ToUniversalTime(),
- MaxPremiereDate = maxPremiereDate?.ToUniversalTime(),
- AudioLanguages = audioLanguages,
- SubtitleLanguages = subtitleLanguages,
- LinkedChildAncestorIds = linkedChildAncestorIds,
+ MediaTypes = mediaTypes,
+ Limit = limit.HasValue ? limit.Value * 3 : null,
+ ParentId = parentId
};
- if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm))
+ var searchResults = await _searchManager.GetSearchResultsAsync(searchProviderQuery, HttpContext.RequestAborted).ConfigureAwait(false);
+ if (searchResults.Count > 0)
{
- query.CollapseBoxSetItems = false;
+ searchResultScores = searchResults.ToDictionary(r => r.ItemId, r => r.Score);
+ itemIds = ids.Length > 0
+ ? ids.Concat(searchResultScores.Keys).Distinct().ToArray()
+ : searchResultScores.Keys.ToArray();
}
+ }
+
+ var query = new InternalItemsQuery(user)
+ {
+ IsPlayed = isPlayed,
+ MediaTypes = mediaTypes,
+ IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = excludeItemTypes,
+ Recursive = recursive ?? false,
+ OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
+ IsFavorite = isFavorite,
+ Limit = searchResultScores is null ? limit : null,
+ StartIndex = searchResultScores is null ? startIndex : null,
+ IsMissing = isMissing,
+ IsUnaired = isUnaired,
+ CollapseBoxSetItems = collapseBoxSetItems,
+ NameLessThan = nameLessThan,
+ NameStartsWith = nameStartsWith,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
+ HasImdbId = hasImdbId,
+ IsPlaceHolder = isPlaceHolder,
+ IsLocked = isLocked,
+ MinWidth = minWidth,
+ MinHeight = minHeight,
+ MaxWidth = maxWidth,
+ MaxHeight = maxHeight,
+ Is3D = is3D,
+ HasTvdbId = hasTvdbId,
+ HasTmdbId = hasTmdbId,
+ IsMovie = isMovie,
+ IsSeries = isSeries,
+ IsNews = isNews,
+ IsKids = isKids,
+ IsSports = isSports,
+ HasOverview = hasOverview,
+ HasOfficialRating = hasOfficialRating,
+ HasParentalRating = hasParentalRating,
+ HasSpecialFeature = hasSpecialFeature,
+ HasSubtitles = hasSubtitles,
+ HasThemeSong = hasThemeSong,
+ HasThemeVideo = hasThemeVideo,
+ HasTrailer = hasTrailer,
+ IsHD = isHd,
+ Is4K = is4K,
+ Tags = tags,
+ OfficialRatings = officialRatings,
+ Genres = genres,
+ ArtistIds = artistIds,
+ AlbumArtistIds = albumArtistIds,
+ ContributingArtistIds = contributingArtistIds,
+ GenreIds = genreIds,
+ StudioIds = studioIds,
+ Person = person,
+ PersonIds = personIds,
+ PersonTypes = personTypes,
+ Years = years,
+ ImageTypes = imageTypes,
+ VideoTypes = videoTypes,
+ AdjacentTo = adjacentTo,
+ ItemIds = itemIds,
+ MinCommunityRating = minCommunityRating,
+ MinCriticRating = minCriticRating,
+ ParentId = parentId ?? Guid.Empty,
+ IndexNumber = indexNumber,
+ ParentIndexNumber = parentIndexNumber,
+ EnableTotalRecordCount = enableTotalRecordCount,
+ ExcludeItemIds = excludeItemIds,
+ DtoOptions = dtoOptions,
+ SearchTerm = searchResultScores is null ? searchTerm : null,
+ MinDateLastSaved = minDateLastSaved?.ToUniversalTime(),
+ MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(),
+ MinPremiereDate = minPremiereDate?.ToUniversalTime(),
+ MaxPremiereDate = maxPremiereDate?.ToUniversalTime(),
+ AudioLanguages = audioLanguages,
+ SubtitleLanguages = subtitleLanguages,
+ LinkedChildAncestorIds = linkedChildAncestorIds,
+ };
+
+ if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm))
+ {
+ query.CollapseBoxSetItems = false;
+ }
- if (query.SubtitleLanguages.Count > 0 && query.HasSubtitles.HasValue)
+ if (query.SubtitleLanguages.Count > 0 && query.HasSubtitles.HasValue)
+ {
+ if (query.HasSubtitles.Value)
{
- if (query.HasSubtitles.Value)
- {
- // if we check for specific subtitles we don't need a separate check for subtitle existence
- query.HasSubtitles = null;
- }
- else
- {
- // if we search for items without subtitles, we don't need to check for subtitles of a specific language
- query.SubtitleLanguages = [];
- }
+ // if we check for specific subtitles we don't need a separate check for subtitle existence
+ query.HasSubtitles = null;
}
-
- // for filter values that rely on media streams, we need to include alternative and linked versions
- if (query.HasSubtitles.HasValue
- || query.SubtitleLanguages.Count > 0
- || query.AudioLanguages.Count > 0
- || query.Is3D.HasValue
- || query.IsHD.HasValue
- || query.Is4K.HasValue
- || query.VideoTypes.Length > 0
- )
+ else
{
- query.IncludeOwnedItems = true;
+ // if we search for items without subtitles, we don't need to check for subtitles of a specific language
+ query.SubtitleLanguages = [];
}
+ }
- query.ApplyFilters(filters);
+ // for filter values that rely on media streams, we need to include alternative and linked versions
+ if (query.HasSubtitles.HasValue
+ || query.SubtitleLanguages.Count > 0
+ || query.AudioLanguages.Count > 0
+ || query.Is3D.HasValue
+ || query.IsHD.HasValue
+ || query.Is4K.HasValue
+ || query.VideoTypes.Length > 0
+ )
+ {
+ query.IncludeOwnedItems = true;
+ }
- // Filter by Series Status
- if (seriesStatus.Length != 0)
- {
- query.SeriesStatuses = seriesStatus;
- }
+ query.ApplyFilters(filters);
- // Exclude Blocked Unrated Items
- var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems);
- if (blockedUnratedItems is not null)
- {
- query.BlockUnratedItems = blockedUnratedItems;
- }
+ // Filter by Series Status
+ if (seriesStatus.Length != 0)
+ {
+ query.SeriesStatuses = seriesStatus;
+ }
- // ExcludeLocationTypes
- if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
- {
- query.IsVirtualItem = false;
- }
+ // Exclude Blocked Unrated Items
+ var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems);
+ if (blockedUnratedItems is not null)
+ {
+ query.BlockUnratedItems = blockedUnratedItems;
+ }
- if (locationTypes.Length > 0 && locationTypes.Length < 4)
- {
- query.IsVirtualItem = locationTypes.Contains(LocationType.Virtual);
- }
+ // ExcludeLocationTypes
+ if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
+ {
+ query.IsVirtualItem = false;
+ }
- // Min official rating
- if (!string.IsNullOrWhiteSpace(minOfficialRating))
- {
- query.MinParentalRating = _localization.GetRatingScore(minOfficialRating);
- }
+ if (locationTypes.Length > 0 && locationTypes.Length < 4)
+ {
+ query.IsVirtualItem = locationTypes.Contains(LocationType.Virtual);
+ }
- // Max official rating
- if (!string.IsNullOrWhiteSpace(maxOfficialRating))
- {
- query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating);
- }
+ // Min official rating
+ if (!string.IsNullOrWhiteSpace(minOfficialRating))
+ {
+ query.MinParentalRating = _localization.GetRatingScore(minOfficialRating);
+ }
+
+ // Max official rating
+ if (!string.IsNullOrWhiteSpace(maxOfficialRating))
+ {
+ query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating);
+ }
- // Artists
- if (artists.Length != 0)
+ // Artists
+ if (artists.Length != 0)
+ {
+ query.ArtistIds = artists.Select(i =>
{
- query.ArtistIds = artists.Select(i =>
+ try
{
- try
- {
- return _libraryManager.GetArtist(i, new DtoOptions(false));
- }
- catch
- {
- return null;
- }
- }).Where(i => i is not null).Select(i => i!.Id).ToArray();
- }
+ return _libraryManager.GetArtist(i, new DtoOptions(false));
+ }
+ catch
+ {
+ return null;
+ }
+ }).Where(i => i is not null).Select(i => i!.Id).ToArray();
+ }
- // ExcludeArtistIds
- if (excludeArtistIds.Length != 0)
- {
- query.ExcludeArtistIds = excludeArtistIds;
- }
+ // ExcludeArtistIds
+ if (excludeArtistIds.Length != 0)
+ {
+ query.ExcludeArtistIds = excludeArtistIds;
+ }
- if (albumIds.Length != 0)
- {
- query.AlbumIds = albumIds;
- }
+ if (albumIds.Length != 0)
+ {
+ query.AlbumIds = albumIds;
+ }
- // Albums
- if (albums.Length != 0)
+ // Albums
+ if (albums.Length != 0)
+ {
+ query.AlbumIds = albums.SelectMany(i =>
{
- query.AlbumIds = albums.SelectMany(i =>
- {
- return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.MusicAlbum], Name = i, Limit = 1 });
- }).ToArray();
- }
+ return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.MusicAlbum], Name = i, Limit = 1 });
+ }).ToArray();
+ }
- // Studios
- if (studios.Length != 0)
+ // Studios
+ if (studios.Length != 0)
+ {
+ query.StudioIds = studios.Select(i =>
{
- query.StudioIds = studios.Select(i =>
+ try
{
- try
- {
- return _libraryManager.GetStudio(i);
- }
- catch
- {
- return null;
- }
- }).Where(i => i is not null).Select(i => i!.Id).ToArray();
- }
-
- // Apply default sorting if none requested
- if (query.OrderBy.Count == 0)
- {
- // Albums by artist
- if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum)
+ return _libraryManager.GetStudio(i);
+ }
+ catch
{
- query.OrderBy = [(ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending)];
+ return null;
}
+ }).Where(i => i is not null).Select(i => i!.Id).ToArray();
+ }
+
+ // Apply default sorting if none requested
+ if (query.OrderBy.Count == 0)
+ {
+ // Albums by artist
+ if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum)
+ {
+ query.OrderBy = [(ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending)];
}
+ }
- query.Parent = null;
+ query.Parent = null;
+ // At the user root an unfiltered, non-recursive request is a plain listing of the user's libraries
+ if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder || query.HasFilters)
+ {
// folder.GetItems applies user-access filtering via the InternalItemsQuery's User.
result = folder.GetItems(query);
if (searchResultScores is not null && searchResultScores.Count > 0)
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 422c40ce5d..3b1f6a961f 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -72,6 +72,102 @@ namespace MediaBrowser.Controller.Entities
}
}
+ /// <summary>
+ /// Gets a value indicating whether the query carries any criteria that narrows the
+ /// result set, as opposed to user context, pagination, sorting or DTO options.
+ /// </summary>
+ public bool HasFilters =>
+ IncludeItemTypes.Length > 0
+ || ExcludeItemTypes.Length > 0
+ || Genres.Count > 0
+ || GenreIds.Count > 0
+ || Years.Length > 0
+ || Tags.Length > 0
+ || ExcludeTags.Length > 0
+ || OfficialRatings.Length > 0
+ || StudioIds.Length > 0
+ || ArtistIds.Length > 0
+ || AlbumArtistIds.Length > 0
+ || ContributingArtistIds.Length > 0
+ || ExcludeArtistIds.Length > 0
+ || AlbumIds.Length > 0
+ || PersonIds.Length > 0
+ || PersonTypes.Length > 0
+ || MediaTypes.Length > 0
+ || VideoTypes.Length > 0
+ || ImageTypes.Length > 0
+ || SeriesStatuses.Length > 0
+ || ItemIds.Length > 0
+ || ExcludeItemIds.Length > 0
+ || AudioLanguages.Count > 0
+ || SubtitleLanguages.Count > 0
+ || LinkedChildAncestorIds.Length > 0
+ || AncestorIds.Length > 0
+ || IsFavorite.HasValue
+ || IsFavoriteOrLiked.HasValue
+ || IsLiked.HasValue
+ || IsPlayed.HasValue
+ || IsResumable.HasValue
+ || IsFolder.HasValue
+ || IsMissing.HasValue
+ || IsUnaired.HasValue
+ || IsSpecialSeason.HasValue
+ || Is3D.HasValue
+ || IsHD.HasValue
+ || Is4K.HasValue
+ || IsLocked.HasValue
+ || IsPlaceHolder.HasValue
+ || IsMovie.HasValue
+ || IsSports.HasValue
+ || IsKids.HasValue
+ || IsNews.HasValue
+ || IsSeries.HasValue
+ || IsAiring.HasValue
+ || IsVirtualItem.HasValue
+ || HasImdbId.HasValue
+ || HasTmdbId.HasValue
+ || HasTvdbId.HasValue
+ || HasOverview.HasValue
+ || HasOfficialRating.HasValue
+ || HasParentalRating.HasValue
+ || HasThemeSong.HasValue
+ || HasThemeVideo.HasValue
+ || HasSubtitles.HasValue
+ || HasSpecialFeature.HasValue
+ || HasTrailer.HasValue
+ || HasChapterImages.HasValue
+ || MinCriticRating.HasValue
+ || MinCommunityRating.HasValue
+ || MinParentalRating is not null
+ || MinIndexNumber.HasValue
+ || MinParentAndIndexNumber.HasValue
+ || IndexNumber.HasValue
+ || ParentIndexNumber.HasValue
+ || AiredDuringSeason.HasValue
+ || MinWidth.HasValue
+ || MinHeight.HasValue
+ || MaxWidth.HasValue
+ || MaxHeight.HasValue
+ || MinPremiereDate.HasValue
+ || MaxPremiereDate.HasValue
+ || MinStartDate.HasValue
+ || MaxStartDate.HasValue
+ || MinEndDate.HasValue
+ || MaxEndDate.HasValue
+ || MinDateCreated.HasValue
+ || MinDateLastSaved.HasValue
+ || MinDateLastSavedForUser.HasValue
+ || AdjacentTo.HasValue
+ || !string.IsNullOrEmpty(NameStartsWith)
+ || !string.IsNullOrEmpty(NameStartsWithOrGreater)
+ || !string.IsNullOrEmpty(NameLessThan)
+ || !string.IsNullOrEmpty(NameContains)
+ || !string.IsNullOrEmpty(MinSortName)
+ || !string.IsNullOrEmpty(Name)
+ || !string.IsNullOrEmpty(Person)
+ || !string.IsNullOrEmpty(SearchTerm)
+ || !string.IsNullOrEmpty(Path);
+
public bool Recursive { get; set; }
public int? StartIndex { get; set; }
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index deed3631b8..d5be997b84 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -69,8 +69,14 @@ namespace MediaBrowser.Controller.Entities
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
{
- if (query.Recursive)
+ // The user root holds no items of its own - a plain listing returns the user's
+ // views. But a request carrying any filter is a search across the libraries, so
+ // resolve it through the recursive query path even when Recursive wasn't set;
+ // otherwise the filters would be silently dropped. Recursive is set so the
+ // downstream query (ancestor/top-parent scoping) treats it as a recursive search.
+ if (query.Recursive || query.HasFilters)
{
+ query.Recursive = true;
return QueryRecursive(query);
}