event-tickets-with-ticket-scanner
Last commit date
3rd
1 week ago
css
1 week ago
img
1 week ago
includes
1 week ago
js
1 week ago
languages
1 week ago
ticket
1 week ago
vendors
1 week ago
SASO_EVENTTICKETS.php
1 week ago
backend.js
1 week ago
changelog-features.json
1 week ago
changelog.txt
1 week ago
db.php
1 week ago
index.php
1 week ago
init_file.php
1 week ago
order_details.js
1 week ago
pwa-sw.js
1 week ago
readme.txt
1 week ago
saso-eventtickets-validator.js
1 week ago
sasoEventtickets_AdminSettings.php
1 week ago
sasoEventtickets_Authtoken.php
1 week ago
sasoEventtickets_Base.php
1 week ago
sasoEventtickets_Core.php
1 week ago
sasoEventtickets_Frontend.php
1 week ago
sasoEventtickets_Messenger.php
1 week ago
sasoEventtickets_Options.php
1 week ago
sasoEventtickets_PDF.php
1 week ago
sasoEventtickets_Seating.php
1 week ago
sasoEventtickets_Ticket.php
1 week ago
sasoEventtickets_TicketBadge.php
1 week ago
sasoEventtickets_TicketDesigner.php
1 week ago
sasoEventtickets_TicketQR.php
1 week ago
ticket_events.js
1 week ago
ticket_scanner.js
1 week ago
validator.js
1 week ago
version-notices.json
1 week ago
vollstart-cross-promo.php
1 week ago
wc_backend.js
1 week ago
wc_frontend.js
1 week ago
woocommerce-hooks.php
1 week ago
index.php
2506 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: 3.1.2 |
| 7 | * Author: Vollstart |
| 8 | * Author URI: https://vollstart.com |
| 9 | * Requires at least: 6.0 |
| 10 | * Tested up to: 7.0 |
| 11 | * Requires PHP: 8.1 |
| 12 | * Text Domain: event-tickets-with-ticket-scanner |
| 13 | * License: GPLv2 or later |
| 14 | * License URI: https://www.gnu.org/licenses/gpl-2.0.html |
| 15 | * |
| 16 | * Event Tickets with Ticket Scanner is distributed in the hope that it will be useful, |
| 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | * GNU General Public License for more details. |
| 20 | */ |
| 21 | // https://semver.org/ |
| 22 | // https://developer.wordpress.org/plugins/security/securing-output/ |
| 23 | // https://developer.wordpress.org/plugins/security/securing-input/ |
| 24 | |
| 25 | include_once(plugin_dir_path(__FILE__)."init_file.php"); |
| 26 | |
| 27 | if (!defined('SASO_EVENTTICKETS_PLUGIN_VERSION')) |
| 28 | define('SASO_EVENTTICKETS_PLUGIN_VERSION', '3.1.2'); |
| 29 | if (!defined('SASO_EVENTTICKETS_PLUGIN_DIR_PATH')) |
| 30 | define('SASO_EVENTTICKETS_PLUGIN_DIR_PATH', plugin_dir_path(__FILE__)); |
| 31 | |
| 32 | /** |
| 33 | * Record first activation timestamp (free-to-paid analytics). |
| 34 | * Set ONCE on first activation. Premium plugin reads + sends with license check. |
| 35 | * Stored in wp_options so it survives plugin updates / DB cleanups within WP. |
| 36 | */ |
| 37 | function saso_eventtickets_record_first_activation() { |
| 38 | if (get_option('saso_eventtickets_first_activated_at') === false) { |
| 39 | add_option('saso_eventtickets_first_activated_at', gmdate('Y-m-d H:i:s'), '', 'no'); |
| 40 | } |
| 41 | } |
| 42 | register_activation_hook(__FILE__, 'saso_eventtickets_record_first_activation'); |
| 43 | |
| 44 | include_once plugin_dir_path(__FILE__)."SASO_EVENTTICKETS.php"; |
| 45 | |
| 46 | class sasoEventtickets_fakeprem{} |
| 47 | class sasoEventtickets { |
| 48 | private $_js_version; |
| 49 | private $_js_file = 'saso-eventtickets-validator.js'; |
| 50 | public $_js_nonce = 'sasoEventtickets'; |
| 51 | public $_do_action_prefix = 'saso_eventtickets_'; |
| 52 | public $_add_filter_prefix = 'saso_eventtickets_'; |
| 53 | protected $_prefix = 'sasoEventtickets'; |
| 54 | public $_prefix_session = 'sasoEventtickets_'; |
| 55 | protected $_shortcode = 'sasoEventTicketsValidator'; |
| 56 | protected $_shortcode_mycode = 'sasoEventTicketsValidator_code'; |
| 57 | protected $_shortcode_eventviews = 'sasoEventTicketsValidator_eventsview'; |
| 58 | protected $_shortcode_ticket_scanner = 'sasoEventTicketsValidator_ticket_scanner'; |
| 59 | protected $_shortcode_feature_list = 'sasoEventTicketsValidator_feature_list'; |
| 60 | protected $_shortcode_ticket_detail = 'sasoEventTicketsValidator_ticket_detail'; |
| 61 | protected $_divId = 'sasoEventtickets'; |
| 62 | |
| 63 | private $_isPrem = null; |
| 64 | private $_isCheckingSubscription = false; |
| 65 | private $_premium_plugin_name = 'event-tickets-with-ticket-scanner-premium'; |
| 66 | private $_premium_function_file = 'sasoEventtickets_PremiumFunctions.php'; |
| 67 | private $PREMFUNCTIONS = null; |
| 68 | private $_oldPremiumDetected = false; |
| 69 | private $_starterOrStopDetected = false; |
| 70 | private $BASE = null; |
| 71 | private $CORE = null; |
| 72 | private $ADMIN = null; |
| 73 | private $FRONTEND = null; |
| 74 | private $OPTIONS = null; |
| 75 | private $CONGRESS_REPO = null; |
| 76 | private $CONGRESS_ADMIN = null; |
| 77 | private $CONGRESS_PAGE = null; |
| 78 | |
| 79 | private $isAllowedAccess = null; |
| 80 | |
| 81 | public static function Instance() { |
| 82 | static $inst = null; |
| 83 | if ($inst === null) { |
| 84 | $inst = new self(); |
| 85 | } |
| 86 | return $inst; |
| 87 | } |
| 88 | |
| 89 | public function __construct() { |
| 90 | global $sasoEventtickets; |
| 91 | $sasoEventtickets = $this; // Set early to prevent circular dependency with sasoEventtickets_Ticket |
| 92 | $this->_js_version = $this->getPluginVersion() . (defined('WP_DEBUG') && WP_DEBUG ? '.' . time() : ''); |
| 93 | $this->initHandlers(); |
| 94 | } |
| 95 | |
| 96 | public function initHandlers() { |
| 97 | add_action( 'init', [$this, 'load_plugin_textdomain'] ); |
| 98 | add_action( 'upgrader_process_complete', [$this, 'listener_upgrader_process_complete'], 10, 2 ); |
| 99 | //add_action('admin_init', [$this, 'initialize_plugin']); |
| 100 | if (is_admin()) { // called in backend admin, admin-ajax! |
| 101 | $this->init_backend(); |
| 102 | } else { // called in front end |
| 103 | $this->init_frontend(); |
| 104 | } |
| 105 | add_action( 'sasoEventtickets_cronjob_daily', [$this, 'relay_sasoEventtickets_cronjob_daily'], 10, 0 ); // set in tickets.php |
| 106 | add_action( 'plugins_loaded', [$this, 'WooCommercePluginLoaded'], 20, 0 ); |
| 107 | if (basename($_SERVER['SCRIPT_NAME'] ?? '') == "admin-ajax.php") { |
| 108 | add_action('wp_ajax_nopriv_'.$this->_prefix.'_executeFrontend', [$this,'executeFrontend_a'], 10, 0); // nicht angemeldete user, sollen eine antwort erhalten |
| 109 | add_action('wp_ajax_'.$this->_prefix.'_executeFrontend', [$this,'executeFrontend_a'], 10, 0); // falls eingeloggt ist |
| 110 | add_action('wp_ajax_'.$this->_prefix.'_executeWCBackend', [$this,'executeWCBackend'], 10, 0); // falls eingeloggt ist |
| 111 | add_action('wp_ajax_'.$this->_prefix.'_downloadMyCodesAsPDF', [$this,'downloadMyCodesAsPDF'], 10, 0); // logged in users only |
| 112 | } |
| 113 | if (method_exists($this->getPremiumFunctions(), "initHandlers")) { |
| 114 | $this->getPremiumFunctions()->initHandlers(); |
| 115 | } |
| 116 | $this->cronjob_daily_activate(); |
| 117 | } |
| 118 | public function cronjob_daily_activate() { |
| 119 | $args = []; |
| 120 | if (! wp_next_scheduled ( 'sasoEventtickets_cronjob_daily', $args )) { |
| 121 | wp_schedule_event( strtotime("00:05"), 'daily', 'sasoEventtickets_cronjob_daily', $args ); |
| 122 | } |
| 123 | } |
| 124 | public function cronjob_daily_deactivate() { |
| 125 | wp_clear_scheduled_hook( 'sasoEventtickets_cronjob_daily' ); |
| 126 | } |
| 127 | public function load_plugin_textdomain() { |
| 128 | load_plugin_textdomain( 'event-tickets-with-ticket-scanner', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); |
| 129 | } |
| 130 | public function getPluginPath() { |
| 131 | return SASO_EVENTTICKETS_PLUGIN_DIR_PATH; |
| 132 | } |
| 133 | public function getPluginVersion() { |
| 134 | return SASO_EVENTTICKETS_PLUGIN_VERSION; |
| 135 | } |
| 136 | public function getPluginVersions() { |
| 137 | $ret = ['basic'=>SASO_EVENTTICKETS_PLUGIN_VERSION, 'premium'=>'', 'debug'=>'']; |
| 138 | if (defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION')) { |
| 139 | $ret['premium'] = SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION; |
| 140 | } |
| 141 | if (defined('WP_DEBUG') && WP_DEBUG) { |
| 142 | $ret['debug'] = esc_html__('is active', 'event-tickets-with-ticket-scanner'); |
| 143 | } |
| 144 | return $ret; |
| 145 | } |
| 146 | public function getDB() { |
| 147 | return SASO_EVENTTICKETS::getDB(plugin_dir_path(__FILE__), "sasoEventticketsDB", $this); |
| 148 | } |
| 149 | public function getBase() { |
| 150 | if ($this->BASE == null) { |
| 151 | if (!class_exists('sasoEventtickets_Base')) { |
| 152 | include_once plugin_dir_path(__FILE__)."sasoEventtickets_Base.php"; |
| 153 | } |
| 154 | $this->BASE = new sasoEventtickets_Base($this); |
| 155 | } |
| 156 | return $this->BASE; |
| 157 | } |
| 158 | public function getCore() { |
| 159 | if ($this->CORE == null) { |
| 160 | if (!class_exists('sasoEventtickets_Core')) { |
| 161 | include_once plugin_dir_path(__FILE__)."sasoEventtickets_Core.php"; |
| 162 | } |
| 163 | $this->CORE = new sasoEventtickets_Core($this); |
| 164 | } |
| 165 | return $this->CORE; |
| 166 | } |
| 167 | public function getAdmin() { |
| 168 | if ($this->ADMIN == null) { |
| 169 | if (!class_exists('sasoEventtickets_AdminSettings')) { |
| 170 | include_once plugin_dir_path(__FILE__)."sasoEventtickets_AdminSettings.php"; |
| 171 | } |
| 172 | $this->ADMIN = new sasoEventtickets_AdminSettings($this); |
| 173 | } |
| 174 | return $this->ADMIN; |
| 175 | } |
| 176 | public function getFrontend() { |
| 177 | if ($this->FRONTEND == null) { |
| 178 | if (!class_exists('sasoEventtickets_Frontend')) { |
| 179 | include_once plugin_dir_path(__FILE__)."sasoEventtickets_Frontend.php"; |
| 180 | } |
| 181 | $this->FRONTEND = new sasoEventtickets_Frontend($this); |
| 182 | } |
| 183 | return $this->FRONTEND; |
| 184 | } |
| 185 | public function getOptions() { |
| 186 | if ($this->OPTIONS == null) { |
| 187 | if (!class_exists('sasoEventtickets_Options')) { |
| 188 | include_once plugin_dir_path(__FILE__)."sasoEventtickets_Options.php"; |
| 189 | } |
| 190 | $this->OPTIONS = new sasoEventtickets_Options($this, $this->_prefix); |
| 191 | $this->OPTIONS->initOptions(); |
| 192 | } |
| 193 | return $this->OPTIONS; |
| 194 | } |
| 195 | public function getNewPDFObject() { |
| 196 | if (!class_exists('sasoEventtickets_PDF')) { |
| 197 | require_once("sasoEventtickets_PDF.php"); |
| 198 | } |
| 199 | $pdf = new sasoEventtickets_PDF(); |
| 200 | $pdf->setFontSize($this->getOptions()->getOptionValue('wcTicketPDFFontSize')); |
| 201 | $pdf->setFontFamily($this->getOptions()->getOptionValue('wcTicketPDFFontFamily')); |
| 202 | $pdf = apply_filters( $this->_add_filter_prefix.'main_getNewPDFObject', $pdf ); |
| 203 | return $pdf; |
| 204 | } |
| 205 | public function loadOnce($className, $filename="") { |
| 206 | if (!class_exists($className)) { |
| 207 | if ($filename == "") $filename = $className; |
| 208 | include_once __DIR__.'/'.$filename.'.php'; |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * Load class from /includes/ folder structure |
| 214 | * |
| 215 | * @param string $className The class name to load |
| 216 | * @param string $relativePath Path relative to plugin root (e.g., 'includes/woocommerce/class-base.php') |
| 217 | * @return void |
| 218 | */ |
| 219 | private function loadClass(string $className, string $relativePath): void { |
| 220 | if (class_exists($className)) { |
| 221 | return; // Already loaded |
| 222 | } |
| 223 | |
| 224 | $path = __DIR__ . '/' . $relativePath; |
| 225 | |
| 226 | if (file_exists($path)) { |
| 227 | require_once $path; |
| 228 | } |
| 229 | } |
| 230 | public function getWC() { |
| 231 | $this->loadOnce('sasoEventtickets_WC', "woocommerce-hooks"); |
| 232 | return sasoEventtickets_WC::Instance(); |
| 233 | } |
| 234 | public function getTicketHandler() { |
| 235 | $this->loadOnce('sasoEventtickets_Ticket'); |
| 236 | return sasoEventtickets_Ticket::Instance($_SERVER["REQUEST_URI"]); |
| 237 | } |
| 238 | public function getTicketDesignerHandler($template="") { |
| 239 | $this->loadOnce('sasoEventtickets_TicketDesigner'); |
| 240 | return sasoEventtickets_TicketDesigner::Instance($this, $template); |
| 241 | } |
| 242 | public function getTicketBadgeHandler() { |
| 243 | $this->loadOnce('sasoEventtickets_TicketBadge'); |
| 244 | return sasoEventtickets_TicketBadge::Instance(); |
| 245 | } |
| 246 | public function getTicketQRHandler() { |
| 247 | $this->loadOnce('sasoEventtickets_TicketQR'); |
| 248 | return sasoEventtickets_TicketQR::Instance(); |
| 249 | } |
| 250 | public function getAuthtokenHandler() { |
| 251 | $this->loadOnce('sasoEventtickets_Authtoken'); |
| 252 | return sasoEventtickets_Authtoken::Instance(); |
| 253 | } |
| 254 | public function getSeating() { |
| 255 | $this->loadOnce('sasoEventtickets_Seating'); |
| 256 | return sasoEventtickets_Seating::Instance($this); |
| 257 | } |
| 258 | public function getCongressRepository() { |
| 259 | if ($this->CONGRESS_REPO === null) { |
| 260 | require_once plugin_dir_path(__FILE__) . 'includes/congress/class-congress-repository.php'; |
| 261 | $this->CONGRESS_REPO = new sasoEventtickets_CongressRepository($this); |
| 262 | } |
| 263 | return $this->CONGRESS_REPO; |
| 264 | } |
| 265 | public function getCongressAdmin() { |
| 266 | if ($this->CONGRESS_ADMIN === null) { |
| 267 | require_once plugin_dir_path(__FILE__) . 'includes/congress/class-congress-admin.php'; |
| 268 | $this->CONGRESS_ADMIN = new sasoEventtickets_CongressAdmin($this); |
| 269 | } |
| 270 | return $this->CONGRESS_ADMIN; |
| 271 | } |
| 272 | public function getCongressPage() { |
| 273 | if ($this->CONGRESS_PAGE === null) { |
| 274 | require_once plugin_dir_path(__FILE__) . 'includes/congress/class-congress-page.php'; |
| 275 | $this->CONGRESS_PAGE = new sasoEventtickets_CongressPage($this); |
| 276 | } |
| 277 | return $this->CONGRESS_PAGE; |
| 278 | } |
| 279 | |
| 280 | public function isOldPremiumDetected(): bool { |
| 281 | return $this->_oldPremiumDetected; |
| 282 | } |
| 283 | |
| 284 | public function isStarterOrStopDetected(): bool { |
| 285 | return $this->_starterOrStopDetected; |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * After a valid license key is saved, automatically upgrade the premium |
| 290 | * plugin from starter/stop to the real premium version. |
| 291 | * |
| 292 | * Customers install the starter, enter their license, but don't manually |
| 293 | * click "Update" — they see expiration warnings and contact support. |
| 294 | * This triggers the upgrade in the background. |
| 295 | * |
| 296 | * Runs synchronously (takes ~5-10s) — acceptable since user just clicked Save. |
| 297 | * Errors are logged but not surfaced to the user. |
| 298 | */ |
| 299 | public function autoUpgradePremiumAfterLicenseSave(): void { |
| 300 | try { |
| 301 | $premFolder = trim($this->getPremiumPluginFolder(), '/'); |
| 302 | if (empty($premFolder)) return; |
| 303 | |
| 304 | // Find the premium plugin file in active_plugins |
| 305 | $pluginFile = null; |
| 306 | foreach (get_option('active_plugins', []) as $p) { |
| 307 | if (strpos($p, $premFolder . '/') === 0) { |
| 308 | $pluginFile = $p; |
| 309 | break; |
| 310 | } |
| 311 | } |
| 312 | if (empty($pluginFile)) return; |
| 313 | |
| 314 | // Force WP to re-check for plugin updates (clears PUC cache) |
| 315 | delete_site_transient('update_plugins'); |
| 316 | delete_site_transient('puc_request_info_saso-event-tickets-with-ticket-scanner-premium'); |
| 317 | wp_update_plugins(); |
| 318 | |
| 319 | // Check if an update is available |
| 320 | $updates = get_site_transient('update_plugins'); |
| 321 | if (empty($updates->response[$pluginFile])) { |
| 322 | return; // Already on latest — nothing to do |
| 323 | } |
| 324 | |
| 325 | // Silently upgrade |
| 326 | if (!class_exists('Plugin_Upgrader')) { |
| 327 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; |
| 328 | } |
| 329 | if (!class_exists('Automatic_Upgrader_Skin')) { |
| 330 | require_once ABSPATH . 'wp-admin/includes/class-automatic-upgrader-skin.php'; |
| 331 | } |
| 332 | $upgrader = new Plugin_Upgrader(new \Automatic_Upgrader_Skin()); |
| 333 | $result = $upgrader->upgrade($pluginFile); |
| 334 | |
| 335 | // Post-upgrade cleanup: the update_plugins transient still contains the |
| 336 | // "update available" entry until the next WP-Cron run (12h). This makes |
| 337 | // the red "update" badge stick around even though the plugin is already |
| 338 | // on the latest version. Clear + re-check now so the badge disappears |
| 339 | // immediately on the next page load. |
| 340 | if ($result === true) { |
| 341 | delete_site_transient('update_plugins'); |
| 342 | delete_site_transient('puc_request_info_saso-event-tickets-with-ticket-scanner-premium'); |
| 343 | // Clear PUC's own state store so it can't re-inject the stale |
| 344 | // "update available" entry on next request (belt + suspenders). |
| 345 | delete_site_option('external_updates-saso-event-tickets-with-ticket-scanner-premium'); |
| 346 | wp_update_plugins(); |
| 347 | } |
| 348 | } catch (\Throwable $e) { |
| 349 | error_log('Event Tickets: auto-upgrade after license save failed: ' . $e->getMessage()); |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | public function getPremiumFunctions() { |
| 354 | if ($this->_isPrem == null && $this->PREMFUNCTIONS == null) { |
| 355 | $this->_isPrem = false; |
| 356 | $this->PREMFUNCTIONS = new sasoEventtickets_fakeprem(); |
| 357 | |
| 358 | // Check for Premium class - function-based compatibility check |
| 359 | if (class_exists('sasoEventtickets_PremiumFunctions')) { |
| 360 | // Check if this is Starter or Stop plugin (both are "update-only" placeholders) |
| 361 | $is_starter_or_stop = defined('SASO_EVENTTICKETS_STARTER_VERSION') || defined('SASO_EVENTTICKETS_STOP_VERSION'); |
| 362 | |
| 363 | if ($is_starter_or_stop) { |
| 364 | // Starter/Stop plugin detected - treat as "premium not active" |
| 365 | // Show message: Enter license, then update via plugin area |
| 366 | $this->_starterOrStopDetected = true; |
| 367 | $this->PREMFUNCTIONS = new sasoEventtickets_fakeprem(); |
| 368 | } elseif (!defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION')) { |
| 369 | // Premium plugin exists but no version defined - likely very old or corrupted |
| 370 | $this->_oldPremiumDetected = true; |
| 371 | } else { |
| 372 | // Compatibility check BEFORE instantiation — the premium constructor |
| 373 | // registers 25+ WordPress hooks on $this. If we instantiate first and |
| 374 | // discard the object afterwards, WP keeps the callbacks alive in memory |
| 375 | // and fires them later, which crashes the site on old premiums that |
| 376 | // reference methods removed in the WC manager refactor (2.7.x+). |
| 377 | // |
| 378 | // Use ReflectionClass to inspect methods WITHOUT running the constructor. |
| 379 | $min_premium_version = '1.5.0'; |
| 380 | $version_too_old = version_compare(SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION, $min_premium_version, '<'); |
| 381 | |
| 382 | // Fingerprint methods added in Premium 1.5.0 — Premium 1.5.x still works |
| 383 | // with the current basic because it only calls the deprecated proxies |
| 384 | // getTicketsFromOrder() / add_serialcode_to_order(). 1.3.x / 1.4.x do |
| 385 | // not have these methods and their hook callbacks crash later. |
| 386 | $has_required_methods = false; |
| 387 | try { |
| 388 | $reflection = new ReflectionClass('sasoEventtickets_PremiumFunctions'); |
| 389 | $has_required_methods = $reflection->hasMethod('maxValues') |
| 390 | && $reflection->hasMethod('ticket_outputTicketInfo_template') |
| 391 | && $reflection->hasMethod('wc_order_add_meta_boxes'); |
| 392 | } catch (\Throwable $e) { |
| 393 | // Reflection itself failed — treat as incompatible |
| 394 | $has_required_methods = false; |
| 395 | } |
| 396 | |
| 397 | if ($version_too_old || !$has_required_methods) { |
| 398 | // Old premium detected — DO NOT instantiate. Instantiating would |
| 399 | // register hooks that crash later when WooCommerce fires events. |
| 400 | $this->_oldPremiumDetected = true; |
| 401 | error_log('Event Tickets: Incompatible old premium detected (v' . SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION . ') — not instantiated to prevent hook crashes.'); |
| 402 | } else { |
| 403 | // Version and methods are OK — safe to instantiate. |
| 404 | // Keep try/catch as safety net for unexpected constructor errors. |
| 405 | try { |
| 406 | $this->PREMFUNCTIONS = new sasoEventtickets_PremiumFunctions($this, plugin_dir_path(__FILE__), $this->_prefix, $this->getDB()); |
| 407 | $this->_isPrem = $this->PREMFUNCTIONS->isPremium(); |
| 408 | } catch (\Throwable $e) { |
| 409 | $this->_oldPremiumDetected = true; |
| 410 | $this->PREMFUNCTIONS = new sasoEventtickets_fakeprem(); |
| 411 | error_log('Event Tickets: Premium instantiation failed: ' . $e->getMessage()); |
| 412 | } |
| 413 | } |
| 414 | } |
| 415 | } else { |
| 416 | // Premium class not yet available — basic plugin loaded before premium. |
| 417 | // Defer premium setup until all plugins are loaded (plugins_loaded hook). |
| 418 | $premPluginFolder = $this->getPremiumPluginFolder(); |
| 419 | if (!empty($premPluginFolder)) { |
| 420 | add_action('plugins_loaded', [$this, '_lateLoadPremium'], 1); |
| 421 | } |
| 422 | } |
| 423 | } |
| 424 | return $this->PREMFUNCTIONS; |
| 425 | } |
| 426 | /** |
| 427 | * Late-load premium plugin when basic loaded before premium (plugin loading order). |
| 428 | * Hooked to plugins_loaded with priority 1 so it runs before WooCommercePluginLoaded (priority 20). |
| 429 | */ |
| 430 | public function _lateLoadPremium(): void { |
| 431 | if (!class_exists('sasoEventtickets_PremiumFunctions')) { |
| 432 | return; // Premium plugin not installed/active |
| 433 | } |
| 434 | if ($this->PREMFUNCTIONS instanceof sasoEventtickets_PremiumFunctions) { |
| 435 | return; // Already loaded |
| 436 | } |
| 437 | |
| 438 | // Check for Starter/Stop plugin first (update-only placeholders, no premium features) |
| 439 | if (defined('SASO_EVENTTICKETS_STARTER_VERSION') || defined('SASO_EVENTTICKETS_STOP_VERSION')) { |
| 440 | $this->_starterOrStopDetected = true; |
| 441 | return; |
| 442 | } |
| 443 | |
| 444 | $min_premium_version = '1.5.0'; |
| 445 | if (!defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION') |
| 446 | || version_compare(SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION, $min_premium_version, '<')) { |
| 447 | $this->_oldPremiumDetected = true; |
| 448 | return; |
| 449 | } |
| 450 | |
| 451 | // Reflection-based method check BEFORE instantiation — same reason as in |
| 452 | // getPremiumFunctions(): old premium constructors register hooks that crash |
| 453 | // later if methods were removed in the WC manager refactor. |
| 454 | // Fingerprint methods added in Premium 1.5.0. |
| 455 | try { |
| 456 | $reflection = new ReflectionClass('sasoEventtickets_PremiumFunctions'); |
| 457 | $has_required_methods = $reflection->hasMethod('maxValues') |
| 458 | && $reflection->hasMethod('ticket_outputTicketInfo_template') |
| 459 | && $reflection->hasMethod('wc_order_add_meta_boxes'); |
| 460 | } catch (\Throwable $e) { |
| 461 | $has_required_methods = false; |
| 462 | } |
| 463 | if (!$has_required_methods) { |
| 464 | $this->_oldPremiumDetected = true; |
| 465 | error_log('Event Tickets: Incompatible old premium detected in late-load — not instantiated.'); |
| 466 | return; |
| 467 | } |
| 468 | |
| 469 | try { |
| 470 | $this->PREMFUNCTIONS = new sasoEventtickets_PremiumFunctions($this, plugin_dir_path(__FILE__), $this->_prefix, $this->getDB()); |
| 471 | $this->_isPrem = $this->PREMFUNCTIONS->isPremium(); |
| 472 | |
| 473 | if (method_exists($this->PREMFUNCTIONS, 'initHandlers')) { |
| 474 | $this->PREMFUNCTIONS->initHandlers(); |
| 475 | } |
| 476 | } catch (\Throwable $e) { |
| 477 | // Premium loading failed — degrade gracefully to free mode |
| 478 | $this->PREMFUNCTIONS = new sasoEventtickets_fakeprem(); |
| 479 | $this->_isPrem = false; |
| 480 | error_log('Event Tickets: Premium late-load failed: ' . $e->getMessage()); |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | public function getPremiumPluginFolder(): string { |
| 485 | $plugins = get_option('active_plugins', []); |
| 486 | $premiumFile = ""; |
| 487 | foreach($plugins as $plugin) { |
| 488 | if (strpos(" ".$plugin, $this->_premium_plugin_name) > 0) { |
| 489 | $premiumFile = plugin_dir_path($plugin); |
| 490 | break; |
| 491 | } |
| 492 | } |
| 493 | return $premiumFile; |
| 494 | } |
| 495 | public function isPremium() { |
| 496 | if ($this->_isPrem === null) { |
| 497 | $this->getPremiumFunctions(); |
| 498 | // If PREMFUNCTIONS already existed, getPremiumFunctions() skipped re-evaluation. |
| 499 | // Re-query the premium plugin directly. |
| 500 | if ($this->_isPrem === null && $this->PREMFUNCTIONS !== null) { |
| 501 | $this->_isPrem = ($this->PREMFUNCTIONS instanceof sasoEventtickets_PremiumFunctions) |
| 502 | ? $this->PREMFUNCTIONS->isPremium() |
| 503 | : false; |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | // Eigene Verifikation: auch wenn Premium-Plugin "ja" sagt, |
| 508 | // Basic Plugin prüft den gespeicherten Lizenzstatus selbst. |
| 509 | // Rekursionsschutz: isPremium() -> getTicketHandler() -> Konstruktor -> getOptions() -> isPremium() |
| 510 | if ($this->_isPrem === true && !$this->_isCheckingSubscription) { |
| 511 | $this->_isCheckingSubscription = true; |
| 512 | try { |
| 513 | if (!$this->getTicketHandler()->isSubscriptionActive()) { |
| 514 | $this->_isPrem = false; |
| 515 | } |
| 516 | } finally { |
| 517 | $this->_isCheckingSubscription = false; |
| 518 | } |
| 519 | } |
| 520 | |
| 521 | return $this->_isPrem; |
| 522 | } |
| 523 | /** |
| 524 | * Invalidate the cached premium status so the next isPremium() call re-evaluates. |
| 525 | */ |
| 526 | public function invalidatePremiumCache(): void { |
| 527 | $this->_isPrem = null; |
| 528 | $this->PREMFUNCTIONS = null; |
| 529 | } |
| 530 | public function getPrefix() { |
| 531 | return $this->_prefix; |
| 532 | } |
| 533 | public function getMV() { |
| 534 | $v = ['storeip'=>false,'allowuserreg'=>false,'codes_total'=>0x13,'codes'=>0x12,'lists'=>5,'authtokens_total'=>3]; |
| 535 | $v["codes"] = (int) hexdec(0x80 / 0x002) / 2; |
| 536 | $v["codes_total"] = (int) hexdec(0x80 / 0x002) / 2; |
| 537 | $v["seatingplans"] = (int) (0x04 >> 0x02); |
| 538 | $v["seats_per_plan"] = (int) (0x50 >> 0x02); |
| 539 | return $v; |
| 540 | } |
| 541 | public function listener_upgrader_process_complete( $upgrader_object, $options ) { |
| 542 | $current_plugin_path_name = plugin_basename( __FILE__ ); |
| 543 | if ($options['action'] == 'update' && $options['type'] == 'plugin' ) { |
| 544 | if (isset($options['plugins'])) { |
| 545 | foreach($options['plugins'] as $each_plugin) { |
| 546 | if ($each_plugin==$current_plugin_path_name) { |
| 547 | // .......................... YOUR CODES ............. |
| 548 | } |
| 549 | } |
| 550 | } |
| 551 | } |
| 552 | do_action( $this->_do_action_prefix.'main_listener_upgrader_process_complete' ); |
| 553 | } |
| 554 | /** |
| 555 | * check for ticket detail page request |
| 556 | */ |
| 557 | public function wc_checkTicketDetailPage() { |
| 558 | if( is_404() ){ |
| 559 | include_once("SASO_EVENTTICKETS.php"); |
| 560 | // /wp-content/plugins/event-tickets-with-ticket-scanner/ticket/ |
| 561 | $p = $this->getCore()->getTicketURLPath(true); |
| 562 | $t = explode("/", $_SERVER["REQUEST_URI"]); |
| 563 | if (count($t) > 1) { |
| 564 | if ($t[count($t)-2] != "scanner") { |
| 565 | if(substr($_SERVER["REQUEST_URI"], 0, strlen($p)) == $p) { |
| 566 | $this->getTicketHandler()->initFilterAndActions(); |
| 567 | } else { |
| 568 | $wcTicketCompatibilityModeURLPath = trim($this->getOptions()->getOptionValue('wcTicketCompatibilityModeURLPath')); |
| 569 | $wcTicketCompatibilityModeURLPath = trim(trim($wcTicketCompatibilityModeURLPath, "/")); |
| 570 | if (!empty($wcTicketCompatibilityModeURLPath)) { |
| 571 | $uri = trim($_SERVER["REQUEST_URI"]); |
| 572 | if (!empty($uri)) { |
| 573 | $pos = strpos($uri, $wcTicketCompatibilityModeURLPath); |
| 574 | if ($pos > 0) { |
| 575 | $this->getTicketHandler()->initFilterAndActions(); |
| 576 | } |
| 577 | } |
| 578 | } |
| 579 | } |
| 580 | } |
| 581 | |
| 582 | if ($t[count($t)-2] == "scanner") { |
| 583 | if(substr($_SERVER["REQUEST_URI"], 0, strlen($p)) == $p) { |
| 584 | //$this->replacingShortcodeTicketScanner(); |
| 585 | $this->getTicketHandler()->initFilterAndActionsTicketScanner(); |
| 586 | } else { |
| 587 | $wcTicketCompatibilityModeURLPath = trim($this->getOptions()->getOptionValue('wcTicketCompatibilityModeURLPath')); |
| 588 | $wcTicketCompatibilityModeURLPath = trim(trim($wcTicketCompatibilityModeURLPath, "/")); |
| 589 | if (!empty($wcTicketCompatibilityModeURLPath)) { |
| 590 | $uri = trim($_SERVER["REQUEST_URI"]); |
| 591 | if (!empty($uri)) { |
| 592 | $pos = strpos($_SERVER["REQUEST_URI"], $wcTicketCompatibilityModeURLPath."/scanner/"); |
| 593 | if ($pos > 0) { |
| 594 | $this->getTicketHandler()->initFilterAndActionsTicketScanner(); |
| 595 | } |
| 596 | } |
| 597 | } |
| 598 | } |
| 599 | } |
| 600 | } |
| 601 | } // endif 404 |
| 602 | } |
| 603 | private function init_frontend() { |
| 604 | add_shortcode($this->_shortcode, [$this, 'replacingShortcode']); |
| 605 | add_shortcode($this->_shortcode_mycode, [$this, 'replacingShortcodeMyCode']); |
| 606 | add_shortcode($this->_shortcode_ticket_scanner, [$this, 'replacingShortcodeTicketScanner']); |
| 607 | add_shortcode($this->_shortcode_eventviews, [$this, 'replacingShortcodeEventViews']); |
| 608 | add_shortcode($this->_shortcode_feature_list, [$this, 'replacingShortcodeFeatureList']); |
| 609 | add_shortcode($this->_shortcode_ticket_detail, [$this, 'replacingShortcodeTicketDetail']); |
| 610 | |
| 611 | // Congress: REST API always registered; frontend page only when active |
| 612 | // (guarded — class files arrive in later tasks) |
| 613 | add_action('rest_api_init', function() { |
| 614 | $apiFile = plugin_dir_path(__FILE__) . 'includes/congress/class-congress-api.php'; |
| 615 | if (!file_exists($apiFile)) return; |
| 616 | require_once $apiFile; |
| 617 | (new sasoEventtickets_CongressApi($this))->registerRoutes(); |
| 618 | }); |
| 619 | // Congress page is served via the ticket route (…/ticket/{TICKETID}?congress) — see |
| 620 | // sasoEventtickets_Ticket::output(). No separate front route needed. |
| 621 | |
| 622 | do_action( $this->_do_action_prefix.'main_init_frontend' ); |
| 623 | } |
| 624 | private function init_backend() { |
| 625 | add_action('admin_menu', [$this, 'register_options_page']); |
| 626 | register_activation_hook(__FILE__, [$this, 'plugin_activated']); |
| 627 | register_deactivation_hook( __FILE__, [$this, 'plugin_deactivated'] ); |
| 628 | //register_uninstall_hook( __FILE__, 'sasoEventticketsDB::plugin_uninstall' ); // MUSS NOCH GETESTE WERDEN |
| 629 | add_action( 'plugins_loaded', [$this, 'plugins_loaded'] ); |
| 630 | add_action( 'show_user_profile', [$this, 'show_user_profile'] ); |
| 631 | add_action( 'admin_init', [$this, 'handleFormatWarningDismiss'] ); |
| 632 | add_action( 'admin_notices', [$this, 'showSubscriptionWarning'] ); |
| 633 | add_action( 'admin_notices', [$this, 'showFomoBanner'] ); |
| 634 | add_action( 'admin_notices', [$this, 'showOutdatedPremiumWarning'] ); |
| 635 | add_action( 'admin_notices', [$this, 'showFormatWarning'] ); |
| 636 | add_action( 'admin_notices', [$this, 'showPhpVersionWarning'] ); |
| 637 | add_action( 'admin_notices', [$this, 'showOptionsMigrationNotice'] ); |
| 638 | add_action( 'wp_ajax_saso_et_dismiss_fomo', [$this, 'ajaxDismissFomo'] ); |
| 639 | |
| 640 | if (basename($_SERVER['SCRIPT_NAME'] ?? '') == "admin-ajax.php") { |
| 641 | add_action('wp_ajax_'.$this->_prefix.'_executeAdminSettings', [$this,'executeAdminSettings_a'], 10, 0); |
| 642 | add_action('wp_ajax_'.$this->_prefix.'_executeSeatingAdmin', [$this,'executeSeatingAdmin_a'], 10, 0); |
| 643 | } |
| 644 | |
| 645 | add_action('admin_init', [$this, 'periodicLicenseCheck']); |
| 646 | |
| 647 | // Congress: Admin menu always active (guarded — class files arrive in later tasks) |
| 648 | if (file_exists(plugin_dir_path(__FILE__) . 'includes/congress/class-congress-admin.php')) { |
| 649 | add_action('admin_menu', [$this, 'relay_congress_register_menu']); |
| 650 | |
| 651 | // Congress AJAX: only when on admin-ajax.php |
| 652 | if (basename($_SERVER['SCRIPT_NAME'] ?? '') == "admin-ajax.php") { |
| 653 | add_action('wp_ajax_saso_et_congress', [$this, 'relay_congress_handle_ajax']); |
| 654 | } |
| 655 | } |
| 656 | |
| 657 | do_action( $this->_do_action_prefix.'main_init_backend' ); |
| 658 | } |
| 659 | |
| 660 | /** |
| 661 | * Periodic license check on admin page loads. |
| 662 | * Hard-throttled via site transient so that even if many admin requests come in |
| 663 | * (bots, health-checks, other plugins hammering admin-ajax) only one actual |
| 664 | * license call fires per interval. |
| 665 | */ |
| 666 | public function periodicLicenseCheck(): void { |
| 667 | // Hard throttle: site transient = max 1 check per interval, independent of |
| 668 | // option-based last_run throttling below. Prevents runaway requests when |
| 669 | // admin pages are hit rapidly (monitoring tools, page builders, broken cron). |
| 670 | $throttleKey = 'saso_et_periodic_license_check_lock'; |
| 671 | if (get_site_transient($throttleKey)) return; |
| 672 | |
| 673 | // Also run when premium plugin is installed with a serial key but isPremium() is false |
| 674 | // This breaks the deadlock where isPremium()=false prevents the license check from ever running |
| 675 | $hasPremiumPlugin = class_exists('sasoEventtickets_PremiumFunctions'); |
| 676 | $hasSerial = !empty(trim(get_option("saso-event-tickets-premium_serial", ""))); |
| 677 | if (!$this->isPremium() && !($hasPremiumPlugin && $hasSerial)) return; |
| 678 | |
| 679 | // Set throttle lock IMMEDIATELY — before any other work — so concurrent |
| 680 | // admin_init calls bail out. Full interval even on recovery mode. |
| 681 | $interval = $this->isPremium() ? 86400 : 3600; |
| 682 | set_site_transient($throttleKey, time(), $interval); |
| 683 | |
| 684 | $info = $this->getTicketHandler()->get_expiration(); |
| 685 | $last_run = intval($info['last_run']); |
| 686 | if ($last_run > 0 && (time() - $last_run) < $interval) return; |
| 687 | |
| 688 | // Check ausführen |
| 689 | $this->getTicketHandler()->checkForPremiumSerialExpiration(); |
| 690 | |
| 691 | // isPremium Cache invalidieren damit neuer Wert gilt |
| 692 | $this->invalidatePremiumCache(); |
| 693 | } |
| 694 | public function WooCommercePluginLoaded() { |
| 695 | // DON'T load WC here - let relay functions do lazy loading |
| 696 | //$this->getWC(); // um die wc handler zu laden |
| 697 | add_action('woocommerce_review_order_after_cart_contents', [$this, 'relay_woocommerce_review_order_after_cart_contents']); |
| 698 | add_action('woocommerce_checkout_process', [$this, 'relay_woocommerce_checkout_process']); |
| 699 | add_action('woocommerce_before_cart_table', [$this, 'relay_woocommerce_before_cart_table']); |
| 700 | add_action('woocommerce_cart_updated', [$this, 'relay_woocommerce_cart_updated']); |
| 701 | add_filter('woocommerce_email_attachments', [$this, 'relay_woocommerce_email_attachments'], 10, 3); |
| 702 | add_action('woocommerce_checkout_create_order_line_item', [$this, 'relay_woocommerce_checkout_create_order_line_item'], 20, 4 ); |
| 703 | add_action('woocommerce_check_cart_items', [$this, 'relay_woocommerce_check_cart_items'] ); |
| 704 | add_action('woocommerce_new_order', [$this, 'relay_woocommerce_new_order'], 10, 1); |
| 705 | add_action('woocommerce_checkout_update_order_meta', [$this, 'relay_woocommerce_checkout_update_order_meta'], 20, 2); |
| 706 | add_action('woocommerce_order_status_changed', [$this, 'relay_woocommerce_order_status_changed'], 10, 3); |
| 707 | add_filter('woocommerce_order_item_display_meta_key', [$this, 'relay_woocommerce_order_item_display_meta_key'], 20, 3 ); |
| 708 | add_filter('woocommerce_order_item_display_meta_value', [$this, 'relay_woocommerce_order_item_display_meta_value'], 20, 3); |
| 709 | add_action('wpo_wcpdf_after_item_meta', [$this, 'relay_wpo_wcpdf_after_item_meta'], 20, 3 ); |
| 710 | add_action('woocommerce_order_item_meta_start', [$this, 'relay_woocommerce_order_item_meta_start'], 201, 4); |
| 711 | add_action('woocommerce_product_after_variable_attributes', [$this, 'relay_woocommerce_product_after_variable_attributes'], 10, 3); |
| 712 | add_action('woocommerce_save_product_variation',[$this, 'relay_woocommerce_save_product_variation'], 10 ,2 ); |
| 713 | add_action('woocommerce_email_order_meta', [$this, 'relay_woocommerce_email_order_meta'], 10, 4 ); |
| 714 | add_action('woocommerce_thankyou', [$this, 'relay_woocommerce_thankyou'], 5); |
| 715 | if (wp_doing_ajax()) { |
| 716 | // erlaube ajax nonpriv und registriere handler |
| 717 | add_action('wp_ajax_nopriv_'.$this->getPrefix().'_executeWCFrontend', [$this,'relay_executeWCFrontend']); // nicht angemeldete user, sollen eine antwort erhalten |
| 718 | add_action('wp_ajax_'.$this->getPrefix().'_executeWCFrontend', [$this,'relay_executeWCFrontend']); // nicht angemeldete user, sollen eine antwort erhalten |
| 719 | // Seating Frontend AJAX (seat selection in shop) |
| 720 | add_action('wp_ajax_nopriv_'.$this->getPrefix().'_executeSeatingFrontend', [$this,'relay_executeSeatingFrontend']); |
| 721 | add_action('wp_ajax_'.$this->getPrefix().'_executeSeatingFrontend', [$this,'relay_executeSeatingFrontend']); |
| 722 | } |
| 723 | if (is_admin()) { |
| 724 | add_action('woocommerce_delete_order', [$this, 'relay_woocommerce_delete_order'], 10, 1 ); |
| 725 | add_action('woocommerce_delete_order_item', [$this, 'relay_woocommerce_delete_order_item'], 20, 1); |
| 726 | add_action('woocommerce_pre_delete_order_refund', [$this, 'relay_woocommerce_pre_delete_order_refund'], 10, 3); |
| 727 | add_action('woocommerce_delete_order_refund', [$this, 'relay_woocommerce_delete_order_refund'], 10, 1 ); |
| 728 | add_action('woocommerce_order_partially_refunded', [$this, 'relay_woocommerce_order_partially_refunded'], 10, 2); |
| 729 | add_filter('woocommerce_product_data_tabs', [$this, 'relay_woocommerce_product_data_tabs'], 98 ); |
| 730 | add_action('woocommerce_product_data_panels', [$this, 'relay_woocommerce_product_data_panels'] ); |
| 731 | add_action('woocommerce_process_product_meta', [$this, 'relay_woocommerce_process_product_meta'], 10, 2 ); |
| 732 | add_action('add_meta_boxes', [$this, 'relay_add_meta_boxes'], 10, 2); |
| 733 | add_filter('manage_edit-product_columns', [$this, 'relay_manage_edit_product_columns']); |
| 734 | add_action('manage_product_posts_custom_column', [$this, 'relay_manage_product_posts_custom_column'], 2); |
| 735 | add_filter("manage_edit-product_sortable_columns", [$this, 'relay_manage_edit_product_sortable_columns']); |
| 736 | } else { |
| 737 | add_action('woocommerce_single_product_summary', [$this, 'relay_woocommerce_single_product_summary']); |
| 738 | } |
| 739 | |
| 740 | // set routing -- NEEDS to be replaced by add_rewrite_rule later |
| 741 | add_action( 'template_redirect', [$this, 'wc_checkTicketDetailPage'], 1 ); |
| 742 | //$this->wc_checkTicketDetailPage(); |
| 743 | add_action('rest_api_init', function () { |
| 744 | SASO_EVENTTICKETS::setRestRoutesTicket(); |
| 745 | // Vollstart Wallet REST API (only if enabled) |
| 746 | if ($this->getOptions()->isOptionCheckboxActive('walletVollstartEnable')) { |
| 747 | include_once plugin_dir_path(__FILE__) . 'includes/wallet/class-wallet-rest.php'; |
| 748 | $walletRest = new sasoEventtickets_Wallet_REST($this); |
| 749 | $walletRest->register_routes(); |
| 750 | $walletRest->register_cors(); |
| 751 | } |
| 752 | }); |
| 753 | |
| 754 | 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 |
| 755 | add_filter('woocommerce_add_to_cart_validation', [$this, 'relay_woocommerce_add_to_cart_validation'], 10, 3); |
| 756 | add_filter('woocommerce_add_cart_item_data', [$this, 'relay_woocommerce_add_cart_item_data'], 10, 3); |
| 757 | add_action('woocommerce_add_to_cart', [$this, 'relay_woocommerce_add_to_cart'], 10, 6); |
| 758 | add_action('woocommerce_cart_item_removed', [$this, 'relay_woocommerce_cart_item_removed'], 10, 2); |
| 759 | add_action('woocommerce_after_cart_item_quantity_update', [$this, 'relay_woocommerce_after_cart_item_quantity_update'], 10, 4); |
| 760 | add_filter('woocommerce_update_cart_validation', [$this, 'relay_woocommerce_update_cart_validation'], 10, 4); |
| 761 | add_action('woocommerce_before_add_to_cart_button', [$this, 'relay_woocommerce_before_add_to_cart_button'], 15); |
| 762 | |
| 763 | // Vollstart Wallet: "Add to Wallet" button on ticket detail page |
| 764 | add_action($this->_do_action_prefix . 'ticket_outputTicketInfo_after', [$this, 'wallet_render_ticket_button'], 10, 2); |
| 765 | |
| 766 | // Vollstart Wallet: link in order emails |
| 767 | add_action($this->_do_action_prefix . 'woocommerce-hooks_woocommerce_email_order_meta', [$this, 'wallet_render_email_link'], 10, 4); |
| 768 | |
| 769 | do_action( $this->_do_action_prefix.'main_WooCommercePluginLoaded' ); |
| 770 | } |
| 771 | |
| 772 | /** |
| 773 | * Render the "Add to Vollstart Wallet" button on the ticket detail page. |
| 774 | */ |
| 775 | public function wallet_render_ticket_button(array $codeObj, bool $forPDFOutput): void { |
| 776 | if ($forPDFOutput) return; |
| 777 | if (!$this->getOptions()->isOptionCheckboxActive('walletVollstartEnable')) return; |
| 778 | |
| 779 | $codeObj = $this->getCore()->setMetaObj($codeObj); |
| 780 | $metaObj = $codeObj['metaObj']; |
| 781 | $walletUrl = $metaObj['wc_ticket']['_wallet_url'] ?? ''; |
| 782 | if (empty($walletUrl)) return; |
| 783 | |
| 784 | echo '<p style="text-align:center;margin-top:10px;">'; |
| 785 | echo '<a class="button" href="' . esc_url($walletUrl) . '" target="_blank" rel="noopener">'; |
| 786 | echo esc_html__('Add to Vollstart Wallet', 'event-tickets-with-ticket-scanner'); |
| 787 | echo '</a>'; |
| 788 | echo '</p>'; |
| 789 | } |
| 790 | |
| 791 | /** |
| 792 | * Render Vollstart Wallet links in WooCommerce order emails. |
| 793 | */ |
| 794 | public function wallet_render_email_link($order, bool $sent_to_admin, bool $plain_text, $email): void { |
| 795 | if ($sent_to_admin) return; |
| 796 | if (!$this->getOptions()->isOptionCheckboxActive('walletVollstartEnable')) return; |
| 797 | |
| 798 | $codes = $this->getCore()->getCodesByOrderId($order->get_id()); |
| 799 | if (empty($codes)) return; |
| 800 | |
| 801 | $walletLinks = []; |
| 802 | foreach ($codes as $codeRow) { |
| 803 | try { |
| 804 | $codeObj = $this->getCore()->retrieveCodeByCode($codeRow['code']); |
| 805 | $codeObj = $this->getCore()->setMetaObj($codeObj); |
| 806 | $metaObj = $codeObj['metaObj']; |
| 807 | if (intval($metaObj['wc_ticket']['is_ticket'] ?? 0) !== 1) continue; |
| 808 | $walletUrl = $metaObj['wc_ticket']['_wallet_url'] ?? ''; |
| 809 | if (empty($walletUrl)) continue; |
| 810 | |
| 811 | $walletLinks[] = $walletUrl; |
| 812 | } catch (\Exception $e) { |
| 813 | continue; |
| 814 | } |
| 815 | } |
| 816 | |
| 817 | if (empty($walletLinks)) return; |
| 818 | |
| 819 | if ($plain_text) { |
| 820 | echo "\n" . __('Add to Vollstart Wallet', 'event-tickets-with-ticket-scanner') . ":\n"; |
| 821 | foreach ($walletLinks as $url) { |
| 822 | echo $url . "\n"; |
| 823 | } |
| 824 | } else { |
| 825 | echo '<p><b>' . esc_html__('Add to Vollstart Wallet', 'event-tickets-with-ticket-scanner') . '</b></p>'; |
| 826 | foreach ($walletLinks as $url) { |
| 827 | echo '<p><a href="' . esc_url($url) . '" target="_blank" rel="noopener">' . esc_html__('Add to Wallet', 'event-tickets-with-ticket-scanner') . '</a></p>'; |
| 828 | } |
| 829 | } |
| 830 | } |
| 831 | |
| 832 | public function relay_congress_register_menu(): void { |
| 833 | $this->getCongressAdmin()->registerMenu(); |
| 834 | } |
| 835 | public function relay_congress_handle_ajax(): void { |
| 836 | $this->getCongressAdmin()->handleAjax(); |
| 837 | } |
| 838 | |
| 839 | /** |
| 840 | * product_id => public congress URL template (…/ticket/__TICKETID__?congress) for products that |
| 841 | * have a congress assigned. The admin ticket detail swaps __TICKETID__ for the public ticket id. |
| 842 | */ |
| 843 | public function getCongressProductUrlMap(): array { |
| 844 | $map = []; |
| 845 | $repoFile = plugin_dir_path(__FILE__) . 'includes/congress/class-congress-repository.php'; |
| 846 | if (!file_exists($repoFile)) return $map; |
| 847 | global $wpdb; |
| 848 | $rows = $wpdb->get_results( |
| 849 | "SELECT cp.product_id, c.slug |
| 850 | FROM {$wpdb->prefix}saso_eventtickets_congress_products cp |
| 851 | JOIN {$wpdb->prefix}saso_eventtickets_congresses c ON c.id = cp.congress_id", |
| 852 | ARRAY_A |
| 853 | ); |
| 854 | if (!$rows) return $map; |
| 855 | $repo = $this->getCongressRepository(); |
| 856 | foreach ($rows as $r) { |
| 857 | // URL template with a placeholder; JS swaps in the public ticket id. |
| 858 | $map[(int)$r['product_id']] = $repo->getUrl('__TICKETID__'); |
| 859 | } |
| 860 | return $map; |
| 861 | } |
| 862 | |
| 863 | public function relay_woocommerce_after_shop_loop_item() { |
| 864 | $this->getWC()->getFrontendManager()->woocommerce_after_shop_loop_item_handler(); |
| 865 | } |
| 866 | public function relay_woocommerce_add_to_cart_validation() { |
| 867 | $args = func_get_args(); |
| 868 | return $this->getWC()->getFrontendManager()->woocommerce_add_to_cart_validation_handler(...$args); |
| 869 | } |
| 870 | public function relay_woocommerce_add_cart_item_data() { |
| 871 | $args = func_get_args(); |
| 872 | return $this->getWC()->getFrontendManager()->woocommerce_add_cart_item_data_handler(...$args); |
| 873 | } |
| 874 | public function relay_woocommerce_add_to_cart() { |
| 875 | $args = func_get_args(); |
| 876 | return $this->getWC()->getFrontendManager()->woocommerce_add_to_cart_handler(...$args); |
| 877 | } |
| 878 | public function relay_woocommerce_cart_item_removed() { |
| 879 | $args = func_get_args(); |
| 880 | $this->getWC()->getFrontendManager()->woocommerce_cart_item_removed_handler(...$args); |
| 881 | } |
| 882 | public function relay_woocommerce_after_cart_item_quantity_update() { |
| 883 | $args = func_get_args(); |
| 884 | $this->getWC()->getFrontendManager()->woocommerce_after_cart_item_quantity_update_handler(...$args); |
| 885 | } |
| 886 | public function relay_woocommerce_update_cart_validation() { |
| 887 | $args = func_get_args(); |
| 888 | return $this->getWC()->getFrontendManager()->woocommerce_update_cart_validation_handler(...$args); |
| 889 | } |
| 890 | public function relay_woocommerce_before_add_to_cart_button() { |
| 891 | $this->getWC()->getFrontendManager()->woocommerce_before_add_to_cart_button_handler(); |
| 892 | } |
| 893 | public function relay_woocommerce_review_order_after_cart_contents() { |
| 894 | $this->getWC()->getFrontendManager()->woocommerce_review_order_after_cart_contents(); |
| 895 | } |
| 896 | public function relay_woocommerce_checkout_process() { |
| 897 | $this->getWC()->getFrontendManager()->woocommerce_checkout_process(); |
| 898 | } |
| 899 | public function relay_woocommerce_before_cart_table() { |
| 900 | $this->getWC()->getFrontendManager()->woocommerce_before_cart_table(); |
| 901 | } |
| 902 | public function relay_woocommerce_cart_updated() { |
| 903 | $this->getWC()->getFrontendManager()->woocommerce_cart_updated_handler(); |
| 904 | } |
| 905 | public function relay_woocommerce_email_attachments() { |
| 906 | $args = func_get_args(); |
| 907 | return $this->getWC()->getEmailHandler()->woocommerce_email_attachments(...$args); |
| 908 | } |
| 909 | public function relay_woocommerce_checkout_create_order_line_item() { |
| 910 | $args = func_get_args(); |
| 911 | return $this->getWC()->getOrderManager()->woocommerce_checkout_create_order_line_item(...$args); |
| 912 | } |
| 913 | public function relay_woocommerce_check_cart_items() { |
| 914 | $this->getWC()->getFrontendManager()->woocommerce_check_cart_items(); |
| 915 | } |
| 916 | public function relay_woocommerce_new_order() { |
| 917 | $args = func_get_args(); |
| 918 | return $this->getWC()->getOrderManager()->woocommerce_new_order(...$args); |
| 919 | } |
| 920 | public function relay_woocommerce_checkout_update_order_meta() { |
| 921 | $args = func_get_args(); |
| 922 | return $this->getWC()->getOrderManager()->woocommerce_checkout_update_order_meta(...$args); |
| 923 | } |
| 924 | public function relay_executeWCFrontend() { |
| 925 | return $this->getWC()->getFrontendManager()->executeWCFrontend(); |
| 926 | } |
| 927 | public function relay_executeSeatingFrontend() { |
| 928 | return $this->getSeating()->getFrontendManager()->executeSeatingFrontend(); |
| 929 | } |
| 930 | public function relay_woocommerce_delete_order() { |
| 931 | $args = func_get_args(); |
| 932 | $this->getWC()->getOrderManager()->woocommerce_delete_order(...$args); |
| 933 | } |
| 934 | public function relay_woocommerce_delete_order_item() { |
| 935 | $args = func_get_args(); |
| 936 | $this->getWC()->getOrderManager()->woocommerce_delete_order_item(...$args); |
| 937 | } |
| 938 | public function relay_woocommerce_pre_delete_order_refund() { |
| 939 | $args = func_get_args(); |
| 940 | $this->getWC()->getOrderManager()->woocommerce_pre_delete_order_refund(...$args); |
| 941 | } |
| 942 | public function relay_woocommerce_delete_order_refund() { |
| 943 | $args = func_get_args(); |
| 944 | $this->getWC()->getOrderManager()->woocommerce_delete_order_refund(...$args); |
| 945 | } |
| 946 | public function relay_woocommerce_product_data_tabs() { |
| 947 | $args = func_get_args(); |
| 948 | return $this->getWC()->getProductManager()->woocommerce_product_data_tabs(...$args); |
| 949 | } |
| 950 | public function relay_woocommerce_product_data_panels() { |
| 951 | $this->getWC()->getProductManager()->woocommerce_product_data_panels(); |
| 952 | } |
| 953 | public function relay_woocommerce_process_product_meta() { |
| 954 | $args = func_get_args(); |
| 955 | $this->getWC()->getProductManager()->woocommerce_process_product_meta(...$args); |
| 956 | } |
| 957 | public function relay_add_meta_boxes(...$args) { |
| 958 | $this->getWC()->add_meta_boxes(...$args); |
| 959 | } |
| 960 | public function relay_manage_edit_product_columns() { |
| 961 | $args = func_get_args(); |
| 962 | return $this->getWC()->getProductManager()->manage_edit_product_columns(...$args); |
| 963 | } |
| 964 | public function relay_manage_product_posts_custom_column() { |
| 965 | $args = func_get_args(); |
| 966 | $this->getWC()->getProductManager()->manage_product_posts_custom_column(...$args); |
| 967 | } |
| 968 | public function relay_manage_edit_product_sortable_columns() { |
| 969 | $args = func_get_args(); |
| 970 | return $this->getWC()->getProductManager()->manage_edit_product_sortable_columns(...$args); |
| 971 | } |
| 972 | public function relay_woocommerce_single_product_summary() { |
| 973 | $this->getWC()->getFrontendManager()->woocommerce_single_product_summary(); |
| 974 | } |
| 975 | public function relay_woocommerce_order_status_changed() { |
| 976 | $args = func_get_args(); |
| 977 | $this->getWC()->getOrderManager()->woocommerce_order_status_changed(...$args); |
| 978 | } |
| 979 | public function relay_woocommerce_order_partially_refunded() { |
| 980 | $args = func_get_args(); |
| 981 | $this->getWC()->getOrderManager()->woocommerce_order_partially_refunded(...$args); |
| 982 | } |
| 983 | public function relay_woocommerce_order_item_display_meta_key() { |
| 984 | $args = func_get_args(); |
| 985 | return $this->getWC()->getOrderManager()->woocommerce_order_item_display_meta_key(...$args); |
| 986 | } |
| 987 | public function relay_woocommerce_order_item_display_meta_value() { |
| 988 | $args = func_get_args(); |
| 989 | return $this->getWC()->getOrderManager()->woocommerce_order_item_display_meta_value(...$args); |
| 990 | } |
| 991 | public function relay_wpo_wcpdf_after_item_meta() { |
| 992 | $args = func_get_args(); |
| 993 | $this->getWC()->getOrderManager()->wpo_wcpdf_after_item_meta(...$args); |
| 994 | } |
| 995 | public function relay_woocommerce_order_item_meta_start() { |
| 996 | $args = func_get_args(); |
| 997 | $this->getWC()->getOrderManager()->woocommerce_order_item_meta_start(...$args); |
| 998 | } |
| 999 | public function relay_woocommerce_product_after_variable_attributes() { |
| 1000 | $args = func_get_args(); |
| 1001 | $this->getWC()->getProductManager()->woocommerce_product_after_variable_attributes(...$args); |
| 1002 | } |
| 1003 | public function relay_woocommerce_save_product_variation() { |
| 1004 | $args = func_get_args(); |
| 1005 | $this->getWC()->getProductManager()->woocommerce_save_product_variation(...$args); |
| 1006 | } |
| 1007 | public function relay_woocommerce_email_order_meta() { |
| 1008 | $args = func_get_args(); |
| 1009 | $this->getWC()->getEmailHandler()->woocommerce_email_order_meta(...$args); |
| 1010 | } |
| 1011 | public function relay_woocommerce_thankyou() { |
| 1012 | $args = func_get_args(); |
| 1013 | $this->getWC()->getFrontendManager()->woocommerce_thankyou(...$args); |
| 1014 | } |
| 1015 | public function relay_sasoEventtickets_cronjob_daily() { |
| 1016 | $this->getTicketHandler()->cronJobDaily(); |
| 1017 | $this->getAdmin()->cleanupOptionsHistory(); |
| 1018 | } |
| 1019 | |
| 1020 | public function plugin_deactivated() { |
| 1021 | $this->cronjob_daily_deactivate(); |
| 1022 | $this->getDB(); |
| 1023 | sasoEventticketsDB::plugin_deactivated(); |
| 1024 | do_action( $this->_do_action_prefix.'main_plugin_deactivated' ); |
| 1025 | } |
| 1026 | public function plugin_uninstall() { |
| 1027 | $this->getDB(); |
| 1028 | sasoEventticketsDB::plugin_uninstall(); |
| 1029 | $this->getAdmin(); |
| 1030 | sasoEventtickets_AdminSettings::plugin_uninstall(); |
| 1031 | do_action( $this->_do_action_prefix.'main_WooCommercePluginLoaded' ); |
| 1032 | } |
| 1033 | public function plugin_activated($is_network_wide=false) { // und auch für updates, macht es einfacher |
| 1034 | $this->getDB(); // um installiere Tabellen auszuführen |
| 1035 | update_option('SASO_EVENTTICKETS_PLUGIN_VERSION', SASO_EVENTTICKETS_PLUGIN_VERSION); |
| 1036 | // Only reset migration flag if options exist in wp_options (downgrade scenario). |
| 1037 | // Do NOT blindly delete — the migration already removed options from wp_options, |
| 1038 | // so deleting the flag would cause one request to read empty wp_options → all defaults. |
| 1039 | $sentinelKey = $this->_prefix . 'qrAttachQRFilesToMailAsOnePDF'; |
| 1040 | if (get_option($sentinelKey, '__NOT_SET__') !== '__NOT_SET__') { |
| 1041 | delete_option('saso_eventtickets_options_migrated'); |
| 1042 | } |
| 1043 | $this->getAdmin()->generateFirstCodeList(); |
| 1044 | $this->cronjob_daily_activate(); |
| 1045 | do_action( $this->_do_action_prefix.'activated' ); |
| 1046 | do_action( $this->_do_action_prefix.'main_plugin_activated' ); |
| 1047 | } |
| 1048 | public function plugins_loaded() { |
| 1049 | 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 |
| 1050 | } |
| 1051 | public function initialize_plugin() { |
| 1052 | $this->getDB(); // um installiere Tabellen auszuführen |
| 1053 | do_action( $this->_do_action_prefix.'initialized' ); |
| 1054 | do_action( $this->_do_action_prefix.'main_initialize_plugin' ); |
| 1055 | } |
| 1056 | function show_user_profile($profileuser) { |
| 1057 | $this->getAdmin()->show_user_profile($profileuser); |
| 1058 | do_action( $this->_do_action_prefix.'main_show_user_profile' ); |
| 1059 | } |
| 1060 | function register_options_page() { |
| 1061 | $allowed = $this->isUserAllowedToAccessAdminArea(); |
| 1062 | $allowed = apply_filters( $this->_add_filter_prefix.'main_options_page', $allowed ); |
| 1063 | if ($allowed) { |
| 1064 | //add_options_page(__('Event Tickets', 'event-tickets-with-ticket-scanner'), 'Event Tickets', 'manage_options', 'event-tickets-with-ticket-scanner', [$this,'options_page']); |
| 1065 | 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 ); |
| 1066 | } |
| 1067 | do_action( $this->_do_action_prefix.'main_register_options_page' ); |
| 1068 | } |
| 1069 | |
| 1070 | function options_page() { |
| 1071 | $allowed = $this->isUserAllowedToAccessAdminArea(); |
| 1072 | $allowed = apply_filters( $this->_add_filter_prefix.'main_options_page', $allowed ); |
| 1073 | if ( !$allowed ) { |
| 1074 | wp_die( __( 'You do not have sufficient permissions to access this page.', 'event-tickets-with-ticket-scanner' ) ); |
| 1075 | } |
| 1076 | |
| 1077 | wp_enqueue_style("wp-jquery-ui-dialog"); |
| 1078 | wp_enqueue_style( |
| 1079 | 'event-tickets-backend', |
| 1080 | plugins_url('css/styles_backend.css', __FILE__), |
| 1081 | array(), |
| 1082 | $this->getPluginVersion() . '.' . filemtime(__DIR__ . '/css/styles_backend.css') |
| 1083 | ); |
| 1084 | wp_enqueue_editor(); |
| 1085 | wp_enqueue_media(); |
| 1086 | |
| 1087 | $js_url = "jquery.qrcode.min.js?_v=".$this->_js_version; |
| 1088 | wp_register_script('ajax_script2', plugins_url( "3rd/".$js_url,__FILE__ ), array('jquery', 'jquery-ui-dialog')); |
| 1089 | wp_enqueue_script('ajax_script2'); |
| 1090 | |
| 1091 | wp_enqueue_media(); // um die js wp.media lib zu laden |
| 1092 | |
| 1093 | // einbinden das js starter skript |
| 1094 | $js_url = $this->_js_file."?_v=".$this->_js_version; |
| 1095 | if (defined( 'WP_DEBUG')) $js_url .= '&debug=1'; |
| 1096 | wp_register_script('ajax_script_backend', plugins_url( $js_url,__FILE__ ), array('jquery', 'jquery-ui-dialog', 'wp-i18n')); |
| 1097 | wp_enqueue_script('ajax_script_backend'); |
| 1098 | wp_set_script_translations('ajax_script_backend', 'event-tickets-with-ticket-scanner', __DIR__.'/languages'); |
| 1099 | |
| 1100 | // per script eine variable einbinden, die url hat den wp-admin prefix |
| 1101 | // damit im backend.js dann die richtige callback url genutzt werden kann |
| 1102 | $vars = array( |
| 1103 | '_plugin_home_url' =>plugins_url( "",__FILE__ ), |
| 1104 | '_plugin_version' => $this->getPluginVersion(), |
| 1105 | '_congress_assets_v' => $this->getPluginVersion() . '.' . max( |
| 1106 | @filemtime(__DIR__ . '/js/congress-admin.js') ?: 0, |
| 1107 | @filemtime(__DIR__ . '/css/congress-admin.css') ?: 0 |
| 1108 | ), |
| 1109 | '_action' => $this->_prefix.'_executeAdminSettings', |
| 1110 | '_max'=>$this->getBase()->getMaxValues(), |
| 1111 | '_isPremium'=>$this->isPremium(), |
| 1112 | '_isUserLoggedin'=>is_user_logged_in(), |
| 1113 | '_premJS'=>$this->isPremium() && method_exists($this->getPremiumFunctions(), "getJSBackendFile") ? $this->getPremiumFunctions()->getJSBackendFile() : '', |
| 1114 | 'url' => admin_url( 'admin-ajax.php' ), |
| 1115 | 'ticket_url' => $this->getCore()->getTicketURLPath(), |
| 1116 | 'nonce' => wp_create_nonce( $this->_js_nonce ), |
| 1117 | 'ajaxActionPrefix' => $this->_prefix, |
| 1118 | 'divPrefix' => $this->_prefix, |
| 1119 | 'divId' => $this->_divId, |
| 1120 | 'jsFiles' => plugins_url( 'backend.js?_v='.$this->_js_version.'&_f='.filemtime(__DIR__.'/backend.js'),__FILE__ ), |
| 1121 | // product_id => congress URL template (…/ticket/__TICKETID__?congress); JS swaps in the public ticket id. |
| 1122 | // Lets the admin jump straight to a ticket's congress page from the ticket detail. |
| 1123 | '_congressProducts' => $this->getCongressProductUrlMap(), |
| 1124 | // Twig placeholder variables for the congress section editor's "Insert variable…" dropdown. |
| 1125 | // Threaded through here because the integrated admin loads congress-admin.js via $.getScript |
| 1126 | // (no wp_localize_script), so the standalone-page global sasoEtCongress is absent there. |
| 1127 | '_congressVariables' => (function () { |
| 1128 | require_once __DIR__ . '/includes/congress/class-template-variables.php'; |
| 1129 | return sasoEventtickets_TemplateVariables::getList(); |
| 1130 | })() |
| 1131 | ); |
| 1132 | // Version notices for "What's New" banner |
| 1133 | $versionNoticesFile = __DIR__ . '/version-notices.json'; |
| 1134 | $vars['_versionNotices'] = file_exists($versionNoticesFile) ? json_decode(file_get_contents($versionNoticesFile), true) : []; |
| 1135 | |
| 1136 | $vars = apply_filters( $this->_add_filter_prefix.'main_options_page', $vars ); |
| 1137 | wp_localize_script( |
| 1138 | 'ajax_script_backend', |
| 1139 | 'Ajax_'.$this->_prefix, // name der injected variable |
| 1140 | $vars |
| 1141 | ); |
| 1142 | |
| 1143 | do_action( $this->_do_action_prefix.'main_options_page' ); |
| 1144 | |
| 1145 | $versions = $this->getPluginVersions(); |
| 1146 | $versions_tail = $versions['basic'].($versions['premium'] != "" ? ', Premium: '.$versions['premium'] : ''); |
| 1147 | $version_tail_add = ""; |
| 1148 | if ($versions['debug'] != "") $version_tail_add .= 'DEBUG: '.$versions['debug'].', LANG: '.determine_locale(); |
| 1149 | ?> |
| 1150 | <style>.event-tickets-with-ticket-scanner-admin-page{opacity:0;transition:opacity .3s ease}.event-tickets-with-ticket-scanner-admin-page.et-ready{opacity:1}</style> |
| 1151 | <div class="event-tickets-with-ticket-scanner-admin-page"> |
| 1152 | <div class="event-tickets-with-ticket-scanner-header"> |
| 1153 | <div class="event-tickets-with-ticket-scanner-header-left"> |
| 1154 | <img src="<?php echo plugins_url( "",__FILE__ ); ?>/img/logo_event-tickets-with-ticket-scanner.gif" |
| 1155 | alt="Event Tickets" |
| 1156 | class="event-tickets-with-ticket-scanner-header-logo"> |
| 1157 | |
| 1158 | <div class="event-tickets-with-ticket-scanner-header-title"> |
| 1159 | <div class="event-tickets-with-ticket-scanner-header-name"> |
| 1160 | Event Tickets with Ticket Scanner |
| 1161 | </div> |
| 1162 | <div class="event-tickets-with-ticket-scanner-header-meta"> |
| 1163 | <?php esc_html_e('Version', 'event-tickets-with-ticket-scanner'); ?>: <?php echo $versions_tail; ?> <?php echo $version_tail_add; ?> |
| 1164 | </div> |
| 1165 | </div> |
| 1166 | </div> |
| 1167 | |
| 1168 | <div class="event-tickets-with-ticket-scanner-header-right" id="event-tickets-with-ticket-scanner-header-actions"> |
| 1169 | <!-- Button kommt via JS --> |
| 1170 | </div> |
| 1171 | </div> |
| 1172 | |
| 1173 | <div style="clear:both;" data-id="plugin_addons"></div> |
| 1174 | <div style="clear:both;" data-id="plugin_info_area"></div> |
| 1175 | <div style="clear:both;" id="<?php echo esc_attr($this->_divId); ?>" class="et-content-area"> |
| 1176 | <div class="et-loading"><span class="lds-dual-ring"></span></div> |
| 1177 | </div> |
| 1178 | <div class="et-footer"> |
| 1179 | <a name="shortcodedetails"></a> |
| 1180 | <div class="et-footer-grid"> |
| 1181 | <div class="et-card et-footer-card"> |
| 1182 | <div class="et-card-header"> |
| 1183 | <span class="dashicons dashicons-book" style="color:#9333ea;margin-right:6px;"></span> |
| 1184 | Documentation |
| 1185 | </div> |
| 1186 | <p><a href="https://vollstart.com/event-tickets-with-ticket-scanner/docs/" target="_blank"><?php esc_html_e('Click here, to visit the documentation of this plugin.', 'event-tickets-with-ticket-scanner'); ?></a></p> |
| 1187 | <p style="margin-top:12px;"><?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> |
| 1188 | </div> |
| 1189 | <div class="et-card et-footer-card"> |
| 1190 | <div class="et-card-header"> |
| 1191 | <span class="dashicons dashicons-star-filled" style="color:#f59e0b;margin-right:6px;"></span> |
| 1192 | <?php esc_html_e('Plugin Rating', 'event-tickets-with-ticket-scanner'); ?> |
| 1193 | </div> |
| 1194 | <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">5-Star Rating</a>.</p> |
| 1195 | </div> |
| 1196 | <div class="et-card et-footer-card"> |
| 1197 | <div class="et-card-header"> |
| 1198 | <span class="dashicons dashicons-superhero" style="color:#9333ea;margin-right:6px;"></span> |
| 1199 | <?php esc_html_e('Premium Homepage', 'event-tickets-with-ticket-scanner'); ?> |
| 1200 | </div> |
| 1201 | <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> |
| 1202 | </div> |
| 1203 | </div> |
| 1204 | |
| 1205 | <div class="et-card et-footer-shortcodes"> |
| 1206 | <div class="et-card-header"> |
| 1207 | <span class="dashicons dashicons-shortcode" style="color:#9333ea;margin-right:6px;"></span> |
| 1208 | <?php esc_html_e('Shortcodes', 'event-tickets-with-ticket-scanner'); ?> |
| 1209 | </div> |
| 1210 | |
| 1211 | <h3><?php esc_html_e('Shortcode to display the event calendar form within a page', 'event-tickets-with-ticket-scanner'); ?></h3> |
| 1212 | <b>[<?php echo esc_html($this->_shortcode_eventviews); ?>]</b> |
| 1213 | <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> |
| 1214 | <ul> |
| 1215 | <li>months_to_show - Values can be a number higher than 0. Default: 3</li> |
| 1216 | </ul> |
| 1217 | <p>CSS file: <a href="<?php echo plugins_url( "",__FILE__ ); ?>/css/calendar.css" target="_blank">calendar.css</a></p> |
| 1218 | |
| 1219 | <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> |
| 1220 | <b>[<?php echo esc_html($this->_shortcode_mycode); ?>]</b> |
| 1221 | <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> |
| 1222 | <ul> |
| 1223 | <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> |
| 1224 | <?php esc_html_e('Example:', 'event-tickets-with-ticket-scanner'); ?> [<?php echo esc_html($this->_shortcode_mycode); ?> order_id="123"]</li> |
| 1225 | <li><b>format</b> - <?php esc_html_e('Output format. Values: json', 'event-tickets-with-ticket-scanner'); ?></li> |
| 1226 | <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> |
| 1227 | <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> |
| 1228 | </ul> |
| 1229 | <p> |
| 1230 | <?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"] |
| 1231 | </p> |
| 1232 | |
| 1233 | <h3><?php esc_html_e('Shortcode to display the ticket scanner within a page', 'event-tickets-with-ticket-scanner'); ?></h3> |
| 1234 | <?php esc_html_e('Useful if you cannot open the ticket scanner due to security issues.', 'event-tickets-with-ticket-scanner'); ?><br> |
| 1235 | <b>[<?php echo esc_html($this->_shortcode_ticket_scanner); ?>]</b> |
| 1236 | |
| 1237 | <h3><?php esc_html_e('Shortcode to display ticket detail view within a page', 'event-tickets-with-ticket-scanner'); ?></h3> |
| 1238 | <?php esc_html_e('Useful if the /ticket/ URL path does not work on your server.', 'event-tickets-with-ticket-scanner'); ?><br> |
| 1239 | <b>[<?php echo esc_html($this->_shortcode_ticket_detail); ?>]</b> |
| 1240 | <p> |
| 1241 | <?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> |
| 1242 | <?php esc_html_e('Example:', 'event-tickets-with-ticket-scanner'); ?> yoursite.com/ticket-page/?ticket=ABC-123-XYZ<br> |
| 1243 | <?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"] |
| 1244 | </p> |
| 1245 | |
| 1246 | <h3><?php esc_html_e('PHP Filters', 'event-tickets-with-ticket-scanner'); ?></h3> |
| 1247 | <p><?php esc_html_e('You can use PHP code to register your filter functions for the validation check.', 'event-tickets-with-ticket-scanner'); ?> |
| 1248 | <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> |
| 1249 | </p> |
| 1250 | <ul> |
| 1251 | <li>add_filter('<?php echo $this->_add_filter_prefix.'beforeCheckCodePre'; ?>', 'myfunc', 20, 1)</li> |
| 1252 | <li>add_filter('<?php echo $this->_add_filter_prefix.'beforeCheckCode'; ?>', 'myfunc', 20, 1)</li> |
| 1253 | <li>add_filter('<?php echo $this->_add_filter_prefix.'afterCheckCodePre'; ?>', 'myfunc', 20, 1)</li> |
| 1254 | <li>add_filter('<?php echo $this->_add_filter_prefix.'afterCheckCode'; ?>', 'myfunc', 20, 1)</li> |
| 1255 | </ul> |
| 1256 | <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>.</p> |
| 1257 | </div> |
| 1258 | |
| 1259 | <div class="et-footer-credits"> |
| 1260 | <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> |
| 1261 | </div> |
| 1262 | </div> |
| 1263 | </div> |
| 1264 | <?php |
| 1265 | do_action( $this->_do_action_prefix.'options_page' ); |
| 1266 | } |
| 1267 | |
| 1268 | public function isUserAllowedToAccessAdminArea() { |
| 1269 | if ($this->isAllowedAccess != null) return $this->isAllowedAccess; |
| 1270 | if ($this->getOptions()->isOptionCheckboxActive('allowOnlySepcificRoleAccessToAdmin')) { |
| 1271 | // check welche rollen |
| 1272 | $user = wp_get_current_user(); |
| 1273 | $user_roles = (array) $user->roles; |
| 1274 | if (in_array("administrator", $user_roles)) { |
| 1275 | $this->isAllowedAccess = true; |
| 1276 | } else { |
| 1277 | $adminAreaAllowedRoles = $this->getOptions()->getOptionValue('adminAreaAllowedRoles'); |
| 1278 | if (!is_array($adminAreaAllowedRoles)) { |
| 1279 | if (empty($adminAreaAllowedRoles)) { |
| 1280 | $adminAreaAllowedRoles = []; |
| 1281 | } else { |
| 1282 | $adminAreaAllowedRoles = [$adminAreaAllowedRoles]; |
| 1283 | } |
| 1284 | } |
| 1285 | foreach($adminAreaAllowedRoles as $role_name) { |
| 1286 | if (in_array($role_name, $user_roles)) { |
| 1287 | $this->isAllowedAccess = true; |
| 1288 | break; |
| 1289 | }; |
| 1290 | } |
| 1291 | } |
| 1292 | } else { |
| 1293 | // Standard: Only administrators have access |
| 1294 | $this->isAllowedAccess = current_user_can('manage_options'); |
| 1295 | } |
| 1296 | $this->isAllowedAccess = apply_filters( $this->_add_filter_prefix.'main_isUserAllowedToAccessAdminArea', $this->isAllowedAccess ); |
| 1297 | return $this->isAllowedAccess; |
| 1298 | } |
| 1299 | |
| 1300 | public function executeAdminSettings_a() { |
| 1301 | if (!SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a_sngmbh not provided"); |
| 1302 | return $this->executeAdminSettings(SASO_EVENTTICKETS::getRequestPara('a_sngmbh')); // to prevent WP adds parameters |
| 1303 | } |
| 1304 | |
| 1305 | public function executeAdminSettings($a=0, $data=null) { |
| 1306 | if (!$this->isUserAllowedToAccessAdminArea()) { |
| 1307 | return wp_send_json_error("Access denied", 403); |
| 1308 | } |
| 1309 | if ($a === 0 && !SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a not provided"); |
| 1310 | |
| 1311 | if ($data == null) { |
| 1312 | $data = SASO_EVENTTICKETS::issetRPara('data') ? SASO_EVENTTICKETS::getRequestPara('data') : []; |
| 1313 | } |
| 1314 | if ($a === 0 || empty($a) || trim($a) == "") { |
| 1315 | $a = SASO_EVENTTICKETS::getRequestPara('a_sngmbh'); |
| 1316 | } |
| 1317 | do_action( $this->_do_action_prefix.'executeAdminSettings', $a, $data ); |
| 1318 | return $this->getAdmin()->executeJSON($a, $data, false, false); // with nonce check |
| 1319 | } |
| 1320 | |
| 1321 | public function executeSeatingAdmin_a() { |
| 1322 | return $this->executeSeatingAdmin(SASO_EVENTTICKETS::getRequestPara('a')); |
| 1323 | } |
| 1324 | |
| 1325 | public function executeSeatingAdmin($a = '', $data = null) { |
| 1326 | if (!$this->isUserAllowedToAccessAdminArea()) { |
| 1327 | return wp_send_json_error('Access denied', 403); |
| 1328 | } |
| 1329 | if (empty($a) && !SASO_EVENTTICKETS::issetRPara('a')) { |
| 1330 | return wp_send_json_error('a not provided'); |
| 1331 | } |
| 1332 | if ($data === null) { |
| 1333 | $data = SASO_EVENTTICKETS::getRequest(); |
| 1334 | } |
| 1335 | if (empty($a)) { |
| 1336 | $a = SASO_EVENTTICKETS::getRequestPara('a'); |
| 1337 | } |
| 1338 | return $this->getSeating()->getAdminHandler()->executeSeatingJSON($a, $data); |
| 1339 | } |
| 1340 | |
| 1341 | public function executeFrontend_a() { |
| 1342 | return $this->executeFrontend(); // to prevent WP adds parameters |
| 1343 | } |
| 1344 | |
| 1345 | public function executeWCBackend() { |
| 1346 | if (!$this->isUserAllowedToAccessAdminArea()) { |
| 1347 | return wp_send_json_error("Access denied", 403); |
| 1348 | } |
| 1349 | if (!SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a_sngmbh not provided"); |
| 1350 | $data = SASO_EVENTTICKETS::issetRPara('data') ? SASO_EVENTTICKETS::getRequestPara('data') : []; |
| 1351 | return $this->getWC()->executeJSON(SASO_EVENTTICKETS::getRequestPara('a_sngmbh'), $data); |
| 1352 | } |
| 1353 | |
| 1354 | public function executeFrontend($a=0, $data=null) { |
| 1355 | $sasoEventtickets_Frontend = $this->getFrontend(); |
| 1356 | if ($a === 0 && !SASO_EVENTTICKETS::issetRPara('a_sngmbh')) return wp_send_json_success("a not provided"); |
| 1357 | |
| 1358 | if ($data == null) { |
| 1359 | $data = SASO_EVENTTICKETS::issetRPara('data') ? SASO_EVENTTICKETS::getRequestPara('data') : []; |
| 1360 | } |
| 1361 | if ($a === 0 || empty($a) || trim($a) == "") { |
| 1362 | $a = SASO_EVENTTICKETS::getRequestPara('a_sngmbh'); |
| 1363 | } |
| 1364 | do_action( $this->_do_action_prefix.'executeFrontend', $a, $data ); |
| 1365 | return $sasoEventtickets_Frontend->executeJSON($a, $data); |
| 1366 | } |
| 1367 | |
| 1368 | public function replacingShortcode($attr=[], $content = null, $tag = '') { |
| 1369 | add_filter( $this->_add_filter_prefix.'replaceShortcode', [$this, 'replaceShortcode'], 10, 3 ); |
| 1370 | $ret = apply_filters( $this->_add_filter_prefix.'replaceShortcode', $attr, $content, $tag ); |
| 1371 | return $ret; |
| 1372 | } |
| 1373 | |
| 1374 | public function setTicketScannerJS() { |
| 1375 | wp_enqueue_style("wp-jquery-ui-dialog"); |
| 1376 | |
| 1377 | $js_url = "jquery.qrcode.min.js?_v=".$this->getPluginVersion(); |
| 1378 | wp_enqueue_script( |
| 1379 | 'ajax_script2', |
| 1380 | plugins_url( "3rd/".$js_url,__FILE__ ), |
| 1381 | array('jquery', 'jquery-ui-dialog') |
| 1382 | ); |
| 1383 | |
| 1384 | $js_url = plugin_dir_url(__FILE__)."3rd/html5-qrcode.min.js?_v=".$this->getPluginVersion(); |
| 1385 | wp_register_script('html5-qrcode', $js_url, array('jquery', 'jquery-ui-dialog')); |
| 1386 | wp_enqueue_script('html5-qrcode'); |
| 1387 | |
| 1388 | // https://github.com/nimiq/qr-scanner - NEW scanner lib |
| 1389 | $js_url = plugin_dir_url(__FILE__)."3rd/qr-scanner-1.4.2/qr-scanner.umd.min.js?_v=".$this->getPluginVersion(); |
| 1390 | wp_register_script('qr-scanner', $js_url, array('jquery', 'jquery-ui-dialog')); |
| 1391 | wp_enqueue_script('qr-scanner'); |
| 1392 | |
| 1393 | $js_url = "ticket_scanner.js?_v=".$this->getPluginVersion(); |
| 1394 | if (defined('WP_DEBUG')) $js_url .= '&t='.time(); |
| 1395 | $js_url = plugins_url( $js_url,__FILE__ ); |
| 1396 | wp_register_script('ajax_script_ticket_scanner', $js_url, array('jquery', 'jquery-ui-dialog', 'wp-i18n')); |
| 1397 | wp_enqueue_script('ajax_script_ticket_scanner'); |
| 1398 | wp_set_script_translations('ajax_script_ticket_scanner', 'event-tickets-with-ticket-scanner', __DIR__.'/languages'); |
| 1399 | |
| 1400 | $ticketScannerDontRememberCamChoice = $this->getOptions()->isOptionCheckboxActive("ticketScannerDontRememberCamChoice") ? true : false; |
| 1401 | |
| 1402 | $pwaEnabled = $this->getOptions()->isOptionCheckboxActive('ticketScannerPWA'); |
| 1403 | $vars = [ |
| 1404 | 'root' => esc_url_raw( rest_url() ), |
| 1405 | '_plugin_home_url' =>plugins_url( "",__FILE__ ), |
| 1406 | '_action' => $this->_prefix.'_executeAdminSettings', |
| 1407 | '_isPremium'=>$this->isPremium(), |
| 1408 | '_isUserLoggedin'=>is_user_logged_in(), |
| 1409 | '_userId'=>get_current_user_id(), |
| 1410 | '_restPrefixUrl'=>SASO_EVENTTICKETS::getRESTPrefixURL(), |
| 1411 | '_siteUrl'=>get_site_url(), |
| 1412 | '_params'=>["auth"=>$this->getAuthtokenHandler()::$authtoken_param], |
| 1413 | '_pwaSWUrl'=>$pwaEnabled ? rest_url(SASO_EVENTTICKETS::getRESTPrefixURL().'/ticket/scanner/pwa-sw') : '', |
| 1414 | //'url' => admin_url( 'admin-ajax.php' ), // not used for now in ticketscanner.js |
| 1415 | 'url' => rest_get_server(), // not used for now in ticketscanner.js |
| 1416 | 'nonce' => wp_create_nonce( 'wp_rest' ), |
| 1417 | //'nonce' => wp_create_nonce( $this->_js_nonce ), |
| 1418 | 'ajaxActionPrefix' => $this->_prefix, |
| 1419 | 'wcTicketCompatibilityModeRestURL' => $this->getOptions()->getOptionValue('wcTicketCompatibilityModeRestURL', ''), |
| 1420 | 'IS_PRETTY_PERMALINK_ACTIVATED' => get_option('permalink_structure') ? true :false, |
| 1421 | 'ticketScannerDontRememberCamChoice' => $ticketScannerDontRememberCamChoice, |
| 1422 | 'ticketScannerStartCamWithoutButtonClicked' => $this->getOptions()->isOptionCheckboxActive('ticketScannerStartCamWithoutButtonClicked'), |
| 1423 | 'ticketScannerDontShowOptionControls' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDontShowOptionControls'), |
| 1424 | 'ticketScannerScanAndRedeemImmediately' => $this->getOptions()->isOptionCheckboxActive('ticketScannerScanAndRedeemImmediately'), |
| 1425 | 'ticketScannerHideTicketInformation' => $this->getOptions()->isOptionCheckboxActive('ticketScannerHideTicketInformation'), |
| 1426 | 'ticketScannerHideTicketInformationShowShortDesc' => $this->getOptions()->isOptionCheckboxActive('ticketScannerHideTicketInformationShowShortDesc'), |
| 1427 | 'ticketScannerDontShowBtnPDF' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDontShowBtnPDF'), |
| 1428 | 'ticketScannerDontShowBtnBadge' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDontShowBtnBadge'), |
| 1429 | 'ticketScannerDisplayTimes' => $this->getOptions()->isOptionCheckboxActive('ticketScannerDisplayTimes'), |
| 1430 | 'ticketScannerThemeColor' => $this->getOptions()->getOptionValue('ticketScannerThemeColor', '#2e74b5'), |
| 1431 | 'ticketScannerVibrate' => $this->getOptions()->isOptionCheckboxActive('ticketScannerVibrate'), |
| 1432 | '_cvvMaskInput' => (bool) $this->getOptions()->isOptionCheckboxActive('wcTicketScannerCVVMaskInput'), |
| 1433 | '_cvvOneStage' => (bool) $this->getOptions()->isOptionCheckboxActive('wcTicketScannerCVVOneStageFlow'), |
| 1434 | ]; |
| 1435 | $vars = apply_filters( $this->_add_filter_prefix.'main_setTicketScannerJS', $vars ); |
| 1436 | wp_localize_script( |
| 1437 | 'ajax_script_ticket_scanner', |
| 1438 | 'Ajax_'.$this->_prefix, // name der injected variable |
| 1439 | $vars |
| 1440 | ); |
| 1441 | |
| 1442 | do_action( $this->_do_action_prefix.'main_setTicketScannerJS', $js_url ); |
| 1443 | } |
| 1444 | |
| 1445 | public function replacingShortcodeTicketScanner($attr=[], $content = null, $tag = '') { |
| 1446 | $this->setTicketScannerJS(); |
| 1447 | return ' |
| 1448 | <center> |
| 1449 | <div style="width:90%;max-width:1024px;">'.$this->getTicketHandler()->getTicketScannerHTMLBoilerplate().' |
| 1450 | </div> |
| 1451 | </center> |
| 1452 | '; |
| 1453 | } |
| 1454 | |
| 1455 | /** |
| 1456 | * Shortcode to display ticket detail view on any page |
| 1457 | * Usage: [sasoEventTicketsValidator_ticket_detail] with ?ticket=CODE in URL |
| 1458 | * Or: [sasoEventTicketsValidator_ticket_detail code="TICKET-CODE"] |
| 1459 | */ |
| 1460 | public function replacingShortcodeTicketDetail($attr = [], $content = null, $tag = ''): string { |
| 1461 | $code = ''; |
| 1462 | if (!empty($attr['code'])) { |
| 1463 | $code = sanitize_text_field($attr['code']); |
| 1464 | } elseif (isset($_GET['ticket'])) { |
| 1465 | $code = sanitize_text_field($_GET['ticket']); |
| 1466 | } |
| 1467 | |
| 1468 | if (empty($code)) { |
| 1469 | return '<p>' . esc_html__('No ticket code provided. Use ?ticket=YOUR-CODE in the URL.', 'event-tickets-with-ticket-scanner') . '</p>'; |
| 1470 | } |
| 1471 | |
| 1472 | // Build a fake request URI for the ticket |
| 1473 | $ticketPath = $this->getCore()->getTicketURLPath(true); |
| 1474 | $fakeUri = $ticketPath . $code; |
| 1475 | |
| 1476 | include_once plugin_dir_path(__FILE__) . "sasoEventtickets_Ticket.php"; |
| 1477 | $ticketInstance = sasoEventtickets_Ticket::Instance($fakeUri); |
| 1478 | |
| 1479 | return $ticketInstance->renderTicketDetailForShortcode(); |
| 1480 | } |
| 1481 | |
| 1482 | public function getCodesTextAsShortList($codes) { |
| 1483 | $ret = ""; |
| 1484 | if (count($codes) > 0) { |
| 1485 | $ret = '<table>'; |
| 1486 | $wcTicketUserProfileDisplayTicketDetailURL = $this->getOptions()->isOptionCheckboxActive("wcTicketUserProfileDisplayTicketDetailURL"); |
| 1487 | $wcTicketUserProfileDisplayRedeemAmount = $this->getOptions()->isOptionCheckboxActive("wcTicketUserProfileDisplayRedeemAmount"); |
| 1488 | |
| 1489 | $label_expired = $this->getOptions()->getOptionValue('wcTicketTransTicketExpired', 'EXPIRED'); |
| 1490 | $label_stolen = $this->getOptions()->getOptionValue('wcTicketTransTicketIsStolen', 'REPORTED AS STOLEN'); |
| 1491 | $label_notvalid = $this->getOptions()->getOptionValue('wcTicketTransTicketNotValid', 'DISABLED'); |
| 1492 | |
| 1493 | $myCodes = []; |
| 1494 | foreach($codes as $idx => $codeObj) { |
| 1495 | $metaObj = $this->getCore()->encodeMetaValuesAndFillObject($codeObj['meta'], $codeObj); |
| 1496 | |
| 1497 | $_c = '<tr><td style="text-align:right;">'.($idx + 1).'.</td><td>'.$codeObj['code_display'].'</td><td>'; |
| 1498 | if ($codeObj['aktiv'] == 1) { |
| 1499 | if ($this->getCore()->checkCodeExpired($codeObj)) { |
| 1500 | $_c .= $label_expired; |
| 1501 | } |
| 1502 | } else if ($codeObj['aktiv'] == 0) { |
| 1503 | $_c .= $label_notvalid; |
| 1504 | } else if ($codeObj['aktiv'] == 2) { |
| 1505 | $_c .= $label_stolen; |
| 1506 | } |
| 1507 | $_c .= '</td>'; |
| 1508 | |
| 1509 | if ($wcTicketUserProfileDisplayTicketDetailURL) { |
| 1510 | $_c .= "<td>"; |
| 1511 | $url = $this->getCore()->getTicketURL($codeObj, $metaObj); |
| 1512 | if (!empty($url)) { |
| 1513 | $_c .= '<a href="'.$url.'" target="_blank">Ticket Details</a>'; |
| 1514 | } |
| 1515 | $_c .= "</td>"; |
| 1516 | } |
| 1517 | if ($wcTicketUserProfileDisplayRedeemAmount && function_exists("wc_get_product")) { |
| 1518 | $_c .= "<td>"; |
| 1519 | $text_redeem_amount = $this->getTicketHandler()->getRedeemAmountText($codeObj, $metaObj, false); |
| 1520 | if (!empty($text_redeem_amount)) { |
| 1521 | $_c .= $text_redeem_amount; |
| 1522 | } |
| 1523 | $_c .= "<td>"; |
| 1524 | } |
| 1525 | $_c .= "</tr>"; |
| 1526 | $myCodes[] = $_c; |
| 1527 | } |
| 1528 | $ret .= implode("", $myCodes); |
| 1529 | $ret .= "</table>"; |
| 1530 | } |
| 1531 | $ret = apply_filters( $this->_add_filter_prefix.'main_getCodesTextAsShortList', $ret, $codes ); |
| 1532 | return $ret; |
| 1533 | } |
| 1534 | |
| 1535 | public function getMyCodeText($user_id, $attr=[], $content = null, $tag = '', $codes = null) { |
| 1536 | $ret = ''; |
| 1537 | // check ob eingeloggt |
| 1538 | $pre_text = $this->getOptions()->getOptionValue('userDisplayCodePrefix', ''); |
| 1539 | if (!empty($pre_text)) $pre_text .= " "; |
| 1540 | |
| 1541 | // If codes are provided (e.g., from order_id), use them; otherwise fetch by user_id |
| 1542 | if ($codes === null && $user_id > 0) { |
| 1543 | $codes = $this->getCore()->getCodesByRegUserId($user_id); |
| 1544 | } |
| 1545 | |
| 1546 | if ($codes !== null && count($codes) > 0) { |
| 1547 | $ret .= "<b>".$pre_text."</b><br>"; |
| 1548 | $ret .= $this->getCodesTextAsShortList($codes); |
| 1549 | |
| 1550 | // Download All as PDF button |
| 1551 | $show_download_btn = isset($attr['download_all_pdf']) && |
| 1552 | in_array(strtolower($attr['download_all_pdf']), ['true', '1', 'yes'], true); |
| 1553 | |
| 1554 | if ($show_download_btn && count($codes) > 0) { |
| 1555 | $max_tickets = isset($attr['download_all_pdf_max']) ? intval($attr['download_all_pdf_max']) : 100; |
| 1556 | $btn_label = isset($attr['download_all_pdf_label']) ? |
| 1557 | sanitize_text_field($attr['download_all_pdf_label']) : |
| 1558 | __('Download All Tickets as PDF', 'event-tickets-with-ticket-scanner'); |
| 1559 | |
| 1560 | $ticket_count = count($codes); |
| 1561 | if ($ticket_count > $max_tickets) { |
| 1562 | $ret .= '<p><em>' . sprintf( |
| 1563 | /* translators: %d: maximum number of tickets */ |
| 1564 | esc_html__('Too many tickets (%1$d). Maximum %2$d tickets can be downloaded at once.', 'event-tickets-with-ticket-scanner'), |
| 1565 | $ticket_count, |
| 1566 | $max_tickets |
| 1567 | ) . '</em></p>'; |
| 1568 | } else { |
| 1569 | // Generate secure download URL |
| 1570 | $nonce = wp_create_nonce('download_my_codes_pdf_' . $user_id); |
| 1571 | $download_url = admin_url('admin-ajax.php') . '?' . http_build_query([ |
| 1572 | 'action' => $this->_prefix . '_downloadMyCodesAsPDF', |
| 1573 | 'nonce' => $nonce |
| 1574 | ]); |
| 1575 | $ret .= '<p style="margin-top:10px;"><a href="' . esc_url($download_url) . '" class="button" target="_blank">' . |
| 1576 | esc_html($btn_label) . ' (' . $ticket_count . ')</a></p>'; |
| 1577 | } |
| 1578 | } |
| 1579 | } |
| 1580 | if (empty($ret) && $this->getOptions()->isOptionCheckboxActive('userDisplayCodePrefixAlways')) { |
| 1581 | $ret .= $pre_text; |
| 1582 | } |
| 1583 | $ret = apply_filters( $this->_add_filter_prefix.'main_getMyCodeText', $ret, $user_id, $attr, $content, $tag); |
| 1584 | return $ret; |
| 1585 | } |
| 1586 | |
| 1587 | /** |
| 1588 | * AJAX handler: Download all user's tickets as one PDF |
| 1589 | * Used by shortcode [sasoEventTicketsValidator_code download_all_pdf="true"] |
| 1590 | */ |
| 1591 | public function downloadMyCodesAsPDF(): void { |
| 1592 | $user_id = get_current_user_id(); |
| 1593 | |
| 1594 | // Must be logged in |
| 1595 | if ($user_id <= 0) { |
| 1596 | wp_die(esc_html__('You must be logged in to download tickets.', 'event-tickets-with-ticket-scanner'), 403); |
| 1597 | } |
| 1598 | |
| 1599 | // Verify nonce |
| 1600 | $nonce = isset($_GET['nonce']) ? sanitize_text_field($_GET['nonce']) : ''; |
| 1601 | if (!wp_verify_nonce($nonce, 'download_my_codes_pdf_' . $user_id)) { |
| 1602 | wp_die(esc_html__('Security check failed. Please refresh the page and try again.', 'event-tickets-with-ticket-scanner'), 403); |
| 1603 | } |
| 1604 | |
| 1605 | // Get user's codes |
| 1606 | $codes = $this->getCore()->getCodesByRegUserId($user_id); |
| 1607 | |
| 1608 | if (empty($codes)) { |
| 1609 | wp_die(esc_html__('No tickets found.', 'event-tickets-with-ticket-scanner'), 404); |
| 1610 | } |
| 1611 | |
| 1612 | // Limit to 100 tickets |
| 1613 | $max_tickets = 100; |
| 1614 | if (count($codes) > $max_tickets) { |
| 1615 | wp_die( |
| 1616 | sprintf( |
| 1617 | /* translators: %d: maximum number of tickets */ |
| 1618 | esc_html__('Too many tickets. Maximum %d tickets can be downloaded at once.', 'event-tickets-with-ticket-scanner'), |
| 1619 | $max_tickets |
| 1620 | ), |
| 1621 | 400 |
| 1622 | ); |
| 1623 | } |
| 1624 | |
| 1625 | // Extract code strings |
| 1626 | $code_strings = array_map(function($codeObj) { |
| 1627 | return $codeObj['code']; |
| 1628 | }, $codes); |
| 1629 | |
| 1630 | // Generate merged PDF |
| 1631 | $filename = 'my_tickets_' . wp_date('Ymd_Hi') . '.pdf'; |
| 1632 | |
| 1633 | try { |
| 1634 | $this->getTicketHandler()->generateOnePDFForCodes($code_strings, $filename, 'I'); |
| 1635 | } catch (Exception $e) { |
| 1636 | $this->getAdmin()->logErrorToDB($e, null, 'downloadMyCodesAsPDF'); |
| 1637 | wp_die(esc_html__('Error generating PDF. Please try again later.', 'event-tickets-with-ticket-scanner'), 500); |
| 1638 | } |
| 1639 | |
| 1640 | exit; |
| 1641 | } |
| 1642 | |
| 1643 | public function getMyCodeFormatted($user_id, $attr=[], $content = null, $tag = '', $codes = null) { |
| 1644 | $format = "json"; |
| 1645 | if (isset($attr["format"])) { |
| 1646 | $format = strtolower(trim(sanitize_key($attr["format"]))); |
| 1647 | } |
| 1648 | $display = ["codes"]; |
| 1649 | if (isset($attr["display"])) { |
| 1650 | |
| 1651 | $_d = trim(sanitize_text_field($attr["display"])); |
| 1652 | $_da = explode(",", $_d); |
| 1653 | if (count($_da) > 0) { |
| 1654 | $display = []; |
| 1655 | } |
| 1656 | foreach ($_da as $item) { |
| 1657 | $item = trim($item); |
| 1658 | $display[] = $item; |
| 1659 | } |
| 1660 | } |
| 1661 | |
| 1662 | $output = []; |
| 1663 | //codes,validation,user,used,confirmedCount,woocommerce,wc_rp,wc_ticket |
| 1664 | // If codes are provided (e.g., from order_id), use them; otherwise fetch by user_id |
| 1665 | if ($codes === null) { |
| 1666 | $codes = $this->getCore()->getCodesByRegUserId($user_id); |
| 1667 | } |
| 1668 | $metas = []; |
| 1669 | foreach($codes as $codeObj) { |
| 1670 | $metas[$codeObj["code"]] = $this->getCore()->encodeMetaValuesAndFillObject($codeObj['meta'], $codeObj); |
| 1671 | } |
| 1672 | foreach ($display as $item) { |
| 1673 | $output[$item] = []; |
| 1674 | if ($item == "codes") { |
| 1675 | foreach($codes as $codeObj) { |
| 1676 | if (isset($codeObj["meta"])) { |
| 1677 | unset($codeObj["meta"]); |
| 1678 | } |
| 1679 | $output[$item][] = $codeObj; |
| 1680 | } |
| 1681 | } elseif($item == "confirmedCount") { |
| 1682 | foreach($metas as $key => $meta) { |
| 1683 | $output[$item][] = array_merge(["value"=>$meta[$item]], ["code"=>$key]); |
| 1684 | } |
| 1685 | } else { |
| 1686 | foreach($metas as $key => $m) { |
| 1687 | if (is_array($m) && isset($m[$item])) { |
| 1688 | $meta = $m[$item]; |
| 1689 | if (isset($meta["stats_redeemed"])) { |
| 1690 | unset($meta["stats_redeemed"]); |
| 1691 | } |
| 1692 | if (isset($meta["set_by_admin"])) { |
| 1693 | unset($meta["set_by_admin"]); |
| 1694 | } |
| 1695 | if (isset($meta["redeemed_by_admin"])) { |
| 1696 | unset($meta["redeemed_by_admin"]); |
| 1697 | } |
| 1698 | $output[$item][] = array_merge($meta, ["code"=>$key]); |
| 1699 | } |
| 1700 | } |
| 1701 | } |
| 1702 | } |
| 1703 | |
| 1704 | switch($format) { |
| 1705 | case "json": |
| 1706 | default: |
| 1707 | $ret = $this->getCore()->json_encode_with_error_handling($output); |
| 1708 | } |
| 1709 | $ret = apply_filters( $this->_add_filter_prefix.'main_getMyCodeFormatted', $ret, $user_id, $attr, $content, $tag, $output); |
| 1710 | return $ret; |
| 1711 | } |
| 1712 | |
| 1713 | public function replacingShortcodeMyCode($attr=[], $content = null, $tag = '') { |
| 1714 | $user_id = get_current_user_id(); |
| 1715 | $codes = null; // Will be set if order_id is used |
| 1716 | |
| 1717 | // Check if order_id parameter is provided |
| 1718 | if (isset($attr['order_id']) && !empty($attr['order_id'])) { |
| 1719 | $order_id = intval($attr['order_id']); |
| 1720 | if ($order_id > 0) { |
| 1721 | // Security check: can current user access this order? |
| 1722 | if (!$this->canUserAccessOrder($order_id)) { |
| 1723 | return '<p>' . esc_html__('You do not have permission to view tickets for this order.', 'event-tickets-with-ticket-scanner') . '</p>'; |
| 1724 | } |
| 1725 | // Get codes by order_id |
| 1726 | $codes = $this->getCore()->getCodesByOrderId($order_id); |
| 1727 | } |
| 1728 | } |
| 1729 | |
| 1730 | if (count($attr) > 0 && isset($attr["format"])) { |
| 1731 | return $this->getMyCodeFormatted($user_id, $attr, $content, $tag, $codes); |
| 1732 | } else { |
| 1733 | return $this->getMyCodeText($user_id, $attr, $content, $tag, $codes); |
| 1734 | } |
| 1735 | } |
| 1736 | |
| 1737 | /** |
| 1738 | * Check if current user can access a specific order's tickets |
| 1739 | * |
| 1740 | * @param int $order_id WooCommerce order ID |
| 1741 | * @return bool True if user can access, false otherwise |
| 1742 | */ |
| 1743 | private function canUserAccessOrder(int $order_id): bool { |
| 1744 | $order = wc_get_order($order_id); |
| 1745 | if (!$order) { |
| 1746 | return false; |
| 1747 | } |
| 1748 | |
| 1749 | // 1. Admin/Shop-Manager can access all orders |
| 1750 | if (current_user_can('manage_woocommerce')) { |
| 1751 | return true; |
| 1752 | } |
| 1753 | |
| 1754 | $current_user_id = get_current_user_id(); |
| 1755 | |
| 1756 | // 2. Logged-in user owns this order |
| 1757 | if ($current_user_id > 0 && $order->get_user_id() == $current_user_id) { |
| 1758 | return true; |
| 1759 | } |
| 1760 | |
| 1761 | // 3. Valid order key in URL (WooCommerce thank-you page pattern) |
| 1762 | $order_key_from_url = isset($_GET['key']) ? sanitize_text_field($_GET['key']) : ''; |
| 1763 | if (!empty($order_key_from_url) && $order->get_order_key() === $order_key_from_url) { |
| 1764 | return true; |
| 1765 | } |
| 1766 | |
| 1767 | return false; |
| 1768 | } |
| 1769 | |
| 1770 | public function replacingShortcodeFeatureList($attr=[], $content = null, $tag = '') { |
| 1771 | //$features = $this->getAdmin()->getOptions(); |
| 1772 | //$options = $features["options"]; |
| 1773 | $options = $this->getOptions()->getOptions(); |
| 1774 | $features = []; |
| 1775 | $act_heading = ""; |
| 1776 | foreach ($options as $option) { |
| 1777 | if ($option["key"] == "serial") continue; |
| 1778 | if ($option["type"] == "heading") { |
| 1779 | $act_heading = $option["key"]; |
| 1780 | $features[$act_heading] = ["heading"=>$option, "options"=>[]]; |
| 1781 | } else { |
| 1782 | if ($act_heading != "") { |
| 1783 | $features[$act_heading]["options"][] = $option; |
| 1784 | } |
| 1785 | } |
| 1786 | } |
| 1787 | |
| 1788 | $ret = ""; |
| 1789 | uasort($features, function($a, $b) { |
| 1790 | return strnatcasecmp($a["heading"]["label"], $b["heading"]["label"]); |
| 1791 | }); |
| 1792 | foreach ($features as $key => $feature) { |
| 1793 | $ret .= '<h3>'.$feature["heading"]["label"].'</h3>'; |
| 1794 | $video = $feature["heading"]["_doc_video"] != "" ? ' <a href="'.$feature["heading"]["_doc_video"].'" target="_blank"><span class="dashicons dashicons-video-alt3"></span> Introduction video</a>' : ""; |
| 1795 | $ret .= '<p>'.trim($feature["heading"]["desc"].$video).'</p>'; |
| 1796 | if (count($feature["options"]) > 0) { |
| 1797 | $ret .= '<ul>'; |
| 1798 | foreach ($feature["options"] as $option) { |
| 1799 | $label = $option["label"]; |
| 1800 | $desc = $option["desc"]; |
| 1801 | $desc .= $option["_doc_video"] != "" ? ' <a href="'.$option["_doc_video"].'" target="_blank"><span class="dashicons dashicons-video-alt3"></span> Introduction video</a>' : ""; |
| 1802 | $desc = trim($desc); |
| 1803 | if ($desc != "") { |
| 1804 | $desc = '<p>'.$desc.'</p>'; |
| 1805 | } |
| 1806 | $label = '<span class="dashicons dashicons-yes"></span> '.$label; |
| 1807 | $ret .= '<li>'.$label.$desc.'</li>'; |
| 1808 | } |
| 1809 | $ret .= '</ul>'; |
| 1810 | } |
| 1811 | $ret .= '<hr>'; |
| 1812 | } |
| 1813 | |
| 1814 | return $ret; |
| 1815 | } |
| 1816 | |
| 1817 | public function replacingShortcodeEventViews($attr=[], $content = null, $tag = '') { |
| 1818 | // iterate over all woocommerce products and check if they are an event |
| 1819 | $view = "calendar|list"; |
| 1820 | if (isset($attr["view"])) { |
| 1821 | $view = strtolower(trim(sanitize_key($attr["view"]))); |
| 1822 | } |
| 1823 | $months_to_show = 3; |
| 1824 | if (isset($attr["months_to_show"])) { |
| 1825 | $m = intval($attr["months_to_show"]); |
| 1826 | if ($m > 0) $months_to_show = $m; |
| 1827 | } |
| 1828 | |
| 1829 | $month_start = strtotime(wp_date("Y-m-1 00:00:00")); |
| 1830 | //$month_end = strtotime(wp_date("Y-m-t 23:59:59")); |
| 1831 | $month_end = strtotime(wp_date("Y-m-1 23:59:59", strtotime("+".$months_to_show." month", $month_start))); |
| 1832 | |
| 1833 | $products_args = array( |
| 1834 | 'post_type' => 'product', |
| 1835 | 'post_status' => 'publish', |
| 1836 | 'posts_per_page' => -1, // -1 zeigt alle Produkte an |
| 1837 | 'meta_query' => array( |
| 1838 | [ |
| 1839 | 'key' => 'saso_eventtickets_is_ticket', |
| 1840 | 'value' => 'yes', |
| 1841 | 'compare' => '=' |
| 1842 | ] |
| 1843 | ) |
| 1844 | ); |
| 1845 | $posts = get_posts($products_args); // get only ticket products |
| 1846 | |
| 1847 | $list_infos = [ |
| 1848 | 'months_to_show'=>$months_to_show, |
| 1849 | 'month_start'=>$month_start, |
| 1850 | 'month_end'=>$month_end, |
| 1851 | 'view'=>$view |
| 1852 | ]; |
| 1853 | $products_to_show = []; |
| 1854 | // Ergebnisse überprüfen |
| 1855 | foreach ($posts as $post) { |
| 1856 | $product_ids = []; |
| 1857 | $product = wc_get_product( $post->ID ); |
| 1858 | |
| 1859 | // check if event is variant product |
| 1860 | $is_variable = $product->get_type() == "variable"; |
| 1861 | if ($is_variable) { |
| 1862 | // check if event dates are the same for all variants |
| 1863 | $date_is_for_all_variants = get_post_meta( $product_id, 'saso_eventtickets_is_date_for_all_variants', true ) == "yes" ? true : false; |
| 1864 | if ($date_is_for_all_variants == false) { |
| 1865 | // if not add also the variants |
| 1866 | $childs = $product->get_children(); |
| 1867 | foreach ($childs as $child_id) { |
| 1868 | if (get_post_meta($child_id, '_saso_eventtickets_is_not_ticket', true) == "yes") { |
| 1869 | continue; |
| 1870 | } |
| 1871 | $product_ids[] = $child_id; |
| 1872 | } |
| 1873 | } |
| 1874 | } else { |
| 1875 | $product_ids[] = $product->get_id(); |
| 1876 | } |
| 1877 | |
| 1878 | foreach ($product_ids as $product_id) { |
| 1879 | $product = wc_get_product( $product_id ); |
| 1880 | $dates = $this->getTicketHandler()->calcDateStringAllowedRedeemFrom($product_id); |
| 1881 | //if ($dates['ticket_end_date_timestamp'] > $month_start && $dates['ticket_start_date_timestamp'] < $month_end) { |
| 1882 | if ($dates['ticket_end_date_timestamp'] >= $month_start || ($dates['ticket_start_date_timestamp'] >= $month_start && $dates['ticket_start_date_timestamp'] <= $month_end)) { |
| 1883 | // set product to hidden |
| 1884 | $product_data = array( |
| 1885 | 'ID' => $product_id, |
| 1886 | 'dates' => $dates, |
| 1887 | 'event'=> [ |
| 1888 | 'location' => trim(get_post_meta( $product_id, 'saso_eventtickets_event_location', true )), |
| 1889 | 'location_label' => wp_kses_post(trim($this->getAdmin()->getOptionValue("wcTicketTransLocation"))) |
| 1890 | ], |
| 1891 | 'product' => [ |
| 1892 | 'title' => $product->get_name(), |
| 1893 | 'url' => get_permalink($product_id), |
| 1894 | 'price' =>$product->get_price(), |
| 1895 | 'price_html' => $product->get_price_html(), |
| 1896 | 'type' => $product->get_type(), |
| 1897 | ] |
| 1898 | ); |
| 1899 | $products_to_show[] = $product_data; |
| 1900 | } |
| 1901 | } |
| 1902 | } |
| 1903 | |
| 1904 | $divId = "sasoEventTicketsValidator_eventsview"; |
| 1905 | |
| 1906 | // add js for the events |
| 1907 | wp_enqueue_style("wp-jquery-ui-dialog"); |
| 1908 | |
| 1909 | $js_url = "ticket_events.js?_v=".$this->getPluginVersion(); |
| 1910 | if (defined('WP_DEBUG')) $js_url .= '&t='.time(); |
| 1911 | $js_url = plugins_url( $js_url,__FILE__ ); |
| 1912 | wp_register_script('ajax_script_ticket_events', $js_url, array('jquery', 'jquery-ui-dialog', 'wp-i18n')); |
| 1913 | wp_enqueue_script('ajax_script_ticket_events'); |
| 1914 | wp_set_script_translations('ajax_script_ticket_events', 'event-tickets-with-ticket-scanner', __DIR__.'/languages'); |
| 1915 | wp_enqueue_style("ticket_events_css", plugins_url( "",__FILE__ ).'/css/calendar.css', [], true); |
| 1916 | |
| 1917 | // add all events as an array for max 3 months??? or config parameter |
| 1918 | $vars = [ |
| 1919 | 'root' => esc_url_raw( rest_url() ), |
| 1920 | '_plugin_home_url' =>plugins_url( "",__FILE__ ), |
| 1921 | '_action' => $this->_prefix.'_executeFrontendEvents', |
| 1922 | '_isPremium'=>$this->isPremium(), |
| 1923 | '_isUserLoggedin'=>is_user_logged_in(), |
| 1924 | '_userId'=>get_current_user_id(), |
| 1925 | '_premJS'=>$this->isPremium() && method_exists($this->getPremiumFunctions(), "getJSFrontEventFile") ? $this->getPremiumFunctions()->getJSFrontEventFile() : '', |
| 1926 | '_siteUrl'=>get_site_url(), |
| 1927 | 'events' => $products_to_show, |
| 1928 | 'list_infos' => $list_infos, |
| 1929 | 'format_date' => $this->getOptions()->getOptionDateFormat(), |
| 1930 | 'format_time' => $this->getOptions()->getOptionTimeFormat(), |
| 1931 | 'format_datetime' => $this->getOptions()->getOptionDateTimeFormat(), |
| 1932 | 'url' => admin_url( 'admin-ajax.php' ), |
| 1933 | 'nonce' => wp_create_nonce( $this->_js_nonce ), |
| 1934 | 'ajaxActionPrefix' => $this->_prefix, |
| 1935 | 'divId' => $divId |
| 1936 | ]; |
| 1937 | $vars = apply_filters( $this->_add_filter_prefix.'main_setTicketEventJS', $vars ); |
| 1938 | wp_localize_script( |
| 1939 | 'ajax_script_ticket_events', |
| 1940 | 'Ajax_ticket_events_'.$this->_prefix, // name der injected variable |
| 1941 | $vars |
| 1942 | ); |
| 1943 | |
| 1944 | do_action( $this->_do_action_prefix.'main_setTicketEventJS', $js_url ); |
| 1945 | |
| 1946 | $ret = ''; |
| 1947 | if (!isset($attr['divid']) || trim($attr['divid']) == "") { |
| 1948 | $ret .= '<div id="'.$divId.'">'.__('...loading...', 'event-tickets-with-ticket-scanner').'</div>'; |
| 1949 | } |
| 1950 | |
| 1951 | $ret = apply_filters( $this->_add_filter_prefix.'main_replacingShortcodeEventViews', $ret ); |
| 1952 | do_action( $this->_do_action_prefix.'main_replacingShortcodeEventViews', $vars, $ret ); |
| 1953 | |
| 1954 | return $ret; |
| 1955 | } |
| 1956 | |
| 1957 | public function replaceShortcode($attr=[], $content = null, $tag = '') { |
| 1958 | // einbinden das js starter skript |
| 1959 | $js_url = $this->_js_file."?_v=".$this->_js_version; |
| 1960 | if (defined( 'WP_DEBUG')) $js_url .= '&debug=1'; |
| 1961 | $userDivId = !isset($attr['divid']) || trim($attr['divid']) == "" ? '' : trim($attr['divid']); |
| 1962 | |
| 1963 | $attr = array_change_key_case( (array) $attr, CASE_LOWER ); |
| 1964 | |
| 1965 | wp_enqueue_script( |
| 1966 | 'ajax_script_validator', |
| 1967 | plugins_url( $js_url,__FILE__ ), |
| 1968 | array('jquery', 'wp-i18n') |
| 1969 | ); |
| 1970 | wp_set_script_translations('ajax_script_validator', 'event-tickets-with-ticket-scanner', __DIR__.'/languages'); |
| 1971 | |
| 1972 | $vars = array( |
| 1973 | 'shortcode_attr'=>json_encode($attr), |
| 1974 | '_plugin_home_url' =>plugins_url( "",__FILE__ ), |
| 1975 | '_action' => $this->_prefix.'_executeFrontend', |
| 1976 | '_isPremium'=>$this->isPremium(), |
| 1977 | '_isUserLoggedin'=>is_user_logged_in(), |
| 1978 | '_premJS'=>$this->isPremium() && method_exists($this->getPremiumFunctions(), "getJSFrontFile") ? $this->getPremiumFunctions()->getJSFrontFile() : '', |
| 1979 | 'url' => admin_url( 'admin-ajax.php' ), |
| 1980 | 'nonce' => wp_create_nonce( $this->_js_nonce ), |
| 1981 | 'ajaxActionPrefix' => $this->_prefix, |
| 1982 | 'divPrefix' => $userDivId == "" ? $this->_prefix : $userDivId, |
| 1983 | 'divId' => $this->_divId, |
| 1984 | 'jsFiles' => plugins_url( 'validator.js?_v='.$this->_js_version, __FILE__ ) |
| 1985 | ); |
| 1986 | $vars['_messages'] = [ |
| 1987 | 'msgCheck0'=>$this->getOptions()->getOptionValue('textValidationMessage0'), |
| 1988 | 'msgCheck1'=>$this->getOptions()->getOptionValue('textValidationMessage1'), |
| 1989 | 'msgCheck2'=>$this->getOptions()->getOptionValue('textValidationMessage2'), |
| 1990 | 'msgCheck3'=>$this->getOptions()->getOptionValue('textValidationMessage3'), |
| 1991 | 'msgCheck4'=>$this->getOptions()->getOptionValue('textValidationMessage4'), |
| 1992 | 'msgCheck5'=>$this->getOptions()->getOptionValue('textValidationMessage5'), |
| 1993 | 'msgCheck6'=>$this->getOptions()->getOptionValue('textValidationMessage6') |
| 1994 | ]; |
| 1995 | $enableQR = $this->getOptions()->isOptionCheckboxActive('enableQRScanner'); |
| 1996 | $vars['_enableQRScanner'] = $enableQR; |
| 1997 | if ($enableQR) { |
| 1998 | wp_enqueue_script( |
| 1999 | 'saso-html5-qrcode', |
| 2000 | plugins_url('3rd/html5-qrcode.min.js', __FILE__), |
| 2001 | array(), |
| 2002 | $this->_js_version, |
| 2003 | true |
| 2004 | ); |
| 2005 | } |
| 2006 | |
| 2007 | $vars = apply_filters( $this->_add_filter_prefix.'main_replaceShortcode', $vars ); |
| 2008 | |
| 2009 | if ($this->isPremium() && method_exists($this->getPremiumFunctions(), "addJSFrontFile")) $this->getPremiumFunctions()->addJSFrontFile(); |
| 2010 | |
| 2011 | wp_localize_script( |
| 2012 | 'ajax_script_validator', |
| 2013 | 'Ajax_'.$this->_prefix, // name of the injected variable |
| 2014 | $vars |
| 2015 | ); |
| 2016 | $ret = ''; |
| 2017 | if (!isset($attr['divid']) || trim($attr['divid']) == "") { |
| 2018 | $ret .= '<div id="'.$this->_divId.'">'.__('...loading...', 'event-tickets-with-ticket-scanner').'</div>'; |
| 2019 | } |
| 2020 | |
| 2021 | $ret = apply_filters( $this->_add_filter_prefix.'main_replaceShortcode_2', $ret ); |
| 2022 | do_action( $this->_do_action_prefix.'main_replaceShortcode', $vars, $ret ); |
| 2023 | |
| 2024 | return $ret; |
| 2025 | } |
| 2026 | |
| 2027 | /** |
| 2028 | * Show admin notice when premium subscription is about to expire or has expired |
| 2029 | * |
| 2030 | * - Warning: 14 days before expiration |
| 2031 | * - Error: After expiration (during grace period or after) |
| 2032 | * |
| 2033 | * @return void |
| 2034 | */ |
| 2035 | public function showSubscriptionWarning(): void { |
| 2036 | // Only show when premium plugin is installed (not dependent on subscription status, |
| 2037 | // otherwise the expiration warning itself would never be shown) |
| 2038 | if (!class_exists('sasoEventtickets_PremiumFunctions')) { |
| 2039 | return; |
| 2040 | } |
| 2041 | |
| 2042 | // Only show in admin |
| 2043 | if (!is_admin()) { |
| 2044 | return; |
| 2045 | } |
| 2046 | |
| 2047 | // Only show to users who can manage options |
| 2048 | if (!current_user_can('manage_options')) { |
| 2049 | return; |
| 2050 | } |
| 2051 | |
| 2052 | // Don't show "expired" warning when no serial is configured yet — |
| 2053 | // customer is still setting up, not really expired. |
| 2054 | $serial = trim((string) get_option('saso-event-tickets-premium_serial', '')); |
| 2055 | if ($serial === '') { |
| 2056 | return; |
| 2057 | } |
| 2058 | |
| 2059 | // Don't show during license-save grace period (suppresses flash of stale banner |
| 2060 | // after the user just entered a new license key) |
| 2061 | if (get_transient('saso_license_just_saved')) { |
| 2062 | return; |
| 2063 | } |
| 2064 | |
| 2065 | $info = $this->getTicketHandler()->get_expiration(); |
| 2066 | |
| 2067 | // No expiration date or lifetime license - no warning needed |
| 2068 | if (empty($info['timestamp']) || $info['timestamp'] <= 0 || $info['timestamp'] == -1) { |
| 2069 | return; |
| 2070 | } |
| 2071 | |
| 2072 | // Lifetime subscription type - no warning needed |
| 2073 | if (isset($info['subscription_type']) && $info['subscription_type'] === 'lifetime') { |
| 2074 | return; |
| 2075 | } |
| 2076 | |
| 2077 | $days_left = ($info['timestamp'] - time()) / 86400; |
| 2078 | $renewal_url = 'https://vollstart.com/event-tickets-with-ticket-scanner/'; |
| 2079 | |
| 2080 | // Warning 14 days before expiration |
| 2081 | if ($days_left <= 14 && $days_left > 0) { |
| 2082 | $date = date_i18n(get_option('date_format'), $info['timestamp']); |
| 2083 | echo '<div class="notice notice-warning is-dismissible saso-license-banner"><p>'; |
| 2084 | printf( |
| 2085 | /* translators: %1$s: expiration date, %2$s: renewal URL */ |
| 2086 | 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'), |
| 2087 | '<strong>' . esc_html($date) . '</strong>', |
| 2088 | '<a href="' . esc_url($renewal_url) . '" target="_blank">', |
| 2089 | '</a>' |
| 2090 | ); |
| 2091 | echo '</p></div>'; |
| 2092 | } |
| 2093 | |
| 2094 | // Error after expiration |
| 2095 | if ($days_left <= 0) { |
| 2096 | $grace_days = isset($info['grace_period_days']) ? intval($info['grace_period_days']) : 7; |
| 2097 | $grace_left = $grace_days + $days_left; // days_left is negative |
| 2098 | |
| 2099 | echo '<div class="notice notice-error saso-license-banner"><p>'; |
| 2100 | if ($grace_left > 0) { |
| 2101 | printf( |
| 2102 | /* translators: %1$d: days remaining in grace period, %2$s: renewal URL */ |
| 2103 | 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'), |
| 2104 | ceil($grace_left), |
| 2105 | '<a href="' . esc_url($renewal_url) . '" target="_blank">', |
| 2106 | '</a>' |
| 2107 | ); |
| 2108 | } else { |
| 2109 | printf( |
| 2110 | /* translators: %1$s: renewal URL */ |
| 2111 | esc_html__('Your Premium subscription has expired. Premium features are now limited. %1$sReactivate now%2$s', 'event-tickets-with-ticket-scanner'), |
| 2112 | '<a href="' . esc_url($renewal_url) . '" target="_blank">', |
| 2113 | '</a>' |
| 2114 | ); |
| 2115 | } |
| 2116 | echo '</p></div>'; |
| 2117 | } |
| 2118 | } |
| 2119 | |
| 2120 | /** |
| 2121 | * FOMO banner: show missed premium features to users with expired subscriptions. |
| 2122 | * Dismissable for 30 days, then re-appears with an even longer feature list. |
| 2123 | */ |
| 2124 | public function showFomoBanner(): void { |
| 2125 | if (!current_user_can('manage_options')) return; |
| 2126 | if ($this->isPremium()) return; |
| 2127 | if (!class_exists('sasoEventtickets_PremiumFunctions')) return; |
| 2128 | if ($this->isStarterOrStopDetected()) return; |
| 2129 | if ($this->isOldPremiumDetected()) return; |
| 2130 | |
| 2131 | $info = $this->getTicketHandler()->get_expiration(); |
| 2132 | |
| 2133 | if (empty($info['expiration_date'])) return; |
| 2134 | if (isset($info['subscription_type']) && $info['subscription_type'] === 'lifetime') return; |
| 2135 | |
| 2136 | $expired_ts = strtotime($info['expiration_date']); |
| 2137 | if (!$expired_ts || $expired_ts > time()) return; |
| 2138 | |
| 2139 | if (get_transient('saso_et_fomo_dismissed')) return; |
| 2140 | if (get_transient('saso_license_just_saved')) return; |
| 2141 | |
| 2142 | $json_path = plugin_dir_path(__FILE__) . 'changelog-features.json'; |
| 2143 | if (!file_exists($json_path)) return; |
| 2144 | |
| 2145 | $changelog = json_decode(file_get_contents($json_path), true); |
| 2146 | if (!is_array($changelog)) return; |
| 2147 | |
| 2148 | $missed = []; |
| 2149 | foreach ($changelog as $release) { |
| 2150 | $release_ts = strtotime($release['date'] ?? ''); |
| 2151 | if ($release_ts && $release_ts > $expired_ts && !empty($release['features'])) { |
| 2152 | foreach ($release['features'] as $feat) { |
| 2153 | $missed[] = $feat; |
| 2154 | } |
| 2155 | } |
| 2156 | } |
| 2157 | |
| 2158 | if (empty($missed)) return; |
| 2159 | |
| 2160 | $total = count($missed); |
| 2161 | $show = array_slice($missed, 0, 5); |
| 2162 | $renewal_url = 'https://vollstart.com/event-tickets-with-ticket-scanner/'; |
| 2163 | $nonce = wp_create_nonce('saso_et_dismiss_fomo'); |
| 2164 | |
| 2165 | echo '<div class="notice notice-info is-dismissible saso-license-banner" id="saso-et-fomo-banner" style="border-left-color:#9333ea;">'; |
| 2166 | echo '<p><strong>'; |
| 2167 | printf( |
| 2168 | /* translators: %d: number of missed features */ |
| 2169 | esc_html__("You're missing %d new premium features!", 'event-tickets-with-ticket-scanner'), |
| 2170 | $total |
| 2171 | ); |
| 2172 | echo '</strong></p>'; |
| 2173 | echo '<ul style="list-style:disc;margin-left:20px;">'; |
| 2174 | foreach ($show as $feat) { |
| 2175 | echo '<li>' . esc_html($feat) . '</li>'; |
| 2176 | } |
| 2177 | if ($total > 5) { |
| 2178 | printf('<li><em>' . esc_html__('...and %d more', 'event-tickets-with-ticket-scanner') . '</em></li>', $total - 5); |
| 2179 | } |
| 2180 | echo '</ul>'; |
| 2181 | echo '<p><a href="' . esc_url($renewal_url) . '" target="_blank" class="button button-primary" style="background:#9333ea;border-color:#7c22d0;">'; |
| 2182 | esc_html_e('Renew now', 'event-tickets-with-ticket-scanner'); |
| 2183 | echo '</a></p>'; |
| 2184 | echo '</div>'; |
| 2185 | echo '<script>jQuery(function($){$("#saso-et-fomo-banner").on("click",".notice-dismiss",function(){$.post(ajaxurl,{action:"saso_et_dismiss_fomo",_wpnonce:"' . $nonce . '"});});});</script>'; |
| 2186 | } |
| 2187 | |
| 2188 | /** |
| 2189 | * AJAX handler: dismiss FOMO banner for 30 days |
| 2190 | */ |
| 2191 | public function ajaxDismissFomo(): void { |
| 2192 | check_ajax_referer('saso_et_dismiss_fomo'); |
| 2193 | set_transient('saso_et_fomo_dismissed', 1, 30 * DAY_IN_SECONDS); |
| 2194 | wp_send_json_success(); |
| 2195 | } |
| 2196 | |
| 2197 | /** |
| 2198 | * Show admin notice when premium plugin version is outdated or incompatible |
| 2199 | * |
| 2200 | * Shows a prominent, non-dismissible error when old premium is detected. |
| 2201 | * Old premium (< 1.6.0) is NOT loaded to prevent fatal errors. |
| 2202 | * |
| 2203 | * @return void |
| 2204 | */ |
| 2205 | public function showOutdatedPremiumWarning(): void { |
| 2206 | if (!is_admin() || !current_user_can('manage_options')) { |
| 2207 | return; |
| 2208 | } |
| 2209 | |
| 2210 | $starter_or_stop = $this->isStarterOrStopDetected(); |
| 2211 | $old_premium = $this->isOldPremiumDetected(); |
| 2212 | |
| 2213 | if (!$starter_or_stop && !$old_premium) { |
| 2214 | return; |
| 2215 | } |
| 2216 | |
| 2217 | // Don't show during license-save grace period (upgrade is happening in background, |
| 2218 | // no point scaring the user while we're actively replacing the plugin for them) |
| 2219 | if (get_transient('saso_license_just_saved')) { |
| 2220 | return; |
| 2221 | } |
| 2222 | |
| 2223 | $old_version = defined('SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION') |
| 2224 | ? SASO_EVENTTICKETS_PREMIUM_PLUGIN_VERSION |
| 2225 | : __('unknown', 'event-tickets-with-ticket-scanner'); |
| 2226 | |
| 2227 | $upgrade_url = 'https://vollstart.com/event-tickets-with-ticket-scanner/'; |
| 2228 | $support_url = 'https://vollstart.com/support/'; |
| 2229 | $premium_url = 'https://vollstart.com/downloads/event-tickets-with-ticket-scanner/'; |
| 2230 | |
| 2231 | echo '<div class="notice notice-error saso-license-banner" style="border-left-color:#dc3232;padding:15px 20px;">'; |
| 2232 | echo '<p style="font-size:15px;font-weight:bold;margin:0 0 10px 0;color:#dc2626;">'; |
| 2233 | echo '⚠ '; |
| 2234 | |
| 2235 | if ($starter_or_stop) { |
| 2236 | $is_stop = defined('SASO_EVENTTICKETS_STOP_VERSION'); |
| 2237 | if ($is_stop) { |
| 2238 | printf( |
| 2239 | esc_html__('Event Tickets: Your Premium subscription has expired. Please renew your license to continue using Premium features.', 'event-tickets-with-ticket-scanner') |
| 2240 | ); |
| 2241 | } else { |
| 2242 | printf( |
| 2243 | esc_html__('Event Tickets: The Starter plugin is installed. Please update to the latest Premium version within the Plugins page.', 'event-tickets-with-ticket-scanner') |
| 2244 | ); |
| 2245 | } |
| 2246 | } else { |
| 2247 | printf( |
| 2248 | esc_html__('Event Tickets: Your Premium plugin (v%s) is outdated and not compatible with this version.', 'event-tickets-with-ticket-scanner'), |
| 2249 | esc_html($old_version) |
| 2250 | ); |
| 2251 | } |
| 2252 | echo '</p>'; |
| 2253 | |
| 2254 | if ($starter_or_stop) { |
| 2255 | // Starter/Stop plugin - different messages |
| 2256 | $is_stop = defined('SASO_EVENTTICKETS_STOP_VERSION'); |
| 2257 | $hasSerial = !empty(trim(get_option("saso-event-tickets-premium_serial", ""))); |
| 2258 | |
| 2259 | echo '<div style="background:#f8f9fa;border-left:4px solid #2563eb;padding:12px;margin:12px 0;">'; |
| 2260 | |
| 2261 | if ($is_stop) { |
| 2262 | // Stop plugin - subscription expired, PUC can update once license is renewed |
| 2263 | echo '<p style="margin:0 0 10px 0;"><strong>' . esc_html__('Solution: Renew Your Premium License', 'event-tickets-with-ticket-scanner') . '</strong></p>'; |
| 2264 | echo '<ol style="margin:0 0 12px 20px;padding-left:20px;">'; |
| 2265 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Renew your license', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2266 | if ($hasSerial) { |
| 2267 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Update your license key in Event Tickets settings', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2268 | } else { |
| 2269 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Enter your license key in Event Tickets settings', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2270 | } |
| 2271 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Click "Check License" to verify your new subscription', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2272 | echo '<li style="margin:0;">' . esc_html__('Update the Premium plugin via Plugins > Updates', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2273 | echo '</ol>'; |
| 2274 | printf( |
| 2275 | '<p style="margin:0 0 8px 0;"><a href="%s" class="button button-primary button-hero" target="_blank">%s</a></p>', |
| 2276 | esc_url($upgrade_url . '?utm_source=plugin&utm_medium=stop-notice&utm_campaign=renew-license'), |
| 2277 | esc_html__('Renew License', 'event-tickets-with-ticket-scanner') |
| 2278 | ); |
| 2279 | echo '<p style="margin:0;font-size:13px;color:#666;">'; |
| 2280 | esc_html_e('Contact support via support@vollstart.com if you think this is not correct.', 'event-tickets-with-ticket-scanner'); |
| 2281 | echo '</p>'; |
| 2282 | } else { |
| 2283 | // Starter plugin - just update within plugins area |
| 2284 | echo '<p style="margin:0 0 10px 0;"><strong>' . esc_html__('Solution: Update to Premium via Plugins Page', 'event-tickets-with-ticket-scanner') . '</strong></p>'; |
| 2285 | echo '<p style="margin:0 0 8px 0;">'; |
| 2286 | esc_html_e('The Starter plugin needs to be updated to Premium. Please update within the Plugins page:', 'event-tickets-with-ticket-scanner'); |
| 2287 | echo '</p>'; |
| 2288 | echo '<ol style="margin:0 0 12px 20px;padding-left:20px;">'; |
| 2289 | if (!$hasSerial) { |
| 2290 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Enter your license key in Event Tickets settings', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2291 | } |
| 2292 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Go to Plugins > Add New > Upload Plugin', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2293 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Upload the Premium ZIP file', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2294 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('WordPress will replace the Starter plugin with Premium', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2295 | echo '<li style="margin:0;">' . esc_html__('Click "Replace current with uploaded" and activate', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2296 | echo '</ol>'; |
| 2297 | } |
| 2298 | |
| 2299 | echo '</div>'; |
| 2300 | } else { |
| 2301 | // Old premium detected - update instructions |
| 2302 | echo '<div style="background:#f8f9fa;border-left:4px solid #2563eb;padding:12px;margin:12px 0;">'; |
| 2303 | echo '<p style="margin:0 0 10px 0;"><strong>' . esc_html__('Solution: Update Premium Plugin', 'event-tickets-with-ticket-scanner') . '</strong></p>'; |
| 2304 | echo '<p style="margin:0 0 8px 0;">'; |
| 2305 | esc_html_e('Your current Premium plugin is outdated and incompatible. Premium features have been temporarily disabled to prevent errors. Your tickets and data are safe.', 'event-tickets-with-ticket-scanner'); |
| 2306 | echo '</p>'; |
| 2307 | echo '<p style="margin:0 0 8px 0;">'; |
| 2308 | printf( |
| 2309 | esc_html__('Required version: 1.6.0 or higher. You have: %s', 'event-tickets-with-ticket-scanner'), |
| 2310 | '<strong>' . esc_html($old_version) . '</strong>' |
| 2311 | ); |
| 2312 | echo '</p>'; |
| 2313 | echo '<p style="margin:0 0 12px 0;">'; |
| 2314 | esc_html_e('To restore premium features:', 'event-tickets-with-ticket-scanner'); |
| 2315 | echo '</p>'; |
| 2316 | echo '<ol style="margin:0 0 12px 20px;padding-left:20px;">'; |
| 2317 | echo '<li style="margin:0 0 6px 0;">' . sprintf( |
| 2318 | /* translators: %s: URL to account */ |
| 2319 | esc_html__('Download the latest Premium from your %1$saccount%2$s', 'event-tickets-with-ticket-scanner'), |
| 2320 | '<a href="' . esc_url($upgrade_url) . '" target="_blank"><strong>', |
| 2321 | '</strong></a>' |
| 2322 | ) . '</li>'; |
| 2323 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Go to Plugins > Add New > Upload Plugin', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2324 | echo '<li style="margin:0 0 6px 0;">' . esc_html__('Upload the new Premium ZIP file', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2325 | echo '<li style="margin:0;">' . esc_html__('WordPress will ask to replace - confirm to update', 'event-tickets-with-ticket-scanner') . '</li>'; |
| 2326 | echo '</ol>'; |
| 2327 | echo '<p style="margin:0;">'; |
| 2328 | printf( |
| 2329 | '<a href="' . esc_url($support_url) . '" class="button" style="margin-right:8px;" target="_blank">%s</a>', |
| 2330 | esc_html__('Contact Support', 'event-tickets-with-ticket-scanner') |
| 2331 | ); |
| 2332 | echo '</p>'; |
| 2333 | } |
| 2334 | |
| 2335 | echo '</div>'; // End gray box |
| 2336 | |
| 2337 | echo '<p style="margin:12px 0 0 0;font-style:italic;color:#666;font-size:13px;">'; |
| 2338 | esc_html_e('Your tickets, orders, and data are completely safe. You can continue using the basic features while updating.', 'event-tickets-with-ticket-scanner'); |
| 2339 | echo '</p>'; |
| 2340 | |
| 2341 | $downgrade_url = 'https://plugins.trac.wordpress.org/browser/event-tickets-with-ticket-scanner/tags'; |
| 2342 | echo '<p style="margin:6px 0 0 0;font-style:italic;color:#666;font-size:13px;">'; |
| 2343 | printf( |
| 2344 | 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'), |
| 2345 | '<a href="' . esc_url($downgrade_url) . '" target="_blank">', |
| 2346 | '</a>' |
| 2347 | ); |
| 2348 | echo '</p>'; |
| 2349 | |
| 2350 | echo '</div>'; // End notice |
| 2351 | } |
| 2352 | |
| 2353 | /** |
| 2354 | * Show admin notice when ticket format is running out of combinations |
| 2355 | * |
| 2356 | * Checks all ticket lists for format warnings and displays notice |
| 2357 | * |
| 2358 | * @return void |
| 2359 | */ |
| 2360 | public function showPhpVersionWarning(): void { |
| 2361 | if (version_compare(PHP_VERSION, '8.1.0', '>=')) { |
| 2362 | return; |
| 2363 | } |
| 2364 | printf( |
| 2365 | '<div class="notice notice-warning is-dismissible"><p><strong>Event Tickets with Ticket Scanner:</strong> %s</p></div>', |
| 2366 | sprintf( |
| 2367 | /* translators: 1: current PHP version 2: required PHP version */ |
| 2368 | 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'), |
| 2369 | PHP_VERSION, |
| 2370 | '8.1' |
| 2371 | ) |
| 2372 | ); |
| 2373 | } |
| 2374 | |
| 2375 | /** |
| 2376 | * Handle format warning dismiss — runs on admin_init (before output) so wp_redirect() works. |
| 2377 | */ |
| 2378 | public function handleFormatWarningDismiss(): void { |
| 2379 | if (!isset($_GET['saso_eventtickets_clear_format_warning']) || !isset($_GET['_wpnonce'])) { |
| 2380 | return; |
| 2381 | } |
| 2382 | if (!current_user_can('manage_options')) { |
| 2383 | return; |
| 2384 | } |
| 2385 | $list_id = intval($_GET['saso_eventtickets_clear_format_warning']); |
| 2386 | $nonce = sanitize_text_field($_GET['_wpnonce']); |
| 2387 | if (wp_verify_nonce($nonce, 'clear_format_warning_' . $list_id)) { |
| 2388 | $this->getAdmin()->clearFormatWarning($list_id); |
| 2389 | wp_redirect(remove_query_arg(['saso_eventtickets_clear_format_warning', '_wpnonce'])); |
| 2390 | exit; |
| 2391 | } |
| 2392 | } |
| 2393 | |
| 2394 | /** |
| 2395 | * Show admin notice when options migration from wp_options to custom table is incomplete. |
| 2396 | * Includes a button to manually trigger the migration. |
| 2397 | */ |
| 2398 | public function showOptionsMigrationNotice(): void { |
| 2399 | if (!current_user_can('manage_options')) { |
| 2400 | return; |
| 2401 | } |
| 2402 | // Fast path: migration already done |
| 2403 | if (get_option('saso_eventtickets_options_migrated', '0') === '1') { |
| 2404 | return; |
| 2405 | } |
| 2406 | // Check if custom table exists (DB upgrade may not have run yet) |
| 2407 | global $wpdb; |
| 2408 | $table = $wpdb->prefix . 'saso_eventtickets_options'; |
| 2409 | if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) !== $table) { |
| 2410 | return; |
| 2411 | } |
| 2412 | // Check sentinel: is the last known option still in wp_options? |
| 2413 | $sentinelKey = $this->_prefix . 'qrAttachQRFilesToMailAsOnePDF'; |
| 2414 | if (get_option($sentinelKey, '__NOT_SET__') === '__NOT_SET__') { |
| 2415 | // Sentinel gone — migration probably completed, set flag |
| 2416 | update_option('saso_eventtickets_options_migrated', '1'); |
| 2417 | return; |
| 2418 | } |
| 2419 | // Migration incomplete — show admin notice with button |
| 2420 | $nonce = wp_create_nonce($this->_prefix); |
| 2421 | printf( |
| 2422 | '<div class="notice notice-warning"><p><strong>Event Tickets:</strong> %s <button class="button button-primary" onclick="sasoEventticketsMigrateOptions(this)" data-nonce="%s">%s</button></p></div>', |
| 2423 | esc_html__('Options migration to custom database table is incomplete.', 'event-tickets-with-ticket-scanner'), |
| 2424 | esc_attr($nonce), |
| 2425 | esc_html__('Migrate Options', 'event-tickets-with-ticket-scanner') |
| 2426 | ); |
| 2427 | } |
| 2428 | |
| 2429 | public function showFormatWarning(): void { |
| 2430 | // Only show in admin |
| 2431 | if (!is_admin()) { |
| 2432 | return; |
| 2433 | } |
| 2434 | |
| 2435 | // Only show to users who can manage options |
| 2436 | if (!current_user_can('manage_options')) { |
| 2437 | return; |
| 2438 | } |
| 2439 | |
| 2440 | try { |
| 2441 | // Get all ticket lists |
| 2442 | $lists = $this->getAdmin()->getLists([], false); |
| 2443 | |
| 2444 | foreach ($lists as $list) { |
| 2445 | $warning = $this->getAdmin()->getFormatWarning($list['id']); |
| 2446 | |
| 2447 | if ($warning) { |
| 2448 | $list_name = esc_html($warning['list_name']); |
| 2449 | $attempts = intval($warning['attempts']); |
| 2450 | |
| 2451 | if ($warning['type'] === 'critical') { |
| 2452 | // Critical - format exhausted |
| 2453 | $clear_url = wp_nonce_url( |
| 2454 | add_query_arg(['saso_eventtickets_clear_format_warning' => $list['id']]), |
| 2455 | 'clear_format_warning_' . $list['id'] |
| 2456 | ); |
| 2457 | |
| 2458 | echo '<div class="notice notice-error"><p>'; |
| 2459 | printf( |
| 2460 | /* translators: 1: list name, 2: attempts, 3: clear URL */ |
| 2461 | 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'), |
| 2462 | $list_name, |
| 2463 | $attempts, |
| 2464 | '<a href="' . esc_url(admin_url('admin.php?page=event-tickets-with-ticket-scanner')) . '">', |
| 2465 | '</a>', |
| 2466 | '<a href="' . esc_url($clear_url) . '">' |
| 2467 | ); |
| 2468 | echo '</p></div>'; |
| 2469 | } else { |
| 2470 | // Warning - running out |
| 2471 | $clear_url = wp_nonce_url( |
| 2472 | add_query_arg(['saso_eventtickets_clear_format_warning' => $list['id']]), |
| 2473 | 'clear_format_warning_' . $list['id'] |
| 2474 | ); |
| 2475 | |
| 2476 | echo '<div class="notice notice-warning is-dismissible"><p>'; |
| 2477 | printf( |
| 2478 | /* translators: 1: list name, 2: attempts, 3: clear URL */ |
| 2479 | 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'), |
| 2480 | $list_name, |
| 2481 | $attempts, |
| 2482 | '<a href="' . esc_url(admin_url('admin.php?page=event-tickets-with-ticket-scanner')) . '">', |
| 2483 | '</a>', |
| 2484 | '<a href="' . esc_url($clear_url) . '">' |
| 2485 | ); |
| 2486 | echo '</p></div>'; |
| 2487 | } |
| 2488 | |
| 2489 | // Only show one warning at a time |
| 2490 | break; |
| 2491 | } |
| 2492 | } |
| 2493 | } catch (Exception $e) { |
| 2494 | // Silently fail - don't break the admin |
| 2495 | } |
| 2496 | } |
| 2497 | } |
| 2498 | $sasoEventtickets = sasoEventtickets::Instance(); |
| 2499 | |
| 2500 | // Cross-Promotion: andere Vollstart Plugins empfehlen |
| 2501 | if (is_admin() && file_exists(__DIR__ . '/vollstart-cross-promo.php')) { |
| 2502 | require_once __DIR__ . '/vollstart-cross-promo.php'; |
| 2503 | vollstart_cross_promo_init('event-tickets-with-ticket-scanner'); |
| 2504 | } |
| 2505 | ?> |
| 2506 |