Skip to content

Commit

Permalink
Merge branch 'main' into sebros/net90
Browse files Browse the repository at this point in the history
  • Loading branch information
agriffard authored Nov 9, 2024
2 parents 62bb01f + ebde3ec commit 557775b
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ await _luceneIndexManager.SearchAsync(model.IndexName, async searcher =>
try
{
var parameterizedQuery = JsonNode.Parse(tokenizedContent).AsObject();
var parameterizedQuery = JsonNode.Parse(tokenizedContent, JOptions.Node, JOptions.Document).AsObject();
var luceneTopDocs = await _queryService.SearchAsync(context, parameterizedQuery);
if (luceneTopDocs != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using Fluid;
using Fluid.Values;
Expand Down Expand Up @@ -60,7 +61,7 @@ await _luceneIndexManager.SearchAsync(metadata.Index, async searcher =>
{
var tokenizedContent = await _liquidTemplateManager.RenderStringAsync(metadata.Template, _javaScriptEncoder, parameters.Select(x => new KeyValuePair<string, FluidValue>(x.Key, FluidValue.Create(x.Value, _templateOptions))));
var parameterizedQuery = JsonNode.Parse(tokenizedContent).AsObject();
var parameterizedQuery = JsonNode.Parse(tokenizedContent, JOptions.Node, JOptions.Document).AsObject();
var analyzer = _luceneAnalyzerManager.CreateAnalyzer(await _luceneIndexSettingsService.GetIndexAnalyzerAsync(metadata.Index));
var context = new LuceneQueryContext(searcher, LuceneSettings.DefaultVersion, analyzer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace OrchardCore.Liquid;
/// <summary>
/// This is a placeholder class that allows modules to extend the `HttpContext` property in the current Liquid scope.
/// </summary>
[Obsolete("This class is obsolete and will be removed in a future version.", error: true)]
public class LiquidHttpContextAccessor
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace OrchardCore.Liquid;
/// <summary>
/// This is a placeholder class that allows modules to extend the `HttpRequest` property in the current Liquid scope.
/// </summary>
[Obsolete("This class is obsolete and will be removed in a future version.", error: true)]
public class LiquidRequestAccessor
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,58 +44,9 @@ public void Configure(TemplateOptions options)

options.Scope.SetValue("Environment", new HostingEnvironmentValue(_hostEnvironment));

options.Scope.SetValue("Request", new ObjectValue(new LiquidRequestAccessor()));
options.MemberAccessStrategy.Register<LiquidRequestAccessor, FluidValue>((obj, name, ctx) =>
{
var request = ((LiquidTemplateContext)ctx).Services.GetRequiredService<IHttpContextAccessor>().HttpContext?.Request;
if (request != null)
{
return name switch
{
nameof(HttpRequest.QueryString) => new StringValue(request.QueryString.Value),
nameof(HttpRequest.ContentType) => new StringValue(request.ContentType),
nameof(HttpRequest.ContentLength) => NumberValue.Create(request.ContentLength ?? 0),
nameof(HttpRequest.Cookies) => new ObjectValue(new CookieCollectionWrapper(request.Cookies)),
nameof(HttpRequest.Headers) => new ObjectValue(new HeaderDictionaryWrapper(request.Headers)),
nameof(HttpRequest.Query) => new ObjectValue(new QueryCollection(request.Query.ToDictionary(kv => kv.Key, kv => kv.Value))),
nameof(HttpRequest.Form) => request.HasFormContentType ? (FluidValue)new ObjectValue(request.Form) : NilValue.Instance,
nameof(HttpRequest.Protocol) => new StringValue(request.Protocol),
nameof(HttpRequest.Path) => new StringValue(request.Path.Value),
nameof(HttpRequest.PathBase) => new StringValue(request.PathBase.Value),
nameof(HttpRequest.Host) => new StringValue(request.Host.Value),
nameof(HttpRequest.IsHttps) => BooleanValue.Create(request.IsHttps),
nameof(HttpRequest.Scheme) => new StringValue(request.Scheme),
nameof(HttpRequest.Method) => new StringValue(request.Method),
nameof(HttpRequest.RouteValues) => new ObjectValue(new RouteValueDictionaryWrapper(request.RouteValues)),
// Provides correct escaping to reconstruct a request or redirect URI.
"UriHost" => new StringValue(request.Host.ToUriComponent(), encode: false),
"UriPath" => new StringValue(request.Path.ToUriComponent(), encode: false),
"UriPathBase" => new StringValue(request.PathBase.ToUriComponent(), encode: false),
"UriQueryString" => new StringValue(request.QueryString.ToUriComponent(), encode: false),
_ => NilValue.Instance
};
}
return NilValue.Instance;
});

options.Scope.SetValue("HttpContext", new ObjectValue(new LiquidHttpContextAccessor()));
options.MemberAccessStrategy.Register<LiquidHttpContextAccessor, FluidValue>((obj, name, ctx) =>
{
var httpContext = ((LiquidTemplateContext)ctx).Services.GetRequiredService<IHttpContextAccessor>().HttpContext;
if (httpContext != null)
{
return name switch
{
nameof(HttpContext.Items) => new ObjectValue(new HttpContextItemsWrapper(httpContext.Items)),
_ => NilValue.Instance
};
}
options.Scope.SetValue("Request", new HttpRequestValue());

return NilValue.Instance;
});
options.Scope.SetValue("HttpContext", new HttpContextValue());

options.MemberAccessStrategy.Register<FormCollection, FluidValue>((forms, name) =>
{
Expand All @@ -113,44 +64,4 @@ public void Configure(TemplateOptions options)
options.MemberAccessStrategy.Register<HeaderDictionaryWrapper, string[]>((headers, name) => headers.HeaderDictionary[name].ToArray());
options.MemberAccessStrategy.Register<RouteValueDictionaryWrapper, object>((headers, name) => headers.RouteValueDictionary[name]);
}

private sealed class CookieCollectionWrapper
{
public readonly IRequestCookieCollection RequestCookieCollection;

public CookieCollectionWrapper(IRequestCookieCollection requestCookieCollection)
{
RequestCookieCollection = requestCookieCollection;
}
}

private sealed class HeaderDictionaryWrapper
{
public readonly IHeaderDictionary HeaderDictionary;

public HeaderDictionaryWrapper(IHeaderDictionary headerDictionary)
{
HeaderDictionary = headerDictionary;
}
}

private sealed class HttpContextItemsWrapper
{
public readonly IDictionary<object, object> Items;

public HttpContextItemsWrapper(IDictionary<object, object> items)
{
Items = items;
}
}

private sealed class RouteValueDictionaryWrapper
{
public readonly IReadOnlyDictionary<string, object> RouteValueDictionary;

public RouteValueDictionaryWrapper(IReadOnlyDictionary<string, object> routeValueDictionary)
{
RouteValueDictionary = routeValueDictionary;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Http;

namespace OrchardCore.DisplayManagement.Liquid.Values;

internal sealed class CookieCollectionWrapper
{
public readonly IRequestCookieCollection RequestCookieCollection;

public CookieCollectionWrapper(IRequestCookieCollection requestCookieCollection)
{
RequestCookieCollection = requestCookieCollection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Http;

namespace OrchardCore.DisplayManagement.Liquid.Values;

internal sealed class HeaderDictionaryWrapper
{
public readonly IHeaderDictionary HeaderDictionary;

public HeaderDictionaryWrapper(IHeaderDictionary headerDictionary)
{
HeaderDictionary = headerDictionary;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OrchardCore.DisplayManagement.Liquid.Values;

internal sealed class HttpContextItemsWrapper
{
public readonly IDictionary<object, object> Items;

public HttpContextItemsWrapper(IDictionary<object, object> items)
{
Items = items;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Globalization;
using System.Text.Encodings.Web;
using Fluid;
using Fluid.Values;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Liquid;

namespace OrchardCore.DisplayManagement.Liquid.Values;

internal sealed class HttpContextValue : FluidValue
{
public override FluidValues Type => FluidValues.Object;

public override bool Equals(FluidValue other)
{
if (other is null)
{
return false;
}

return other is HttpContextValue;
}

public override bool ToBooleanValue() => true;

public override decimal ToNumberValue() => 0;

public override object ToObjectValue() => null;

public override string ToStringValue() => "HttpContext";

#pragma warning disable CS0672 // Member overrides obsolete member
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
#pragma warning restore CS0672 // Member overrides obsolete member
=> writer.Write(ToStringValue());

public async override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)

Check failure on line 38 in src/OrchardCore/OrchardCore.DisplayManagement.Liquid/Values/HttpContextValue.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Modifiers are not ordered (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0036)

Check failure on line 38 in src/OrchardCore/OrchardCore.DisplayManagement.Liquid/Values/HttpContextValue.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Modifiers are not ordered (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0036)
=> await writer.WriteAsync(ToStringValue());

public override ValueTask<FluidValue> GetValueAsync(string name, TemplateContext context)
{
var httpContext = GetHttpContext(context);

if (httpContext is null)
{
return new ValueTask<FluidValue>(NilValue.Instance);
}

return name switch
{
nameof(HttpContext.Items) => new ObjectValue(new HttpContextItemsWrapper(httpContext.Items)),
_ => NilValue.Instance
};
}

private static HttpContext GetHttpContext(TemplateContext context)
{
var ctx = context as LiquidTemplateContext
?? throw new InvalidOperationException($"An implementation of '{nameof(LiquidTemplateContext)}' is required");

var httpContextAccessor = ctx.Services.GetRequiredService<IHttpContextAccessor>();

return httpContextAccessor.HttpContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Globalization;
using System.Text.Encodings.Web;
using Fluid;
using Fluid.Values;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Liquid;

namespace OrchardCore.DisplayManagement.Liquid.Values;

internal sealed class HttpRequestValue : FluidValue
{
public override FluidValues Type => FluidValues.Object;

/// <summary>
/// Creates a new instance of a <see cref="HttpRequestValue"/> for the specified HTTP request.
/// </summary>
public override bool Equals(FluidValue other)
{
if (other is null)
{
return false;
}

return other is HttpRequestValue;
}

public override bool ToBooleanValue() => true;

public override decimal ToNumberValue() => 0;

public override object ToObjectValue() => null;

public override string ToStringValue() => "Request";

#pragma warning disable CS0672 // Member overrides obsolete member
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
#pragma warning restore CS0672 // Member overrides obsolete member
=> writer.Write(ToStringValue());

public async override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)

Check failure on line 41 in src/OrchardCore/OrchardCore.DisplayManagement.Liquid/Values/HttpRequestValue.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Modifiers are not ordered (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0036)

Check failure on line 41 in src/OrchardCore/OrchardCore.DisplayManagement.Liquid/Values/HttpRequestValue.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Modifiers are not ordered (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0036)
=> await writer.WriteAsync(ToStringValue());

public override ValueTask<FluidValue> GetValueAsync(string name, TemplateContext context)
{
var request = GetHttpRequest(context);

if (request is null)
{
return new ValueTask<FluidValue>(NilValue.Instance);
}

return name switch
{
nameof(HttpRequest.QueryString) => new StringValue(request.QueryString.Value),
nameof(HttpRequest.ContentType) => new StringValue(request.ContentType),
nameof(HttpRequest.ContentLength) => NumberValue.Create(request.ContentLength ?? 0),
nameof(HttpRequest.Cookies) => new ObjectValue(new CookieCollectionWrapper(request.Cookies)),
nameof(HttpRequest.Headers) => new ObjectValue(new HeaderDictionaryWrapper(request.Headers)),
nameof(HttpRequest.Query) => new ObjectValue(new QueryCollection(request.Query.ToDictionary(kv => kv.Key, kv => kv.Value))),
nameof(HttpRequest.Form) => request.HasFormContentType ? (FluidValue)new ObjectValue(request.Form) : NilValue.Instance,
nameof(HttpRequest.Protocol) => new StringValue(request.Protocol),
nameof(HttpRequest.Path) => new StringValue(request.Path.Value),
nameof(HttpRequest.PathBase) => new StringValue(request.PathBase.Value),
nameof(HttpRequest.Host) => new StringValue(request.Host.Value),
nameof(HttpRequest.IsHttps) => BooleanValue.Create(request.IsHttps),
nameof(HttpRequest.Scheme) => new StringValue(request.Scheme),
nameof(HttpRequest.Method) => new StringValue(request.Method),
nameof(HttpRequest.RouteValues) => new ObjectValue(new RouteValueDictionaryWrapper(request.RouteValues)),

// Provides correct escaping to reconstruct a request or redirect URI.
"UriHost" => new StringValue(request.Host.ToUriComponent(), encode: false),
"UriPath" => new StringValue(request.Path.ToUriComponent(), encode: false),
"UriPathBase" => new StringValue(request.PathBase.ToUriComponent(), encode: false),
"UriQueryString" => new StringValue(request.QueryString.ToUriComponent(), encode: false),
_ => ValueTask.FromResult<FluidValue>(NilValue.Instance)
};
}

private static HttpRequest GetHttpRequest(TemplateContext context)
{
var ctx = context as LiquidTemplateContext
?? throw new InvalidOperationException($"An implementation of '{nameof(LiquidTemplateContext)}' is required");

var httpContext = ctx.Services.GetRequiredService<IHttpContextAccessor>().HttpContext;

return httpContext.Request;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OrchardCore.DisplayManagement.Liquid.Values;

internal sealed class RouteValueDictionaryWrapper
{
public readonly IReadOnlyDictionary<string, object> RouteValueDictionary;

public RouteValueDictionaryWrapper(IReadOnlyDictionary<string, object> routeValueDictionary)
{
RouteValueDictionary = routeValueDictionary;
}
}
22 changes: 22 additions & 0 deletions test/OrchardCore.Tests/Apis/Lucene/LuceneQueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,26 @@ public async Task TwoWildcardQueriesWithBoostHasResults()
Assert.Contains("Orchard", contentItems.ElementAt(3).DisplayText, StringComparison.OrdinalIgnoreCase);
};
}

[Fact]
public async Task LuceneQueryTemplateWithSpecialCharactersShouldNotThrowError()
{
using var context = new LuceneContext();
await context.InitializeAsync();

// Act
var index = "ArticleIndex";
var queryTemplate = "\r\r\n{% assign testVariable = \"48yvsghn194eft8axztaves25h\" %}\n\n{\n \"query\": {\n \"bool\": {\n \"must\": [\n { \"term\" : { \"Content.ContentItem.ContentType\" : \"Article\" } },\n { \"term\": { \"Content.ContentItem.Published\" : \"true\" } },\n ]\n }\n }\n}";

var content = await context.Client.GetAsync($"api/lucene/content?indexName={index}&query={queryTemplate}");
var queryResults = await content.Content.ReadAsAsync<LuceneQueryResults>();

// Assert
Assert.NotNull(queryResults);
Assert.NotEmpty(queryResults.Items);

var contentItems = queryResults.Items.Select(x => JObject.FromObject(x).Deserialize<ContentItem>());

Assert.Contains("Orchard", contentItems.First().DisplayText, StringComparison.OrdinalIgnoreCase);
}
}

0 comments on commit 557775b

Please sign in to comment.