PluginProbe ʕ •ᴥ•ʔ
Event Tickets with Ticket Scanner / 2.9.3
Event Tickets with Ticket Scanner v2.9.3
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 / index.php
event-tickets-with-ticket-scanner Last commit date
3rd 3 months ago css 3 months ago img 3 months ago includes 3 months ago js 3 months ago languages 3 months ago tests 3 months ago ticket 3 months ago vendors 3 months ago .distignore 3 months ago .gitattributes 3 months ago .gitignore 3 months ago SASO_EVENTTICKETS.php 3 months ago backend.js 3 months ago changelog.txt 3 months ago db.php 3 months ago index.php 3 months ago init_file.php 3 months ago order_details.js 3 months ago pwa-sw.js 3 months ago readme.txt 3 months ago saso-eventtickets-validator.js 3 months ago sasoEventtickets_AdminSettings.php 3 months ago sasoEventtickets_Authtoken.php 3 months ago sasoEventtickets_Base.php 3 months ago sasoEventtickets_Core.php 3 months ago sasoEventtickets_Frontend.php 3 months ago sasoEventtickets_Messenger.php 3 months ago sasoEventtickets_Options.php 3 months ago sasoEventtickets_PDF.php 3 months ago sasoEventtickets_Seating.php 3 months ago sasoEventtickets_Ticket.php 3 months ago sasoEventtickets_TicketBadge.php 3 months ago sasoEventtickets_TicketDesigner.php 3 months ago sasoEventtickets_TicketQR.php 3 months ago ticket_events.js 3 months ago ticket_scanner.js 3 months ago validator.js 3 months ago wc_backend.js 3 months ago wc_frontend.js 3 months ago woocommerce-hooks.php 3 months ago
index.php
1904 lines
1 <?php
2 /**
3 * Plugin Name: Event Tickets with Ticket Scanner
4 * Plugin URI: https://vollstart.com/event-tickets-with-ticket-scanner/docs/
5 * Description: You can create and generate tickets and codes. You can redeem the tickets at entrance using the built-in ticket scanner. You customer can download a PDF with the ticket information. The Premium allows you also to activate user registration and more. This allows your user to register them self to a ticket.
6 * Version: 2.9.3
7 * Author: Vollstart
8 * Author URI: https://vollstart.com
9 * Text Domain: event-tickets-with-ticket-scanner
10 *
11 * Event Tickets with Ticket Scanner is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16 // https://semver.org/
17 // https://developer.wordpress.org/plugins/security/securing-output/
18 // https://developer.wordpress.org/plugins/security/securing-input/
19
20 include_once(plugin_dir_path(__FILE__)."init_file.php");
21
22 if (!defined('SASO_EVENTTICKETS_PLUGIN_VERSION'))
23 define('SASO_EVENTTICKETS_PLUGIN_VERSION', '2.9.3');
24 if (!defined('SASO_EVENTTICKETS_PLUGIN_DIR_PATH'))
25 define('SASO_EVENTTICKETS_PLUGIN_DIR_PATH', plugin_dir_path(__FILE__));
26
27 include_once plugin_dir_path(__FILE__)."SASO_EVENTTICKETS.php";
28
29 class sasoEventtickets_fakeprem{}
30 class sasoEventtickets {
31 private $_js_version;
32 private $_js_file = 'saso-eventtickets-validator.js';
33 public $_js_nonce = 'sasoEventtickets';
34 public $_do_action_prefix = 'saso_eventtickets_';
35 public $_add_filter_prefix = 'saso_eventtickets_';
36 protected $_prefix = 'sasoEventtickets';
37 public $_prefix_session = 'sasoEventtickets_';
38 protected $_shortcode = 'sasoEventTicketsValidator';
39 protected $_shortcode_mycode = 'sasoEventTicketsValidator_code';
40 protected $_shortcode_eventviews = 'sasoEventTicketsValidator_eventsview';
41 protected $_shortcode_ticket_scanner = 'sasoEventTicketsValidator_ticket_scanner';
42 protected $_shortcode_feature_list = 'sasoEventTicketsValidator_feature_list';
43 protected $_shortcode_ticket_detail = 'sasoEventTicketsValidator_ticket_detail';
44 protected $_divId = 'sasoEventtickets';
45
46 private $_isPrem = null;
47 private $_isCheckingSubscription = false;
48 private $_premium_plugin_name = 'event-tickets-with-ticket-scanner-premium';
49 private $_premium_function_file = 'sasoEventtickets_PremiumFunctions.php';
50 private $PREMFUNCTIONS = null;
51 private $BASE = null;
52 private $CORE = null;
53 private $ADMIN = null;
54 private $FRONTEND = null;
55 private $OPTIONS = null;
56
57 private $isAllowedAccess = null;
58
59 public static function Instance() {
60 static $inst = null;
61 if ($inst === null) {
62 $inst = new self();
63 }
64 return $inst;
65 }
66
67 public function __construct() {
68 global $sasoEventtickets;
69 $sasoEventtickets = $this; // Set early to prevent circular dependency with sasoEventtickets_Ticket
70 $this->_js_version = $this->getPluginVersion() . (defined('WP_DEBUG') && WP_DEBUG ? '.' . time() : '');
71 $this->initHandlers();
72 }
73
74 public function initHandlers() {
75 add_action( 'init', [$this, 'load_plugin_textdomain'] );
76 add_action( 'upgrader_process_complete', [$this, 'listener_upgrader_process_complete'], 10, 2 );
77 //add_action('admin_init', [$this, 'initialize_plugin']);
78 if (is_admin()) { // called in backend admin, admin-ajax!
79 $this->init_backend();
80 } else { // called in front end
81 $this->init_frontend();
82 }
83 add_action( 'sasoEventtickets_cronjob_daily', [$this, 'relay_sasoEventtickets_cronjob_daily'], 10, 0 ); // set in tickets.php
84 add_action( 'plugins_loaded', [$this, 'WooCommercePluginLoaded'], 20, 0 );
85 if (basename($_SERVER['SCRIPT_NAME']) == "admin-ajax.php") {
86 add_action('wp_ajax_nopriv_'.$this->_prefix.'_executeFrontend', [$this,'executeFrontend_a'], 10, 0); // nicht angemeldete user, sollen eine antwort erhalten
87 add_action('wp_ajax_'.$this->_prefix.'_executeFrontend', [$this,'executeFrontend_a'], 10, 0); // falls eingeloggt ist
88 add_action('wp_ajax_'.$this->_prefix.'_executeWCBackend', [$this,'executeWCBackend'], 10, 0); // falls eingeloggt ist
89 add_action('wp_ajax_'.$this->_prefix.'_downloadMyCodesAsPDF', [$this,'downloadMyCodesAsPDF'], 10, 0); // logged in users only
90 }
91 if (method_exists($this->getPremiumFunctions(), "initHandlers")) {
92 $this->getPremiumFunctions()->initHandlers();
93 }
94 $this->cronjob_daily_activate();
95 }
96 public function cronjob_daily_activate() {
97 $args = [];
98 if (! wp_next_scheduled ( 'sasoEventtickets_cronjob_daily', $args )) {
99 wp_schedule_event( strtotime("00:05"), 'daily', 'sasoEventtickets_cronjob_daily', $args );
100 }
101 }
102 public function cronjob_daily_deactivate() {
103 wp_clear_scheduled_hook( 'sasoEventtickets_cronjob_daily' );
104 }
105 public function load_plugin_textdomain() {
106 load_plugin_textdomain( 'event-tickets-with-ticket-scanner', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
107 }
108 public function getPluginPath() {
109 return SASO_EVENTTICKETS_PLUGIN_DIR_PATH;
110 }
111 public function getPluginVersion() {
112 return SASO_EVENTTICKETS_PLUGIN_VERSION;
113 }
114 public function getPluginVersions() {
115 $ret = ['basic'=>SASO_EVENTTICKETS_PLUGIN_VERSION, 'premium'=>'', 'debug'=>''];
116 if (defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION')) {
117 $ret['premium'] = SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION;
118 }
119 if (defined('WP_DEBUG') && WP_DEBUG) {
120 $ret['debug'] = esc_html__('is active', 'event-tickets-with-ticket-scanner');
121 }
122 return $ret;
123 }
124 public function getDB() {
125 return SASO_EVENTTICKETS::getDB(plugin_dir_path(__FILE__), "sasoEventticketsDB", $this);
126 }
127 public function getBase() {
128 if ($this->BASE == null) {
129 if (!class_exists('sasoEventtickets_Base')) {
130 include_once plugin_dir_path(__FILE__)."sasoEventtickets_Base.php";
131 }
132 $this->BASE = new sasoEventtickets_Base($this);
133 }
134 return $this->BASE;
135 }
136 public function getCore() {
137 if ($this->CORE == null) {
138 if (!class_exists('sasoEventtickets_Core')) {
139 include_once plugin_dir_path(__FILE__)."sasoEventtickets_Core.php";
140 }
141 $this->CORE = new sasoEventtickets_Core($this);
142 }
143 return $this->CORE;
144 }
145 public function getAdmin() {
146 if ($this->ADMIN == null) {
147 if (!class_exists('sasoEventtickets_AdminSettings')) {
148 include_once plugin_dir_path(__FILE__)."sasoEventtickets_AdminSettings.php";
149 }
150 $this->ADMIN = new sasoEventtickets_AdminSettings($this);
151 }
152 return $this->ADMIN;
153 }
154 public function getFrontend() {
155 if ($this->FRONTEND == null) {
156 if (!class_exists('sasoEventtickets_Frontend')) {
157 include_once plugin_dir_path(__FILE__)."sasoEventtickets_Frontend.php";
158 }
159 $this->FRONTEND = new sasoEventtickets_Frontend($this);
160 }
161 return $this->FRONTEND;
162 }
163 public function getOptions() {
164 if ($this->OPTIONS == null) {
165 if (!class_exists('sasoEventtickets_Options')) {
166 include_once plugin_dir_path(__FILE__)."sasoEventtickets_Options.php";
167 }
168 $this->OPTIONS = new sasoEventtickets_Options($this, $this->_prefix);
169 $this->OPTIONS->initOptions();
170 }
171 return $this->OPTIONS;
172 }
173 public function getNewPDFObject() {
174 if (!class_exists('sasoEventtickets_PDF')) {
175 require_once("sasoEventtickets_PDF.php");
176 }
177 $pdf = new sasoEventtickets_PDF();
178 $pdf->setFontSize($this->getOptions()->getOptionValue('wcTicketPDFFontSize'));
179 $pdf->setFontFamily($this->getOptions()->getOptionValue('wcTicketPDFFontFamily'));
180 $pdf = apply_filters( $this->_add_filter_prefix.'main_getNewPDFObject', $pdf );
181 return $pdf;
182 }
183 public function loadOnce($className, $filename="") {
184 if (!class_exists($className)) {
185 if ($filename == "") $filename = $className;
186 include_once __DIR__.'/'.$filename.'.php';
187 }
188 }
189
190 /**
191 * Load class from /includes/ folder structure
192 *
193 * @param string $className The class name to load
194 * @param string $relativePath Path relative to plugin root (e.g., 'includes/woocommerce/class-base.php')
195 * @return void
196 */
197 private function loadClass(string $className, string $relativePath): void {
198 if (class_exists($className)) {
199 return; // Already loaded
200 }
201
202 $path = __DIR__ . '/' . $relativePath;
203
204 if (file_exists($path)) {
205 require_once $path;
206 }
207 }
208 public function getWC() {
209 $this->loadOnce('sasoEventtickets_WC', "woocommerce-hooks");
210 return sasoEventtickets_WC::Instance();
211 }
212 public function getTicketHandler() {
213 $this->loadOnce('sasoEventtickets_Ticket');
214 return sasoEventtickets_Ticket::Instance($_SERVER["REQUEST_URI"]);
215 }
216 public function getTicketDesignerHandler($template="") {
217 $this->loadOnce('sasoEventtickets_TicketDesigner');
218 return sasoEventtickets_TicketDesigner::Instance($this, $template);
219 }
220 public function getTicketBadgeHandler() {
221 $this->loadOnce('sasoEventtickets_TicketBadge');
222 return sasoEventtickets_TicketBadge::Instance();
223 }
224 public function getTicketQRHandler() {
225 $this->loadOnce('sasoEventtickets_TicketQR');
226 return sasoEventtickets_TicketQR::Instance();
227 }
228 public function getAuthtokenHandler() {
229 $this->loadOnce('sasoEventtickets_Authtoken');
230 return sasoEventtickets_Authtoken::Instance();
231 }
232 public function getSeating() {
233 $this->loadOnce('sasoEventtickets_Seating');
234 return sasoEventtickets_Seating::Instance($this);
235 }
236 /** @var bool Flag: old premium detected but not loaded due to incompatibility */
237 private bool $_oldPremiumDetected = false;
238
239 public function isOldPremiumDetected(): bool {
240 return $this->_oldPremiumDetected;
241 }
242
243 public function getPremiumFunctions() {
244 if ($this->_isPrem == null && $this->PREMFUNCTIONS == null) {
245 $this->_isPrem = false;
246 $this->PREMFUNCTIONS = new sasoEventtickets_fakeprem();
247
248 // Minimum required premium version — older versions are incompatible and will not be loaded
249 $min_premium_version = '1.6.0';
250
251 if (class_exists('sasoEventtickets_PremiumFunctions')) {
252 if (defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION')
253 && version_compare(SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION, $min_premium_version, '<')) {
254 // Old premium detected — do NOT load, run as free to prevent fatal errors
255 $this->_oldPremiumDetected = true;
256 } else {
257 try {
258 $this->PREMFUNCTIONS = new sasoEventtickets_PremiumFunctions($this, plugin_dir_path(__FILE__), $this->_prefix, $this->getDB());
259 $this->_isPrem = $this->PREMFUNCTIONS->isPremium();
260 } catch (\Throwable $e) {
261 $this->PREMFUNCTIONS = new sasoEventtickets_fakeprem();
262 $this->_isPrem = false;
263 error_log('Event Tickets: Premium load failed: ' . $e->getMessage());
264 }
265 }
266 } else {
267 // Premium class not yet available — basic plugin loaded before premium.
268 // Defer premium setup until all plugins are loaded (plugins_loaded hook).
269 $premPluginFolder = $this->getPremiumPluginFolder();
270 if (!empty($premPluginFolder)) {
271 add_action('plugins_loaded', [$this, '_lateLoadPremium'], 1);
272 }
273 }
274 }
275 return $this->PREMFUNCTIONS;
276 }
277 /**
278 * Late-load premium plugin when basic loaded before premium (plugin loading order).
279 * Hooked to plugins_loaded with priority 1 so it runs before WooCommercePluginLoaded (priority 20).
280 */
281 public function _lateLoadPremium(): void {
282 if (!class_exists('sasoEventtickets_PremiumFunctions')) {
283 return; // Premium plugin not installed/active
284 }
285 if ($this->PREMFUNCTIONS instanceof sasoEventtickets_PremiumFunctions) {
286 return; // Already loaded
287 }
288
289 $min_premium_version = '1.6.0';
290 if (defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION')
291 && version_compare(SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION, $min_premium_version, '<')) {
292 $this->_oldPremiumDetected = true;
293 return;
294 }
295
296 try {
297 $this->PREMFUNCTIONS = new sasoEventtickets_PremiumFunctions($this, plugin_dir_path(__FILE__), $this->_prefix, $this->getDB());
298 $this->_isPrem = $this->PREMFUNCTIONS->isPremium();
299
300 if (method_exists($this->PREMFUNCTIONS, 'initHandlers')) {
301 $this->PREMFUNCTIONS->initHandlers();
302 }
303 } catch (\Throwable $e) {
304 // Premium loading failed — degrade gracefully to free mode
305 $this->PREMFUNCTIONS = new sasoEventtickets_fakeprem();
306 $this->_isPrem = false;
307 error_log('Event Tickets: Premium late-load failed: ' . $e->getMessage());
308 }
309 }
310
311 public function getPremiumPluginFolder(): string {
312 $plugins = get_option('active_plugins', []);
313 $premiumFile = "";
314 foreach($plugins as $plugin) {
315 if (strpos(" ".$plugin, $this->_premium_plugin_name) > 0) {
316 $premiumFile = plugin_dir_path($plugin);
317 break;
318 }
319 }
320 return $premiumFile;
321 }
322 public function isPremium() {
323 if ($this->_isPrem === null) {
324 $this->getPremiumFunctions();
325 // If PREMFUNCTIONS already existed, getPremiumFunctions() skipped re-evaluation.
326 // Re-query the premium plugin directly.
327 if ($this->_isPrem === null && $this->PREMFUNCTIONS !== null) {
328 $this->_isPrem = ($this->PREMFUNCTIONS instanceof sasoEventtickets_PremiumFunctions)
329 ? $this->PREMFUNCTIONS->isPremium()
330 : false;
331 }
332 }
333
334 // Eigene Verifikation: auch wenn Premium-Plugin "ja" sagt,
335 // Basic Plugin prüft den gespeicherten Lizenzstatus selbst.
336 // Rekursionsschutz: isPremium() -> getTicketHandler() -> Konstruktor -> getOptions() -> isPremium()
337 if ($this->_isPrem === true && !$this->_isCheckingSubscription) {
338 $this->_isCheckingSubscription = true;
339 try {
340 if (!$this->getTicketHandler()->isSubscriptionActive()) {
341 $this->_isPrem = false;
342 }
343 } finally {
344 $this->_isCheckingSubscription = false;
345 }
346 }
347
348 return $this->_isPrem;
349 }
350 /**
351 * Invalidate the cached premium status so the next isPremium() call re-evaluates.
352 */
353 public function invalidatePremiumCache(): void {
354 $this->_isPrem = null;
355 }
356 public function getPrefix() {
357 return $this->_prefix;
358 }
359 public function getMV() {
360 $v = ['storeip'=>false,'allowuserreg'=>false,'codes_total'=>0x13,'codes'=>0x12,'lists'=>5,'authtokens_total'=>3];
361 $v["codes"] = (int) hexdec(0x80 / 0x002) / 2;
362 $v["codes_total"] = (int) hexdec(0x80 / 0x002) / 2;
363 $v["seatingplans"] = (int) (0x04 >> 0x02);
364 $v["seats_per_plan"] = (int) (0x50 >> 0x02);
365 return $v;
366 }
367 public function listener_upgrader_process_complete( $upgrader_object, $options ) {
368 $current_plugin_path_name = plugin_basename( __FILE__ );
369 if ($options['action'] == 'update' && $options['type'] == 'plugin' ) {
370 if (isset($options['plugins'])) {
371 foreach($options['plugins'] as $each_plugin) {
372 if ($each_plugin==$current_plugin_path_name) {
373 // .......................... YOUR CODES .............
374 }
375 }
376 }
377 }
378 do_action( $this->_do_action_prefix.'main_listener_upgrader_process_complete' );
379 }
380 /**
381 * check for ticket detail page request
382 */
383 public function wc_checkTicketDetailPage() {
384 if( is_404() ){
385 include_once("SASO_EVENTTICKETS.php");
386 // /wp-content/plugins/event-tickets-with-ticket-scanner/ticket/
387 $p = $this->getCore()->getTicketURLPath(true);
388 $t = explode("/", $_SERVER["REQUEST_URI"]);
389 if (count($t) > 1) {
390 if ($t[count($t)-2] != "scanner") {
391 if(substr($_SERVER["REQUEST_URI"], 0, strlen($p)) == $p) {
392 $this->getTicketHandler()->initFilterAndActions();
393 } else {
394 $wcTicketCompatibilityModeURLPath = trim($this->getOptions()->getOptionValue('wcTicketCompatibilityModeURLPath'));
395 $wcTicketCompatibilityModeURLPath = trim(trim($wcTicketCompatibilityModeURLPath, "/"));
396 if (!empty($wcTicketCompatibilityModeURLPath)) {
397 $uri = trim($_SERVER["REQUEST_URI"]);
398 if (!empty($uri)) {
399 $pos = strpos($uri, $wcTicketCompatibilityModeURLPath);
400 if ($pos > 0) {
401 $this->getTicketHandler()->initFilterAndActions();
402 }
403 }
404 }
405 }
406 }
407
408 if ($t[count($t)-2] == "scanner") {
409 if(substr($_SERVER["REQUEST_URI"], 0, strlen($p)) == $p) {
410 //$this->replacingShortcodeTicketScanner();
411 $this->getTicketHandler()->initFilterAndActionsTicketScanner();
412 } else {
413 $wcTicketCompatibilityModeURLPath = trim($this->getOptions()->getOptionValue('wcTicketCompatibilityModeURLPath'));
414 $wcTicketCompatibilityModeURLPath = trim(trim($wcTicketCompatibilityModeURLPath, "/"));
415 if (!empty($wcTicketCompatibilityModeURLPath)) {
416 $uri = trim($_SERVER["REQUEST_URI"]);
417 if (!empty($uri)) {
418 $pos = strpos($_SERVER["REQUEST_URI"], $wcTicketCompatibilityModeURLPath."/scanner/");
419 if ($pos > 0) {
420 $this->getTicketHandler()->initFilterAndActionsTicketScanner();
421 }
422 }
423 }
424 }
425 }
426 }
427 } // endif 404
428 }
429 private function init_frontend() {
430 add_shortcode($this->_shortcode, [$this, 'replacingShortcode']);
431 add_shortcode($this->_shortcode_mycode, [$this, 'replacingShortcodeMyCode']);
432 add_shortcode($this->_shortcode_ticket_scanner, [$this, 'replacingShortcodeTicketScanner']);
433 add_shortcode($this->_shortcode_eventviews, [$this, 'replacingShortcodeEventViews']);
434 add_shortcode($this->_shortcode_feature_list, [$this, 'replacingShortcodeFeatureList']);
435 add_shortcode($this->_shortcode_ticket_detail, [$this, 'replacingShortcodeTicketDetail']);
436 do_action( $this->_do_action_prefix.'main_init_frontend' );
437 }
438 private function init_backend() {
439 add_action('admin_menu', [$this, 'register_options_page']);
440 register_activation_hook(__FILE__, [$this, 'plugin_activated']);
441 register_deactivation_hook( __FILE__, [$this, 'plugin_deactivated'] );
442 //register_uninstall_hook( __FILE__, 'sasoEventticketsDB::plugin_uninstall' ); // MUSS NOCH GETESTE WERDEN
443 add_action( 'plugins_loaded', [$this, 'plugins_loaded'] );
444 add_action( 'show_user_profile', [$this, 'show_user_profile'] );
445 add_action( 'admin_init', [$this, 'handleFormatWarningDismiss'] );
446 add_action( 'admin_notices', [$this, 'showSubscriptionWarning'] );
447 add_action( 'admin_notices', [$this, 'showOutdatedPremiumWarning'] );
448 add_action( 'admin_notices', [$this, 'showFormatWarning'] );
449 add_action( 'admin_notices', [$this, 'showPhpVersionWarning'] );
450
451 if (basename($_SERVER['SCRIPT_NAME']) == "admin-ajax.php") {
452 add_action('wp_ajax_'.$this->_prefix.'_executeAdminSettings', [$this,'executeAdminSettings_a'], 10, 0);
453 add_action('wp_ajax_'.$this->_prefix.'_executeSeatingAdmin', [$this,'executeSeatingAdmin_a'], 10, 0);
454 }
455
456 add_action('admin_init', [$this, 'periodicLicenseCheck']);
457
458 do_action( $this->_do_action_prefix.'main_init_backend' );
459 }
460
461 /**
462 * Periodic license check on admin page loads.
463 * Runs at most once per 24h to catch cases where WP Cron is disabled.
464 */
465 public function periodicLicenseCheck(): void {
466 // Also run when premium plugin is installed with a serial key but isPremium() is false
467 // This breaks the deadlock where isPremium()=false prevents the license check from ever running
468 $hasPremiumPlugin = class_exists('sasoEventtickets_PremiumFunctions');
469 $hasSerial = !empty(trim(get_option("saso-event-tickets-premium_serial", "")));
470 if (!$this->isPremium() && !($hasPremiumPlugin && $hasSerial)) return;
471
472 $info = $this->getTicketHandler()->get_expiration();
473 $last_run = intval($info['last_run']);
474
475 // Maximal 1x pro 24h, nicht bei jedem Page Load
476 // In recovery mode (not premium but has serial), check more frequently (every 1h)
477 $interval = $this->isPremium() ? 86400 : 3600;
478 if ($last_run > 0 && (time() - $last_run) < $interval) return;
479
480 // Check ausführen
481 $this->getTicketHandler()->checkForPremiumSerialExpiration();
482
483 // isPremium Cache invalidieren damit neuer Wert gilt
484 $this->invalidatePremiumCache();
485 }
486 public function WooCommercePluginLoaded() {
487 // DON'T load WC here - let relay functions do lazy loading
488 //$this->getWC(); // um die wc handler zu laden
489 add_action('woocommerce_review_order_after_cart_contents', [$this, 'relay_woocommerce_review_order_after_cart_contents']);
490 add_action('woocommerce_checkout_process', [$this, 'relay_woocommerce_checkout_process']);
491 add_action('woocommerce_before_cart_table', [$this, 'relay_woocommerce_before_cart_table']);
492 add_action('woocommerce_cart_updated', [$this, 'relay_woocommerce_cart_updated']);
493 add_filter('woocommerce_email_attachments', [$this, 'relay_woocommerce_email_attachments'], 10, 3);
494 add_action('woocommerce_checkout_create_order_line_item', [$this, 'relay_woocommerce_checkout_create_order_line_item'], 20, 4 );
495 add_action('woocommerce_check_cart_items', [$this, 'relay_woocommerce_check_cart_items'] );
496 add_action('woocommerce_new_order', [$this, 'relay_woocommerce_new_order'], 10, 1);
497 add_action('woocommerce_checkout_update_order_meta', [$this, 'relay_woocommerce_checkout_update_order_meta'], 20, 2);
498 add_action('woocommerce_order_status_changed', [$this, 'relay_woocommerce_order_status_changed'], 10, 3);
499 add_filter('woocommerce_order_item_display_meta_key', [$this, 'relay_woocommerce_order_item_display_meta_key'], 20, 3 );
500 add_filter('woocommerce_order_item_display_meta_value', [$this, 'relay_woocommerce_order_item_display_meta_value'], 20, 3);
501 add_action('wpo_wcpdf_after_item_meta', [$this, 'relay_wpo_wcpdf_after_item_meta'], 20, 3 );
502 add_action('woocommerce_order_item_meta_start', [$this, 'relay_woocommerce_order_item_meta_start'], 201, 4);
503 add_action('woocommerce_product_after_variable_attributes', [$this, 'relay_woocommerce_product_after_variable_attributes'], 10, 3);
504 add_action('woocommerce_save_product_variation',[$this, 'relay_woocommerce_save_product_variation'], 10 ,2 );
505 add_action('woocommerce_email_order_meta', [$this, 'relay_woocommerce_email_order_meta'], 10, 4 );
506 add_action('woocommerce_thankyou', [$this, 'relay_woocommerce_thankyou'], 5);
507 if (wp_doing_ajax()) {
508 // erlaube ajax nonpriv und registriere handler
509 add_action('wp_ajax_nopriv_'.$this->getPrefix().'_executeWCFrontend', [$this,'relay_executeWCFrontend']); // nicht angemeldete user, sollen eine antwort erhalten
510 add_action('wp_ajax_'.$this->getPrefix().'_executeWCFrontend', [$this,'relay_executeWCFrontend']); // nicht angemeldete user, sollen eine antwort erhalten
511 // Seating Frontend AJAX (seat selection in shop)
512 add_action('wp_ajax_nopriv_'.$this->getPrefix().'_executeSeatingFrontend', [$this,'relay_executeSeatingFrontend']);
513 add_action('wp_ajax_'.$this->getPrefix().'_executeSeatingFrontend', [$this,'relay_executeSeatingFrontend']);
514 }
515 if (is_admin()) {
516 add_action('woocommerce_delete_order', [$this, 'relay_woocommerce_delete_order'], 10, 1 );
517 add_action('woocommerce_delete_order_item', [$this, 'relay_woocommerce_delete_order_item'], 20, 1);
518 add_action('woocommerce_pre_delete_order_refund', [$this, 'relay_woocommerce_pre_delete_order_refund'], 10, 3);
519 add_action('woocommerce_delete_order_refund', [$this, 'relay_woocommerce_delete_order_refund'], 10, 1 );
520 add_action('woocommerce_order_partially_refunded', [$this, 'relay_woocommerce_order_partially_refunded'], 10, 2);
521 add_filter('woocommerce_product_data_tabs', [$this, 'relay_woocommerce_product_data_tabs'], 98 );
522 add_action('woocommerce_product_data_panels', [$this, 'relay_woocommerce_product_data_panels'] );
523 add_action('woocommerce_process_product_meta', [$this, 'relay_woocommerce_process_product_meta'], 10, 2 );
524 add_action('add_meta_boxes', [$this, 'relay_add_meta_boxes'], 10, 2);
525 add_filter('manage_edit-product_columns', [$this, 'relay_manage_edit_product_columns']);
526 add_action('manage_product_posts_custom_column', [$this, 'relay_manage_product_posts_custom_column'], 2);
527 add_filter("manage_edit-product_sortable_columns", [$this, 'relay_manage_edit_product_sortable_columns']);
528 } else {
529 add_action('woocommerce_single_product_summary', [$this, 'relay_woocommerce_single_product_summary']);
530 }
531
532 // set routing -- NEEDS to be replaced by add_rewrite_rule later
533 add_action( 'template_redirect', [$this, 'wc_checkTicketDetailPage'], 1 );
534 //$this->wc_checkTicketDetailPage();
535 add_action('rest_api_init', function () {
536 SASO_EVENTTICKETS::setRestRoutesTicket();
537 });
538
539 add_action('woocommerce_after_shop_loop_item', [$this, 'relay_woocommerce_after_shop_loop_item'], 9); // with 9 we are just before the add to cart button
540 add_filter('woocommerce_add_to_cart_validation', [$this, 'relay_woocommerce_add_to_cart_validation'], 10, 3);
541 add_filter('woocommerce_add_cart_item_data', [$this, 'relay_woocommerce_add_cart_item_data'], 10, 3);
542 add_action('woocommerce_add_to_cart', [$this, 'relay_woocommerce_add_to_cart'], 10, 6);
543 add_action('woocommerce_cart_item_removed', [$this, 'relay_woocommerce_cart_item_removed'], 10, 2);
544 add_action('woocommerce_after_cart_item_quantity_update', [$this, 'relay_woocommerce_after_cart_item_quantity_update'], 10, 4);
545 add_filter('woocommerce_update_cart_validation', [$this, 'relay_woocommerce_update_cart_validation'], 10, 4);
546 add_action('woocommerce_before_add_to_cart_button', [$this, 'relay_woocommerce_before_add_to_cart_button'], 15);
547
548 do_action( $this->_do_action_prefix.'main_WooCommercePluginLoaded' );
549 }
550 public function relay_woocommerce_after_shop_loop_item() {
551 $this->getWC()->getFrontendManager()->woocommerce_after_shop_loop_item_handler();
552 }
553 public function relay_woocommerce_add_to_cart_validation() {
554 $args = func_get_args();
555 return $this->getWC()->getFrontendManager()->woocommerce_add_to_cart_validation_handler(...$args);
556 }
557 public function relay_woocommerce_add_cart_item_data() {
558 $args = func_get_args();
559 return $this->getWC()->getFrontendManager()->woocommerce_add_cart_item_data_handler(...$args);
560 }
561 public function relay_woocommerce_add_to_cart() {
562 $args = func_get_args();
563 return $this->getWC()->getFrontendManager()->woocommerce_add_to_cart_handler(...$args);
564 }
565 public function relay_woocommerce_cart_item_removed() {
566 $args = func_get_args();
567 $this->getWC()->getFrontendManager()->woocommerce_cart_item_removed_handler(...$args);
568 }
569 public function relay_woocommerce_after_cart_item_quantity_update() {
570 $args = func_get_args();
571 $this->getWC()->getFrontendManager()->woocommerce_after_cart_item_quantity_update_handler(...$args);
572 }
573 public function relay_woocommerce_update_cart_validation() {
574 $args = func_get_args();
575 return $this->getWC()->getFrontendManager()->woocommerce_update_cart_validation_handler(...$args);
576 }
577 public function relay_woocommerce_before_add_to_cart_button() {
578 $this->getWC()->getFrontendManager()->woocommerce_before_add_to_cart_button_handler();
579 }
580 public function relay_woocommerce_review_order_after_cart_contents() {
581 $this->getWC()->getFrontendManager()->woocommerce_review_order_after_cart_contents();
582 }
583 public function relay_woocommerce_checkout_process() {
584 $this->getWC()->getFrontendManager()->woocommerce_checkout_process();
585 }
586 public function relay_woocommerce_before_cart_table() {
587 $this->getWC()->getFrontendManager()->woocommerce_before_cart_table();
588 }
589 public function relay_woocommerce_cart_updated() {
590 $this->getWC()->getFrontendManager()->woocommerce_cart_updated_handler();
591 }
592 public function relay_woocommerce_email_attachments() {
593 $args = func_get_args();
594 return $this->getWC()->getEmailHandler()->woocommerce_email_attachments(...$args);
595 }
596 public function relay_woocommerce_checkout_create_order_line_item() {
597 $args = func_get_args();
598 return $this->getWC()->getOrderManager()->woocommerce_checkout_create_order_line_item(...$args);
599 }
600 public function relay_woocommerce_check_cart_items() {
601 $this->getWC()->getFrontendManager()->woocommerce_check_cart_items();
602 }
603 public function relay_woocommerce_new_order() {
604 $args = func_get_args();
605 return $this->getWC()->getOrderManager()->woocommerce_new_order(...$args);
606 }
607 public function relay_woocommerce_checkout_update_order_meta() {
608 $args = func_get_args();
609 return $this->getWC()->getOrderManager()->woocommerce_checkout_update_order_meta(...$args);
610 }
611 public function relay_executeWCFrontend() {
612 return $this->getWC()->getFrontendManager()->executeWCFrontend();
613 }
614 public function relay_executeSeatingFrontend() {
615 return $this->getSeating()->getFrontendManager()->executeSeatingFrontend();
616 }
617 public function relay_woocommerce_delete_order() {
618 $args = func_get_args();
619 $this->getWC()->getOrderManager()->woocommerce_delete_order(...$args);
620 }
621 public function relay_woocommerce_delete_order_item() {
622 $args = func_get_args();
623 $this->getWC()->getOrderManager()->woocommerce_delete_order_item(...$args);
624 }
625 public function relay_woocommerce_pre_delete_order_refund() {
626 $args = func_get_args();
627 $this->getWC()->getOrderManager()->woocommerce_pre_delete_order_refund(...$args);
628 }
629 public function relay_woocommerce_delete_order_refund() {
630 $args = func_get_args();
631 $this->getWC()->getOrderManager()->woocommerce_delete_order_refund(...$args);
632 }
633 public function relay_woocommerce_product_data_tabs() {
634 $args = func_get_args();
635 return $this->getWC()->getProductManager()->woocommerce_product_data_tabs(...$args);
636 }
637 public function relay_woocommerce_product_data_panels() {
638 $this->getWC()->getProductManager()->woocommerce_product_data_panels();
639 }
640 public function relay_woocommerce_process_product_meta() {
641 $args = func_get_args();
642 $this->getWC()->getProductManager()->woocommerce_process_product_meta(...$args);
643 }
644 public function relay_add_meta_boxes(...$args) {
645 $this->getWC()->add_meta_boxes(...$args);
646 }
647 public function relay_manage_edit_product_columns() {
648 $args = func_get_args();
649 return $this->getWC()->getProductManager()->manage_edit_product_columns(...$args);
650 }
651 public function relay_manage_product_posts_custom_column() {
652 $args = func_get_args();
653 $this->getWC()->getProductManager()->manage_product_posts_custom_column(...$args);
654 }
655 public function relay_manage_edit_product_sortable_columns() {
656 $args = func_get_args();
657 return $this->getWC()->getProductManager()->manage_edit_product_sortable_columns(...$args);
658 }
659 public function relay_woocommerce_single_product_summary() {
660 $this->getWC()->getFrontendManager()->woocommerce_single_product_summary();
661 }
662 public function relay_woocommerce_order_status_changed() {
663 $args = func_get_args();
664 $this->getWC()->getOrderManager()->woocommerce_order_status_changed(...$args);
665 }
666 public function relay_woocommerce_order_partially_refunded() {
667 $args = func_get_args();
668 $this->getWC()->getOrderManager()->woocommerce_order_partially_refunded(...$args);
669 }
670 public function relay_woocommerce_order_item_display_meta_key() {
671 $args = func_get_args();
672 return $this->getWC()->getOrderManager()->woocommerce_order_item_display_meta_key(...$args);
673 }
674 public function relay_woocommerce_order_item_display_meta_value() {
675 $args = func_get_args();
676 return $this->getWC()->getOrderManager()->woocommerce_order_item_display_meta_value(...$args);
677 }
678 public function relay_wpo_wcpdf_after_item_meta() {
679 $args = func_get_args();
680 $this->getWC()->getOrderManager()->wpo_wcpdf_after_item_meta(...$args);
681 }
682 public function relay_woocommerce_order_item_meta_start() {
683 $args = func_get_args();
684 $this->getWC()->getOrderManager()->woocommerce_order_item_meta_start(...$args);
685 }
686 public function relay_woocommerce_product_after_variable_attributes() {
687 $args = func_get_args();
688 $this->getWC()->getProductManager()->woocommerce_product_after_variable_attributes(...$args);
689 }
690 public function relay_woocommerce_save_product_variation() {
691 $args = func_get_args();
692 $this->getWC()->getProductManager()->woocommerce_save_product_variation(...$args);
693 }
694 public function relay_woocommerce_email_order_meta() {
695 $args = func_get_args();
696 $this->getWC()->getEmailHandler()->woocommerce_email_order_meta(...$args);
697 }
698 public function relay_woocommerce_thankyou() {
699 $args = func_get_args();
700 $this->getWC()->getFrontendManager()->woocommerce_thankyou(...$args);
701 }
702 public function relay_sasoEventtickets_cronjob_daily() {
703 $this->getTicketHandler()->cronJobDaily();
704 }
705
706 public function plugin_deactivated() {
707 $this->cronjob_daily_deactivate();
708 $this->getDB();
709 sasoEventticketsDB::plugin_deactivated();
710 do_action( $this->_do_action_prefix.'main_plugin_deactivated' );
711 }
712 public function plugin_uninstall() {
713 $this->getDB();
714 sasoEventticketsDB::plugin_uninstall();
715 $this->getAdmin();
716 sasoEventtickets_AdminSettings::plugin_uninstall();
717 do_action( $this->_do_action_prefix.'main_WooCommercePluginLoaded' );
718 }
719 public function plugin_activated($is_network_wide=false) { // und auch für updates, macht es einfacher
720 $this->getDB(); // um installiere Tabellen auszuführen
721 update_option('SASO_EVENTTICKETS_PLUGIN_VERSION', SASO_EVENTTICKETS_PLUGIN_VERSION);
722 $this->getAdmin()->generateFirstCodeList();
723 $this->cronjob_daily_activate();
724 do_action( $this->_do_action_prefix.'activated' );
725 do_action( $this->_do_action_prefix.'main_plugin_activated' );
726 }
727 public function plugins_loaded() {
728 if (SASO_EVENTTICKETS_PLUGIN_VERSION !== get_option('SASO_EVENTTICKETS_PLUGIN_VERSION', '')) $this->plugin_activated(); // vermutlich wurde die aktivierung übersprungen, bei änderungen direkt an den files
729 }
730 public function initialize_plugin() {
731 $this->getDB(); // um installiere Tabellen auszuführen
732 do_action( $this->_do_action_prefix.'initialized' );
733 do_action( $this->_do_action_prefix.'main_initialize_plugin' );
734 }
735 function show_user_profile($profileuser) {
736 $this->getAdmin()->show_user_profile($profileuser);
737 do_action( $this->_do_action_prefix.'main_show_user_profile' );
738 }
739 function register_options_page() {
740 $allowed = $this->isUserAllowedToAccessAdminArea();
741 $allowed = apply_filters( $this->_add_filter_prefix.'main_options_page', $allowed );
742 if ($allowed) {
743 add_options_page(__('Event Tickets', 'event-tickets-with-ticket-scanner'), 'Event Tickets', 'manage_options', 'event-tickets-with-ticket-scanner', [$this,'options_page']);
744 add_menu_page( __('Event Tickets', 'event-tickets-with-ticket-scanner'), 'Event Tickets', 'read', 'event-tickets-with-ticket-scanner', [$this,'options_page'], plugins_url( "",__FILE__ )."/img/icon_event-tickets-with-ticket-scanner_18px.gif", null );
745 }
746 do_action( $this->_do_action_prefix.'main_register_options_page' );
747 }
748
749 function options_page() {
750 $allowed = $this->isUserAllowedToAccessAdminArea();
751 $allowed = apply_filters( $this->_add_filter_prefix.'main_options_page', $allowed );
752 if ( !$allowed ) {
753 wp_die( __( 'You do not have sufficient permissions to access this page.', 'event-tickets-with-ticket-scanner' ) );
754 }
755
756 wp_enqueue_style("wp-jquery-ui-dialog");
757
758 $js_url = "jquery.qrcode.min.js?_v=".$this->_js_version;
759 wp_register_script('ajax_script2', plugins_url( "3rd/".$js_url,__FILE__ ), array('jquery', 'jquery-ui-dialog'));
760 wp_enqueue_script('ajax_script2');
761
762 wp_enqueue_media(); // um die js wp.media lib zu laden
763
764 // einbinden das js starter skript
765 $js_url = $this->_js_file."?_v=".$this->_js_version;
766 if (defined( 'WP_DEBUG')) $js_url .= '&debug=1';
767 wp_register_script('ajax_script_backend', plugins_url( $js_url,__FILE__ ), array('jquery', 'jquery-ui-dialog', 'wp-i18n'));
768 wp_enqueue_script('ajax_script_backend');
769 wp_set_script_translations('ajax_script_backend', 'event-tickets-with-ticket-scanner', __DIR__.'/languages');
770
771 // per script eine variable einbinden, die url hat den wp-admin prefix
772 // damit im backend.js dann die richtige callback url genutzt werden kann
773 $vars = array(
774 '_plugin_home_url' =>plugins_url( "",__FILE__ ),
775 '_plugin_version' => $this->getPluginVersion(),
776 '_action' => $this->_prefix.'_executeAdminSettings',
777 '_max'=>$this->getBase()->getMaxValues(),
778 '_isPremium'=>$this->isPremium(),
779 '_isUserLoggedin'=>is_user_logged_in(),
780 '_premJS'=>$this->isPremium() && method_exists($this->getPremiumFunctions(), "getJSBackendFile") ? $this->getPremiumFunctions()->getJSBackendFile() : '',
781 'url' => admin_url( 'admin-ajax.php' ),
782 'ticket_url' => $this->getCore()->getTicketURLPath(),
783 'nonce' => wp_create_nonce( $this->_js_nonce ),
784 'ajaxActionPrefix' => $this->_prefix,
785 'divPrefix' => $this->_prefix,
786 'divId' => $this->_divId,
787 'jsFiles' => plugins_url( 'backend.js?_v='.$this->_js_version.'&_f='.filemtime(__DIR__.'/backend.js'),__FILE__ )
788 );
789 $vars = apply_filters( $this->_add_filter_prefix.'main_options_page', $vars );
790 wp_localize_script(
791 'ajax_script_backend',
792 'Ajax_'.$this->_prefix, // name der injected variable
793 $vars
794 );
795
796 do_action( $this->_do_action_prefix.'main_options_page' );
797
798 $versions = $this->getPluginVersions();
799 $versions_tail = $versions['basic'].($versions['premium'] != "" ? ', Premium: '.$versions['premium'] : '');
800 $version_tail_add = "";
801 if ($versions['debug'] != "") $version_tail_add .= 'DEBUG: '.$versions['debug'].', LANG: '.determine_locale();
802 ?>
803 <div class="event-tickets-with-ticket-scanner-admin-page">
804 <div class="event-tickets-with-ticket-scanner-header">
805 <div class="event-tickets-with-ticket-scanner-header-left">
806 <img src="<?php echo plugins_url( "",__FILE__ ); ?>/img/logo_event-tickets-with-ticket-scanner.gif"
807 alt="Event Tickets"
808 class="event-tickets-with-ticket-scanner-header-logo">
809
810 <div class="event-tickets-with-ticket-scanner-header-title">
811 <div class="event-tickets-with-ticket-scanner-header-name">
812 Event Tickets with Ticket Scanner
813 </div>
814 <div class="event-tickets-with-ticket-scanner-header-meta">
815 <?php esc_html_e('Version', 'event-tickets-with-ticket-scanner'); ?>: <?php echo $versions_tail; ?> <?php echo $version_tail_add; ?>
816 </div>
817 </div>
818 </div>
819
820 <div class="event-tickets-with-ticket-scanner-header-right" id="event-tickets-with-ticket-scanner-header-actions">
821 <!-- Button kommt via JS -->
822 </div>
823 </div>
824
825 <div style="clear:both;" data-id="plugin_addons"></div>
826 <div style="clear:both;" data-id="plugin_info_area"></div>
827 <div style="clear:both;" id="<?php echo esc_attr($this->_divId); ?>">...loading...</div>
828 <div style="margin-top:100px;">
829 <hr>
830 <a name="shortcodedetails"></a>
831 <h3>Documentation</h3>
832 <p><span class="dashicons dashicons-external"></span><a href="https://vollstart.com/event-tickets-with-ticket-scanner/docs/" target="_blank">Click here, to visit the documentation of this plugin.</a></p>
833 <h3><?php esc_html_e('Plugin Rating', 'event-tickets-with-ticket-scanner'); ?></h3>
834 <p><?php esc_html_e('If you like our plugin, then please give us a', 'event-tickets-with-ticket-scanner'); ?> <a target="_blank" href="https://wordpress.org/support/plugin/event-tickets-with-ticket-scanner/reviews?rate=5#new-post">�
835
836
837
838
839 5-Star Rating</a>.</p>
840 <h3><?php esc_html_e('Ticket Sale option', 'event-tickets-with-ticket-scanner'); ?></h3>
841 <p><?php esc_html_e('You can use this plugin to sell tickets and even redeem them. Check out the documentation for', 'event-tickets-with-ticket-scanner'); ?> <a target="_blank" href="https://vollstart.com/event-tickets-with-ticket-scanner/docs/#ticket"><?php esc_html_e('more details here', 'event-tickets-with-ticket-scanner'); ?></a>.</p>
842 <h3><?php esc_html_e('Premium Homepage', 'event-tickets-with-ticket-scanner'); ?></h3>
843 <p><?php esc_html_e('You can find more details about the', 'event-tickets-with-ticket-scanner'); ?> <a target="_blank" href="https://vollstart.com/event-tickets-with-ticket-scanner/"><?php esc_html_e('premium version here', 'event-tickets-with-ticket-scanner'); ?></a>.</p>
844 <!--
845 <h3>Shortcode parameter In- & Output</h3>
846 <a href="https://vollstart.com/event-tickets-with-ticket-scanner/docs/" target="_blank">Click here for more help about the options</a>
847 <p>You can use your own HTML input, output and trigger component. If you add the parameters (all 3 mandatory to use this feature), then the default input area will not be rendered.</p>
848 <ul>
849 <li><b>inputid</b><br>inputid="html-element-id". The value of this component will be taken. It need to be an HTML input element. We will access the value-parameter of it.</li>
850 <li><b>triggerid</b><br>triggerid="html-element-id". The onclick event of this component will be replaced by our function to call the server validation with the code.</li>
851 <li><b>outputid</b><br>outputid="html-element-id". The content of this component will be replaced by the server result after the check . We will use the innerHTML property of it, so use a DIV, SPAN, TD or similar for best results.</li>
852 </ul>
853 <h3>Shortcode parameter Javascript</h3>
854 <p>You can add your Javascript function name. Both parameters are optional and not required. If functions will be called before the code is sent to the server or displaying the result.</p>
855 <ul>
856 <li><b>jspre</b><br>jspre="function-name". The function will be called. The input parameter will be the code. If your function returns a value, than this returned value will be used otherwise the entered code will be used.</li>
857 <li><b>jsafter</b><br>jsafter="function-name". The function will be called. The input parameter will be the result JSON object from the server.</li>
858 </ul>
859 -->
860 <h3><?php esc_html_e('Shortcode to display the event calendar form within a page', 'event-tickets-with-ticket-scanner'); ?></h3>
861 <b>[<?php echo esc_html($this->_shortcode_eventviews); ?>]</b>
862 <p><?php esc_html_e('The event calendar form will be displayed. You can add the following parameters to change the output:', 'event-tickets-with-ticket-scanner'); ?></p>
863 <ul>
864 <!--<li>view<br>Values: calendar, list or calendar|list<br>Default: list</li>-->
865 <li>months_to_show<br>Values can be a number higher than 0. Default: 3</li>
866 </ul>
867 <p>CSS file: <a href="<?php echo plugins_url( "",__FILE__ ); ?>/css/calendar.css" target="_blank">calendar.css</a></p>
868 <h3><?php esc_html_e('Shortcode to display the assigned tickets and codes of an user within a page', 'event-tickets-with-ticket-scanner'); ?></h3>
869 <b>[<?php echo esc_html($this->_shortcode_mycode); ?>]</b>
870 <p><?php esc_html_e('Displays tickets assigned to the current logged-in user (default) or tickets from a specific order.', 'event-tickets-with-ticket-scanner'); ?></p>
871 <ul>
872 <li><b>order_id</b> - <?php esc_html_e('Show tickets from a specific order instead of user tickets. Security: User must own the order or have valid order key in URL.', 'event-tickets-with-ticket-scanner'); ?><br>
873 <?php esc_html_e('Example:', 'event-tickets-with-ticket-scanner'); ?> [<?php echo esc_html($this->_shortcode_mycode); ?> order_id="123"]</li>
874 <li><b>format</b> - <?php esc_html_e('Output format. Values: json', 'event-tickets-with-ticket-scanner'); ?></li>
875 <li><b>display</b> - <?php esc_html_e('Fields to show (comma-separated). Values: codes, validation, user, used, confirmedCount, woocommerce, wc_rp, wc_ticket', 'event-tickets-with-ticket-scanner'); ?></li>
876 <li><b>download_all_pdf</b> - <?php esc_html_e('Show download button for all tickets as one PDF. Values: true/false', 'event-tickets-with-ticket-scanner'); ?></li>
877 </ul>
878 <p>
879 <?php esc_html_e('Example with JSON output:', 'event-tickets-with-ticket-scanner'); ?> [<?php echo esc_html($this->_shortcode_mycode); ?> format="json" display="code,wc_ticket"]
880 </p>
881 <h3><?php esc_html_e('Shortcode to display the ticket scanner within a page', 'event-tickets-with-ticket-scanner'); ?></h3>
882 <?php esc_html_e('Useful if you cannot open the ticket scanner due to security issues.', 'event-tickets-with-ticket-scanner'); ?><br>
883 <b>[<?php echo esc_html($this->_shortcode_ticket_scanner); ?>]</b>
884 <h3><?php esc_html_e('Shortcode to display ticket detail view within a page', 'event-tickets-with-ticket-scanner'); ?></h3>
885 <?php esc_html_e('Useful if the /ticket/ URL path does not work on your server.', 'event-tickets-with-ticket-scanner'); ?><br>
886 <b>[<?php echo esc_html($this->_shortcode_ticket_detail); ?>]</b>
887 <p>
888 <?php esc_html_e('Usage: Add the shortcode to a page and access it with ?ticket=YOUR-TICKET-CODE in the URL.', 'event-tickets-with-ticket-scanner'); ?><br>
889 <?php esc_html_e('Example:', 'event-tickets-with-ticket-scanner'); ?> yoursite.com/ticket-page/?ticket=ABC-123-XYZ<br>
890 <?php esc_html_e('Or use the code attribute:', 'event-tickets-with-ticket-scanner'); ?> [<?php echo esc_html($this->_shortcode_ticket_detail); ?> code="ABC-123-XYZ"]
891 </p>
892 <h3><?php esc_html_e('PHP Filters', 'event-tickets-with-ticket-scanner'); ?></h3>
893 <p><?php esc_html_e('You can use PHP code to register your filter functions for the validation check.', 'event-tickets-with-ticket-scanner'); ?>
894 <a href="https://vollstart.com/event-tickets-with-ticket-scanner/docs/#filters" target="_blank"><?php esc_html_e('Click here for more help about the functions', 'event-tickets-with-ticket-scanner'); ?></a>
895 </p>
896 <ul>
897 <li>add_filter('<?php echo $this->_add_filter_prefix.'beforeCheckCodePre'; ?>', 'myfunc', 20, 1)</li>
898 <li>add_filter('<?php echo $this->_add_filter_prefix.'beforeCheckCode'; ?>', 'myfunc', 20, 1)</li>
899 <li>add_filter('<?php echo $this->_add_filter_prefix.'afterCheckCodePre'; ?>', 'myfunc', 20, 1)</li>
900 <li>add_filter('<?php echo $this->_add_filter_prefix.'afterCheckCode'; ?>', 'myfunc', 20, 1)</li>
901 </ul>
902 <p>More BETA filters and actions hooks can be found <a href="https://vollstart.com/event-tickets-with-ticket-scanner/docs/ticket-plugin-api/" target="_blank">here (NOT STABLE, be aware that they might be changed in the future)</a>.
903 <p style="text-align:center;"><a target="_blank" href="https://vollstart.com">VOLLSTART</a> - More plugins: <a target="_blank" href="https://wordpress.org/plugins/serial-codes-generator-and-validator/">Serial Code Validator</a></p>
904 </div>
905 </div>
906 <?php
907 do_action( $this->_do_action_prefix.'options_page' );
908 }
909
910 public function isUserAllowedToAccessAdminArea() {
911 if ($this->isAllowedAccess != null) return $this->isAllowedAccess;
912 if ($this->getOptions()->isOptionCheckboxActive('allowOnlySepcificRoleAccessToAdmin')) {
913 // check welche rollen
914 $user = wp_get_current_user();
915 $user_roles = (array) $user->roles;
916 if (in_array("administrator", $user_roles)) {
917 $this->isAllowedAccess = true;
918 } else {
919 $adminAreaAllowedRoles = $this->getOptions()->getOptionValue('adminAreaAllowedRoles');
920 if (!is_array($adminAreaAllowedRoles)) {
921 if (empty($adminAreaAllowedRoles)) {
922 $adminAreaAllowedRoles = [];
923 } else {
924 $adminAreaAllowedRoles = [$adminAreaAllowedRoles];
925 }
926 }
927 foreach($adminAreaAllowedRoles as $role_name) {
928 if (in_array($role_name, $user_roles)) {
929 $this->isAllowedAccess = true;
930 break;
931 };
932 }
933 }
934 } else {
935 // Standard: Only administrators have access
936 $this->isAllowedAccess = current_user_can('manage_options');
937 }
938 $this->isAllowedAccess = apply_filters( $this->_add_filter_prefix.'main_isUserAllowedToAccessAdminArea', $this->isAllowedAccess );
939 return $this->isAllowedAccess;
940 }
941
942 public function executeAdminSettings_a() {
943 if (!SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a_sngmbh not provided");
944 return $this->executeAdminSettings(SASO_EVENTTICKETS::getRequestPara('a_sngmbh')); // to prevent WP adds parameters
945 }
946
947 public function executeAdminSettings($a=0, $data=null) {
948 if (!$this->isUserAllowedToAccessAdminArea()) {
949 return wp_send_json_error("Access denied", 403);
950 }
951 if ($a === 0 && !SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a not provided");
952
953 if ($data == null) {
954 $data = SASO_EVENTTICKETS::issetRPara('data') ? SASO_EVENTTICKETS::getRequestPara('data') : [];
955 }
956 if ($a === 0 || empty($a) || trim($a) == "") {
957 $a = SASO_EVENTTICKETS::getRequestPara('a_sngmbh');
958 }
959 do_action( $this->_do_action_prefix.'executeAdminSettings', $a, $data );
960 return $this->getAdmin()->executeJSON($a, $data, false, false); // with nonce check
961 }
962
963 public function executeSeatingAdmin_a() {
964 return $this->executeSeatingAdmin(SASO_EVENTTICKETS::getRequestPara('a'));
965 }
966
967 public function executeSeatingAdmin($a = '', $data = null) {
968 if (!$this->isUserAllowedToAccessAdminArea()) {
969 return wp_send_json_error('Access denied', 403);
970 }
971 if (empty($a) && !SASO_EVENTTICKETS::issetRPara('a')) {
972 return wp_send_json_error('a not provided');
973 }
974 if ($data === null) {
975 $data = SASO_EVENTTICKETS::getRequest();
976 }
977 if (empty($a)) {
978 $a = SASO_EVENTTICKETS::getRequestPara('a');
979 }
980 return $this->getSeating()->getAdminHandler()->executeSeatingJSON($a, $data);
981 }
982
983 public function executeFrontend_a() {
984 return $this->executeFrontend(); // to prevent WP adds parameters
985 }
986
987 public function executeWCBackend() {
988 if (!$this->isUserAllowedToAccessAdminArea()) {
989 return wp_send_json_error("Access denied", 403);
990 }
991 if (!SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a_sngmbh not provided");
992 $data = SASO_EVENTTICKETS::issetRPara('data') ? SASO_EVENTTICKETS::getRequestPara('data') : [];
993 return $this->getWC()->executeJSON(SASO_EVENTTICKETS::getRequestPara('a_sngmbh'), $data);
994 }
995
996 public function executeFrontend($a=0, $data=null) {
997 $sasoEventtickets_Frontend = $this->getFrontend();
998 if ($a === 0 && !SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a not provided");
999
1000 if ($data == null) {
1001 $data = SASO_EVENTTICKETS::issetRPara('data') ? SASO_EVENTTICKETS::getRequestPara('data') : [];
1002 }
1003 if ($a === 0 || empty($a) || trim($a) == "") {
1004 $a = SASO_EVENTTICKETS::getRequestPara('a_sngmbh');
1005 }
1006 do_action( $this->_do_action_prefix.'executeFrontend', $a, $data );
1007 return $sasoEventtickets_Frontend->executeJSON($a, $data);
1008 }
1009
1010 public function replacingShortcode($attr=[], $content = null, $tag = '') {
1011 add_filter( $this->_add_filter_prefix.'replaceShortcode', [$this, 'replaceShortcode'], 10, 3 );
1012 $ret = apply_filters( $this->_add_filter_prefix.'replaceShortcode', $attr, $content, $tag );
1013 return $ret;
1014 }
1015
1016 public function setTicketScannerJS() {
1017 wp_enqueue_style("wp-jquery-ui-dialog");
1018
1019 $js_url = "jquery.qrcode.min.js?_v=".$this->getPluginVersion();
1020 wp_enqueue_script(
1021 'ajax_script2',
1022 plugins_url( "3rd/".$js_url,__FILE__ ),
1023 array('jquery', 'jquery-ui-dialog')
1024 );
1025
1026 $js_url = plugin_dir_url(__FILE__)."3rd/html5-qrcode.min.js?_v=".$this->getPluginVersion();
1027 wp_register_script('html5-qrcode', $js_url, array('jquery', 'jquery-ui-dialog'));
1028 wp_enqueue_script('html5-qrcode');
1029
1030 // https://github.com/nimiq/qr-scanner - NEW scanner lib
1031 $js_url = plugin_dir_url(__FILE__)."3rd/qr-scanner-1.4.2/qr-scanner.umd.min.js?_v=".$this->getPluginVersion();
1032 wp_register_script('qr-scanner', $js_url, array('jquery', 'jquery-ui-dialog'));
1033 wp_enqueue_script('qr-scanner');
1034
1035 $js_url = "ticket_scanner.js?_v=".$this->getPluginVersion();
1036 if (defined('WP_DEBUG')) $js_url .= '&t='.time();
1037 $js_url = plugins_url( $js_url,__FILE__ );
1038 wp_register_script('ajax_script_ticket_scanner', $js_url, array('jquery', 'jquery-ui-dialog', 'wp-i18n'));
1039 wp_enqueue_script('ajax_script_ticket_scanner');
1040 wp_set_script_translations('ajax_script_ticket_scanner', 'event-tickets-with-ticket-scanner', __DIR__.'/languages');
1041
1042 $ticketScannerDontRememberCamChoice = $this->getOptions()->isOptionCheckboxActive("ticketScannerDontRememberCamChoice") ? true : false;
1043
1044 $pwaEnabled = $this->getOptions()->isOptionCheckboxActive('ticketScannerPWA');
1045 $vars = [
1046 'root' => esc_url_raw( rest_url() ),
1047 '_plugin_home_url' =>plugins_url( "",__FILE__ ),
1048 '_action' => $this->_prefix.'_executeAdminSettings',
1049 '_isPremium'=>$this->isPremium(),
1050 '_isUserLoggedin'=>is_user_logged_in(),
1051 '_userId'=>get_current_user_id(),
1052 '_restPrefixUrl'=>SASO_EVENTTICKETS::getRESTPrefixURL(),
1053 '_siteUrl'=>get_site_url(),
1054 '_params'=>["auth"=>$this->getAuthtokenHandler()::$authtoken_param],
1055 '_pwaSWUrl'=>$pwaEnabled ? rest_url(SASO_EVENTTICKETS::getRESTPrefixURL().'/ticket/scanner/pwa-sw') : '',
1056 //'url' => admin_url( 'admin-ajax.php' ), // not used for now in ticketscanner.js
1057 'url' => rest_get_server(), // not used for now in ticketscanner.js
1058 'nonce' => wp_create_nonce( 'wp_rest' ),
1059 //'nonce' => wp_create_nonce( $this->_js_nonce ),
1060 'ajaxActionPrefix' => $this->_prefix,
1061 'wcTicketCompatibilityModeRestURL' => $this->getOptions()->getOptionValue('wcTicketCompatibilityModeRestURL', ''),
1062 'IS_PRETTY_PERMALINK_ACTIVATED' => get_option('permalink_structure') ? true :false,
1063 'ticketScannerDontRememberCamChoice' => $ticketScannerDontRememberCamChoice,
1064 'ticketScannerStartCamWithoutButtonClicked' => $this->getOptions()->isOptionCheckboxActive('ticketScannerStartCamWithoutButtonClicked'),
1065 'ticketScannerDontShowOptionControls' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDontShowOptionControls'),
1066 'ticketScannerScanAndRedeemImmediately' => $this->getOptions()->isOptionCheckboxActive('ticketScannerScanAndRedeemImmediately'),
1067 'ticketScannerHideTicketInformation' => $this->getOptions()->isOptionCheckboxActive('ticketScannerHideTicketInformation'),
1068 'ticketScannerHideTicketInformationShowShortDesc' => $this->getOptions()->isOptionCheckboxActive('ticketScannerHideTicketInformationShowShortDesc'),
1069 'ticketScannerDontShowBtnPDF' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDontShowBtnPDF'),
1070 'ticketScannerDontShowBtnBadge' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDontShowBtnBadge'),
1071 'ticketScannerDisplayTimes' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDisplayTimes'),
1072 'ticketScannerThemeColor' => $this->getOptions()->getOptionValue('ticketScannerThemeColor', '#2e74b5'),
1073 'ticketScannerVibrate' => $this->getOptions()->isOptionCheckboxActive('ticketScannerVibrate')
1074 ];
1075 $vars = apply_filters( $this->_add_filter_prefix.'main_setTicketScannerJS', $vars );
1076 wp_localize_script(
1077 'ajax_script_ticket_scanner',
1078 'Ajax_'.$this->_prefix, // name der injected variable
1079 $vars
1080 );
1081
1082 do_action( $this->_do_action_prefix.'main_setTicketScannerJS', $js_url );
1083 }
1084
1085 public function replacingShortcodeTicketScanner($attr=[], $content = null, $tag = '') {
1086 $this->setTicketScannerJS();
1087 return '
1088 <center>
1089 <div style="width:90%;max-width:1024px;">'.$this->getTicketHandler()->getTicketScannerHTMLBoilerplate().'
1090 </div>
1091 </center>
1092 ';
1093 }
1094
1095 /**
1096 * Shortcode to display ticket detail view on any page
1097 * Usage: [sasoEventTicketsValidator_ticket_detail] with ?ticket=CODE in URL
1098 * Or: [sasoEventTicketsValidator_ticket_detail code="TICKET-CODE"]
1099 */
1100 public function replacingShortcodeTicketDetail($attr = [], $content = null, $tag = ''): string {
1101 $code = '';
1102 if (!empty($attr['code'])) {
1103 $code = sanitize_text_field($attr['code']);
1104 } elseif (isset($_GET['ticket'])) {
1105 $code = sanitize_text_field($_GET['ticket']);
1106 }
1107
1108 if (empty($code)) {
1109 return '<p>' . esc_html__('No ticket code provided. Use ?ticket=YOUR-CODE in the URL.', 'event-tickets-with-ticket-scanner') . '</p>';
1110 }
1111
1112 // Build a fake request URI for the ticket
1113 $ticketPath = $this->getCore()->getTicketURLPath(true);
1114 $fakeUri = $ticketPath . $code;
1115
1116 include_once plugin_dir_path(__FILE__) . "sasoEventtickets_Ticket.php";
1117 $ticketInstance = sasoEventtickets_Ticket::Instance($fakeUri);
1118
1119 return $ticketInstance->renderTicketDetailForShortcode();
1120 }
1121
1122 public function getCodesTextAsShortList($codes) {
1123 $ret = "";
1124 if (count($codes) > 0) {
1125 $ret = '<table>';
1126 $wcTicketUserProfileDisplayTicketDetailURL = $this->getOptions()->isOptionCheckboxActive("wcTicketUserProfileDisplayTicketDetailURL");
1127 $wcTicketUserProfileDisplayRedeemAmount = $this->getOptions()->isOptionCheckboxActive("wcTicketUserProfileDisplayRedeemAmount");
1128
1129 $label_expired = $this->getOptions()->getOptionValue('wcTicketTransTicketExpired', 'EXPIRED');
1130 $label_stolen = $this->getOptions()->getOptionValue('wcTicketTransTicketIsStolen', 'REPORTED AS STOLEN');
1131 $label_notvalid = $this->getOptions()->getOptionValue('wcTicketTransTicketNotValid', 'DISABLED');
1132
1133 $myCodes = [];
1134 foreach($codes as $idx => $codeObj) {
1135 $metaObj = $this->getCore()->encodeMetaValuesAndFillObject($codeObj['meta'], $codeObj);
1136
1137 $_c = '<tr><td style="text-align:right;">'.($idx + 1).'.</td><td>'.$codeObj['code_display'].'</td><td>';
1138 if ($codeObj['aktiv'] == 1) {
1139 if ($this->getCore()->checkCodeExpired($codeObj)) {
1140 $_c .= $label_expired;
1141 }
1142 } else if ($codeObj['aktiv'] == 0) {
1143 $_c .= $label_notvalid;
1144 } else if ($codeObj['aktiv'] == 2) {
1145 $_c .= $label_stolen;
1146 }
1147 $_c .= '</td>';
1148
1149 if ($wcTicketUserProfileDisplayTicketDetailURL) {
1150 $_c .= "<td>";
1151 $url = $this->getCore()->getTicketURL($codeObj, $metaObj);
1152 if (!empty($url)) {
1153 $_c .= '<a href="'.$url.'" target="_blank">Ticket Details</a>';
1154 }
1155 $_c .= "</td>";
1156 }
1157 if ($wcTicketUserProfileDisplayRedeemAmount && function_exists("wc_get_product")) {
1158 $_c .= "<td>";
1159 $text_redeem_amount = $this->getTicketHandler()->getRedeemAmountText($codeObj, $metaObj, false);
1160 if (!empty($text_redeem_amount)) {
1161 $_c .= $text_redeem_amount;
1162 }
1163 $_c .= "<td>";
1164 }
1165 $_c .= "</tr>";
1166 $myCodes[] = $_c;
1167 }
1168 $ret .= implode("", $myCodes);
1169 $ret .= "</table>";
1170 }
1171 $ret = apply_filters( $this->_add_filter_prefix.'main_getCodesTextAsShortList', $ret, $codes );
1172 return $ret;
1173 }
1174
1175 public function getMyCodeText($user_id, $attr=[], $content = null, $tag = '', $codes = null) {
1176 $ret = '';
1177 // check ob eingeloggt
1178 $pre_text = $this->getOptions()->getOptionValue('userDisplayCodePrefix', '');
1179 if (!empty($pre_text)) $pre_text .= " ";
1180
1181 // If codes are provided (e.g., from order_id), use them; otherwise fetch by user_id
1182 if ($codes === null && $user_id > 0) {
1183 $codes = $this->getCore()->getCodesByRegUserId($user_id);
1184 }
1185
1186 if ($codes !== null && count($codes) > 0) {
1187 $ret .= "<b>".$pre_text."</b><br>";
1188 $ret .= $this->getCodesTextAsShortList($codes);
1189
1190 // Download All as PDF button
1191 $show_download_btn = isset($attr['download_all_pdf']) &&
1192 in_array(strtolower($attr['download_all_pdf']), ['true', '1', 'yes'], true);
1193
1194 if ($show_download_btn && count($codes) > 0) {
1195 $max_tickets = isset($attr['download_all_pdf_max']) ? intval($attr['download_all_pdf_max']) : 100;
1196 $btn_label = isset($attr['download_all_pdf_label']) ?
1197 sanitize_text_field($attr['download_all_pdf_label']) :
1198 __('Download All Tickets as PDF', 'event-tickets-with-ticket-scanner');
1199
1200 $ticket_count = count($codes);
1201 if ($ticket_count > $max_tickets) {
1202 $ret .= '<p><em>' . sprintf(
1203 /* translators: %d: maximum number of tickets */
1204 esc_html__('Too many tickets (%1$d). Maximum %2$d tickets can be downloaded at once.', 'event-tickets-with-ticket-scanner'),
1205 $ticket_count,
1206 $max_tickets
1207 ) . '</em></p>';
1208 } else {
1209 // Generate secure download URL
1210 $nonce = wp_create_nonce('download_my_codes_pdf_' . $user_id);
1211 $download_url = admin_url('admin-ajax.php') . '?' . http_build_query([
1212 'action' => $this->_prefix . '_downloadMyCodesAsPDF',
1213 'nonce' => $nonce
1214 ]);
1215 $ret .= '<p style="margin-top:10px;"><a href="' . esc_url($download_url) . '" class="button" target="_blank">' .
1216 esc_html($btn_label) . ' (' . $ticket_count . ')</a></p>';
1217 }
1218 }
1219 }
1220 if (empty($ret) && $this->getOptions()->isOptionCheckboxActive('userDisplayCodePrefixAlways')) {
1221 $ret .= $pre_text;
1222 }
1223 $ret = apply_filters( $this->_add_filter_prefix.'main_getMyCodeText', $ret, $user_id, $attr, $content, $tag);
1224 return $ret;
1225 }
1226
1227 /**
1228 * AJAX handler: Download all user's tickets as one PDF
1229 * Used by shortcode [sasoEventTicketsValidator_code download_all_pdf="true"]
1230 */
1231 public function downloadMyCodesAsPDF(): void {
1232 $user_id = get_current_user_id();
1233
1234 // Must be logged in
1235 if ($user_id <= 0) {
1236 wp_die(esc_html__('You must be logged in to download tickets.', 'event-tickets-with-ticket-scanner'), 403);
1237 }
1238
1239 // Verify nonce
1240 $nonce = isset($_GET['nonce']) ? sanitize_text_field($_GET['nonce']) : '';
1241 if (!wp_verify_nonce($nonce, 'download_my_codes_pdf_' . $user_id)) {
1242 wp_die(esc_html__('Security check failed. Please refresh the page and try again.', 'event-tickets-with-ticket-scanner'), 403);
1243 }
1244
1245 // Get user's codes
1246 $codes = $this->getCore()->getCodesByRegUserId($user_id);
1247
1248 if (empty($codes)) {
1249 wp_die(esc_html__('No tickets found.', 'event-tickets-with-ticket-scanner'), 404);
1250 }
1251
1252 // Limit to 100 tickets
1253 $max_tickets = 100;
1254 if (count($codes) > $max_tickets) {
1255 wp_die(
1256 sprintf(
1257 /* translators: %d: maximum number of tickets */
1258 esc_html__('Too many tickets. Maximum %d tickets can be downloaded at once.', 'event-tickets-with-ticket-scanner'),
1259 $max_tickets
1260 ),
1261 400
1262 );
1263 }
1264
1265 // Extract code strings
1266 $code_strings = array_map(function($codeObj) {
1267 return $codeObj['code'];
1268 }, $codes);
1269
1270 // Generate merged PDF
1271 $filename = 'my_tickets_' . wp_date('Ymd_Hi') . '.pdf';
1272
1273 try {
1274 $this->getTicketHandler()->generateOnePDFForCodes($code_strings, $filename, 'I');
1275 } catch (Exception $e) {
1276 $this->getAdmin()->logErrorToDB($e, null, 'downloadMyCodesAsPDF');
1277 wp_die(esc_html__('Error generating PDF. Please try again later.', 'event-tickets-with-ticket-scanner'), 500);
1278 }
1279
1280 exit;
1281 }
1282
1283 public function getMyCodeFormatted($user_id, $attr=[], $content = null, $tag = '', $codes = null) {
1284 $format = "json";
1285 if (isset($attr["format"])) {
1286 $format = strtolower(trim(sanitize_key($attr["format"])));
1287 }
1288 $display = ["codes"];
1289 if (isset($attr["display"])) {
1290
1291 $_d = trim(sanitize_text_field($attr["display"]));
1292 $_da = explode(",", $_d);
1293 if (count($_da) > 0) {
1294 $display = [];
1295 }
1296 foreach ($_da as $item) {
1297 $item = trim($item);
1298 $display[] = $item;
1299 }
1300 }
1301
1302 $output = [];
1303 //codes,validation,user,used,confirmedCount,woocommerce,wc_rp,wc_ticket
1304 // If codes are provided (e.g., from order_id), use them; otherwise fetch by user_id
1305 if ($codes === null) {
1306 $codes = $this->getCore()->getCodesByRegUserId($user_id);
1307 }
1308 $metas = [];
1309 foreach($codes as $codeObj) {
1310 $metas[$codeObj["code"]] = $this->getCore()->encodeMetaValuesAndFillObject($codeObj['meta'], $codeObj);
1311 }
1312 foreach ($display as $item) {
1313 $output[$item] = [];
1314 if ($item == "codes") {
1315 foreach($codes as $codeObj) {
1316 if (isset($codeObj["meta"])) {
1317 unset($codeObj["meta"]);
1318 }
1319 $output[$item][] = $codeObj;
1320 }
1321 } elseif($item == "confirmedCount") {
1322 foreach($metas as $key => $meta) {
1323 $output[$item][] = array_merge(["value"=>$meta[$item]], ["code"=>$key]);
1324 }
1325 } else {
1326 foreach($metas as $key => $m) {
1327 if (is_array($m) && isset($m[$item])) {
1328 $meta = $m[$item];
1329 if (isset($meta["stats_redeemed"])) {
1330 unset($meta["stats_redeemed"]);
1331 }
1332 if (isset($meta["set_by_admin"])) {
1333 unset($meta["set_by_admin"]);
1334 }
1335 if (isset($meta["redeemed_by_admin"])) {
1336 unset($meta["redeemed_by_admin"]);
1337 }
1338 $output[$item][] = array_merge($meta, ["code"=>$key]);
1339 }
1340 }
1341 }
1342 }
1343
1344 switch($format) {
1345 case "json":
1346 default:
1347 $ret = $this->getCore()->json_encode_with_error_handling($output);
1348 }
1349 $ret = apply_filters( $this->_add_filter_prefix.'main_getMyCodeFormatted', $ret, $user_id, $attr, $content, $tag, $output);
1350 return $ret;
1351 }
1352
1353 public function replacingShortcodeMyCode($attr=[], $content = null, $tag = '') {
1354 $user_id = get_current_user_id();
1355 $codes = null; // Will be set if order_id is used
1356
1357 // Check if order_id parameter is provided
1358 if (isset($attr['order_id']) && !empty($attr['order_id'])) {
1359 $order_id = intval($attr['order_id']);
1360 if ($order_id > 0) {
1361 // Security check: can current user access this order?
1362 if (!$this->canUserAccessOrder($order_id)) {
1363 return '<p>' . esc_html__('You do not have permission to view tickets for this order.', 'event-tickets-with-ticket-scanner') . '</p>';
1364 }
1365 // Get codes by order_id
1366 $codes = $this->getCore()->getCodesByOrderId($order_id);
1367 }
1368 }
1369
1370 if (count($attr) > 0 && isset($attr["format"])) {
1371 return $this->getMyCodeFormatted($user_id, $attr, $content, $tag, $codes);
1372 } else {
1373 return $this->getMyCodeText($user_id, $attr, $content, $tag, $codes);
1374 }
1375 }
1376
1377 /**
1378 * Check if current user can access a specific order's tickets
1379 *
1380 * @param int $order_id WooCommerce order ID
1381 * @return bool True if user can access, false otherwise
1382 */
1383 private function canUserAccessOrder(int $order_id): bool {
1384 $order = wc_get_order($order_id);
1385 if (!$order) {
1386 return false;
1387 }
1388
1389 // 1. Admin/Shop-Manager can access all orders
1390 if (current_user_can('manage_woocommerce')) {
1391 return true;
1392 }
1393
1394 $current_user_id = get_current_user_id();
1395
1396 // 2. Logged-in user owns this order
1397 if ($current_user_id > 0 && $order->get_user_id() == $current_user_id) {
1398 return true;
1399 }
1400
1401 // 3. Valid order key in URL (WooCommerce thank-you page pattern)
1402 $order_key_from_url = isset($_GET['key']) ? sanitize_text_field($_GET['key']) : '';
1403 if (!empty($order_key_from_url) && $order->get_order_key() === $order_key_from_url) {
1404 return true;
1405 }
1406
1407 return false;
1408 }
1409
1410 public function replacingShortcodeFeatureList($attr=[], $content = null, $tag = '') {
1411 //$features = $this->getAdmin()->getOptions();
1412 //$options = $features["options"];
1413 $options = $this->getOptions()->getOptions();
1414 $features = [];
1415 $act_heading = "";
1416 foreach ($options as $option) {
1417 if ($option["key"] == "serial") continue;
1418 if ($option["type"] == "heading") {
1419 $act_heading = $option["key"];
1420 $features[$act_heading] = ["heading"=>$option, "options"=>[]];
1421 } else {
1422 if ($act_heading != "") {
1423 $features[$act_heading]["options"][] = $option;
1424 }
1425 }
1426 }
1427
1428 $ret = "";
1429 uasort($features, function($a, $b) {
1430 return strnatcasecmp($a["heading"]["label"], $b["heading"]["label"]);
1431 });
1432 foreach ($features as $key => $feature) {
1433 $ret .= '<h3>'.$feature["heading"]["label"].'</h3>';
1434 $video = $feature["heading"]["_doc_video"] != "" ? ' <a href="'.$feature["heading"]["_doc_video"].'" target="_blank"><span class="dashicons dashicons-video-alt3"></span> Introduction video</a>' : "";
1435 $ret .= '<p>'.trim($feature["heading"]["desc"].$video).'</p>';
1436 if (count($feature["options"]) > 0) {
1437 $ret .= '<ul>';
1438 foreach ($feature["options"] as $option) {
1439 $label = $option["label"];
1440 $desc = $option["desc"];
1441 $desc .= $option["_doc_video"] != "" ? ' <a href="'.$option["_doc_video"].'" target="_blank"><span class="dashicons dashicons-video-alt3"></span> Introduction video</a>' : "";
1442 $desc = trim($desc);
1443 if ($desc != "") {
1444 $desc = '<p>'.$desc.'</p>';
1445 }
1446 $label = '<span class="dashicons dashicons-yes"></span> '.$label;
1447 $ret .= '<li>'.$label.$desc.'</li>';
1448 }
1449 $ret .= '</ul>';
1450 }
1451 $ret .= '<hr>';
1452 }
1453
1454 return $ret;
1455 }
1456
1457 public function replacingShortcodeEventViews($attr=[], $content = null, $tag = '') {
1458 // iterate over all woocommerce products and check if they are an event
1459 $view = "calendar|list";
1460 if (isset($attr["view"])) {
1461 $view = strtolower(trim(sanitize_key($attr["view"])));
1462 }
1463 $months_to_show = 3;
1464 if (isset($attr["months_to_show"])) {
1465 $m = intval($attr["months_to_show"]);
1466 if ($m > 0) $months_to_show = $m;
1467 }
1468
1469 $month_start = strtotime(wp_date("Y-m-1 00:00:00"));
1470 //$month_end = strtotime(wp_date("Y-m-t 23:59:59"));
1471 $month_end = strtotime(wp_date("Y-m-1 23:59:59", strtotime("+".$months_to_show." month", $month_start)));
1472
1473 $products_args = array(
1474 'post_type' => 'product',
1475 'post_status' => 'publish',
1476 'posts_per_page' => -1, // -1 zeigt alle Produkte an
1477 'meta_query' => array(
1478 [
1479 'key' => 'saso_eventtickets_is_ticket',
1480 'value' => 'yes',
1481 'compare' => '='
1482 ]
1483 )
1484 );
1485 $posts = get_posts($products_args); // get only ticket products
1486
1487 $list_infos = [
1488 'months_to_show'=>$months_to_show,
1489 'month_start'=>$month_start,
1490 'month_end'=>$month_end,
1491 'view'=>$view
1492 ];
1493 $products_to_show = [];
1494 // Ergebnisse überprüfen
1495 foreach ($posts as $post) {
1496 $product_ids = [];
1497 $product = wc_get_product( $post->ID );
1498
1499 // check if event is variant product
1500 $is_variable = $product->get_type() == "variable";
1501 if ($is_variable) {
1502 // check if event dates are the same for all variants
1503 $date_is_for_all_variants = get_post_meta( $product_id, 'saso_eventtickets_is_date_for_all_variants', true ) == "yes" ? true : false;
1504 if ($date_is_for_all_variants == false) {
1505 // if not add also the variants
1506 $childs = $product->get_children();
1507 foreach ($childs as $child_id) {
1508 if (get_post_meta($child_id, '_saso_eventtickets_is_not_ticket', true) == "yes") {
1509 continue;
1510 }
1511 $product_ids[] = $child_id;
1512 }
1513 }
1514 } else {
1515 $product_ids[] = $product->get_id();
1516 }
1517
1518 foreach ($product_ids as $product_id) {
1519 $product = wc_get_product( $product_id );
1520 $dates = $this->getTicketHandler()->calcDateStringAllowedRedeemFrom($product_id);
1521 //if ($dates['ticket_end_date_timestamp'] > $month_start && $dates['ticket_start_date_timestamp'] < $month_end) {
1522 if ($dates['ticket_end_date_timestamp'] >= $month_start || ($dates['ticket_start_date_timestamp'] >= $month_start && $dates['ticket_start_date_timestamp'] <= $month_end)) {
1523 // set product to hidden
1524 $product_data = array(
1525 'ID' => $product_id,
1526 'dates' => $dates,
1527 'event'=> [
1528 'location' => trim(get_post_meta( $product_id, 'saso_eventtickets_event_location', true )),
1529 'location_label' => wp_kses_post(trim($this->getAdmin()->getOptionValue("wcTicketTransLocation")))
1530 ],
1531 'product' => [
1532 'title' => $product->get_name(),
1533 'url' => get_permalink($product_id),
1534 'price' =>$product->get_price(),
1535 'price_html' => $product->get_price_html(),
1536 'type' => $product->get_type(),
1537 ]
1538 );
1539 $products_to_show[] = $product_data;
1540 }
1541 }
1542 }
1543
1544 $divId = "sasoEventTicketsValidator_eventsview";
1545
1546 // add js for the events
1547 wp_enqueue_style("wp-jquery-ui-dialog");
1548
1549 $js_url = "ticket_events.js?_v=".$this->getPluginVersion();
1550 if (defined('WP_DEBUG')) $js_url .= '&t='.time();
1551 $js_url = plugins_url( $js_url,__FILE__ );
1552 wp_register_script('ajax_script_ticket_events', $js_url, array('jquery', 'jquery-ui-dialog', 'wp-i18n'));
1553 wp_enqueue_script('ajax_script_ticket_events');
1554 wp_set_script_translations('ajax_script_ticket_events', 'event-tickets-with-ticket-scanner', __DIR__.'/languages');
1555 wp_enqueue_style("ticket_events_css", plugins_url( "",__FILE__ ).'/css/calendar.css', [], true);
1556
1557 // add all events as an array for max 3 months??? or config parameter
1558 $vars = [
1559 'root' => esc_url_raw( rest_url() ),
1560 '_plugin_home_url' =>plugins_url( "",__FILE__ ),
1561 '_action' => $this->_prefix.'_executeFrontendEvents',
1562 '_isPremium'=>$this->isPremium(),
1563 '_isUserLoggedin'=>is_user_logged_in(),
1564 '_userId'=>get_current_user_id(),
1565 '_premJS'=>$this->isPremium() && method_exists($this->getPremiumFunctions(), "getJSFrontEventFile") ? $this->getPremiumFunctions()->getJSFrontEventFile() : '',
1566 '_siteUrl'=>get_site_url(),
1567 'events' => $products_to_show,
1568 'list_infos' => $list_infos,
1569 'format_date' => $this->getOptions()->getOptionDateFormat(),
1570 'format_time' => $this->getOptions()->getOptionTimeFormat(),
1571 'format_datetime' => $this->getOptions()->getOptionDateTimeFormat(),
1572 'url' => admin_url( 'admin-ajax.php' ),
1573 'nonce' => wp_create_nonce( $this->_js_nonce ),
1574 'ajaxActionPrefix' => $this->_prefix,
1575 'divId' => $divId
1576 ];
1577 $vars = apply_filters( $this->_add_filter_prefix.'main_setTicketEventJS', $vars );
1578 wp_localize_script(
1579 'ajax_script_ticket_events',
1580 'Ajax_ticket_events_'.$this->_prefix, // name der injected variable
1581 $vars
1582 );
1583
1584 do_action( $this->_do_action_prefix.'main_setTicketEventJS', $js_url );
1585
1586 $ret = '';
1587 if (!isset($attr['divid']) || trim($attr['divid']) == "") {
1588 $ret .= '<div id="'.$divId.'">'.__('...loading...', 'event-tickets-with-ticket-scanner').'</div>';
1589 }
1590
1591 $ret = apply_filters( $this->_add_filter_prefix.'main_replacingShortcodeEventViews', $ret );
1592 do_action( $this->_do_action_prefix.'main_replacingShortcodeEventViews', $vars, $ret );
1593
1594 return $ret;
1595 }
1596
1597 public function replaceShortcode($attr=[], $content = null, $tag = '') {
1598 // einbinden das js starter skript
1599 $js_url = $this->_js_file."?_v=".$this->_js_version;
1600 if (defined( 'WP_DEBUG')) $js_url .= '&debug=1';
1601 $userDivId = !isset($attr['divid']) || trim($attr['divid']) == "" ? '' : trim($attr['divid']);
1602
1603 $attr = array_change_key_case( (array) $attr, CASE_LOWER );
1604
1605 wp_enqueue_script(
1606 'ajax_script_validator',
1607 plugins_url( $js_url,__FILE__ ),
1608 array('jquery', 'wp-i18n')
1609 );
1610 wp_set_script_translations('ajax_script_validator', 'event-tickets-with-ticket-scanner', __DIR__.'/languages');
1611
1612 $vars = array(
1613 'shortcode_attr'=>json_encode($attr),
1614 '_plugin_home_url' =>plugins_url( "",__FILE__ ),
1615 '_action' => $this->_prefix.'_executeFrontend',
1616 '_isPremium'=>$this->isPremium(),
1617 '_isUserLoggedin'=>is_user_logged_in(),
1618 '_premJS'=>$this->isPremium() && method_exists($this->getPremiumFunctions(), "getJSFrontFile") ? $this->getPremiumFunctions()->getJSFrontFile() : '',
1619 'url' => admin_url( 'admin-ajax.php' ),
1620 'nonce' => wp_create_nonce( $this->_js_nonce ),
1621 'ajaxActionPrefix' => $this->_prefix,
1622 'divPrefix' => $userDivId == "" ? $this->_prefix : $userDivId,
1623 'divId' => $this->_divId,
1624 'jsFiles' => plugins_url( 'validator.js?_v='.$this->_js_version, __FILE__ )
1625 );
1626 $vars['_messages'] = [
1627 'msgCheck0'=>$this->getOptions()->getOptionValue('textValidationMessage0'),
1628 'msgCheck1'=>$this->getOptions()->getOptionValue('textValidationMessage1'),
1629 'msgCheck2'=>$this->getOptions()->getOptionValue('textValidationMessage2'),
1630 'msgCheck3'=>$this->getOptions()->getOptionValue('textValidationMessage3'),
1631 'msgCheck4'=>$this->getOptions()->getOptionValue('textValidationMessage4'),
1632 'msgCheck5'=>$this->getOptions()->getOptionValue('textValidationMessage5'),
1633 'msgCheck6'=>$this->getOptions()->getOptionValue('textValidationMessage6')
1634 ];
1635 $vars = apply_filters( $this->_add_filter_prefix.'main_replaceShortcode', $vars );
1636
1637 if ($this->isPremium() && method_exists($this->getPremiumFunctions(), "addJSFrontFile")) $this->getPremiumFunctions()->addJSFrontFile();
1638
1639 wp_localize_script(
1640 'ajax_script_validator',
1641 'Ajax_'.$this->_prefix, // name of the injected variable
1642 $vars
1643 );
1644 $ret = '';
1645 if (!isset($attr['divid']) || trim($attr['divid']) == "") {
1646 $ret .= '<div id="'.$this->_divId.'">'.__('...loading...', 'event-tickets-with-ticket-scanner').'</div>';
1647 }
1648
1649 $ret = apply_filters( $this->_add_filter_prefix.'main_replaceShortcode_2', $ret );
1650 do_action( $this->_do_action_prefix.'main_replaceShortcode', $vars, $ret );
1651
1652 return $ret;
1653 }
1654
1655 /**
1656 * Show admin notice when premium subscription is about to expire or has expired
1657 *
1658 * - Warning: 14 days before expiration
1659 * - Error: After expiration (during grace period or after)
1660 *
1661 * @return void
1662 */
1663 public function showSubscriptionWarning(): void {
1664 // Only show when premium plugin is installed (not dependent on subscription status,
1665 // otherwise the expiration warning itself would never be shown)
1666 if (!class_exists('sasoEventtickets_PremiumFunctions')) {
1667 return;
1668 }
1669
1670 // Only show in admin
1671 if (!is_admin()) {
1672 return;
1673 }
1674
1675 // Only show to users who can manage options
1676 if (!current_user_can('manage_options')) {
1677 return;
1678 }
1679
1680 $info = $this->getTicketHandler()->get_expiration();
1681
1682 // No expiration date or lifetime license - no warning needed
1683 if (empty($info['timestamp']) || $info['timestamp'] <= 0 || $info['timestamp'] == -1) {
1684 return;
1685 }
1686
1687 // Lifetime subscription type - no warning needed
1688 if (isset($info['subscription_type']) && $info['subscription_type'] === 'lifetime') {
1689 return;
1690 }
1691
1692 $days_left = ($info['timestamp'] - time()) / 86400;
1693 $renewal_url = 'https://vollstart.com/event-tickets-with-ticket-scanner/';
1694
1695 // Warning 14 days before expiration
1696 if ($days_left <= 14 && $days_left > 0) {
1697 $date = date_i18n(get_option('date_format'), $info['timestamp']);
1698 echo '<div class="notice notice-warning is-dismissible"><p>';
1699 printf(
1700 /* translators: %1$s: expiration date, %2$s: renewal URL */
1701 esc_html__('Your Event-Tickets Premium subscription expires on %1$s. %2$sRenew now%3$s to keep all features.', 'event-tickets-with-ticket-scanner'),
1702 '<strong>' . esc_html($date) . '</strong>',
1703 '<a href="' . esc_url($renewal_url) . '" target="_blank">',
1704 '</a>'
1705 );
1706 echo '</p></div>';
1707 }
1708
1709 // Error after expiration
1710 if ($days_left <= 0) {
1711 $grace_days = isset($info['grace_period_days']) ? intval($info['grace_period_days']) : 7;
1712 $grace_left = $grace_days + $days_left; // days_left is negative
1713
1714 echo '<div class="notice notice-error"><p>';
1715 if ($grace_left > 0) {
1716 printf(
1717 /* translators: %1$d: days remaining in grace period, %2$s: renewal URL */
1718 esc_html__('Your Premium subscription has expired. You have %1$d days remaining before features are disabled. %2$sReactivate now%3$s', 'event-tickets-with-ticket-scanner'),
1719 ceil($grace_left),
1720 '<a href="' . esc_url($renewal_url) . '" target="_blank">',
1721 '</a>'
1722 );
1723 } else {
1724 printf(
1725 /* translators: %1$s: renewal URL */
1726 esc_html__('Your Premium subscription has expired. Premium features are now limited. %1$sReactivate now%2$s', 'event-tickets-with-ticket-scanner'),
1727 '<a href="' . esc_url($renewal_url) . '" target="_blank">',
1728 '</a>'
1729 );
1730 }
1731 echo '</p></div>';
1732 }
1733 }
1734
1735 /**
1736 * Show admin notice when premium plugin version is outdated or incompatible
1737 *
1738 * Shows a prominent, non-dismissible error when old premium is detected.
1739 * Old premium (< 1.6.0) is NOT loaded to prevent fatal errors.
1740 *
1741 * @return void
1742 */
1743 public function showOutdatedPremiumWarning(): void {
1744 if (!is_admin() || !current_user_can('manage_options')) {
1745 return;
1746 }
1747
1748 if (!$this->isOldPremiumDetected()) {
1749 return;
1750 }
1751
1752 $old_version = defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION')
1753 ? SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION
1754 : __('unknown', 'event-tickets-with-ticket-scanner');
1755 $upgrade_url = 'https://vollstart.com/event-tickets-with-ticket-scanner/';
1756 $support_url = 'https://vollstart.com/support/';
1757
1758 echo '<div class="notice notice-error" style="border-left-color:#dc3232;padding:12px 15px;">';
1759 echo '<p style="font-size:14px;font-weight:bold;margin:0 0 8px 0;">';
1760 echo '&#9888; ';
1761 printf(
1762 esc_html__('Event Tickets: Your Premium plugin (v%s) is not compatible with this version of Event Tickets.', 'event-tickets-with-ticket-scanner'),
1763 esc_html($old_version)
1764 );
1765 echo '</p>';
1766 echo '<p style="margin:0 0 8px 0;">';
1767 esc_html_e('To prevent errors on your website, premium features have been temporarily disabled. Your tickets and data are safe.', 'event-tickets-with-ticket-scanner');
1768 echo '</p>';
1769 echo '<p style="margin:0 0 8px 0;">';
1770 printf(
1771 esc_html__('Please update the Premium plugin to version 1.6.0 or higher to restore all premium features. You can download the latest version from your %1$saccount%2$s or %3$scontact support%4$s for help.', 'event-tickets-with-ticket-scanner'),
1772 '<a href="' . esc_url($upgrade_url) . '" target="_blank"><strong>',
1773 '</strong></a>',
1774 '<a href="' . esc_url($support_url) . '" target="_blank">',
1775 '</a>'
1776 );
1777 echo '</p>';
1778 echo '<p style="margin:0 0 8px 0;font-style:italic;color:#666;">';
1779 esc_html_e('How to update: Go to Plugins > Add New > Upload Plugin, then upload the new Premium ZIP file. WordPress will ask to replace the existing version.', 'event-tickets-with-ticket-scanner');
1780 echo '</p>';
1781 $downgrade_url = 'https://plugins.trac.wordpress.org/browser/event-tickets-with-ticket-scanner/tags';
1782 echo '<p style="margin:0;font-style:italic;color:#666;">';
1783 printf(
1784 esc_html__('Alternatively, your old Premium plugin still works with basic plugin versions below 2.8.0. You can %1$sdowngrade the basic plugin here%2$s.', 'event-tickets-with-ticket-scanner'),
1785 '<a href="' . esc_url($downgrade_url) . '" target="_blank">',
1786 '</a>'
1787 );
1788 echo '</p>';
1789 echo '</div>';
1790 }
1791
1792 /**
1793 * Show admin notice when ticket format is running out of combinations
1794 *
1795 * Checks all ticket lists for format warnings and displays notice
1796 *
1797 * @return void
1798 */
1799 public function showPhpVersionWarning(): void {
1800 if (version_compare(PHP_VERSION, '8.1.0', '>=')) {
1801 return;
1802 }
1803 printf(
1804 '<div class="notice notice-warning is-dismissible"><p><strong>Event Tickets with Ticket Scanner:</strong> %s</p></div>',
1805 sprintf(
1806 /* translators: 1: current PHP version 2: required PHP version */
1807 esc_html__('Your server is running PHP %1$s. This plugin requires PHP %2$s or higher. Please upgrade PHP to ensure full compatibility.', 'event-tickets-with-ticket-scanner'),
1808 PHP_VERSION,
1809 '8.1'
1810 )
1811 );
1812 }
1813
1814 /**
1815 * Handle format warning dismiss — runs on admin_init (before output) so wp_redirect() works.
1816 */
1817 public function handleFormatWarningDismiss(): void {
1818 if (!isset($_GET['saso_eventtickets_clear_format_warning']) || !isset($_GET['_wpnonce'])) {
1819 return;
1820 }
1821 if (!current_user_can('manage_options')) {
1822 return;
1823 }
1824 $list_id = intval($_GET['saso_eventtickets_clear_format_warning']);
1825 $nonce = sanitize_text_field($_GET['_wpnonce']);
1826 if (wp_verify_nonce($nonce, 'clear_format_warning_' . $list_id)) {
1827 $this->getAdmin()->clearFormatWarning($list_id);
1828 wp_redirect(remove_query_arg(['saso_eventtickets_clear_format_warning', '_wpnonce']));
1829 exit;
1830 }
1831 }
1832
1833 public function showFormatWarning(): void {
1834 // Only show in admin
1835 if (!is_admin()) {
1836 return;
1837 }
1838
1839 // Only show to users who can manage options
1840 if (!current_user_can('manage_options')) {
1841 return;
1842 }
1843
1844 try {
1845 // Get all ticket lists
1846 $lists = $this->getAdmin()->getLists([], false);
1847
1848 foreach ($lists as $list) {
1849 $warning = $this->getAdmin()->getFormatWarning($list['id']);
1850
1851 if ($warning) {
1852 $list_name = esc_html($warning['list_name']);
1853 $attempts = intval($warning['attempts']);
1854
1855 if ($warning['type'] === 'critical') {
1856 // Critical - format exhausted
1857 $clear_url = wp_nonce_url(
1858 add_query_arg(['saso_eventtickets_clear_format_warning' => $list['id']]),
1859 'clear_format_warning_' . $list['id']
1860 );
1861
1862 echo '<div class="notice notice-error"><p>';
1863 printf(
1864 /* translators: 1: list name, 2: attempts, 3: clear URL */
1865 esc_html__('⚠️ CRITICAL: Ticket format for "%1$s" is exhausted! It took %2$d attempts to generate a code. Future ticket sales may fail. %3$sEdit list%4$s | %5$sDismiss%4$s', 'event-tickets-with-ticket-scanner'),
1866 $list_name,
1867 $attempts,
1868 '<a href="' . esc_url(admin_url('admin.php?page=event-tickets-with-ticket-scanner')) . '">',
1869 '</a>',
1870 '<a href="' . esc_url($clear_url) . '">'
1871 );
1872 echo '</p></div>';
1873 } else {
1874 // Warning - running out
1875 $clear_url = wp_nonce_url(
1876 add_query_arg(['saso_eventtickets_clear_format_warning' => $list['id']]),
1877 'clear_format_warning_' . $list['id']
1878 );
1879
1880 echo '<div class="notice notice-warning is-dismissible"><p>';
1881 printf(
1882 /* translators: 1: list name, 2: attempts, 3: clear URL */
1883 esc_html__('⚠️ WARNING: Ticket format for "%1$s" is running out of combinations. It took %2$d attempts to generate a code. Consider increasing code length. %3$sEdit list%4$s | %5$sDismiss%4$s', 'event-tickets-with-ticket-scanner'),
1884 $list_name,
1885 $attempts,
1886 '<a href="' . esc_url(admin_url('admin.php?page=event-tickets-with-ticket-scanner')) . '">',
1887 '</a>',
1888 '<a href="' . esc_url($clear_url) . '">'
1889 );
1890 echo '</p></div>';
1891 }
1892
1893 // Only show one warning at a time
1894 break;
1895 }
1896 }
1897 } catch (Exception $e) {
1898 // Silently fail - don't break the admin
1899 }
1900 }
1901 }
1902 $sasoEventtickets = sasoEventtickets::Instance();
1903 ?>
1904