HTML lang setting and Head tags for POS and Crowdfund public pages (#6229)

* HTML lang setting and Head tags for POS and Crowdfund public pages

* updates #6229

* updates 6229

* resolve conflict

* updated according to Nicolas' recommendations

* updates #6229

* Add RawMeta method in safe.cs

* ...

* resolve conflicts

* resolve conflict

* resolve conflicts

* Updates as Nicolas request

* updates

---------

Co-authored-by: d11n <mail@dennisreimann.de>
This commit is contained in:
Nisaba
2025-01-15 05:49:25 +00:00
committed by GitHub
parent e1f47b2738
commit 2250853b3e
14 changed files with 174 additions and 8 deletions

View File

@@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
@@ -22,6 +23,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Ganss.Xss;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Identity;
@@ -51,7 +53,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
UserManager<ApplicationUser> userManager,
FormDataService formDataService,
IStringLocalizer stringLocalizer,
CrowdfundAppType app)
CrowdfundAppType app,
Safe safe)
{
_currencies = currencies;
_appService = appService;
@@ -64,6 +67,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
_invoiceController = invoiceController;
FormDataService = formDataService;
StringLocalizer = stringLocalizer;
_safe = safe;
}
private readonly EventAggregator _eventAggregator;
@@ -75,6 +79,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
private readonly UIInvoiceController _invoiceController;
private readonly UserManager<ApplicationUser> _userManager;
private readonly CrowdfundAppType _app;
private readonly Safe _safe;
public FormDataService FormDataService { get; }
public IStringLocalizer StringLocalizer { get; }
@@ -403,6 +409,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
Enabled = settings.Enabled,
EnforceTargetAmount = settings.EnforceTargetAmount,
StartDate = settings.StartDate,
HtmlMetaTags= settings.HtmlMetaTags,
Language = settings.Language,
TargetCurrency = settings.TargetCurrency,
MainImageUrl = settings.MainImageUrl == null ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), settings.MainImageUrl),
Description = settings.Description,
@@ -524,6 +532,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
app.Name = vm.AppName;
app.Archived = vm.Archived;
bool wasHtmlModified;
var newSettings = new CrowdfundSettings
{
Title = vm.Title,
@@ -531,6 +541,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
EnforceTargetAmount = vm.EnforceTargetAmount,
StartDate = vm.StartDate?.ToUniversalTime(),
TargetCurrency = vm.TargetCurrency,
HtmlMetaTags= _safe.RawMeta(vm.HtmlMetaTags, out wasHtmlModified),
Language = vm.Language,
Description = vm.Description,
EndDate = vm.EndDate?.ToUniversalTime(),
TargetAmount = vm.TargetAmount,
@@ -574,7 +586,14 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
StoreId = app.StoreDataId,
Settings = newSettings
});
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value;
if (wasHtmlModified)
{
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value;
}
else
{
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value;
}
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
}

View File

