#7 - QR Codes & payments #22

Merged
Martas merged 1 commits from feat/claude/#7-qr-codes-and-payments into main 2026-06-16 17:35:44 +00:00
11 changed files with 251 additions and 72 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"require": { "require": {
"chillerlan/php-qrcode": "^5.0" "chillerlan/php-qrcode": "^6.0.1"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
Generated
+68 -64
View File
@@ -4,40 +4,44 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "b4f5229f78cd0eed0c7166614bf05110", "content-hash": "c898c79e7e1cf9625a3a3f757c17ee0d",
"packages": [ "packages": [
{ {
"name": "chillerlan/php-qrcode", "name": "chillerlan/php-qrcode",
"version": "5.0.3", "version": "6.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/chillerlan/php-qrcode.git", "url": "https://github.com/chillerlan/php-qrcode.git",
"reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2" "reference": "49006e34bd5328f163e80329e7312f34dceea59b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/42e215640e9ebdd857570c9e4e52245d1ee51de2", "url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/49006e34bd5328f163e80329e7312f34dceea59b",
"reference": "42e215640e9ebdd857570c9e4e52245d1ee51de2", "reference": "49006e34bd5328f163e80329e7312f34dceea59b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"chillerlan/php-settings-container": "^2.1.6 || ^3.2.1", "chillerlan/php-settings-container": "^3.2.1",
"ext-mbstring": "*", "ext-mbstring": "*",
"php": "^7.4 || ^8.0" "php": "^8.2"
}, },
"require-dev": { "require-dev": {
"chillerlan/php-authenticator": "^4.3.1 || ^5.2.1", "chillerlan/php-authenticator": "^5.3",
"ext-fileinfo": "*", "ext-fileinfo": "*",
"phan/phan": "^5.4.5", "intervention/image": "^3.11",
"phpcompatibility/php-compatibility": "10.x-dev", "phan/phan": "^6.0.1",
"phpbench/phpbench": "^1.4",
"phpmd/phpmd": "^2.15", "phpmd/phpmd": "^2.15",
"phpunit/phpunit": "^9.6", "phpstan/phpstan": "^2.1.40",
"setasign/fpdf": "^1.8.2", "phpstan/phpstan-deprecation-rules": "^2.0.4",
"slevomat/coding-standard": "^8.15", "phpunit/phpunit": "^11.5",
"squizlabs/php_codesniffer": "^3.11" "setasign/fpdf": "^1.8.6",
"slevomat/coding-standard": "^8.28",
"squizlabs/php_codesniffer": "^4.0"
}, },
"suggest": { "suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.", "chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
"intervention/image": "More advanced GD and ImageMagick output.",
"setasign/fpdf": "Required to use the QR FPDF output.", "setasign/fpdf": "Required to use the QR FPDF output.",
"simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code" "simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
}, },
@@ -75,7 +79,7 @@
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors" "homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
} }
], ],
"description": "A QR Code generator and reader with a user-friendly API. PHP 7.4+", "description": "A QR Code generator and reader with a user-friendly API. PHP 8.2+",
"homepage": "https://github.com/chillerlan/php-qrcode", "homepage": "https://github.com/chillerlan/php-qrcode",
"keywords": [ "keywords": [
"phpqrcode", "phpqrcode",
@@ -97,20 +101,20 @@
"type": "Ko-Fi" "type": "Ko-Fi"
} }
], ],
"time": "2024-11-21T16:12:34+00:00" "time": "2026-03-18T21:21:07+00:00"
}, },
{ {
"name": "chillerlan/php-settings-container", "name": "chillerlan/php-settings-container",
"version": "3.2.1", "version": "3.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/chillerlan/php-settings-container.git", "url": "https://github.com/chillerlan/php-settings-container.git",
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681" "reference": "a0a487cbf5344f721eb504bf0f59bada40c381b7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/95ed3e9676a1d47cab2e3174d19b43f5dbf52681", "url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/a0a487cbf5344f721eb504bf0f59bada40c381b7",
"reference": "95ed3e9676a1d47cab2e3174d19b43f5dbf52681", "reference": "a0a487cbf5344f721eb504bf0f59bada40c381b7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -118,11 +122,13 @@
"php": "^8.1" "php": "^8.1"
}, },
"require-dev": { "require-dev": {
"phan/phan": "^5.5.2",
"phpmd/phpmd": "^2.15", "phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^1.11", "phpstan/phpstan": "^2.1.31",
"phpstan/phpstan-deprecation-rules": "^1.2", "phpstan/phpstan-deprecation-rules": "^2.0.3",
"phpunit/phpunit": "^10.5", "phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.10" "slevomat/coding-standard": "^8.22",
"squizlabs/php_codesniffer": "^4.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@@ -147,7 +153,8 @@
"Settings", "Settings",
"configuration", "configuration",
"container", "container",
"helper" "helper",
"property hook"
], ],
"support": { "support": {
"issues": "https://github.com/chillerlan/php-settings-container/issues", "issues": "https://github.com/chillerlan/php-settings-container/issues",
@@ -163,7 +170,7 @@
"type": "ko_fi" "type": "ko_fi"
} }
], ],
"time": "2024-07-16T11:13:48+00:00" "time": "2026-03-20T21:10:52+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
@@ -692,16 +699,16 @@
}, },
{ {
"name": "amphp/process", "name": "amphp/process",
"version": "v2.0.3", "version": "v2.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/amphp/process.git", "url": "https://github.com/amphp/process.git",
"reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d" "reference": "583959df17d00304ad7b0b32285373f985935643"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", "url": "https://api.github.com/repos/amphp/process/zipball/583959df17d00304ad7b0b32285373f985935643",
"reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d", "reference": "583959df17d00304ad7b0b32285373f985935643",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -715,7 +722,7 @@
"amphp/php-cs-fixer-config": "^2", "amphp/php-cs-fixer-config": "^2",
"amphp/phpunit-util": "^3", "amphp/phpunit-util": "^3",
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9",
"psalm/phar": "^5.4" "psalm/phar": "6.16.1"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@@ -748,7 +755,7 @@
"homepage": "https://amphp.org/process", "homepage": "https://amphp.org/process",
"support": { "support": {
"issues": "https://github.com/amphp/process/issues", "issues": "https://github.com/amphp/process/issues",
"source": "https://github.com/amphp/process/tree/v2.0.3" "source": "https://github.com/amphp/process/tree/v2.1.0"
}, },
"funding": [ "funding": [
{ {
@@ -756,7 +763,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-04-19T03:13:44+00:00" "time": "2026-05-31T15:11:55+00:00"
}, },
{ {
"name": "amphp/serialization", "name": "amphp/serialization",
@@ -986,28 +993,29 @@
}, },
{ {
"name": "composer/pcre", "name": "composer/pcre",
"version": "3.3.2", "version": "3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/pcre.git", "url": "https://github.com/composer/pcre.git",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" "reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.4 || ^8.0" "php": "^7.4 || ^8.0"
}, },
"conflict": { "conflict": {
"phpstan/phpstan": "<1.11.10" "phpstan/phpstan": "<2.2.2"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.12 || ^2", "phpstan/phpstan": "^2",
"phpstan/phpstan-strict-rules": "^1 || ^2", "phpstan/phpstan-deprecation-rules": "^2",
"phpunit/phpunit": "^8 || ^9" "phpstan/phpstan-strict-rules": "^2",
"phpunit/phpunit": "^9"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@@ -1045,7 +1053,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/composer/pcre/issues", "issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.3.2" "source": "https://github.com/composer/pcre/tree/3.4.0"
}, },
"funding": [ "funding": [
{ {
@@ -1055,13 +1063,9 @@
{ {
"url": "https://github.com/composer", "url": "https://github.com/composer",
"type": "github" "type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
} }
], ],
"time": "2024-11-12T16:29:46+00:00" "time": "2026-06-07T11:47:49+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
@@ -1567,16 +1571,16 @@
}, },
{ {
"name": "johnpbloch/wordpress-core", "name": "johnpbloch/wordpress-core",
"version": "6.9.0", "version": "6.9.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/johnpbloch/wordpress-core.git", "url": "https://github.com/johnpbloch/wordpress-core.git",
"reference": "4626d4e896c36ab77a69ce58627bc76243b5dd07" "reference": "13e02e0047ca5c8ec8dc837c2de8a5bd3583b879"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/johnpbloch/wordpress-core/zipball/4626d4e896c36ab77a69ce58627bc76243b5dd07", "url": "https://api.github.com/repos/johnpbloch/wordpress-core/zipball/13e02e0047ca5c8ec8dc837c2de8a5bd3583b879",
"reference": "4626d4e896c36ab77a69ce58627bc76243b5dd07", "reference": "13e02e0047ca5c8ec8dc837c2de8a5bd3583b879",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -1584,7 +1588,7 @@
"php": ">=7.2.24" "php": ">=7.2.24"
}, },
"provide": { "provide": {
"wordpress/core-implementation": "6.9.0" "wordpress/core-implementation": "6.9.4"
}, },
"type": "wordpress-core", "type": "wordpress-core",
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@@ -1611,7 +1615,7 @@
"source": "https://core.trac.wordpress.org/browser", "source": "https://core.trac.wordpress.org/browser",
"wiki": "https://codex.wordpress.org/" "wiki": "https://codex.wordpress.org/"
}, },
"time": "2025-12-02T19:10:58+00:00" "time": "2026-03-11T15:27:36+00:00"
}, },
{ {
"name": "kelunik/certificate", "name": "kelunik/certificate",
@@ -3246,16 +3250,16 @@
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.38.1", "version": "v1.38.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
"reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3307,7 +3311,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
}, },
"funding": [ "funding": [
{ {
@@ -3327,7 +3331,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2026-05-26T12:51:13+00:00" "time": "2026-05-27T06:59:30+00:00"
}, },
{ {
"name": "symfony/polyfill-php84", "name": "symfony/polyfill-php84",
@@ -3786,16 +3790,16 @@
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",
"version": "2.4.0", "version": "2.4.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/webmozarts/assert.git", "url": "https://github.com/webmozarts/assert.git",
"reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70",
"reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -3846,9 +3850,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/webmozarts/assert/issues", "issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/2.4.0" "source": "https://github.com/webmozarts/assert/tree/2.4.1"
}, },
"time": "2026-05-20T13:07:01+00:00" "time": "2026-06-15T15:31:57+00:00"
}, },
{ {
"name": "wp-hooks/wordpress-core", "name": "wp-hooks/wordpress-core",
+9 -2
View File
@@ -180,11 +180,18 @@ class RsvEmailListener {
global $rsv_template_registry; global $rsv_template_registry;
$engine = new RsvTemplateEngine(registry: $rsv_template_registry); $engine = new RsvTemplateEngine(registry: $rsv_template_registry);
$form_definition = new RsvFormDefinition((string) $form_submit['form_id'], $definition['definition']);
$form_data = new RsvFormData($form_values);
// Calculated values (e.g. price) win over submitted fields so they can't be shadowed.
$symbols = array_merge($form_values, (new RsvFormCalculatedValues())->for($form_definition, $form_data));
// Subject is plain text: render without HTML-escaping, then strip tags/newlines. // Subject is plain text: render without HTML-escaping, then strip tags/newlines.
$subject = sanitize_text_field($engine->render_plain($subject_tpl, $form_values)); $subject = sanitize_text_field($engine->render_plain($subject_tpl, $symbols));
// Body is HTML: the engine HTML-escapes all interpolated values. // Body is HTML: the engine HTML-escapes all interpolated values.
$body = $engine->render($body_tpl, $form_values); $body = $engine->render($body_tpl, $symbols);
error_log("Prc");
(new RsvEmailSender())->send($user_email, $subject, $body); (new RsvEmailSender())->send($user_email, $subject, $body);
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -15,7 +15,7 @@ class RsvGoogleCalendarListener {
return; return;
} }
$calendar_id = self::resolve_calendar_id($gcal, $event->timetable_id); $calendar_id = self::resolve_calendar_id($gcal, $event->reservation->timetable_id);
if (!$calendar_id) { if (!$calendar_id) {
return; return;
} }
@@ -24,8 +24,8 @@ class RsvGoogleCalendarListener {
$gcal->add_event( $gcal->add_event(
$calendar_id, $calendar_id,
"Reservation #{$event->reservation->id}", "Reservation #{$event->reservation->id}",
$event->reservation->start, $event->reservation->start_utc,
$event->reservation->end, $event->reservation->end_utc,
$event->reservation->user_email, $event->reservation->user_email,
$event->reservation->id, $event->reservation->id,
$status, $status,
@@ -50,6 +50,7 @@ class RsvTimetableRepository {
'name' => $timetable->name, 'name' => $timetable->name,
'block_size' => $timetable->block_size, 'block_size' => $timetable->block_size,
'maintainer_email' => $timetable->maintainer_email, 'maintainer_email' => $timetable->maintainer_email,
'google_calendar_id' => $timetable->google_calendar_id,
], ],
['id' => $id] ['id' => $id]
); );
@@ -0,0 +1,21 @@
<?php
/** Computes a form's total price as the sum of its elements' prices. */
class RsvFormPriceCalculator {
public function calculate(RsvFormDefinition $definition, RsvFormData $data): float {
global $rsv_form_price_registry;
$total = 0.0;
foreach ($definition->getElements() as $element) {
$calculator = $rsv_form_price_registry->get($element->getType());
if ($calculator === null) {
continue; // Unpriced element type contributes nothing.
}
$total += (float) $calculator($element, $data->getValue($element->getName()));
}
return $total;
}
}
@@ -0,0 +1,21 @@
<?php
class RsvFormPriceCalculatorRegistry {
/** @var array<string, callable(RsvFormElementDefinition, mixed): float> */
private array $calculators = [];
public function register(string $type, callable $calculator): void {
$this->calculators[$type] = $calculator;
}
public function get(string $type): ?callable {
return $this->calculators[$type] ?? null;
}
/** Builds the registry and lets other modules contribute calculators. */
public static function boot(): self {
$registry = new self();
do_action('rsv-register-price-calculator', $registry);
return $registry;
}
}
@@ -0,0 +1,19 @@
<?php
/** Values derived from a submission (not entered by the visitor), exposed to templates. */
final class RsvFormCalculatedValues {
/** @return array<string, mixed> */
public function for(RsvFormDefinition $definition, RsvFormData $data): array {
return [
'price' => (new RsvFormPriceCalculator())->calculate($definition, $data),
];
}
/**
* The names these values expose, so template validation accepts {{ price }}.
* @return list<string>
*/
public static function names(): array {
return ['price'];
}
}
@@ -44,7 +44,7 @@ final class RsvFormDefinitionValidator {
* @return list<string> * @return list<string>
*/ */
private function symbols(array $elements): array { private function symbols(array $elements): array {
$names = []; $names = RsvFormCalculatedValues::names(); // calculated values are referencable too
foreach ($elements as $el) { foreach ($elements as $el) {
$name = is_array($el) ? ($el['name'] ?? '') : ''; $name = is_array($el) ? ($el['name'] ?? '') : '';
if (is_string($name) && $name !== '') { if (is_string($name) && $name !== '') {
@@ -0,0 +1,90 @@
<?php
namespace Reservair\Templating\Elements;
use Reservair\Templating\RsvTemplateElement;
use Reservair\Templating\RsvTemplateSymbols;
use chillerlan\QRCode\Output\QRGdImagePNG;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Output\QROutputInterface;
/** Renders a QR payment code for the form's total price into an email body. */
class RsvQrPaymentElement implements RsvTemplateElement {
public function render(RsvTemplateSymbols $symbols): string {
$account = trim((string) $symbols->get('account', ''));
if ($account === '') {
return ''; // No payee account configured — nothing to render.
}
$payload = $this->spayd(
$account,
(float) $symbols->get('price', 0),
(string) $symbols->get('currency', 'CZK'),
(string) $symbols->get('message', ''),
(string) $symbols->get('variable_symbol', '')
);
$src = $this->image_url($payload);
if ($src === '') {
return ''; // Image could not be written.
}
return '<img class="rsv-qr-payment" alt="' . esc_attr__('QR platba', 'reservair')
. '" src="' . esc_url($src) . '" />';
}
public function symbols(): array {
return ['account', 'currency', 'message', 'variable_symbol'];
}
/** Builds a SPAYD string (Czech QR payment). '*' is the field delimiter. */
private function spayd(string $account, float $price, string $currency, string $message, string $vs): string {
$parts = [
'SPD*1.0',
'ACC:' . $account,
'AM:' . number_format($price, 2, '.', ''),
'CC:' . $currency,
];
if ($message !== '') {
$parts[] = 'MSG:' . str_replace('*', ' ', $message);
}
if ($vs !== '') {
$parts[] = 'X-VS:' . preg_replace('/\D/', '', $vs);
}
return implode('*', $parts);
}
/**
* Writes the QR PNG under uploads (deduped by payload hash) and returns its
* public URL, or '' if it could not be written.
*/
private function image_url(string $payload): string {
$uploads = wp_upload_dir();
$dir = $uploads['basedir'] . '/reservair-qr';
$name = hash('sha256', $payload) . '.png';
$path = $dir . '/' . $name;
if (!file_exists($path)) {
wp_mkdir_p($dir);
if (file_put_contents($path, $this->png($payload)) === false) {
return '';
}
}
return $uploads['baseurl'] . '/reservair-qr/' . $name;
}
private function png(string $payload): string {
$options = new QROptions();
$options->version = 7;
$options->outputInterface = QRGdImagePNG::class;
$options->scale = 4;
$options->outputBase64 = false;
$options->bgColor = [200, 150, 200];
return (string) (new QRCode($options))->render($payload);
}
}
+17 -1
View File
@@ -3,6 +3,7 @@
use Reservair\Templating\Elements\RsvReservationSummaryElement; use Reservair\Templating\Elements\RsvReservationSummaryElement;
use Reservair\Templating\Elements\RsvReservationActionsElement; use Reservair\Templating\Elements\RsvReservationActionsElement;
use Reservair\Templating\Elements\RsvResetFormButtonElement; use Reservair\Templating\Elements\RsvResetFormButtonElement;
use Reservair\Templating\Elements\RsvQrPaymentElement;
/** /**
* Plugin Name: Reservair * Plugin Name: Reservair
* Description: A reservation and booking system for WordPress. Site visitors browse available time slots and submit reservation requests via a Gutenberg block; administrators manage timetables, services, forms, and reservations from the WordPress admin panel. * Description: A reservation and booking system for WordPress. Site visitors browse available time slots and submit reservation requests via a Gutenberg block; administrators manage timetables, services, forms, and reservations from the WordPress admin panel.
@@ -33,7 +34,7 @@ register_activation_hook( __FILE__, [ 'RsvInstaller', 'install' ] );
* plugins we might interact with) is fully loaded. * plugins we might interact with) is fully loaded.
*/ */
function rsv_bootstrap(): void { function rsv_bootstrap(): void {
global $rsv_form_registry, $rsv_template_registry; global $rsv_form_registry, $rsv_template_registry, $rsv_form_price_registry;
// Re-grant the custom capability after a plugin *update* (the activation hook // Re-grant the custom capability after a plugin *update* (the activation hook
// only runs on activate). No-op once the stored version matches. // only runs on activate). No-op once the stored version matches.
@@ -53,11 +54,26 @@ function rsv_bootstrap(): void {
$rsv_form_registry->register( 'output-reservation-summary', new RsvReservationSummaryElementHandler() ); $rsv_form_registry->register( 'output-reservation-summary', new RsvReservationSummaryElementHandler() );
$rsv_form_registry->register( 'output-text', new RsvOutputTextElementHandler() ); $rsv_form_registry->register( 'output-text', new RsvOutputTextElementHandler() );
// Price calculators — extensions add per-element calculators via the action.
add_action( 'rsv-register-price-calculator', function ( RsvFormPriceCalculatorRegistry $reg ): void {
$reg->register( 'reservation', function ( RsvFormElementDefinition $def, $value ): float {
if ( ! is_array( $value ) || ! is_array( $value['timetable_reservations'] ?? null ) ) {
return 0.0;
}
$price_per_block = (float) $def->getAttr( 'price_per_block', 0 );
return $price_per_block * count( $value['timetable_reservations'] );
} );
} );
$rsv_form_price_registry = RsvFormPriceCalculatorRegistry::boot();
// Template custom-element registry. Extensions register via the action. // Template custom-element registry. Extensions register via the action.
add_action( 'rsv-template-register-custom-elements', function ( \Reservair\Templating\RsvTemplateRegistry $reg ): void { add_action( 'rsv-template-register-custom-elements', function ( \Reservair\Templating\RsvTemplateRegistry $reg ): void {
$reg->register( 'reservation-summary', new RsvReservationSummaryElement() ); $reg->register( 'reservation-summary', new RsvReservationSummaryElement() );
$reg->register( 'reservation-actions', new RsvReservationActionsElement() ); $reg->register( 'reservation-actions', new RsvReservationActionsElement() );
$reg->register( 'reset-form-button', new RsvResetFormButtonElement() ); $reg->register( 'reset-form-button', new RsvResetFormButtonElement() );
$reg->register( 'qr-payment', new RsvQrPaymentElement() );
} ); } );
$rsv_template_registry = new \Reservair\Templating\RsvTemplateRegistry(); $rsv_template_registry = new \Reservair\Templating\RsvTemplateRegistry();
do_action( 'rsv-template-register-custom-elements', $rsv_template_registry ); do_action( 'rsv-template-register-custom-elements', $rsv_template_registry );