From 6772f539099eba7a256ac2832d3a9c53bb65c868 Mon Sep 17 00:00:00 2001 From: Michael Petrinolis Date: Thu, 14 Nov 2024 18:16:37 +0200 Subject: [PATCH] Fix JsonDynamicObject usage in Liquid (#17015) Co-authored-by: Sebastien Ros --- .../OrchardCore.Liquid/Startup.cs | 9 ++ .../TemplateOptionsConfigurations.cs | 1 + .../DisplayManagement/LiquidTests.cs | 98 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/src/OrchardCore.Modules/OrchardCore.Liquid/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Liquid/Startup.cs index 2491270ca6a..0da12b0b4ad 100644 --- a/src/OrchardCore.Modules/OrchardCore.Liquid/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Liquid/Startup.cs @@ -1,3 +1,7 @@ +using System.Globalization; +using System.Numerics; +using System.Text.Json; +using System; using System.Text.Json.Dynamic; using System.Text.Json.Nodes; using Fluid; @@ -49,6 +53,9 @@ public override void ConfigureServices(IServiceCollection services) // When a property of a 'JsonObject' value is accessed, try to look into its properties. options.MemberAccessStrategy.Register((source, name) => source[name]); + // When a property of a 'JsonDynamicObject' value is accessed, try to look into its properties. + options.MemberAccessStrategy.Register((json, name) => json[name]); + // Convert JToken to FluidValue options.ValueConverters.Add(x => { @@ -57,8 +64,10 @@ public override void ConfigureServices(IServiceCollection services) JsonObject o => new ObjectValue(o), JsonDynamicObject o => new ObjectValue((JsonObject)o), JsonValue o => o.GetObjectValue(), + JsonDynamicValue o => ((JsonValue)(o.Node)).GetObjectValue(), DateTime d => new ObjectValue(d), _ => null + }; }); diff --git a/src/OrchardCore/OrchardCore.DisplayManagement.Liquid/TemplateOptionsConfigurations.cs b/src/OrchardCore/OrchardCore.DisplayManagement.Liquid/TemplateOptionsConfigurations.cs index bb9f2a639ae..4fb69db6bb9 100644 --- a/src/OrchardCore/OrchardCore.DisplayManagement.Liquid/TemplateOptionsConfigurations.cs +++ b/src/OrchardCore/OrchardCore.DisplayManagement.Liquid/TemplateOptionsConfigurations.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Text.Json.Dynamic; using Fluid; using Fluid.Values; using Microsoft.AspNetCore.Html; diff --git a/test/OrchardCore.Tests/DisplayManagement/LiquidTests.cs b/test/OrchardCore.Tests/DisplayManagement/LiquidTests.cs index 1653d73d35e..7a831070679 100644 --- a/test/OrchardCore.Tests/DisplayManagement/LiquidTests.cs +++ b/test/OrchardCore.Tests/DisplayManagement/LiquidTests.cs @@ -110,6 +110,103 @@ await context.UsingTenantScopeAsync(async scope => }); } + [Fact] + public async Task SortingContentItems_ShouldSortTheArrayOnIntegerField() + { + var context = new SiteContext(); + await context.InitializeAsync(); + await context.UsingTenantScopeAsync(async scope => + { + var template = """ + unsorted:{% for action in Model %}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %} + {% assign hotActions = Model | sort: "Content.HotActionsPart.Order.Value" %} + sorted:{% for action in hotActions %}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %} + """; + + var json = JConvert.SerializeObject(FakeContentItems); + + var testModel = JConvert.DeserializeObject(json); + + var liquidTemplateManager = scope.ServiceProvider.GetRequiredService(); + var result = await liquidTemplateManager.RenderStringAsync(template, + NullEncoder.Default, + testModel); + + Assert.Equal("unsorted:302040605010sorted:102030405060", result.ReplaceLineEndings("")); + }); + } + + [Fact] + public async Task FilteringContentItems_ShouldFilterTheArrayOnBooleanField() + { + var context = new SiteContext(); + await context.InitializeAsync(); + await context.UsingTenantScopeAsync(async scope => + { + var template = """ + total:{{Model | size}} + {% assign hotActions = Model | where: "Content.HotActionsPart.AddtoHotActionsMenu.Value", true %} + filtered:{{ hotActions | size }} + """; + + var json = JConvert.SerializeObject(FakeContentItems); + + var testModel = JConvert.DeserializeObject(json); + + var liquidTemplateManager = scope.ServiceProvider.GetRequiredService(); + var result = await liquidTemplateManager.RenderStringAsync(template, + NullEncoder.Default, + testModel); + + Assert.Equal("total:6filtered:5", result.ReplaceLineEndings("")); + }); + } + + [Fact] + public async Task FilteringAndSortingContentItems_ShouldFilterAndSortTheArrayOnContentFields() + { + var context = new SiteContext(); + await context.InitializeAsync(); + await context.UsingTenantScopeAsync(async scope => + { + var template = """ + original:{% for action in Model%}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %} + {% assign hotActions = Model | where: "Content.HotActionsPart.AddtoHotActionsMenu.Value", true | sort: "Content.HotActionsPart.Order.Value" %} + filtered_sorted:{% for action in hotActions %}{{ action.Content.HotActionsPart.Order.Value }}{% endfor %} + """; + + var json = JConvert.SerializeObject(FakeContentItems); + + var testModel = JConvert.DeserializeObject(json); + + var liquidTemplateManager = scope.ServiceProvider.GetRequiredService(); + var result = await liquidTemplateManager.RenderStringAsync(template, + NullEncoder.Default, + testModel); + + Assert.Equal("original:302040605010filtered_sorted:1030405060", result.ReplaceLineEndings("")); + }); + } + + + public static ContentItem[] FakeContentItems => new[] { "30_true", "20_false", "40_true", "60_true", "50_true", "10_true" }.Select(x => CreateFakeContentItem(decimal.Parse(x.Split('_')[0]), x.Split('_')[1] == "true")).ToArray(); + + public static ContentItem CreateFakeContentItem(decimal order, bool addtoHotActionsMenu) + { + var contentItem = new ContentItem(); + contentItem.GetOrCreate("HotActionsPart"); + contentItem.Alter("HotActionsPart", x => + { + x.GetOrCreate("AddtoHotActionsMenu"); + x.Alter("AddtoHotActionsMenu", f => f.Value = addtoHotActionsMenu); + + x.GetOrCreate("Order"); + x.Alter("Order", f => f.Value = order); + + }); + return contentItem; + } + public class MyPart : ContentPart { public string Text { get; set; } @@ -119,4 +216,5 @@ public class MyField : ContentField { public int Value { get; set; } } + }