Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add form originated http redirect task #14610

Merged
merged 12 commits into from
Nov 15, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using OrchardCore.Forms.Activities.ViewModels;
using OrchardCore.Workflows.Display;

namespace OrchardCore.Forms.Activities.Drivers;

public class HttpRedirectToFormLocationTaskDisplayDriver : ActivityDisplayDriver<HttpRedirectToFormLocationTask, HttpRedirectToFormLocationTaskViewModel>
{
protected override void EditActivity(HttpRedirectToFormLocationTask activity, HttpRedirectToFormLocationTaskViewModel model)
{
model.FormLocationKey = activity.FormLocationKey;
}

protected override void UpdateActivity(HttpRedirectToFormLocationTaskViewModel model, HttpRedirectToFormLocationTask activity)
{
activity.FormLocationKey = model.FormLocationKey?.Trim();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Localization;
using OrchardCore.Workflows;
using OrchardCore.Workflows.Abstractions.Models;
using OrchardCore.Workflows.Activities;
using OrchardCore.Workflows.Models;

namespace OrchardCore.Forms.Activities;

public class HttpRedirectToFormLocationTask : TaskActivity<HttpRedirectToFormLocationTask>
{
private readonly IHttpContextAccessor _httpContextAccessor;
protected readonly IStringLocalizer S;

public HttpRedirectToFormLocationTask(
IHttpContextAccessor httpContextAccessor,
IStringLocalizer<HttpRedirectToFormLocationTask> stringLocalizer
)
{
_httpContextAccessor = httpContextAccessor;
S = stringLocalizer;
}

public override LocalizedString DisplayText => S["Http Redirect To Form Location Task"];

public override LocalizedString Category => S["HTTP"];

public override IEnumerable<Outcome> GetPossibleOutcomes(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
=> Outcomes(S["Done"], S["Failed"]);

public string FormLocationKey
{
get => GetProperty<string>();
set => SetProperty(value);
}

public override Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
if (workflowContext.Output.TryGetValue(WorkflowConstants.HttpFormLocationOutputKeyName, out var obj)
&& obj is Dictionary<string, string> formLocations)
{
// if no custom location-key was provided, we use empty string as the default key.
var location = FormLocationKey ?? string.Empty;

if (formLocations.TryGetValue(location, out var path))
{
_httpContextAccessor.HttpContext.Items[WorkflowConstants.FormOriginatedLocationItemsKey] = path;

return Task.FromResult(Outcomes("Done"));
}
}

return Task.FromResult(Outcomes("Failed"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace OrchardCore.Forms.Activities.ViewModels;

public class HttpRedirectToFormLocationTaskViewModel
{
public string FormLocationKey { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public override IDisplayResult Edit(FormPart part)
m.WorkflowTypeId = part.WorkflowTypeId;
m.EncType = part.EncType;
m.EnableAntiForgeryToken = part.EnableAntiForgeryToken;
m.SaveFormLocation = part.SaveFormLocation;
});
}

Expand All @@ -32,6 +33,7 @@ public async override Task<IDisplayResult> UpdateAsync(FormPart part, IUpdateMod
part.WorkflowTypeId = viewModel.WorkflowTypeId;
part.EncType = viewModel.EncType;
part.EnableAntiForgeryToken = viewModel.EnableAntiForgeryToken;
part.SaveFormLocation = viewModel.SaveFormLocation;
}

return Edit(part);
Expand Down
9 changes: 9 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Forms/Models/FormPart.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
using System.ComponentModel;
using OrchardCore.ContentManagement;

namespace OrchardCore.Forms.Models
{
public class FormPart : ContentPart
{
public string Action { get; set; }

public string Method { get; set; }

public string WorkflowTypeId { get; set; }

public string EncType { get; set; }

[DefaultValue(true)]
public bool EnableAntiForgeryToken { get; set; } = true;

[DefaultValue(true)]
public bool SaveFormLocation { get; set; } = true;
}
}
12 changes: 12 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Forms/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Display.ContentDisplay;
using OrchardCore.Data.Migration;
using OrchardCore.Forms.Activities;
using OrchardCore.Forms.Activities.Drivers;
using OrchardCore.Forms.Drivers;
using OrchardCore.Forms.Filters;
using OrchardCore.Forms.Models;
using OrchardCore.Modules;
using OrchardCore.Workflows.Helpers;

namespace OrchardCore.Forms
{
Expand Down Expand Up @@ -75,4 +78,13 @@ public override void ConfigureServices(IServiceCollection services)
services.AddDataMigration<Migrations>();
}
}

[RequireFeatures("OrchardCore.Workflows")]
public class WorkflowStartup : StartupBase
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddActivity<HttpRedirectToFormLocationTask, HttpRedirectToFormLocationTaskDisplayDriver>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ namespace OrchardCore.Forms.ViewModels
public class FormPartEditViewModel
{
public string Action { get; set; }

public string Method { get; set; }

public string WorkflowTypeId { get; set; }

public string EncType { get; set; }

public bool EnableAntiForgeryToken { get; set; } = true;

public bool SaveFormLocation { get; set; } = true;
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
@using Microsoft.AspNetCore.Html
@using Microsoft.AspNetCore.Html
@using OrchardCore.ContentManagement
@using OrchardCore.Forms.Models
@using OrchardCore.Mvc.Utilities
@using OrchardCore.Workflows

@{
var formPart = ((ContentItem)Model.ContentItem).As<FormPart>();
string encType = formPart.Method == "POST" ? formPart.EncType : string.Empty;
var formContentTypeClassName = "form-" + ((string)Model.ContentItem.ContentType).HtmlClassify();

var elementId = ((ContentItem)Model.ContentItem).As<FormElementPart>()?.Id;
string formId = string.IsNullOrWhiteSpace(elementId) ? $"form_{Model.ContentItem.ContentItemId}" : elementId;
var elementId = ((ContentItem)Model.ContentItem).As<FormElementPart>()?.Id;
string formId = string.IsNullOrWhiteSpace(elementId) ? $"form_{Model.ContentItem.ContentItemId}" : elementId;

}

<form id="@formId" action="@formPart.Action" method="@formPart.Method" enctype="@encType" class="form-content @formContentTypeClassName">
<form id="@formId" action="@formPart.Action" method="@formPart.Method" enctype="@encType" class="form-content @formContentTypeClassName">
@if (formPart.EnableAntiForgeryToken)
{
@Html.AntiForgeryToken()
}

@if (formPart.SaveFormLocation)
{
<input type="hidden" name="@WorkflowConstants.FormLocationKeyInputName" value="@ViewContext.HttpContext.Request.Path" />
}

@await DisplayAsync((IHtmlContent)Model.Metadata.ChildContent)
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<label asp-for="EncType" class="@Orchard.GetLabelClasses()">@T["EncType"]</label>
<div class="@Orchard.GetEndClasses()">
<select asp-for="EncType" class="form-select content-preview-select">
<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded </option>
<option value="application/x-www-form-urlencoded">application/x-www-form-urlencoded </option>
<option value="multipart/form-data">multipart/form-data</option>
<option value="text/plain">text/plain</option>
</select>
Expand All @@ -42,3 +42,13 @@
</div>
</div>
</div>

<div class="@Orchard.GetWrapperClasses()">
<div class="@Orchard.GetEndClasses(true)">
<div class="form-check">
<input asp-for="SaveFormLocation" class="form-check-input" />
<label asp-for="SaveFormLocation" class="form-check-label">@T["Save Form Location"]</label>
<span class="hint">@T["Check in order to store the current form location. This stored location can later be used to return the user to their initial location."]</span>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@using OrchardCore.Forms.Activities

@model ActivityViewModel<HttpRedirectToFormLocationTask>

<header>
<h4><i class="fa-solid fa-globe" aria-hidden="true"></i>@Model.Activity.GetTitleOrDefault(() => T["HTTP Redirect To Form Location"])</h4>
</header>

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@using OrchardCore.Forms.Activities.ViewModels

@model HttpRedirectToFormLocationTaskViewModel

<div class="mb-3" asp-validation-class-for="FormLocationKey">
<label asp-for="FormLocationKey">@T["Form Location Key"]</label>
<input type="text" asp-for="FormLocationKey" class="form-control" />
<span asp-validation-for="FormLocationKey"></span>
<span class="hint">@T["This value needs to correspond with the Form Location Key value utilized in the HTTP request event settings. Leave blank if the workflow handles a single form event."]</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h4 class="card-title"><i class="fa-solid fa-globe" aria-hidden="true"></i>@T["HTTP Redirect To Form Location"]</h4>
<p>@T["Redirect the user agent to the original location of the form."]</p>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Localization;
using OrchardCore.Workflows.Abstractions.Models;
Expand Down Expand Up @@ -52,6 +53,12 @@ public int TokenLifeSpan
set => SetProperty(value);
}

public string FormLocationKey
{
get => GetProperty<string>();
set => SetProperty(value);
}

public override bool CanExecute(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
var httpContext = _httpContextAccessor.HttpContext;
Expand All @@ -61,14 +68,43 @@ public override bool CanExecute(WorkflowExecutionContext workflowContext, Activi
return isMatch;
}

public override IEnumerable<Outcome> GetPossibleOutcomes(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
public override Task<ActivityExecutionResult> ExecuteAsync(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
{
return Outcomes(S["Done"]);
if (_httpContextAccessor.HttpContext.Request.HasFormContentType
&& _httpContextAccessor.HttpContext.Request.Form.TryGetValue(WorkflowConstants.FormLocationKeyInputName, out var value)
&& !string.IsNullOrWhiteSpace(value))
{
if (!workflowContext.Output.TryGetValue(WorkflowConstants.HttpFormLocationOutputKeyName, out var obj)
|| obj is not Dictionary<string, string> formLocation)
{
formLocation = new Dictionary<string, string>();
}

// if no custom location-key was provided, we use empty string as the default key.
var location = FormLocationKey ?? string.Empty;

formLocation[location] = GetLocationUrl(value);

workflowContext.Output[WorkflowConstants.HttpFormLocationOutputKeyName] = formLocation;
}

return Task.FromResult(Outcomes("Done"));
}

public override IEnumerable<Outcome> GetPossibleOutcomes(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
=> Outcomes(S["Done"]);

public override ActivityExecutionResult Resume(WorkflowExecutionContext workflowContext, ActivityContext activityContext)
=> Outcomes("Done");

private static string GetLocationUrl(string value)
{
return Outcomes("Done");
if (value.StartsWith('/'))
{
return "~" + value;
}

return value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,11 @@ private IActionResult GetWorkflowActionResult()
return new EmptyResult();
}

if (HttpContext.Items.TryGetValue(WorkflowConstants.FormOriginatedLocationItemsKey, out var value))
{
return Redirect(value.ToString().ToUriComponents());
}

return Accepted();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ protected override void EditActivity(HttpRequestEvent activity, HttpRequestEvent
model.Url = activity.Url;
model.ValidateAntiforgeryToken = activity.ValidateAntiforgeryToken;
model.TokenLifeSpan = activity.TokenLifeSpan;
model.FormLocationKey = activity.FormLocationKey;
}

protected override void UpdateActivity(HttpRequestEventViewModel model, HttpRequestEvent activity)
Expand All @@ -20,6 +21,7 @@ protected override void UpdateActivity(HttpRequestEventViewModel model, HttpRequ
activity.Url = model.Url?.Trim();
activity.ValidateAntiforgeryToken = model.ValidateAntiforgeryToken;
activity.TokenLifeSpan = model.TokenLifeSpan;
activity.FormLocationKey = model.FormLocationKey?.Trim();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,27 @@ namespace OrchardCore.Workflows.Http.ViewModels
{
public class HttpRequestEventViewModel
{
private static readonly IEnumerable<string> _availableHttpMethods = new[]
{
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS"
};

public string HttpMethod { get; set; }

public string Url { get; set; }

public bool ValidateAntiforgeryToken { get; set; }

public int TokenLifeSpan { get; set; }

public string FormLocationKey { get; set; }

public static IList<SelectListItem> GetAvailableHttpMethods()
{
var availableHttpMethods = new[] { "GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS" };
return availableHttpMethods.Select(x => new SelectListItem { Text = x, Value = x }).ToList();
}
=> _availableHttpMethods.Select(x => new SelectListItem { Text = x, Value = x }).ToList();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,13 @@
<span class="hint">@T["Number of days to expire the token. 0 for the token to never expire"]</span>
</div>


<div class="mb-3" asp-validation-class-for="FormLocationKey">
<label asp-for="FormLocationKey">@T["Form Location"]</label>
<input type="text" asp-for="FormLocationKey" class="form-control" />
<span asp-validation-for="FormLocationKey"></span>
<span class="hint">@T["This key serves to differentiate the current form's location. Leave blank if the workflow handles a single form event."]</span>
</div>

<script depends-on="jQuery" asp-src="~/OrchardCore.Workflows/Scripts/orchard.http-request-event-editor.min.js" debug-src="~/OrchardCore.Workflows/Scripts/orchard.http-request-event-editor.js" at="Foot"></script>
<script asp-src="~/OrchardCore.Workflows/Scripts/crossbrowserclipboardcopy.min.js" debug-src="~/OrchardCore.Workflows/Scripts/crossbrowserclipboardcopy.js" at="Foot"></script>
Loading