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

Form Builder #4137

Merged
merged 49 commits into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e329368
wip
Kukks Aug 3, 2022
5de665d
Cleanups
dennisreimann Sep 23, 2022
6bd6a04
UI updates
dennisreimann Sep 23, 2022
1653750
Update UIFormsController.cs
dennisreimann Oct 20, 2022
cec83ab
Make predefined forms usable statically
Kukks Oct 30, 2022
3513e60
Add support for pos app + forms
Kukks Nov 13, 2022
0c3b345
pay request form rough support
Kukks Nov 13, 2022
383520c
invoice form through receipt page
Kukks Nov 15, 2022
44b0add
Display form name in inherit from store setting
dennisreimann Nov 16, 2022
b3bc4e5
Do not request additional forms on invoice from pay request
Kukks Nov 18, 2022
469ff5b
fix up code
Kukks Nov 18, 2022
6747f91
move checkoutform id in checkout appearance outside of checkotu v2 to…
Kukks Nov 19, 2022
33c92c1
general fixes for form system
Kukks Nov 20, 2022
d8094e9
fix pav bug
Kukks Nov 21, 2022
bc65250
UI updates
dennisreimann Nov 21, 2022
3769339
Fix warnings in Form builder (#4331)
jesterhodl Nov 22, 2022
b89918f
Fix: If reverse proxy wasn't well configured, and error message shoul…
NicolasDorier Nov 21, 2022
a056e9b
fix monero issue
Kukks Nov 21, 2022
86361d0
Server Settings: Update Policies page (#4326)
dennisreimann Nov 22, 2022
fa2a3dd
Change confirmed to settled. (#4328)
ndeet Nov 22, 2022
bb9c098
POS: Fix null pointer
dennisreimann Nov 21, 2022
95c6817
Add documentation link to plugins (#4329)
NicolasDorier Nov 22, 2022
f0f0918
Fix flaky test (#4330)
NicolasDorier Nov 22, 2022
6f54775
Remove invoice and store level form
Kukks Nov 22, 2022
399fbe2
add form test
Kukks Nov 22, 2022
4e828f4
Merge remote-tracking branch 'origin/master' into form_builder
Kukks Nov 22, 2022
e0f15c8
fix migration for forms
Kukks Nov 22, 2022
f671f8f
fix
Kukks Nov 23, 2022
2fe2efc
make pay request form submission redirect to invoice
Kukks Nov 23, 2022
426a65d
Merge remote-tracking branch 'origin/master' into form_builder
Kukks Nov 24, 2022
d65835b
Refactor FormQuery to only be able to query single store and single form
NicolasDorier Nov 24, 2022
ec35b75
Put the Authorize at controller level on UIForms
NicolasDorier Nov 24, 2022
8195acb
Fix warnings
NicolasDorier Nov 24, 2022
4e8126a
Fix ef request
NicolasDorier Nov 24, 2022
15ab7c0
Fix query to forms, ensure no permission bypass
NicolasDorier Nov 24, 2022
50d08de
Fix modify
NicolasDorier Nov 24, 2022
9c88c53
Remove storeId from step form
NicolasDorier Nov 24, 2022
3cb7c64
Remove useless storeId parameter
NicolasDorier Nov 24, 2022
3e1511c
Merge branch 'master' into form_builder
dennisreimann Nov 24, 2022
7218c30
Hide custom form feature in UI
dennisreimann Nov 24, 2022
ad17234
Minor cleanups
dennisreimann Nov 24, 2022
dcade8b
Remove custom form options from select for now
dennisreimann Nov 24, 2022
82a6906
More minor syntax cleanups
dennisreimann Nov 24, 2022
713b87d
Update test
dennisreimann Nov 24, 2022
8b7b772
Add index - needs migration
dennisreimann Nov 24, 2022
4b95264
Refactoring: Use PostRedirect instead of TempData for data transfer
dennisreimann Nov 24, 2022
71c2bff
Remove untested and unfinished code
NicolasDorier Nov 25, 2022
315a771
formResponse should be a JObject, not a string
NicolasDorier Nov 25, 2022
13cb895
Fix case for Form type
NicolasDorier Nov 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 17 additions & 18 deletions BTCPayServer.Abstractions/Form/Field.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace BTCPayServer.Abstractions.Form;

public abstract class Field
public class Field
{
// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
public string Type;

// The name of the HTML5 node. Should be used as the key for the posted data.
public string Name;
// The translated label of the field.
public string Label;

// HTML5 compatible type string like "text", "textarea", "email", "password", etc. Each type is a class and may contain more fields (i.e. "select" would have options).
public string Type;

// The value field is what is currently in the DB or what the user entered, but possibly not saved yet due to validation errors.
// If this is the first the user sees the form, then value and original value are the same. Value changes as the user starts interacting with the form.
public string Value;

// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
public string OriginalValue;

// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
public string HelpText;

// The field is considered "valid" if there are no validation errors
public List<string> ValidationErrors = new List<string>();

public bool Required = false;

public bool IsValid()
public virtual bool IsValid()
{
return ValidationErrors.Count == 0;
return ValidationErrors.Count == 0 && Fields.All(field => field.IsValid());
}

[JsonExtensionData] public IDictionary<string, JToken> AdditionalData { get; set; }
public List<Field> Fields { get; set; } = new();


}
12 changes: 5 additions & 7 deletions BTCPayServer.Abstractions/Form/Fieldset.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using System.Collections.Generic;

namespace BTCPayServer.Abstractions.Form;

public class Fieldset
public class Fieldset : Field
{
public bool Hidden { get; set; }
public string Label { get; set; }

public Fieldset()
{
this.Fields = new List<Field>();
Type = "fieldset";
}

public string Label { get; set; }
public List<Field> Fields { get; set; }
}
130 changes: 109 additions & 21 deletions BTCPayServer.Abstractions/Form/Form.cs
Original file line number Diff line number Diff line change
@@ -1,60 +1,148 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Linq;

namespace BTCPayServer.Abstractions.Form;

public class Form
{

// Messages to be shown at the top of the form indicating user feedback like "Saved successfully" or "Please change X because of Y." or a warning, etc...
public List<AlertMessage> TopMessages { get; set; } = new();

// Groups of fields in the form
public List<Fieldset> Fieldsets { get; set; } = new();
public List<Field> Fields { get; set; } = new();


// Are all the fields valid in the form?
public bool IsValid()
{
foreach (var fieldset in Fieldsets)
{
foreach (var field in fieldset.Fields)
{
if (!field.IsValid())
{
return false;
}
}
}

return true;
return Fields.All(field => field.IsValid());
}

public Field GetFieldByName(string name)
{
foreach (var fieldset in Fieldsets)
return GetFieldByName(name, Fields, null);
}

private static Field GetFieldByName(string name, List<Field> fields, string prefix)
{
prefix ??= string.Empty;
foreach (var field in fields)
{
foreach (var field in fieldset.Fields)
var currentPrefix = prefix;
if (!string.IsNullOrEmpty(field.Name))
{
if (name.Equals(field.Name))

currentPrefix = $"{prefix}{field.Name}";
if (currentPrefix.Equals(name, StringComparison.InvariantCultureIgnoreCase))
{
return field;
}

currentPrefix += "_";
}

var subFieldResult = GetFieldByName(name, field.Fields, currentPrefix);
if (subFieldResult is not null)
{
return subFieldResult;
}

}
return null;
}

public List<string> GetAllNames()
{
return GetAllNames(Fields);
}

private static List<string> GetAllNames(List<Field> fields)
{
var names = new List<string>();
foreach (var fieldset in Fieldsets)

foreach (var field in fields)
{
foreach (var field in fieldset.Fields)
string prefix = string.Empty;
if (!string.IsNullOrEmpty(field.Name))
{
names.Add(field.Name);
prefix = $"{field.Name}_";
}

if (field.Fields.Any())
{
names.AddRange(GetAllNames(field.Fields).Select(s => $"{prefix}{s}" ));
}
}

return names;
}

public void ApplyValuesFromOtherForm(Form form)
{
foreach (var fieldset in Fields)
{
foreach (var field in fieldset.Fields)
{
field.Value = form
.GetFieldByName(
$"{(string.IsNullOrEmpty(fieldset.Name) ? string.Empty : fieldset.Name + "_")}{field.Name}")
?.Value;
}
}
}

public void ApplyValuesFromForm(IFormCollection form, string ignorePrefix = null)
{
var names = GetAllNames();
foreach (var name in names)
{
if (ignorePrefix is not null && name.StartsWith(ignorePrefix))
{
continue;
}

var field = GetFieldByName(name);
if (field is null || !form.TryGetValue(name, out var val))
{
continue;
}

field.Value = val;
}
}

public Dictionary<string, object> GetValues()
{
return GetValues(Fields);
}

private static Dictionary<string, object> GetValues(List<Field> fields)
{
var result = new Dictionary<string, object>();
foreach (Field field in fields)
{
var name = field.Name ?? string.Empty;
if (field.Fields.Any())
{
var values = GetValues(fields);
values.Remove(string.Empty, out var keylessValue);

result.TryAdd(name, values);

if (keylessValue is not Dictionary<string, object> dict) continue;
foreach (KeyValuePair<string,object> keyValuePair in dict)
{
result.TryAdd(keyValuePair.Key, keyValuePair.Value);
}
}
else
{
result.TryAdd(name, field.Value);
}
}

return result;
}
}
27 changes: 27 additions & 0 deletions BTCPayServer.Abstractions/Form/HtmlInputField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace BTCPayServer.Abstractions.Form;

public class HtmlInputField : Field
{
// The translated label of the field.
public string Label;

// The original value is the value that is currently saved in the backend. A "reset" button can be used to revert back to this. Should only be set from the constructor.
public string OriginalValue;

// A useful note shown below the field or via a tooltip / info icon. Should be translated for the user.
public string HelpText;

public bool Required;
public HtmlInputField(string label, string name, string value, bool required, string helpText, string type = "text")
{
Label = label;
Name = name;
Value = value;
OriginalValue = value;
Required = required;
HelpText = helpText;
Type = type;
}

// TODO JSON parsing from string to objects again probably won't work out of the box because of the different field types.
}
19 changes: 0 additions & 19 deletions BTCPayServer.Abstractions/Form/TextField.cs

This file was deleted.

2 changes: 1 addition & 1 deletion BTCPayServer.Client/Models/CreateAppRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class CreatePointOfSaleAppRequest : CreateAppRequest
public string RedirectUrl { get; set; } = null;
public bool? RedirectAutomatically { get; set; } = null;
public bool? RequiresRefundEmail { get; set; } = null;
public string CheckoutFormId { get; set; } = null;
public string FormId { get; set; } = null;
public string EmbeddedCSS { get; set; } = null;
public CheckoutType? CheckoutType { get; set; } = null;
}
Expand Down
2 changes: 0 additions & 2 deletions BTCPayServer.Client/Models/InvoiceData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ public class CheckoutOptions
public bool? RedirectAutomatically { get; set; }
public bool? RequiresRefundEmail { get; set; } = null;
public string DefaultLanguage { get; set; }
[JsonProperty("checkoutFormId")]
public string CheckoutFormId { get; set; }
public CheckoutType? CheckoutType { get; set; }
}
}
Expand Down
4 changes: 4 additions & 0 deletions BTCPayServer.Client/Models/PaymentRequestBaseData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,9 @@ public class PaymentRequestBaseData

[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; }

public string FormId { get; set; }

public string FormResponse { get; set; }
}
}
1 change: 0 additions & 1 deletion BTCPayServer.Client/Models/PaymentRequestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class PaymentRequestData : PaymentRequestBaseData
public DateTimeOffset CreatedTime { get; set; }
public string Id { get; set; }
public bool Archived { get; set; }

