Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
45d3f16
Replace legacy package template with RCL
ronaldbarendse Nov 17, 2023
9637416
Align framework parameter casing with .NET default templates and remo…
ronaldbarendse Nov 17, 2023
f4c1e29
Update Microsoft.ICU.ICU4C.Runtime to 72.1.0.3
ronaldbarendse Nov 17, 2023
65ab158
Merge branch 'release/13.0' into feature/remove-legacy-umbracopackage…
ronaldbarendse Nov 21, 2023
92a8640
Fixed filter queries for numeric and date based filters (#15286)
kjac Nov 27, 2023
88f9079
Cache block item constructors for block based editors (#15264)
kjac Nov 29, 2023
a8dc058
V13: Delivery API composite id handler (#15305)
Zeegaan Nov 29, 2023
b596900
Log meaningful job names and use template based log messages (#15307)
kjac Nov 29, 2023
d088ed8
V13: Webhook all the things (#15161)
warrenbuckley Nov 30, 2023
c0e0e7b
Add HealthCheckCompletedNotification (#15276)
erikjanwestendorp Nov 30, 2023
e7f7492
Configure Angular cookie using defaults from antiforgery options and …
ronaldbarendse Nov 15, 2023
feaac58
localization for webhooks (#15191)
erikjanwestendorp Nov 30, 2023
212b566
Add webhook labels to FR file (#15183)
mikecp Nov 30, 2023
ecc31a3
Added BlockEditorDataConverter method to BlockListPropertyEditorBase …
FullStackChef Nov 30, 2023
71a3452
Added [NotNullWhen(true)] attribute to IPublishedSnapshotAccessor and…
abjerner Oct 1, 2023
8f497c8
V13 RC: Fix regression for external login providers (#15334)
iOvergaard Dec 1, 2023
50baed1
Use version range on project references (#14719)
ronaldbarendse Dec 1, 2023
a4b4107
Expose the Delivery API CLR type (#15150)
vsilvar Dec 1, 2023
55c901e
Merge branch 'v13/dev' into release/13.0
kjac Dec 3, 2023
12a43c2
v13: Update deploy types (add setter to `ArtifactDependency.Checksum`…
ronaldbarendse Dec 4, 2023
212a143
Merge branch 'release/13.0' into feature/remove-legacy-umbracopackage…
ronaldbarendse Dec 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,13 @@
<PropertyGroup>
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
</PropertyGroup>

<!-- Use version range on project references (to limit on major version in generated packages) -->
<Target Name="_GetProjectReferenceVersionRanges" AfterTargets="_GetProjectReferenceVersions">
<ItemGroup>
<_ProjectReferencesWithVersions Condition="'%(ProjectVersion)' != ''">
<ProjectVersion>[%(ProjectVersion), $([MSBuild]::Add($([System.Text.RegularExpressions.Regex]::Match('%(ProjectVersion)', '^\d+').Value), 1)))</ProjectVersion>
</_ProjectReferencesWithVersions>
</ItemGroup>
</Target>
</Project>
189 changes: 15 additions & 174 deletions src/Umbraco.Cms.Api.Delivery/Services/ApiContentQueryProvider.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
using Examine;
using Examine.Lucene.Providers;
using Examine.Lucene.Search;
using Examine.Search;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Api.Delivery.Services.QueryBuilders;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.DeliveryApi;
using Umbraco.Cms.Infrastructure.Examine;
using Umbraco.Extensions;

namespace Umbraco.Cms.Api.Delivery.Services;

Expand All @@ -21,10 +19,10 @@ internal sealed class ApiContentQueryProvider : IApiContentQueryProvider
{
private const string ItemIdFieldName = "itemId";
private readonly IExamineManager _examineManager;
private readonly DeliveryApiSettings _deliveryApiSettings;
private readonly ILogger<ApiContentQueryProvider> _logger;
private readonly string _fallbackGuidValue;
private readonly Dictionary<string, FieldType> _fieldTypes;
private readonly ApiContentQuerySelectorBuilder _selectorBuilder;
private readonly ApiContentQueryFilterBuilder _filterBuilder;
private readonly ApiContentQuerySortBuilder _sortBuilder;

public ApiContentQueryProvider(
IExamineManager examineManager,
Expand All @@ -33,18 +31,20 @@ public ApiContentQueryProvider(
ILogger<ApiContentQueryProvider> logger)
{
_examineManager = examineManager;
_deliveryApiSettings = deliveryApiSettings.Value;
_logger = logger;

// A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string
// It is set to a random guid since this would be highly unlikely to yield any results
_fallbackGuidValue = Guid.NewGuid().ToString("D");

// build a look-up dictionary of field types by field name
_fieldTypes = indexHandlers
var fieldTypes = indexHandlers
.SelectMany(handler => handler.GetFields())
.DistinctBy(field => field.FieldName)
.ToDictionary(field => field.FieldName, field => field.FieldType, StringComparer.InvariantCultureIgnoreCase);

// for the time being we're going to keep these as internal implementation details.
// perhaps later on it will make sense to expose them through the DI.
_selectorBuilder = new ApiContentQuerySelectorBuilder(deliveryApiSettings.Value);
_filterBuilder = new ApiContentQueryFilterBuilder(fieldTypes, _logger);
_sortBuilder = new ApiContentQuerySortBuilder(fieldTypes, _logger);

}

[Obsolete($"Use the {nameof(ExecuteQuery)} method that accepts {nameof(ProtectedAccess)}. Will be removed in V14.")]
Expand Down Expand Up @@ -75,10 +75,9 @@ public PagedModel<Guid> ExecuteQuery(
return new PagedModel<Guid>();
}

IBooleanOperation queryOperation = BuildSelectorOperation(selectorOption, index, culture, protectedAccess, preview);

ApplyFiltering(filterOptions, queryOperation);
ApplySorting(sortOptions, queryOperation);
IBooleanOperation queryOperation = _selectorBuilder.Build(selectorOption, index, culture, protectedAccess, preview);
_filterBuilder.Append(filterOptions, queryOperation);
_sortBuilder.Append(sortOptions, queryOperation);

ISearchResults? results = queryOperation
.SelectField(ItemIdFieldName)
Expand All @@ -102,162 +101,4 @@ public PagedModel<Guid> ExecuteQuery(
{
FieldName = UmbracoExamineFieldNames.CategoryFieldName, Values = new[] { "content" }
};

private IBooleanOperation BuildSelectorOperation(SelectorOption selectorOption, IIndex index, string culture, ProtectedAccess protectedAccess, bool preview)
{
// Needed for enabling leading wildcards searches
BaseLuceneSearcher searcher = index.Searcher as BaseLuceneSearcher ?? throw new InvalidOperationException($"Index searcher must be of type {nameof(BaseLuceneSearcher)}.");

IQuery query = searcher.CreateQuery(
IndexTypes.Content,
BooleanOperation.And,
searcher.LuceneAnalyzer,
new LuceneSearchOptions { AllowLeadingWildcard = true });

IBooleanOperation selectorOperation = selectorOption.Values.Length == 1
? query.Field(selectorOption.FieldName, selectorOption.Values.First())
: query.GroupedOr(new[] { selectorOption.FieldName }, selectorOption.Values);

AddCultureQuery(culture, selectorOperation);

if (_deliveryApiSettings.MemberAuthorizationIsEnabled())
{
AddProtectedAccessQuery(protectedAccess, selectorOperation);
}

// when not fetching for preview, make sure the "published" field is "y"
if (preview is false)
{
selectorOperation.And().Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Published, "y");
}

return selectorOperation;
}

private void AddCultureQuery(string culture, IBooleanOperation selectorOperation) =>
selectorOperation
.And()
.GroupedOr(
// Item culture must be either the requested culture or "none"
new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture },
culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue),
"none");

private void AddProtectedAccessQuery(ProtectedAccess protectedAccess, IBooleanOperation selectorOperation)
{
var protectedAccessValues = new List<string>();
if (protectedAccess.MemberKey is not null)
{
protectedAccessValues.Add($"u:{protectedAccess.MemberKey}");
}

if (protectedAccess.MemberRoles?.Any() is true)
{
protectedAccessValues.AddRange(protectedAccess.MemberRoles.Select(r => $"r:{r}"));
}

if (protectedAccessValues.Any())
{
selectorOperation.And(
inner => inner
.Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n")
.Or(protectedAccessInner => protectedAccessInner
.GroupedOr(
new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.ProtectedAccess },
protectedAccessValues.ToArray())),
BooleanOperation.Or);
}
else
{
selectorOperation.And().Field(UmbracoExamineFieldNames.DeliveryApiContentIndex.Protected, "n");
}
}

private void ApplyFiltering(IList<FilterOption> filterOptions, IBooleanOperation queryOperation)
{
void HandleExact(IQuery query, string fieldName, string[] values)
{
if (values.Length == 1)
{
query.Field(fieldName, values[0]);
}
else
{
query.GroupedOr(new[] { fieldName }, values);
}
}

void HandleContains(IQuery query, string fieldName, string[] values)
{
if (values.Length == 1)
{
// The trailing wildcard is added automatically
query.Field(fieldName, (IExamineValue)new ExamineValue(Examineness.ComplexWildcard, $"*{values[0]}"));
}
else
{
// The trailing wildcard is added automatically
IExamineValue[] examineValues = values
.Select(value => (IExamineValue)new ExamineValue(Examineness.ComplexWildcard, $"*{value}"))
.ToArray();
query.GroupedOr(new[] { fieldName }, examineValues);
}
}

foreach (FilterOption filterOption in filterOptions)
{
var values = filterOption.Values.Any()
? filterOption.Values
: new[] { _fallbackGuidValue };

switch (filterOption.Operator)
{
case FilterOperation.Is:
HandleExact(queryOperation.And(), filterOption.FieldName, values);
break;
case FilterOperation.IsNot:
HandleExact(queryOperation.Not(), filterOption.FieldName, values);
break;
case FilterOperation.Contains:
HandleContains(queryOperation.And(), filterOption.FieldName, values);
break;
case FilterOperation.DoesNotContain:
HandleContains(queryOperation.Not(), filterOption.FieldName, values);
break;
default:
continue;
}
}
}

private void ApplySorting(IList<SortOption> sortOptions, IOrdering ordering)
{
foreach (SortOption sort in sortOptions)
{
if (_fieldTypes.TryGetValue(sort.FieldName, out FieldType fieldType) is false)
{
_logger.LogWarning(
"Sort implementation for field name {FieldName} does not match an index handler implementation, cannot resolve field type.",
sort.FieldName);
continue;
}

SortType sortType = fieldType switch
{
FieldType.Number => SortType.Int,
FieldType.Date => SortType.Long,
FieldType.StringRaw => SortType.String,
FieldType.StringAnalyzed => SortType.String,
FieldType.StringSortable => SortType.String,
_ => throw new ArgumentOutOfRangeException(nameof(fieldType))
};

ordering = sort.Direction switch
{
Direction.Ascending => ordering.OrderBy(new SortableField(sort.FieldName, sortType)),
Direction.Descending => ordering.OrderByDescending(new SortableField(sort.FieldName, sortType)),
_ => ordering
};
}
}
}
Loading