aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs')
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs140
1 files changed, 129 insertions, 11 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs
index 0f74847061..05ff720ddf 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs
@@ -429,6 +429,17 @@ public sealed partial class BaseItemRepository
}
/// <summary>
+ /// Checks whether the user restricts access to items by parental rating or tags.
+ /// </summary>
+ /// <param name="filter">The query filter.</param>
+ /// <returns><c>true</c> if the query carries parental restrictions.</returns>
+ private static bool RequiresParentalRestrictions(InternalItemsQuery filter)
+ => filter.IncludeInheritedTags.Length > 0
+ || filter.ExcludeInheritedTags.Length > 0
+ || filter.MaxParentalRating is not null
+ || filter.BlockUnratedItems.Length > 0;
+
+ /// <summary>
/// Applies user access filtering to a query.
/// Includes TopParentIds, parental rating, and tag filtering.
/// </summary>
@@ -438,13 +449,127 @@ public sealed partial class BaseItemRepository
IQueryable<BaseItemEntity> baseQuery,
InternalItemsQuery filter)
{
- // Apply TopParentIds filtering (library folder access)
- if (filter.TopParentIds.Length > 0)
+ baseQuery = ApplyTopParentFiltering(context, baseQuery, filter);
+
+ baseQuery = ApplyParentalRestrictions(context, baseQuery, filter);
+
+ // Exclude alternate versions (have PrimaryVersionId set) and owned non-extra items.
+ // Extras (trailers, etc.) have OwnerId set but also have ExtraType set — keep those.
+ if (!filter.IncludeOwnedItems)
{
- var topParentIds = filter.TopParentIds;
- baseQuery = baseQuery.Where(e => topParentIds.Contains(e.TopParentId!.Value));
+ baseQuery = baseQuery.Where(e => e.PrimaryVersionId == null && (e.OwnerId == null || e.ExtraType != null));
}
+ return baseQuery;
+ }
+
+ /// <summary>
+ /// Restricts a query to the libraries the user may open, exempting requested by-name items.
+ /// </summary>
+ /// <param name="context">The database context.</param>
+ /// <param name="baseQuery">The query to filter.</param>
+ /// <param name="filter">The query filter.</param>
+ /// <returns>The filtered query.</returns>
+ private IQueryable<BaseItemEntity> ApplyTopParentFiltering(
+ JellyfinDbContext context,
+ IQueryable<BaseItemEntity> baseQuery,
+ InternalItemsQuery filter)
+ {
+ var queryTopParentIds = filter.TopParentIds;
+ if (queryTopParentIds.Length == 0)
+ {
+ return baseQuery;
+ }
+
+ var exemptedItemByNameTypes = GetExemptedItemByNameTypes(filter);
+ if (exemptedItemByNameTypes.Count == 0)
+ {
+ return baseQuery.WhereOneOrMany(queryTopParentIds, e => e.TopParentId!.Value);
+ }
+
+ baseQuery = baseQuery.Where(e => exemptedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w == e.TopParentId!.Value));
+ if (filter.UserHasContentRestrictions)
+ {
+ baseQuery = ApplyItemByNameAccessFiltering(baseQuery, context, filter, exemptedItemByNameTypes, queryTopParentIds);
+ }
+
+ return baseQuery;
+ }
+
+ /// <summary>
+ /// Returns the by-name types a query asks for, which carry no TopParentId to filter on.
+ /// </summary>
+ /// <param name="filter">The query filter.</param>
+ /// <returns>The type names exempt from library filtering.</returns>
+ private List<string> GetExemptedItemByNameTypes(InternalItemsQuery filter)
+ {
+ var includedItemByNameTypes = GetItemByNameTypesInQuery(filter);
+ if ((filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0)
+ {
+ return includedItemByNameTypes;
+ }
+
+ return _itemByNameKinds.Where(filter.IncludeItemTypes.Contains).Select(e => _itemTypeLookup.BaseItemKindNames[e]!).ToList();
+ }
+
+ /// <summary>
+ /// Keeps a by-name row only when at least one item behind its name is reachable for the user.
+ /// </summary>
+ /// <param name="baseQuery">The query to filter.</param>
+ /// <param name="context">The database context.</param>
+ /// <param name="filter">The query filter.</param>
+ /// <param name="itemByNameTypes">The exempted by-name type names.</param>
+ /// <param name="topParentIds">The libraries the user may open.</param>
+ /// <returns>The filtered query.</returns>
+ private IQueryable<BaseItemEntity> ApplyItemByNameAccessFiltering(
+ IQueryable<BaseItemEntity> baseQuery,
+ JellyfinDbContext context,
+ InternalItemsQuery filter,
+ IReadOnlyList<string> itemByNameTypes,
+ Guid[] topParentIds)
+ {
+ // IncludeOwnedItems: a credit on an alternate version of a reachable movie still counts.
+ var accessibleItems = ApplyAccessFiltering(
+ context,
+ context.BaseItems.AsNoTracking(),
+ new InternalItemsQuery(filter.User) { TopParentIds = topParentIds, IncludeOwnedItems = true });
+
+ var personType = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
+ if (itemByNameTypes.Contains(personType))
+ {
+ baseQuery = baseQuery.Where(e => e.Type != personType
+ || context.Peoples.Any(p => p.Name == e.Name
+ && context.PeopleBaseItemMap.Any(m => m.PeopleId == p.Id && accessibleItems.Any(i => i.Id == m.ItemId))));
+ }
+
+ foreach (var (kind, valueTypes) in _itemByNameValueTypes)
+ {
+ var typeName = _itemTypeLookup.BaseItemKindNames[kind];
+ if (!itemByNameTypes.Contains(typeName))
+ {
+ continue;
+ }
+
+ baseQuery = baseQuery.Where(e => e.Type != typeName
+ || context.ItemValues.Any(v => valueTypes.Contains(v.Type) && v.CleanValue == e.CleanName
+ && context.ItemValuesMap.Any(m => m.ItemValueId == v.ItemValueId && accessibleItems.Any(i => i.Id == m.ItemId))));
+ }
+
+ return baseQuery;
+ }
+
+ /// <summary>
+ /// Applies the user's parental rating and tag restrictions to a query.
+ /// </summary>
+ /// <param name="context">The database context.</param>
+ /// <param name="baseQuery">The query to filter.</param>
+ /// <param name="filter">The query filter.</param>
+ /// <returns>The filtered query.</returns>
+ private IQueryable<BaseItemEntity> ApplyParentalRestrictions(
+ JellyfinDbContext context,
+ IQueryable<BaseItemEntity> baseQuery,
+ InternalItemsQuery filter)
+ {
// Apply parental rating filtering
if (filter.MaxParentalRating is not null)
{
@@ -495,13 +620,6 @@ public sealed partial class BaseItemRepository
|| e.Type == personTypeName);
}
- // Exclude alternate versions (have PrimaryVersionId set) and owned non-extra items.
- // Extras (trailers, etc.) have OwnerId set but also have ExtraType set — keep those.
- if (!filter.IncludeOwnedItems)
- {
- baseQuery = baseQuery.Where(e => e.PrimaryVersionId == null && (e.OwnerId == null || e.ExtraType != null));
- }
-
return baseQuery;
}