@@ -194,6 +194,8 @@ namespace BTCPayServer.Plugins.Crowdfund
{
Title = settings.Title,
Tagline = settings.Tagline,
Lang = settings.Language,
HtmlMetaTags= settings.HtmlMetaTags,
Description = settings.Description,
StoreName = store.StoreName,
StoreId = appData.StoreDataId,

View File

@@ -28,6 +28,13 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
public string Tagline { get; set; }
public string Language { get; set; }
[Display(Name = "HTML Meta Tags")]
public string HtmlMetaTags{ get; set; }
[Required]
public string Description { get; set; }

View File

@@ -14,6 +14,8 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
public string AppId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Lang { get; set; }
public string HtmlMetaTags{ get; set; }
public string MainImageUrl { get; set; }
public string StoreName { get; set; }
public DateTime? StartDate { get; set; }

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
@@ -11,6 +12,7 @@ using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
@@ -26,6 +28,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using Ganss.Xss;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Http.Extensions;
@@ -55,7 +58,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
IStringLocalizer stringLocalizer,
DisplayFormatter displayFormatter,
IRateLimitService rateLimitService,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
Safe safe)
{
_currencies = currencies;
_appService = appService;
@@ -66,6 +70,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
_displayFormatter = displayFormatter;
_rateLimitService = rateLimitService;
_authorizationService = authorizationService;
_safe = safe;
StringLocalizer = stringLocalizer;
FormDataService = formDataService;
}
@@ -79,6 +84,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
private readonly DisplayFormatter _displayFormatter;
private readonly IRateLimitService _rateLimitService;
private readonly IAuthorizationService _authorizationService;
private readonly Safe _safe;
public FormDataService FormDataService { get; }
public IStringLocalizer StringLocalizer { get; }
@@ -132,6 +138,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
CustomTipPercentages = settings.CustomTipPercentages,
AppId = appId,
StoreId = store.Id,
Lang = settings.Language,
HtmlMetaTags= settings.HtmlMetaTags,
Description = settings.Description,
});
}
@@ -611,6 +619,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
CustomButtonText = settings.CustomButtonText ?? PointOfSaleSettings.CUSTOM_BUTTON_TEXT_DEF,
CustomTipText = settings.CustomTipText ?? PointOfSaleSettings.CUSTOM_TIP_TEXT_DEF,
CustomTipPercentages = settings.CustomTipPercentages != null ? string.Join(",", settings.CustomTipPercentages) : string.Join(",", PointOfSaleSettings.CUSTOM_TIP_PERCENTAGES_DEF),
Language = settings.Language,
HtmlMetaTags= settings.HtmlMetaTags,
Description = settings.Description,
NotificationUrl = settings.NotificationUrl,
RedirectUrl = settings.RedirectUrl,
@@ -687,6 +697,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
return View("PointOfSale/UpdatePointOfSale", vm);
}
bool wasHtmlModified;
var settings = new PointOfSaleSettings
{
Title = vm.Title,
@@ -705,6 +716,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
CustomTipPercentages = ListSplit(vm.CustomTipPercentages),
NotificationUrl = vm.NotificationUrl,
RedirectUrl = vm.RedirectUrl,
Language = vm.Language,
HtmlMetaTags = _safe.RawMeta(vm.HtmlMetaTags, out wasHtmlModified),
Description = vm.Description,
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? null : bool.Parse(vm.RedirectAutomatically),
FormId = vm.FormId
@@ -714,7 +727,12 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
app.Archived = vm.Archived;
app.SetSettings(settings);
await _appService.UpdateOrCreateApp(app);
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value;
if (wasHtmlModified)
{
TempData[WellKnownTempData.ErrorMessage] = StringLocalizer["Only meta tags are allowed in HTML headers. Your HTML code has been cleaned up accordingly."].Value;
} else {
TempData[WellKnownTempData.SuccessMessage] = StringLocalizer["App updated"].Value;
}
return RedirectToAction(nameof(UpdatePointOfSale), new { appId });
}

View File

@@ -86,6 +86,11 @@ namespace BTCPayServer.Plugins.PointOfSale.Models
new() { Text = "Use Store Settings", Value = "" }
}, nameof(SelectListItem.Value), nameof(SelectListItem.Text), RedirectAutomatically);
public string Language { get; set; }
[Display(Name = "HTML Meta Tags")]
public string HtmlMetaTags{ get; set; }
public string Description { get; set; }
[Display(Name = "Request customer data on checkout")]

View File

@@ -68,6 +68,8 @@ namespace BTCPayServer.Plugins.PointOfSale.Models
public string CustomButtonText { get; set; }
public string CustomTipText { get; set; }
public int[] CustomTipPercentages { get; set; }
public string Lang { get; set; }
public string HtmlMetaTags{ get; set; }
public string Description { get; set; }
public SelectList AllCategories { get; set; }
public string StoreId { get; set; }

View File

