PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / view / frontend / wpa-events-single / wpa-events / template-helpers.php
ameliabooking / view / frontend / wpa-events-single / wpa-events Last commit date
partials 1 month ago template-helpers.php 1 month ago wpa-events-single.php 1 month ago
template-helpers.php
638 lines
1 <?php
2 /**
3 * Helpers for wpa-events single template (scoped; loaded once per request).
4 *
5 * @copyright © Melograno Ventures. All rights reserved.
6 * @licence See LICENCE.md for license details.
7 */
8
9 defined('ABSPATH') || die('No script kiddies please!');
10
11 use AmeliaBooking\Domain\Entity\Entities;
12 use AmeliaBooking\Infrastructure\Common\Container;
13 use AmeliaBooking\Infrastructure\WP\Translations\BackendStrings;
14 use AmeliaBooking\Infrastructure\WP\Translations\LiteBackendStrings;
15 use AmeliaBooking\Infrastructure\Licence\Licence;
16 use AmeliaBooking\Infrastructure\Licence\LicenceConstants;
17
18 if (!function_exists('amelia_wpa_events_container')) {
19 function amelia_wpa_events_container(): ?Container
20 {
21 static $container = null;
22 static $failed = false;
23
24 if ($failed) {
25 return null;
26 }
27
28 if ($container instanceof Container) {
29 return $container;
30 }
31
32 if (!defined('AMELIA_PATH')) {
33 $failed = true;
34
35 return null;
36 }
37
38 try {
39 /** @var Container $container */
40 $container = require AMELIA_PATH . '/src/Infrastructure/ContainerConfig/container.php';
41 } catch (Throwable $e) {
42 $container = null;
43 $failed = true;
44 }
45
46 return $container;
47 }
48 }
49
50 if (!function_exists('amelia_wpa_events_format_datetime')) {
51 function amelia_wpa_events_format_datetime(string|null $mysqlDatetime): string
52 {
53 if (empty($mysqlDatetime) || !is_string($mysqlDatetime)) {
54 return '';
55 }
56
57 try {
58 $utc = new DateTimeZone('UTC');
59 $dt = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $mysqlDatetime, $utc);
60
61 if (!$dt) {
62 return esc_html($mysqlDatetime);
63 }
64
65 $local = $dt->setTimezone(wp_timezone());
66 $fmt = get_option('date_format') . ' ' . get_option('time_format');
67
68 return esc_html(wp_date($fmt, $local->getTimestamp()));
69 } catch (Throwable $e) {
70 return esc_html($mysqlDatetime);
71 }
72 }
73 }
74
75 if (!function_exists('amelia_wpa_events_parse_period_mysql')) {
76 function amelia_wpa_events_parse_period_mysql(string|null $mysqlDatetime): ?DateTimeImmutable
77 {
78 if (empty($mysqlDatetime) || !is_string($mysqlDatetime)) {
79 return null;
80 }
81
82 try {
83 $dt = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $mysqlDatetime, wp_timezone());
84
85 return $dt ?: null;
86 } catch (Throwable $e) {
87 return null;
88 }
89 }
90 }
91
92 if (!function_exists('amelia_wpa_events_timetable_rows')) {
93 function amelia_wpa_events_timetable_rows(string|null $periodStart, string|null $periodEnd): array
94 {
95 $s = amelia_wpa_events_parse_period_mysql($periodStart);
96 $e = amelia_wpa_events_parse_period_mysql($periodEnd);
97
98 if (!$s || !$e) {
99 return [];
100 }
101
102 $s = $s->setTimezone(wp_timezone());
103 $e = $e->setTimezone(wp_timezone());
104
105 $startDate = $s->setTime(0, 0, 0);
106 $endDate = $e->setTime(0, 0, 0);
107 $endTime = wp_date('H:i:s', $e->getTimestamp());
108
109 if ($endTime === '00:00:00') {
110 $endDate = $endDate->modify('-1 day');
111 }
112
113 $rows = [];
114 $date = $startDate;
115
116 while ($date <= $endDate) {
117 $rows[] = [
118 'date' => wp_date(get_option('date_format'), $date->getTimestamp()),
119 'timeStart' => wp_date(get_option('time_format'), $s->getTimestamp()),
120 'timeEnd' => $endTime === '00:00:00'
121 ? '24:00'
122 : wp_date(get_option('time_format'), $e->getTimestamp()),
123 'sort' => $date->getTimestamp(),
124 ];
125
126 $date = $date->modify('+1 day');
127 }
128
129 return $rows;
130 }
131 }
132
133 if (!function_exists('amelia_wpa_events_begin_block')) {
134 function amelia_wpa_events_begin_block(array $eventData): ?array
135 {
136 if (empty($eventData['periods'][0]['periodStart'])) {
137 return null;
138 }
139
140 $s = amelia_wpa_events_parse_period_mysql($eventData['periods'][0]['periodStart']);
141
142 if (!$s) {
143 return null;
144 }
145
146 $s = $s->setTimezone(wp_timezone());
147
148 return [
149 'label' => LiteBackendStrings::get('event_begins'),
150 'day' => wp_date('j', $s->getTimestamp()),
151 'month' => wp_date('M', $s->getTimestamp()),
152 'year' => wp_date('Y', $s->getTimestamp()),
153 'time' => wp_date(get_option('time_format'), $s->getTimestamp()),
154 ];
155 }
156 }
157
158 if (!function_exists('amelia_wpa_events_carousel_sources')) {
159 function amelia_wpa_events_carousel_sources(array $eventData): array
160 {
161 $urls = [];
162
163 if (!empty($eventData['pictureFullPath'])) {
164 $urls[] = (string) $eventData['pictureFullPath'];
165 }
166
167 if (!empty($eventData['gallery']) && is_array($eventData['gallery'])) {
168 foreach ($eventData['gallery'] as $row) {
169 if (!empty($row['pictureFullPath'])) {
170 $u = (string) $row['pictureFullPath'];
171 if (!in_array($u, $urls, true)) {
172 $urls[] = $u;
173 }
174 }
175 }
176 }
177
178 return $urls;
179 }
180 }
181
182 if (!function_exists('amelia_wpa_events_slots_left_event')) {
183 function amelia_wpa_events_slots_left_event(array $eventData): ?int
184 {
185 $customTickets = $eventData['customTickets'] ?? [];
186
187 if (($eventData['customPricing'] ?? false) === true && $customTickets !== []) {
188 $slotsLeft = 0;
189
190 foreach ($customTickets as $ticket) {
191 if ($ticket['enabled'] !== true) {
192 continue;
193 }
194
195 $slotsLeft += max(0, (int) $ticket['spots'] - (int) $ticket['sold']);
196 }
197
198 return $slotsLeft;
199 }
200
201 $max = isset($eventData['maxCapacity']) ? (int) $eventData['maxCapacity'] : 0;
202 if ($max <= 0) {
203 return null;
204 }
205
206 $sold = isset($eventData['spotsSold']) ? (int) $eventData['spotsSold'] : 0;
207
208 return max(0, $max - $sold);
209 }
210 }
211
212 if (!function_exists('amelia_wpa_events_format_price_number_with_settings')) {
213 function amelia_wpa_events_format_price_number_with_settings(array $paymentSettings, float $price): string
214 {
215 $thousandSeparatorMap = [',', '.', ' ', ' '];
216 $decimalSeparatorMap = ['.', ',', '.', ','];
217
218 $sepIndex = isset($paymentSettings['priceSeparator']) ? (int) $paymentSettings['priceSeparator'] - 1 : 0;
219 $sepIndex = max(0, min(3, $sepIndex));
220
221 $thousandSeparator = $thousandSeparatorMap[$sepIndex];
222 $decimalSeparator = $decimalSeparatorMap[$sepIndex];
223 $decimals = isset($paymentSettings['priceNumberOfDecimals'])
224 ? (int) $paymentSettings['priceNumberOfDecimals']
225 : 2;
226
227 return number_format($price, $decimals, $decimalSeparator, $thousandSeparator);
228 }
229 }
230
231 if (!function_exists('amelia_wpa_events_format_money_display')) {
232 function amelia_wpa_events_format_money_display($amount): string
233 {
234 if ($amount === null || $amount === '') {
235 return '';
236 }
237
238 if (!is_numeric($amount)) {
239 return esc_html((string) $amount);
240 }
241
242 $value = (float) $amount;
243 $c = amelia_wpa_events_container();
244
245 if ($c) {
246 try {
247 /** @var \AmeliaBooking\Domain\Services\Settings\SettingsService $settingsService */
248 $settingsService = $c->get('domain.settings.service');
249 $paymentSettings = $settingsService->getCategorySettings('payments');
250
251 if (!empty($paymentSettings['hideCurrencySymbolFrontend'])) {
252 return esc_html(amelia_wpa_events_format_price_number_with_settings($paymentSettings, $value));
253 }
254
255 /** @var \AmeliaBooking\Application\Services\Helper\HelperService $helper */
256 $helper = $c->get('application.helper.service');
257 return esc_html($helper->getFormattedPrice($value));
258 } catch (Throwable $e) {
259 // Safe fallback below.
260 }
261 }
262
263 return esc_html(number_format($value, 2, '.', ''));
264 }
265 }
266
267 if (!function_exists('amelia_wpa_events_public_price_for_listing')) {
268 function amelia_wpa_events_public_price_for_listing(array $eventData): array
269 {
270 $tickets = (!empty($eventData['customPricing'])
271 && !empty($eventData['customTickets'])
272 && is_array($eventData['customTickets']))
273 ? $eventData['customTickets']
274 : [];
275
276 if ($tickets !== []) {
277 $candidates = [];
278
279 foreach ($tickets as $ticket) {
280 if (!is_array($ticket)) {
281 continue;
282 }
283
284 $enabled = !array_key_exists('enabled', $ticket)
285 || $ticket['enabled'] === true
286 || $ticket['enabled'] === 1
287 || $ticket['enabled'] === '1';
288
289 if (!$enabled) {
290 continue;
291 }
292
293 $tPrice = 0.0;
294 if (isset($ticket['price']) && is_numeric($ticket['price'])) {
295 $tPrice = (float) $ticket['price'];
296 }
297
298 if (!empty($ticket['dateRangePrice']) && is_numeric($ticket['dateRangePrice'])) {
299 $tPrice = (float) $ticket['dateRangePrice'];
300 }
301
302 $candidates[] = $tPrice;
303 }
304
305 $minPositive = null;
306 foreach ($candidates as $p) {
307 if ($p > 0 && ($minPositive === null || $p < $minPositive)) {
308 $minPositive = $p;
309 }
310 }
311
312 if ($minPositive !== null) {
313 return ['amount' => $minPositive, 'show_from' => true];
314 }
315
316 return ['amount' => null, 'show_from' => false];
317 }
318
319 if (!isset($eventData['price']) || !is_numeric($eventData['price'])) {
320 return ['amount' => null, 'show_from' => false];
321 }
322
323 $base = (float) $eventData['price'];
324
325 return $base > 0
326 ? ['amount' => $base, 'show_from' => false]
327 : ['amount' => null, 'show_from' => false];
328 }
329 }
330
331 if (!function_exists('amelia_wpa_events_price_vat_pill_label')) {
332 function amelia_wpa_events_price_vat_pill_label(int $eventId): string
333 {
334 if ($eventId <= 0) {
335 return '';
336 }
337
338 $c = amelia_wpa_events_container();
339 if (!$c) {
340 return '';
341 }
342
343 try {
344 /** @var \AmeliaBooking\Domain\Services\Settings\SettingsService $settingsService */
345 $settingsService = $c->get('domain.settings.service');
346
347 if (!$settingsService->isFeatureEnabled('tax')) {
348 return '';
349 }
350
351 $taxService = $c->get('application.tax.service');
352 if (!is_object($taxService) || !method_exists($taxService, 'getTaxData') || !method_exists($taxService, 'getAll')) {
353 return '';
354 }
355
356 $taxes = $taxService->getAll();
357 $json = $taxService->getTaxData($eventId, Entities::EVENT, $taxes);
358
359 if ($json === null || $json === '' || $json === '[]') {
360 return '';
361 }
362
363 $taxesSettings = $settingsService->getSetting('payments', 'taxes');
364 $excluded = !empty($taxesSettings['excluded']);
365
366 return $excluded ? '+ ' . BackendStrings::get('total_tax_colon') : BackendStrings::get('incl_tax');
367 } catch (Throwable $e) {
368 return '';
369 }
370 }
371 }
372
373 if (!function_exists('amelia_wpa_events_location_line')) {
374 function amelia_wpa_events_location_line(array $eventData): string
375 {
376 $location = $eventData['customLocation'] ?? ($eventData['location']['name'] ?? '');
377
378 return $location !== '' ? esc_html((string) $location) : '';
379 }
380 }
381
382 if (!function_exists('amelia_wpa_events_person_card')) {
383 function amelia_wpa_events_person_card(array $person, string $role): ?array
384 {
385 $first = $person['firstName'] ?? '';
386 $last = $person['lastName'] ?? '';
387 $name = trim($first . ' ' . $last);
388
389 if ($name === '') {
390 return null;
391 }
392
393 $picture = '';
394 if (!empty($person['pictureFullPath'])) {
395 $picture = (string) $person['pictureFullPath'];
396 } elseif (!empty($person['pictureThumbPath'])) {
397 $picture = (string) $person['pictureThumbPath'];
398 }
399
400 return [
401 'name' => $name,
402 'picture' => $picture,
403 'role' => $role,
404 ];
405 }
406 }
407
408 if (!function_exists('amelia_wpa_events_primary_host')) {
409 function amelia_wpa_events_primary_host(array $eventData): ?array
410 {
411 $organizerLabel = LiteBackendStrings::get('event_organizer');
412
413 if (!empty($eventData['organizer']) && is_array($eventData['organizer'])) {
414 $card = amelia_wpa_events_person_card($eventData['organizer'], $organizerLabel);
415 if ($card !== null) {
416 return $card;
417 }
418 }
419
420 return null;
421 }
422 }
423
424 if (!function_exists('amelia_wpa_events_staff_cards')) {
425 /**
426 * @return list<array{name: string, picture: string, role: string}>
427 */
428 function amelia_wpa_events_staff_cards(array $eventData): array
429 {
430 $providers = $eventData['providers'] ?? [];
431 if (!is_array($providers) || $providers === []) {
432 return [];
433 }
434
435 $isLite = !Licence::hasLicenseAccess(LicenceConstants::STARTER);
436 $staffLabel = $isLite ? '' : LiteBackendStrings::get('event_staff');
437 $cards = [];
438 $seen = [];
439
440 foreach ($providers as $provider) {
441 if (!is_array($provider)) {
442 continue;
443 }
444
445 $card = amelia_wpa_events_person_card($provider, $staffLabel);
446 if ($card === null) {
447 continue;
448 }
449
450 $key = md5($card['name'] . '|' . $card['picture']);
451 if (isset($seen[$key])) {
452 continue;
453 }
454 $seen[$key] = true;
455
456 $cards[] = $card;
457 }
458
459 return $cards;
460 }
461 }
462
463 if (!function_exists('amelia_wpa_events_sidebar_tickets_payload')) {
464 function amelia_wpa_events_sidebar_tickets_payload(array $eventData, bool $useWaitingList = false): array
465 {
466 if (empty($eventData['customPricing'])
467 || empty($eventData['customTickets'])
468 || !is_array($eventData['customTickets'])
469 ) {
470 return [];
471 }
472
473 $payload = [];
474 $maxCustomCapacity = !empty($eventData['maxCustomCapacity']);
475
476 foreach ($eventData['customTickets'] as $ticket) {
477 if (!is_array($ticket) || empty($ticket['name'])) {
478 continue;
479 }
480
481 $enabled = !array_key_exists('enabled', $ticket)
482 || $ticket['enabled'] === true
483 || $ticket['enabled'] === 1
484 || $ticket['enabled'] === '1';
485
486 if (!$enabled) {
487 continue;
488 }
489
490 $tid = !empty($ticket['id']) ? (int) $ticket['id'] : 0;
491 if ($tid <= 0) {
492 continue;
493 }
494
495 $spots = isset($ticket['spots']) ? (int) $ticket['spots'] : 0;
496 $sold = isset($ticket['sold']) ? (int) $ticket['sold'] : 0;
497 $left = max(0, $spots - $sold);
498
499 if ($useWaitingList && $left <= 0) {
500 $waiting = isset($ticket['waiting']) ? (int) $ticket['waiting'] : 0;
501 if (!$maxCustomCapacity) {
502 $waitingListSpots = isset($ticket['waitingListSpots']) ? (int) $ticket['waitingListSpots'] : 0;
503 $left = max(0, $waitingListSpots - $waiting);
504 } else {
505 $left = isset($eventData['waitingListSpotsLeft'])
506 ? max(0, (int) $eventData['waitingListSpotsLeft'])
507 : max(
508 0,
509 (int) ($eventData['waitingCapacity'] ?? 0) - (int) ($eventData['waiting'] ?? 0)
510 );
511 }
512 }
513
514 $price = null;
515 if (!empty($ticket['dateRangePrice']) && is_numeric($ticket['dateRangePrice'])) {
516 $price = $ticket['dateRangePrice'];
517 } elseif (isset($ticket['price'])) {
518 $price = $ticket['price'];
519 }
520
521 $payload[] = [
522 'id' => $tid,
523 'name' => (string) $ticket['name'],
524 'spots' => $spots,
525 'sold' => $sold,
526 'left' => $left,
527 'price' => $price,
528 'priceFormatted' => ($price !== null && $price !== '' && is_numeric($price))
529 ? amelia_wpa_events_format_money_display($price)
530 : '',
531 ];
532 }
533
534 return $payload;
535 }
536 }
537
538 if (!function_exists('amelia_wpa_events_display_status_label')) {
539 function amelia_wpa_events_display_status_label(string $displayStatus): string
540 {
541 $labels = [
542 'open' => LiteBackendStrings::get('open'),
543 'closed' => LiteBackendStrings::get('closed'),
544 'canceled' => LiteBackendStrings::get('canceled'),
545 'full' => LiteBackendStrings::get('full'),
546 'upcoming' => LiteBackendStrings::get('upcoming'),
547 ];
548
549 return $labels[$displayStatus] ?? LiteBackendStrings::get('closed');
550 }
551 }
552
553 if (!function_exists('amelia_wpa_events_show_slots_for_status')) {
554 function amelia_wpa_events_show_slots_for_status(string $displayStatus): bool
555 {
556 return in_array($displayStatus, ['open', 'upcoming'], true);
557 }
558 }
559
560 if (!function_exists('amelia_wpa_events_attendees_payload')) {
561 /**
562 * Sidebar persons selector for spot-priced events (mirrors EventInfo BringingAnyone).
563 *
564 * @param array $eventData
565 * @param array{available?: bool, spotsLeft?: int, maxExtraPeople?: int|null}|null $waitingListAvailability
566 * From WaitingListService::getEventWaitingListAvailability().
567 *
568 * @return array{min: int, max: int, default: int, slotsLeft: int}|null
569 */
570 function amelia_wpa_events_attendees_payload(array $eventData, ?array $waitingListAvailability = null): ?array
571 {
572 if (!empty($eventData['customPricing'])) {
573 return null;
574 }
575
576 if (empty($eventData['bringingAnyone'])) {
577 return null;
578 }
579
580 $capacity = 0;
581 $maxExtraPeople = null;
582 $useWaitingList = !empty($waitingListAvailability['available']);
583
584 if ($useWaitingList) {
585 $capacity = isset($waitingListAvailability['spotsLeft'])
586 ? max(0, (int) $waitingListAvailability['spotsLeft'])
587 : 0;
588
589 if (array_key_exists('maxExtraPeople', $waitingListAvailability)
590 && $waitingListAvailability['maxExtraPeople'] !== null
591 ) {
592 $maxExtraPeople = (int) $waitingListAvailability['maxExtraPeople'];
593 }
594 } else {
595 if (isset($eventData['places']) && is_numeric($eventData['places'])) {
596 $capacity = max(0, (int) $eventData['places']);
597 } else {
598 $slots = amelia_wpa_events_slots_left_event($eventData);
599 $capacity = $slots !== null ? max(0, $slots) : 0;
600 }
601
602 if (isset($eventData['maxExtraPeople']) && is_numeric($eventData['maxExtraPeople'])) {
603 $maxExtraPeople = (int) $eventData['maxExtraPeople'];
604 }
605 }
606
607 if ($capacity <= 1) {
608 return null;
609 }
610
611 $maxPersons = $capacity;
612 if ($maxExtraPeople !== null && $maxExtraPeople > 0 && $capacity > $maxExtraPeople) {
613 $maxPersons = $maxExtraPeople + 1;
614 }
615
616 if ($maxPersons <= 1) {
617 return null;
618 }
619
620 return [
621 'min' => 1,
622 'max' => $maxPersons,
623 'default' => 1,
624 'slotsLeft' => $capacity,
625 ];
626 }
627 }
628
629 if (!function_exists('amelia_wpa_events_enqueue_shell_block_support_styles')) {
630 /** Flush block-supports CSS after template parts (before wp_footer). */
631 function amelia_wpa_events_enqueue_shell_block_support_styles(bool $useBlockThemeShell): void
632 {
633 if ($useBlockThemeShell && function_exists('wp_enqueue_stored_styles')) {
634 wp_enqueue_stored_styles();
635 }
636 }
637 }
638