public enum PaymentRequestStatus
{
Pending = 0,
Expand Down
2 changes: 0 additions & 2 deletions BTCPayServer.Client/Models/StoreBaseData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ public abstract class StoreBaseData
public bool AnyoneCanCreateInvoice { get; set; }
public string DefaultCurrency { get; set; }
public bool RequiresRefundEmail { get; set; }

public string CheckoutFormId { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public CheckoutType CheckoutType { get; set; }
public bool LightningAmountInSatoshi { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions BTCPayServer.Data/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, bool
public DbSet<WebhookData> Webhooks { get; set; }
public DbSet<LightningAddressData> LightningAddresses{ get; set; }
public DbSet<PayoutProcessorData> PayoutProcessors { get; set; }
public DbSet<FormData> Forms { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
Expand Down Expand Up @@ -122,6 +123,7 @@ protected override void OnModelCreating(ModelBuilder builder)
LightningAddressData.OnModelCreating(builder);
PayoutProcessorData.OnModelCreating(builder);
//WebhookData.OnModelCreating(builder);
FormData.OnModelCreating(builder, Database);


if (Database.IsSqlite() && !_designTime)
Expand Down
30 changes: 30 additions & 0 deletions BTCPayServer.Data/Data/FormData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace BTCPayServer.Data.Data;

public class FormData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
public string Name { get; set; }
public string StoreId { get; set; }
public StoreData Store { get; set; }
public string Config { get; set; }

internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<FormData>()
.HasOne(o => o.Store)
.WithMany(o => o.Forms).OnDelete(DeleteBehavior.Cascade);
builder.Entity<FormData>().HasIndex(o => o.StoreId);

if (databaseFacade.IsNpgsql())
{
builder.Entity<FormData>()
.Property(o => o.Config)
.HasColumnType("JSONB");
}
}
}
1 change: 1 addition & 0 deletions BTCPayServer.Data/Data/StoreData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ public class StoreData
public IEnumerable<PayoutData> Payouts { get; set; }
public IEnumerable<CustodianAccountData> CustodianAccounts { get; set; }
public IEnumerable<StoreSettingData> Settings { get; set; }
public IEnumerable<FormData> Forms { get; set; }
}
}