PluginProbe ʕ •ᴥ•ʔ
Event Tickets with Ticket Scanner / 3.1.2
Event Tickets with Ticket Scanner v3.1.2
3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.4 trunk 2.6.0 2.7.0 2.7.1 2.7.10 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 2.9.8 2.9.9 3.0.0 3.0.1 3.0.2 3.0.3
event-tickets-with-ticket-scanner / includes / congress / class-congress-page.php
event-tickets-with-ticket-scanner / includes / congress Last commit date
class-congress-admin.php 1 week ago class-congress-api.php 1 week ago class-congress-page.php 1 week ago class-congress-repository.php 1 week ago class-template-variables.php 1 week ago
class-congress-page.php
163 lines
1 <?php
2 if (!defined('ABSPATH')) exit;
3
4 class sasoEventtickets_CongressPage {
5
6 private sasoEventtickets $MAIN;
7
8 public function __construct(sasoEventtickets $main) {
9 $this->MAIN = $main;
10 }
11
12 /**
13 * Render the congress page for a ticket. Called from the Ticket handler's output()
14 * when the "congress" marker is present (…/ticket/{TICKETID}?congress or ?code=…&congress)
15 * — so it reuses the whole ticket routing (path-based, compatibility-mode, query fallback).
16 * The congress is derived from the ticket's product; no slug in the URL → no slug collision.
17 */
18 public function renderForTicket(string $ticket_id): void {
19 // Public congress page gated by the option (admin area stays available regardless).
20 if (!$this->MAIN->getOptions()->isOptionCheckboxActive('congressModeActive')) {
21 status_header(404);
22 $this->renderError(__('Congress not found.', 'event-tickets-with-ticket-scanner'));
23 return;
24 }
25 $repo = $this->MAIN->getCongressRepository();
26 // getForTicket applies all access checks (order status, expiry, event window, linkage)
27 $congress = $repo->getForTicket($ticket_id);
28 if (!$congress) {
29 status_header(403);
30 $this->renderError(__('Access denied. Invalid ticket or no congress assigned to this ticket.', 'event-tickets-with-ticket-scanner'));
31 return;
32 }
33
34 // Manifest request (PWA)
35 if (isset($_GET['manifest'])) {
36 $this->renderManifest($congress, $ticket_id);
37 return;
38 }
39
40 $expired = !empty($congress['access_expires_at']) && strtotime($congress['access_expires_at']) < time();
41
42 // ETag / Last-Modified
43 $etag = '"' . strtotime($congress['updated_at']) . '"';
44 $last_modified = gmdate('D, d M Y H:i:s', strtotime($congress['updated_at'])) . ' GMT';
45 if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) === $etag) {
46 status_header(304);
47 return;
48 }
49 // Served from a plugin path that WP first resolves as 404 — force a real 200.
50 status_header(200);
51 header('ETag: ' . $etag);
52 header('Last-Modified: ' . $last_modified);
53 header('Cache-Control: private, no-cache');
54
55 // Sections are NOT rendered here — the minimal shell loads them via the REST API in JS.
56 $this->renderPage($congress, $ticket_id, $expired);
57 }
58
59 /**
60 * Minimal page shell. NO UI is built in PHP — scripts/styles are enqueued via the WP
61 * framework (no echoed tags), config is passed via wp_localize_script, and the sections
62 * are fetched from the REST API and rendered by congress-frontend.js in the browser.
63 * Keeps PHP per-request work tiny (just an access check + shell) → cacheable, light.
64 */
65 private function renderPage(array $congress, string $ticket_id, bool $expired): void {
66 $plugin_url = plugin_dir_url(dirname(dirname(__FILE__)));
67 $plugin_dir = dirname(dirname(__DIR__));
68 $languages = $plugin_dir . '/languages';
69 // filemtime cache-busting so frontend asset changes are picked up without a version bump.
70 $css_file = $plugin_dir . '/css/congress-frontend.css';
71 $js_file = $plugin_dir . '/js/congress-frontend.js';
72 $css_ver = SASO_EVENTTICKETS_PLUGIN_VERSION . '.' . (@filemtime($css_file) ?: '0');
73 $js_ver = SASO_EVENTTICKETS_PLUGIN_VERSION . '.' . (@filemtime($js_file) ?: '0');
74 $show_wallet = (bool) $this->MAIN->getOptions()->isOptionCheckboxActive('congressShowWalletLink');
75
76 // Ticket QR as a data-URI so the portal can always offer a "My ticket" entry —
77 // attendees without a physical badge can show it at the door. The handler caches
78 // the PNG on disk by filename, so this is cheap on repeat loads.
79 $ticketQr = '';
80 try {
81 $qrh = $this->MAIN->getTicketQRHandler();
82 $qrh->setFilepath(trailingslashit(get_temp_dir()));
83 $png = $qrh->renderPNG($ticket_id, 'F'); // returns a file path; content is PNG
84 if ($png && file_exists($png)) {
85 $ticketQr = 'data:image/png;base64,' . base64_encode((string) file_get_contents($png));
86 }
87 } catch (\Throwable $e) { $ticketQr = ''; }
88
89 // Depend on core 'dashicons' so the page-card icons actually render the glyph font —
90 // it isn't loaded on the public frontend for anonymous visitors by default. The
91 // dependency also makes wp_print_styles() below emit it alongside our stylesheet.
92 wp_register_style('saso-congress-frontend', $plugin_url . 'css/congress-frontend.css', ['dashicons'], $css_ver);
93 wp_enqueue_style('saso-congress-frontend');
94
95 wp_register_script('saso-congress-frontend', $plugin_url . 'js/congress-frontend.js', ['wp-i18n'], $js_ver, true);
96 wp_set_script_translations('saso-congress-frontend', 'event-tickets-with-ticket-scanner', $languages);
97 wp_localize_script('saso-congress-frontend', 'sasoEtCongressFrontend', [
98 'restBase' => rest_url('saso-et/v1/'),
99 'slug' => $congress['slug'],
100 'ticket' => $ticket_id,
101 'nonce' => wp_create_nonce('wp_rest'),
102 'title' => $congress['title'],
103 'expired' => $expired ? 1 : 0,
104 'showWalletLink' => $show_wallet ? 1 : 0,
105 'walletUrl' => $show_wallet ? $this->MAIN->getCore()->getWalletImportURL($ticket_id) : '',
106 'ticketQr' => $ticketQr,
107 'ticketLabel' => $ticket_id,
108 ]);
109 wp_enqueue_script('saso-congress-frontend');
110
111 // Seam for premium (separate plugin) to register+enqueue its own assets on this
112 // self-contained document. Fires BEFORE any repo call that could throw.
113 do_action($this->MAIN->_do_action_prefix.'congress_enqueue', $congress, $ticket_id);
114
115 // Premium can add its handle here so it is actually printed below (this is a
116 // standalone document — only handles in this list get emitted). Default is unchanged.
117 $print_handles = apply_filters($this->MAIN->_add_filter_prefix.'congress_print_handles', ['saso-congress-frontend']);
118
119 $manifest_url = add_query_arg('manifest', '1', $this->MAIN->getCongressRepository()->getUrl($ticket_id));
120 ?><!DOCTYPE html>
121 <html <?php language_attributes(); ?>>
122 <head>
123 <meta charset="<?php bloginfo('charset'); ?>">
124 <meta name="viewport" content="width=device-width, initial-scale=1">
125 <title><?php echo esc_html($congress['title']); ?></title>
126 <link rel="manifest" href="<?php echo esc_url($manifest_url); ?>">
127 <?php wp_print_styles($print_handles); ?>
128 </head>
129 <body class="congress-page">
130 <div id="congress-app" class="congress-app" data-loading="1">
131 <div class="congress-loading"><?php esc_html_e('Loading…', 'event-tickets-with-ticket-scanner'); ?></div>
132 </div>
133 <?php wp_print_scripts($print_handles); ?>
134 </body>
135 </html><?php
136 }
137
138 public function renderManifest(array $congress, string $ticket_id = ''): void {
139 header('Content-Type: application/manifest+json');
140 $page_url = $this->MAIN->getCongressRepository()->getUrl($ticket_id);
141 echo wp_json_encode([
142 'name' => $congress['title'],
143 'short_name' => mb_substr($congress['title'], 0, 12),
144 'start_url' => $page_url,
145 'scope' => $page_url,
146 'display' => 'standalone',
147 'background_color' => '#ffffff',
148 'theme_color' => '#0073aa',
149 'icons' => [
150 ['src' => plugin_dir_url(dirname(dirname(__FILE__))) . 'img/pwa-icon-192.png', 'sizes' => '192x192', 'type' => 'image/png'],
151 ['src' => plugin_dir_url(dirname(dirname(__FILE__))) . 'img/pwa-icon-512.png', 'sizes' => '512x512', 'type' => 'image/png'],
152 ],
153 ]);
154 }
155
156 private function renderError(string $message): void {
157 ?><!DOCTYPE html>
158 <html><head><meta charset="utf-8"><title>Fehler</title></head>
159 <body><p style="padding:40px;font-family:sans-serif"><?php echo esc_html($message); ?></p></body>
160 </html><?php
161 }
162 }
163