Skip to content

Commit

Permalink
Validate JavaScript condition in Layer Rule on save (#12968)
Browse files Browse the repository at this point in the history
Co-authored-by: Zoltán Lehóczky <[email protected]>
  • Loading branch information
sobotama and Piedone authored Apr 24, 2024
1 parent e9fea1f commit 9a39f30
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
using System;
using System.Threading.Tasks;
using Esprima;
using Jint.Runtime;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Localization;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.ModelBinding;
using OrchardCore.DisplayManagement.Notify;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Mvc.ModelBinding;
using OrchardCore.Rules.Models;
using OrchardCore.Rules.Services;
using OrchardCore.Rules.ViewModels;

namespace OrchardCore.Rules.Drivers
{
public class JavascriptConditionDisplayDriver : DisplayDriver<Condition, JavascriptCondition>
{
private readonly IHtmlLocalizer H;
private readonly IStringLocalizer S;
private readonly INotifier _notifier;
private readonly JavascriptConditionEvaluator _evaluator;

public JavascriptConditionDisplayDriver(
IHtmlLocalizer<JavascriptConditionDisplayDriver> htmlLocalizer,
IStringLocalizer<JavascriptConditionDisplayDriver> stringLocalizer,
JavascriptConditionEvaluator evaluator,
INotifier notifier)
{
H = htmlLocalizer;
S = stringLocalizer;
_evaluator = evaluator;
_notifier = notifier;
}

public override IDisplayResult Display(JavascriptCondition condition)
{
return
Expand All @@ -32,8 +57,40 @@ public override async Task<IDisplayResult> UpdateAsync(JavascriptCondition condi
var model = new JavascriptConditionViewModel();
await updater.TryUpdateModelAsync(model, Prefix);

// TODO is empty.
condition.Script = model.Script;
// CodeMirror hides the textarea which displays the error when updater.ModelState.AddModelError() is used,
// that's why a notifier is used to show validation errors.
if (string.IsNullOrWhiteSpace(model.Script))
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["Please provide a script."]);
await _notifier.ErrorAsync(H["Please provide a script."]);
return Edit(condition);
}

try
{
_ = await _evaluator.EvaluateAsync(new()
{
ConditionId = condition.ConditionId,
Name = condition.Name,
Script = model.Script
});
condition.Script = model.Script;
}
catch (ParserException ex) // Invalid syntax
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["The script couldn't be parsed. Details: {0}", ex.Message]);
await _notifier.ErrorAsync(H["The script couldn't be parsed. Details: {0}", ex.Message]);
}
catch (JavaScriptException ex) // Evaluation threw an Error
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["JavaScript evaluation resulted in an exception. Details: {0}", ex.Message]);
await _notifier.ErrorAsync(H["JavaScript evaluation resulted in an exception. Details: {0}", ex.Message]);
}
catch (Exception ex) when (ex is InvalidCastException or FormatException) // Evaluation completes successfully, but the result cannot be converted to Boolean
{
updater.ModelState.AddModelError(Prefix, nameof(model.Script), S["The script evaluation failed. Details: {0}", ex.Message]);
await _notifier.ErrorAsync(H["The script evaluation failed. Details: {0}", ex.Message]);
}

return Edit(condition);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Jint" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\OrchardCore\OrchardCore.ContentManagement.Display\OrchardCore.ContentManagement.Display.csproj" />
<ProjectReference Include="..\..\OrchardCore\OrchardCore.DisplayManagement\OrchardCore.DisplayManagement.csproj" />
Expand Down

0 comments on commit 9a39f30

Please sign in to comment.