@@ -1,13 +1,22 @@
using System;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using System.Globalization;
using System.Linq;
namespace BTCPayServer.Services.Apps
{
public class CrowdfundSettings
{
public CrowdfundSettings()
{
Language = "en";
}
public string Title { get; set; }
public string Description { get; set; }
public string Language { get; set; }
public string HtmlMetaTags{ get; set; }
public bool Enabled { get; set; } = true;
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }

View File

@@ -8,6 +8,7 @@ namespace BTCPayServer.Services.Apps
public PointOfSaleSettings()
{
Title = "Tea shop";
Language = "en";
Template = AppService.SerializeTemplate([
new AppItem
{
@@ -99,6 +100,8 @@ namespace BTCPayServer.Services.Apps
public string CustomTipText { get; set; } = CUSTOM_TIP_TEXT_DEF;
public static readonly int[] CUSTOM_TIP_PERCENTAGES_DEF = { 15, 18, 20 };
public int[] CustomTipPercentages { get; set; } = CUSTOM_TIP_PERCENTAGES_DEF;
public string Language { get; set; }
public string HtmlMetaTags{ get; set; }
public string Description { get; set; }
public string NotificationUrl { get; set; }
public string RedirectUrl { get; set; }

View File

@@ -14,7 +14,7 @@
}
}
<!DOCTYPE html>
<html class="h-100" @(Env.IsDeveloping ? " data-devenv" : "") id="Crowdfund-@Model.AppId">
<html lang="@Model.Lang" class="h-100" @(Env.IsDeveloping ? " data-devenv" : "") id="Crowdfund-@Model.AppId">
<head>
<partial name="LayoutHead" />
<link href="~/vendor/bootstrap-vue/bootstrap-vue.min.css" asp-append-version="true" rel="stylesheet" />
@@ -35,6 +35,8 @@
object-fit: scale-down;
}
</style>
@* Html.Raw OK here since Html has been cleaned before in controller *@
@Html.Raw(Model.HtmlMetaTags)
<vc:ui-extension-point location="crowdfund-head" model="@Model"/>
</head>
<body class="min-vh-100 p-2">

View File

@@ -240,6 +240,36 @@
<h3 class="mt-5 mb-2" text-translate="true">Additional Options</h3>
<div class="form-group">
<div class="accordion" id="additional">
<div class="accordion-item">
<h2 class="accordion-header" id="additional-htmlheader-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-htmlheader" aria-expanded="false" aria-controls="additional-htmlheader">
<span text-translate="true">HTML Headers</span>
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-htmlheader" class="accordion-collapse collapse" aria-labelledby="additional-htmlheader-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="Language" class="form-label"></label>
<input asp-for="Language" class="form-control" maxlength="2" required />
<div class="form-text">Fix the HTML page language</div>
<span asp-validation-for="Language" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="HtmlMetaTags" class="form-label"></label>
<textarea asp-for="HtmlMetaTags" rows="5" cols="40" class="form-control"
placeholder='<meta name="description" content="Your description">
<meta name="keywords" content="keyword1, keyword2, keyword3">
<meta name="author" content="John Doe">
Please insert valid HTML here. Only meta tags accepted.'>
</textarea>
<span asp-validation-for="HtmlMetaTags" class="text-danger"></span>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="additional-sound-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-sound" aria-expanded="false" aria-controls="additional-sound">
@@ -295,7 +325,7 @@
<div class="accordion-item">
<h2 class="accordion-header" id="additional-discussion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-discussion" aria-expanded="false" aria-controls="additional-discussion">
Discussion
<span text-translate="true">Discussion</span>
<vc:icon symbol="caret-down" />
</button>
</h2>
@@ -321,7 +351,7 @@
<div class="accordion-item">
<h2 class="accordion-header" id="additional-notification-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-notification" aria-expanded="false" aria-controls="additional-notification">
Notification URL Callbacks
<span text-translate="true">Notification URL Callbacks</span>
<vc:icon symbol="caret-down" />
</button>
</h2>

View File

@@ -33,13 +33,15 @@
}
}
<!DOCTYPE html>
<html class="h-100" lang="en" @(Env.IsDeveloping ? " data-devenv" : "") id="POS-@Model.AppId">
<html class="h-100" lang="@Model.Lang" @(Env.IsDeveloping ? " data-devenv" : "") id="POS-@Model.AppId">
<head>
<partial name="LayoutHead" />
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-startup-image" href="~/img/splash.png">
<link rel="manifest" href="@(await GetDynamicManifest(ViewData["Title"]!.ToString()))">
<link href="~/pos/common.css" asp-append-version="true" rel="stylesheet" />
@* Html.Raw OK here since Html has been cleaned before in controller *@
@Html.Raw(Model.HtmlMetaTags)
@await RenderSectionAsync("PageHeadContent", false)
</head>
<body class="min-vh-100">

View File

@@ -93,6 +93,7 @@
<span asp-validation-for="Currency" class="text-danger"></span>
</div>
</div>
<div id="description" class="row mt-4">
<div class="col-xxl-constrain">
<div class="form-group mb-0">
@@ -196,6 +197,36 @@
<h3 class="mb-2">Additional Options</h3>
<div class="form-group">
<div class="accordion" id="additional">
<div class="accordion-item">
<h2 class="accordion-header" id="additional-htmlheader-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-htmlheader" aria-expanded="false" aria-controls="additional-htmlheader">
<span text-translate="true">HTML Headers</span>
<vc:icon symbol="caret-down" />
</button>
</h2>
<div id="additional-htmlheader" class="accordion-collapse collapse" aria-labelledby="additional-htmlheader-header">
<div class="accordion-body">
<div class="form-group">
<label asp-for="Language" class="form-label"></label>
<input asp-for="Language" class="form-control" maxlength="2" required />
<div class="form-text">Fix the HTML page language</div>
<span asp-validation-for="Language" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="HtmlMetaTags" class="form-label"></label>
<textarea asp-for="HtmlMetaTags" rows="5" cols="40" class="form-control"
placeholder='<meta name="description" content="Your description">
<meta name="keywords" content="keyword1, keyword2, keyword3">
<meta name="author" content="John Doe">
Please insert valid HTML here. Only meta tags accepted.'>
</textarea>
<span asp-validation-for="HtmlMetaTags" class="text-danger"></span>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="additional-embed-payment-button-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#additional-embed-payment-button" aria-expanded="false" aria-controls="additional-embed-payment-button">