diff --git a/backend/app/DomainObjects/EventSettingDomainObject.php b/backend/app/DomainObjects/EventSettingDomainObject.php index baae6217..df26b17c 100644 --- a/backend/app/DomainObjects/EventSettingDomainObject.php +++ b/backend/app/DomainObjects/EventSettingDomainObject.php @@ -4,6 +4,8 @@ use HiEvents\DataTransferObjects\AddressDTO; use HiEvents\Helper\AddressHelper; +use Liquid\Liquid; +use Liquid\Template; class EventSettingDomainObject extends Generated\EventSettingDomainObjectAbstract { @@ -41,4 +43,31 @@ public function getAddress(): AddressDTO country: $this->getLocationDetails()['country'] ?? null, ); } + + /** + * Get the offline payment instructions with Liquid template variables processed + * + * @param array $variables Variables to use in template processing + * @return string|null Processed instructions or null if no instructions set + */ + public function getProcessedOfflinePaymentInstructions(array $variables = []): ?string + { + $instructions = $this->getOfflinePaymentInstructions(); + if (!$instructions) { + return null; + } + + try { + $template = new Template(); + $template->parse($instructions); + return $template->render($variables); + } catch (\Throwable $e) { + // If template processing fails, return original instructions + \Log::error('Error processing Liquid template for offline payment instructions', [ + 'error' => $e->getMessage(), + 'instructions' => $instructions + ]); + return $instructions; + } + } } diff --git a/backend/composer.json b/backend/composer.json index b510c419..30cf05ac 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -19,6 +19,7 @@ "laravel/tinker": "^2.8", "laravel/vapor-core": "^2.37", "league/flysystem-aws-s3-v3": "^3.0", + "liquid/liquid": "^1.4", "maatwebsite/excel": "^3.1", "nette/php-generator": "^4.0", "php-open-source-saver/jwt-auth": "^2.1", diff --git a/backend/composer.lock b/backend/composer.lock index 3fbda137..f1e7668d 100644 --- a/backend/composer.lock +++ b/backend/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "62f38e147541b5968f5fe0e08abb6c57", + "content-hash": "617d2e62c958866b961b8ab4dd605039", "packages": [ { "name": "amphp/amp", @@ -4358,6 +4358,75 @@ ], "time": "2024-12-08T08:18:47+00:00" }, + { + "name": "liquid/liquid", + "version": "1.4.43", + "source": { + "type": "git", + "url": "https://github.com/kalimatas/php-liquid.git", + "reference": "3a472d5a87fd40c075eea2c4afec5028564e00c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kalimatas/php-liquid/zipball/3a472d5a87fd40c075eea2c4afec5028564e00c1", + "reference": "3a472d5a87fd40c075eea2c4afec5028564e00c1", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "ergebnis/composer-normalize": ">=2.8", + "friendsofphp/php-cs-fixer": "^3.22", + "infection/infection": ">=0.17.6", + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^9.2.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Liquid\\": "src/Liquid" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guz Alexander", + "email": "kalimatas@gmail.com", + "homepage": "http://guzalexander.com" + }, + { + "name": "Harald Hanek" + }, + { + "name": "Mateo Murphy" + }, + { + "name": "Alexey Kopytko", + "email": "alexey@kopytko.com", + "homepage": "https://www.alexeykopytko.com/" + } + ], + "description": "Liquid template engine for PHP", + "homepage": "https://github.com/kalimatas/php-liquid", + "keywords": [ + "liquid", + "template" + ], + "support": { + "issues": "https://github.com/kalimatas/php-liquid/issues", + "source": "https://github.com/kalimatas/php-liquid/tree/1.4.43" + }, + "time": "2024-10-04T06:14:41+00:00" + }, { "name": "maatwebsite/excel", "version": "3.1.55", @@ -13040,13 +13109,13 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2", "ext-intl": "*" }, - "platform-dev": [], - "plugin-api-version": "2.2.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/backend/resources/views/emails/orders/summary.blade.php b/backend/resources/views/emails/orders/summary.blade.php index e0b426b7..b1b6bba7 100644 --- a/backend/resources/views/emails/orders/summary.blade.php +++ b/backend/resources/views/emails/orders/summary.blade.php @@ -21,7 +21,17 @@

{{ __('Payment Instructions') }}

{{ __('Please follow the instructions below to complete your payment.') }} -{!! $eventSettings->getOfflinePaymentInstructions() !!} +{!! $eventSettings->getProcessedOfflinePaymentInstructions([ + 'order_short_id' => $order->getShortId(), + 'order_public_id' => $order->getPublicId(), + 'order_first_name' => $order->getFirstName(), + 'order_last_name' => $order->getLastName(), + 'order_email' => $order->getEmail(), + 'order_total_gross' => $order->getTotalGross(), + 'order_currency' => $event->getCurrency(), + 'order_items' => $order->getOrderItems(), + 'client_language' => app()->getLocale() +]) !!}
@endif diff --git a/frontend/package.json b/frontend/package.json index 579dce4e..84c660e9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -56,6 +56,7 @@ "cross-env": "^7.0.3", "dayjs": "^1.11.8", "express": "^4.19.2", + "liquidjs": "^10.21.0", "qr-scanner": "^1.4.2", "query-string": "^8.1.0", "react": "^18.2.0", diff --git a/frontend/src/components/layouts/EventHomepage/EventInformation/index.tsx b/frontend/src/components/layouts/EventHomepage/EventInformation/index.tsx index ef21671e..14fa426e 100644 --- a/frontend/src/components/layouts/EventHomepage/EventInformation/index.tsx +++ b/frontend/src/components/layouts/EventHomepage/EventInformation/index.tsx @@ -6,9 +6,11 @@ import {Button} from "@mantine/core"; import {LoadingMask} from "../../../common/LoadingMask"; import {ShareComponent} from "../../../common/ShareIcon"; import {eventCoverImageUrl, eventHomepageUrl} from "../../../../utilites/urlHelper.ts"; -import {FC} from "react"; +import {FC, useMemo} from "react"; import {Event} from "../../../../types.ts"; import {EventDateRange} from "../../../common/EventDateRange"; +import { getClientLocale } from "../../../../locales.ts"; +import { Liquid } from "liquidjs"; export const EventInformation: FC<{ event: Event @@ -17,6 +19,21 @@ export const EventInformation: FC<{ if (!event) { return ; } + + const processedDescription = useMemo(() => { + if (!event.description) return ''; + + const engine = new Liquid(); + try { + const clientLocale = getClientLocale(); + return engine.parseAndRenderSync(event.description, { + client_language: clientLocale + }); + } catch (error) { + console.error("Error processing liquid template:", error); + return event.description; + } + }, [event.description]); return ( <> @@ -75,7 +92,7 @@ export const EventInformation: FC<{

{t`About`}

)} diff --git a/frontend/src/components/routes/product-widget/OrderSummaryAndProducts/index.tsx b/frontend/src/components/routes/product-widget/OrderSummaryAndProducts/index.tsx index a8c75ff4..7e863ddd 100644 --- a/frontend/src/components/routes/product-widget/OrderSummaryAndProducts/index.tsx +++ b/frontend/src/components/routes/product-widget/OrderSummaryAndProducts/index.tsx @@ -15,6 +15,7 @@ import { IconPrinter, IconUser } from "@tabler/icons-react"; +import { OfflinePaymentMethod } from "../Payment/PaymentMethods/Offline"; import {useGetOrderPublic} from "../../../../queries/useGetOrderPublic.ts"; import {eventCheckoutPath} from "../../../../utilites/urlHelper.ts"; @@ -223,16 +224,9 @@ const PostCheckoutMessage = ({ message }: { message: string }) => (
); -const OfflinePaymentInstructions = ({ event }: { event: Event }) => ( +const OfflinePaymentInstructions = ({ event, order }: { event: Event, order: Order }) => (
-

{t`Payment Instructions`}

- -
- +
); @@ -260,7 +254,7 @@ export const OrderSummaryAndProducts = () => { - {order?.status === 'AWAITING_OFFLINE_PAYMENT' && } + {order?.status === 'AWAITING_OFFLINE_PAYMENT' && }

{t`Order Details`}

diff --git a/frontend/src/components/routes/product-widget/Payment/PaymentMethods/Offline/index.tsx b/frontend/src/components/routes/product-widget/Payment/PaymentMethods/Offline/index.tsx index cd3c5b00..5114871b 100644 --- a/frontend/src/components/routes/product-widget/Payment/PaymentMethods/Offline/index.tsx +++ b/frontend/src/components/routes/product-widget/Payment/PaymentMethods/Offline/index.tsx @@ -1,13 +1,52 @@ -import {Event} from "../../../../../../types.ts"; -import {Card} from "../../../../../common/Card"; -import {t} from "@lingui/macro"; +import { Event, Order } from "../../../../../../types.ts"; +import { Card } from "../../../../../common/Card"; +import {getClientLocale} from "../../../../../../locales"; +import { t } from "@lingui/macro"; +import { Liquid } from 'liquidjs'; +import React from 'react'; interface OfflinePaymentMethodProps { event: Event; + order?: Order; } -export const OfflinePaymentMethod = ({event}: OfflinePaymentMethodProps) => { +export const OfflinePaymentMethod = ({ event, order }: OfflinePaymentMethodProps) => { const eventSettings = event?.settings; + // Initialize Liquid engine with default settings + const engine = new Liquid(); + + const replaceVariables = async (text: string) => { + if (!text || !order) return text; + + const variables = { + order_short_id: order.short_id, + order_public_id: order.public_id, + order_first_name: order.first_name, + order_last_name: order.last_name, + order_email: order.email, + order_total_gross: order.total_gross, + order_currency: order.currency, + order_items: order.order_items, + client_language: getClientLocale() + }; + + try { + return await engine.parseAndRender(text, variables); + } catch (error) { + console.error('Error processing Liquid template:', error); + return text; + } + }; + + const [processedInstructions, setProcessedInstructions] = React.useState(eventSettings?.offline_payment_instructions || ""); + + React.useEffect(() => { + const processInstructions = async () => { + const processed = await replaceVariables(eventSettings?.offline_payment_instructions || ""); + setProcessedInstructions(processed); + }; + processInstructions(); + }, [eventSettings?.offline_payment_instructions, order]); return (
@@ -15,7 +54,7 @@ export const OfflinePaymentMethod = ({event}: OfflinePaymentMethodProps) => {
diff --git a/frontend/src/components/routes/product-widget/Payment/index.tsx b/frontend/src/components/routes/product-widget/Payment/index.tsx index 44ceb77a..3d6729f6 100644 --- a/frontend/src/components/routes/product-widget/Payment/index.tsx +++ b/frontend/src/components/routes/product-widget/Payment/index.tsx @@ -90,7 +90,7 @@ const Payment = () => { {isOfflineEnabled && (
- +
)}