--- a/event-tickets-with-ticket-scanner/SASO_EVENTTICKETS.php
+++ b/event-tickets-with-ticket-scanner/SASO_EVENTTICKETS.php
@@ -293,13 +293,18 @@
}
public static function rest_downloadPDFTicketBadge($web_request) {
try {
- $a = SASO_EVENTTICKETS::issetRPara('action') ? SASO_EVENTTICKETS::getRequestPara('action') : "";
global $sasoEventtickets;
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $sasoEventtickets->getAdmin()->executeJSON($a, $_POST, true, false);
- } else {
- $sasoEventtickets->getAdmin()->executeJSON($a, $_GET, true, false);
+ $code = $web_request->get_param('code');
+ if (empty($code)) {
+ throw new Exception("#6100 ticket code parameter is missing");
}
+ $codeObj = $sasoEventtickets->getCore()->retrieveCodeByCode($code);
+ if (empty($codeObj)) {
+ throw new Exception("#6101 ticket code not found");
+ }
+ $badgeHandler = $sasoEventtickets->getTicketBadgeHandler();
+ $badgeHandler->downloadPDFTicketBadge($codeObj);
+ exit;
} catch (Exception $e) {
wp_send_json_error($e->getMessage());
}
--- a/event-tickets-with-ticket-scanner/includes/woocommerce/class-product.php
+++ b/event-tickets-with-ticket-scanner/includes/woocommerce/class-product.php
@@ -1094,12 +1094,18 @@
}
/**
- * Add background image to flyer PDF
+ * Add background image and color to flyer PDF
*
* @param object $pdf PDF object
* @return void
*/
private function addFlyerBackground($pdf): void {
+ // Background color (as fallback when no image or to fill gaps)
+ $wcTicketPDFBackgroundColor = $this->MAIN->getOptions()->getOptionValue('wcTicketPDFBackgroundColor');
+ if (!empty($wcTicketPDFBackgroundColor)) {
+ $pdf->setBackgroundColor($wcTicketPDFBackgroundColor);
+ }
+
$wcTicketFlyerBG = $this->MAIN->getOptions()->getOptionValue('wcTicketFlyerBG');
if (empty($wcTicketFlyerBG) || intval($wcTicketFlyerBG) <= 0) {
return;
--- a/event-tickets-with-ticket-scanner/index.php
+++ b/event-tickets-with-ticket-scanner/index.php
@@ -3,7 +3,7 @@
* Plugin Name: Event Tickets with Ticket Scanner
* Plugin URI: https://vollstart.com/event-tickets-with-ticket-scanner/docs/
* 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.
- * Version: 2.8.5
+ * Version: 2.8.6
* Author: Vollstart
* Author URI: https://vollstart.com
* Text Domain: event-tickets-with-ticket-scanner
@@ -20,7 +20,7 @@
include_once(plugin_dir_path(__FILE__)."init_file.php");
if (!defined('SASO_EVENTTICKETS_PLUGIN_VERSION'))
- define('SASO_EVENTTICKETS_PLUGIN_VERSION', '2.8.5');
+ define('SASO_EVENTTICKETS_PLUGIN_VERSION', '2.8.6');
if (!defined('SASO_EVENTTICKETS_PLUGIN_DIR_PATH'))
define('SASO_EVENTTICKETS_PLUGIN_DIR_PATH', plugin_dir_path(__FILE__));
@@ -361,6 +361,7 @@
add_action( 'show_user_profile', [$this, 'show_user_profile'] );
add_action( 'admin_notices', [$this, 'showSubscriptionWarning'] );
add_action( 'admin_notices', [$this, 'showOutdatedPremiumWarning'] );
+ add_action( 'admin_notices', [$this, 'showFormatWarning'] );
if (basename($_SERVER['SCRIPT_NAME']) == "admin-ajax.php") {
add_action('wp_ajax_'.$this->_prefix.'_executeAdminSettings', [$this,'executeAdminSettings_a'], 10, 0);
@@ -1654,6 +1655,95 @@
echo '</p></div>';
}
}
+
+ /**
+ * Show admin notice when ticket format is running out of combinations
+ *
+ * Checks all ticket lists for format warnings and displays notice
+ *
+ * @return void
+ */
+ public function showFormatWarning(): void {
+ // Only show in admin
+ if (!is_admin()) {
+ return;
+ }
+
+ // Only show to users who can manage options
+ if (!current_user_can('manage_options')) {
+ return;
+ }
+
+ // Check if user wants to clear a warning
+ if (isset($_GET['saso_eventtickets_clear_format_warning']) && isset($_GET['saso_eventtickets_clear_warning_nonce'])) {
+ $list_id = intval($_GET['saso_eventtickets_clear_format_warning']);
+ $nonce = sanitize_text_field($_GET['saso_eventtickets_clear_warning_nonce']);
+
+ if (wp_verify_nonce($nonce, 'clear_format_warning_' . $list_id)) {
+ $this->getAdmin()->clearFormatWarning($list_id);
+ // Redirect to remove query params
+ wp_redirect(remove_query_arg(['saso_eventtickets_clear_format_warning', 'saso_eventtickets_clear_warning_nonce']));
+ exit;
+ }
+ }
+
+ try {
+ // Get all ticket lists
+ $lists = $this->getAdmin()->getLists([], false);
+
+ foreach ($lists as $list) {
+ $warning = $this->getAdmin()->getFormatWarning($list['id']);
+
+ if ($warning) {
+ $list_name = esc_html($warning['list_name']);
+ $attempts = intval($warning['attempts']);
+
+ if ($warning['type'] === 'critical') {
+ // Critical - format exhausted
+ $clear_url = wp_nonce_url(
+ add_query_arg(['saso_eventtickets_clear_format_warning' => $list['id']]),
+ 'clear_format_warning_' . $list['id']
+ );
+
+ echo '<div class="notice notice-error"><p>';
+ printf(
+ /* translators: 1: list name, 2: attempts, 3: clear URL */
+ 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'),
+ $list_name,
+ $attempts,
+ '<a href="' . esc_url(admin_url('admin.php?page=sasoEventTicketsAdminLists&act=edit&id=' . $list['id'])) . '">',
+ '</a>',
+ '<a href="' . esc_url($clear_url) . '">'
+ );
+ echo '</p></div>';
+ } else {
+ // Warning - running out
+ $clear_url = wp_nonce_url(
+ add_query_arg(['saso_eventtickets_clear_format_warning' => $list['id']]),
+ 'clear_format_warning_' . $list['id']
+ );
+
+ echo '<div class="notice notice-warning is-dismissible"><p>';
+ printf(
+ /* translators: 1: list name, 2: attempts, 3: clear URL */
+ 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'),
+ $list_name,
+ $attempts,
+ '<a href="' . esc_url(admin_url('admin.php?page=sasoEventTicketsAdminLists&act=edit&id=' . $list['id'])) . '">',
+ '</a>',
+ '<a href="' . esc_url($clear_url) . '">'
+ );
+ echo '</p></div>';
+ }
+
+ // Only show one warning at a time
+ break;
+ }
+ }
+ } catch (Exception $e) {
+ // Silently fail - don't break the admin
+ }
+ }
}
$sasoEventtickets = sasoEventtickets::Instance();
?>
--- a/event-tickets-with-ticket-scanner/sasoEventtickets_AdminSettings.php
+++ b/event-tickets-with-ticket-scanner/sasoEventtickets_AdminSettings.php
@@ -19,10 +19,28 @@
if (!$skipNonceTest) {
$nonce_mode = $this->MAIN->_js_nonce;
if (!wp_verify_nonce(SASO_EVENTTICKETS::getRequestPara('nonce'), $nonce_mode)) {
- if (!wp_verify_nonce(SASO_EVENTTICKETS::getRequestPara('nonce'), 'wp_rest')) { // coming from the ticket scanner - for now
- if ($just_ret) throw new Exception("Security check failed");
- return wp_send_json_error ("Security check failed");
- }
+ if ($just_ret) throw new Exception("Security check failed");
+ return wp_send_json_error ("Security check failed");
+ }
+ }
+
+ // Defense in depth: require manage_options for sensitive actions
+ $sensitive_actions = [
+ 'changeOption', 'resetOptions', 'deleteOptions',
+ 'emptyTableCodes', 'emptyTableLists', 'emptyTableErrorLogs',
+ 'removeCode', 'removeCodes', 'removeAllCodesFromList',
+ 'addAuthtoken', 'editAuthtoken', 'removeAuthtoken',
+ 'repairTables', 'expose_desctables', 'testing',
+ 'addList', 'editList', 'removeList',
+ 'addCode', 'addCodes', 'editCode',
+ 'removeWoocommerceOrderInfoFromCode', 'removeWoocommerceRstrPurchaseInfoFromCode',
+ 'removeUserRegistrationFromCode', 'removeUsedInformationFromCode',
+ 'removeUsedInformationFromCodeBulk', 'editTicketMetaEntry',
+ ];
+ if (in_array(trim($a), $sensitive_actions) && !current_user_can('manage_options')) {
+ if (!$this->MAIN->isUserAllowedToAccessAdminArea()) {
+ if ($just_ret) throw new Exception("Permission denied");
+ return wp_send_json_error("Permission denied", 403);
}
}
@@ -666,6 +684,19 @@
$metaObj = $this->_setMetaDataForList($data, $metaObj);
+ // Clear format warning when list is manually saved (user likely adjusted formatter)
+ if (!empty($metaObj['messages']['format_limit_threshold_warning']['last_email']) ||
+ !empty($metaObj['messages']['format_end_warning']['last_email'])) {
+ $metaObj['messages']['format_limit_threshold_warning'] = [
+ 'attempts' => 0,
+ 'last_email' => ''
+ ];
+ $metaObj['messages']['format_end_warning'] = [
+ 'attempts' => 0,
+ 'last_email' => ''
+ ];
+ }
+
if ($this->MAIN->isPremium() && method_exists($this->MAIN->getPremiumFunctions(), 'setFelderListEdit')) {
$felder = $this->MAIN->getPremiumFunctions()->setFelderListEdit($felder, $data, $listObj, $metaObj);
}
@@ -1161,10 +1192,16 @@
$data["code"] = $this->generateCode($formatterValues);
try {
$id = $this->addCode($data);
+ // Check if counter exceeded threshold (50% warning)
+ if ($counter > 50) {
+ $this->checkAndSaveFormatWarning($list_id, $counter, 'format_limit_threshold_warning');
+ }
break;
} catch(Exception $e) {
// code exists already, try a new one
if (substr($e->getMessage(), 0, 5) == "#208 ") { // no premium and limit exceeded
+ // Save critical warning and send email
+ $this->checkAndSaveFormatWarning($list_id, $counter, 'format_end_warning');
$data["code"] = $this->getOptionValue('wcassignmentTextNoCodePossible', __("Please contact our support for the ticket/code", 'event-tickets-with-ticket-scanner'));
return $data["code"];
}
@@ -2270,5 +2307,153 @@
$this->MAIN->getDB()->update("codes", ['semaphorecode'=>"", "order_id"=>0], ["code"=>$code]);
*/
}
+
+ /**
+ * Check if format warning should be saved based on counter threshold
+ * @param int $list_id Ticket list ID
+ * @param int $counter Number of attempts needed to generate code
+ * @param string $warningType 'format_limit_threshold_warning' for 50%, 'format_end_warning' for 100%
+ * @return void
+ */
+ private function checkAndSaveFormatWarning($list_id, $counter, $warningType = 'format_limit_threshold_warning') {
+ try {
+ $listObj = $this->getList(['id' => $list_id]);
+ $metaObj = $this->MAIN->getCore()->encodeMetaValuesAndFillObjectList($listObj['meta']);
+
+ // Check if email already sent today
+ $lastEmail = $metaObj['messages'][$warningType]['last_email'] ?? '';
+ $shouldSendEmail = empty($lastEmail);
+
+ if (!empty($lastEmail)) {
+ $lastEmailTime = strtotime($lastEmail);
+ $timeDiff = (time() - $lastEmailTime) / 3600; // hours
+ if ($timeDiff >= 24) {
+ $shouldSendEmail = true;
+ }
+ }
+
+ // Update warning data
+ $metaObj['messages'][$warningType]['attempts'] = max($counter, $metaObj['messages'][$warningType]['attempts']);
+
+ if ($shouldSendEmail) {
+ $metaObj['messages'][$warningType]['last_email'] = wp_date("Y-m-d H:i:s");
+ // Save to meta
+ $this->editList($list_id, ['meta' => $this->MAIN->getCore()->_json_encode_with_error_handling($metaObj)]);
+ // Send email
+ $severity = ($warningType === 'format_end_warning') ? 'critical' : 'warning';
+ $this->sendFormatWarningEmail($list_id, $severity, $counter);
+ } else {
+ // Just save the updated attempts count
+ $this->editList($list_id, ['meta' => $this->MAIN->getCore()->_json_encode_with_error_handling($metaObj)]);
+ }
+
+ } catch (Exception $e) {
+ // Log error but don't throw
+ $this->logErrorToDB($e, "", "checkAndSaveFormatWarning for list $list_id");
+ }
+ }
+
+ /**
+ * Send email to admin about format exhaustion
+ * @param int $list_id Ticket list ID
+ * @param string $severity 'warning' or 'critical'
+ * @param int $counter Number of attempts
+ * @return void
+ */
+ private function sendFormatWarningEmail($list_id, $severity, $counter) {
+ try {
+ $listObj = $this->getList(['id' => $list_id]);
+ $admin_email = get_option('admin_email');
+ $blog_name = get_bloginfo('name');
+ $site_url = site_url();
+
+ $subject = '';
+ $message = '';
+
+ if ($severity === 'critical') {
+ $subject = sprintf(__('[%s] CRITICAL: Ticket format exhausted for "%s"', 'event-tickets-with-ticket-scanner'), $blog_name, $listObj['name']);
+ $message = sprintf(
+ __("WARNING: The ticket number format for list "%s" is exhausted!nnIt took %d attempts to generate a ticket code.nnActions:n1. Go to: %sn2. Edit the ticket listn3. Increase code length or change character setnnWithout action, future ticket sales may fail!", 'event-tickets-with-ticket-scanner'),
+ $listObj['name'],
+ $counter,
+ admin_url('admin.php?page=sasoEventTicketsAdminLists&act=edit&id=' . $list_id)
+ );
+ } else {
+ $subject = sprintf(__('[%s] WARNING: Ticket format running out for "%s"', 'event-tickets-with-ticket-scanner'), $blog_name, $listObj['name']);
+ $message = sprintf(
+ __("WARNING: The ticket number format for list "%s" is running out of combinations!nnIt took %d attempts to generate a ticket code.nnRecommended action:n1. Go to: %sn2. Edit the ticket listn3. Consider increasing code length or character setnnThis is a proactive warning to prevent future issues.", 'event-tickets-with-ticket-scanner'),
+ $listObj['name'],
+ $counter,
+ admin_url('admin.php?page=sasoEventTicketsAdminLists&act=edit&id=' . $list_id)
+ );
+ }
+
+ $headers = ['Content-Type: text/plain; charset=UTF-8'];
+ wp_mail($admin_email, $subject, $message, $headers);
+
+ } catch (Exception $e) {
+ $this->logErrorToDB($e, "", "sendFormatWarningEmail for list $list_id");
+ }
+ }
+
+ /**
+ * Get format warning from ticket list meta
+ * @param int $list_id Ticket list ID
+ * @return array|null ['type'=>'...', 'attempts'=>...] or null
+ */
+ public function getFormatWarning($list_id) {
+ try {
+ $listObj = $this->getList(['id' => $list_id]);
+ $metaObj = $this->MAIN->getCore()->encodeMetaValuesAndFillObjectList($listObj['meta']);
+
+ // Check for critical warning first
+ if (!empty($metaObj['messages']['format_end_warning']['last_email'])) {
+ return [
+ 'type' => 'critical',
+ 'attempts' => $metaObj['messages']['format_end_warning']['attempts'],
+ 'list_name' => $listObj['name']
+ ];
+ }
+
+ // Check for threshold warning
+ if (!empty($metaObj['messages']['format_limit_threshold_warning']['last_email'])) {
+ return [
+ 'type' => 'warning',
+ 'attempts' => $metaObj['messages']['format_limit_threshold_warning']['attempts'],
+ 'list_name' => $listObj['name']
+ ];
+ }
+
+ return null;
+ } catch (Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Clear format warning from ticket list meta
+ * @param int $list_id Ticket list ID
+ * @return void
+ */
+ public function clearFormatWarning($list_id) {
+ try {
+ $listObj = $this->getList(['id' => $list_id]);
+ $metaObj = $this->MAIN->getCore()->encodeMetaValuesAndFillObjectList($listObj['meta']);
+
+ // Reset warning data
+ $metaObj['messages']['format_limit_threshold_warning'] = [
+ 'attempts' => 0,
+ 'last_email' => ''
+ ];
+ $metaObj['messages']['format_end_warning'] = [
+ 'attempts' => 0,
+ 'last_email' => ''
+ ];
+
+ $this->editList($list_id, ['meta' => $this->MAIN->getCore()->_json_encode_with_error_handling($metaObj)]);
+ } catch (Exception $e) {
+ $this->logErrorToDB($e, "", "clearFormatWarning for list $list_id");
+ }
+ }
}
?>
No newline at end of file
--- a/event-tickets-with-ticket-scanner/sasoEventtickets_Core.php
+++ b/event-tickets-with-ticket-scanner/sasoEventtickets_Core.php
@@ -300,6 +300,16 @@
],
'webhooks'=>[
'webhookURLaddwcticketsold'=>''
+ ],
+ 'messages'=>[
+ 'format_limit_threshold_warning'=>[
+ 'attempts'=>0,
+ 'last_email'=>''
+ ],
+ 'format_end_warning'=>[
+ 'attempts'=>0,
+ 'last_email'=>''
+ ]
]
];
if ($this->MAIN->isPremium() && method_exists($this->MAIN->getPremiumFunctions(), 'getMetaObjectList')) {
--- a/event-tickets-with-ticket-scanner/sasoEventtickets_Options.php
+++ b/event-tickets-with-ticket-scanner/sasoEventtickets_Options.php
@@ -332,6 +332,8 @@
$options[] = ['key'=>'wcTicketTemplateUseDefault', 'label'=>__("Use the default template for the ticket", 'event-tickets-with-ticket-scanner'), 'desc'=>__("If active, then the ticket template code will not be used. Best for beginners, who do not want to adjust the ticket template code. If the ticket template code is empty, then it will also use the default template code.", 'event-tickets-with-ticket-scanner'), 'type'=>"checkbox", 'def'=>"", '_doc_video'=>'https://youtu.be/sV1L2MJtq8M'];
$options[] = ['key'=>'h16_desc', 'label'=>__('The plugin is using the Twig template engine (3.22.0). This is a well documented tempklate engine that gives you a great freedom.<br><a target="_blank" href="https://twig.symfony.com/doc/3.x/">Open Documentation of Twig</a>', 'event-tickets-with-ticket-scanner'), 'desc'=>"You can use the following variables:<ul><li>PRODUCT</li><li>PRODUCT_PARENT</li><li>PRODUCT_ORIGINAL (in case you use WPML plugin, might be helpful - all the event tickets settings are on the original product)</li><li>PRODUCT_PARENT_ORIGINAL (in case you use WPML plugin, might be helpful - all the event tickets settings are on the original parent product - for variant/variable product)</li><li>OPTIONS</li><li>TICKET</li><li>ORDER</li><li>ORDER_ITEM</li><li>CODEOBJ</li><li>METAOBJ</li><li>LISTOBJ</li><li>LIST_METAOBJ</li><li>is_variation</li><li>forPDFOutput</li><li>isScanner</li><li>WPDB</li></ul>ACF support: you can use the function get_field to retrieve an ACF field value. You need to provide the product_id. e.g. {{ get_field('some_value', PRODUCT_PARENT.get_id)|escape }} or {{ get_field('some_value', PRODUCT_PARENT.get_id)|escape('wp_kses_post')|raw }} and so on.", 'type'=>"desc"];
$options[] = ['key'=>'wcTicketPDFZeroMargin', 'label'=>__("Do not use padding within the PDF ticket", 'event-tickets-with-ticket-scanner'), 'desc'=>__("If active, then the PDF content will start directly from the beginning of the paper. You need to add your own padding and margin within the template.", 'event-tickets-with-ticket-scanner'), 'type'=>"checkbox", 'def'=>"", '_doc_video'=>'https://youtu.be/2Ek2qkjHNAY'];
+ $options[] = ['key'=>'wcTicketPDFFullBleed', 'label'=>__("Full bleed mode (no margins at all)", 'event-tickets-with-ticket-scanner'), 'desc'=>__("If active, removes ALL margins, paddings and cell spacings from the PDF. Use this for edge-to-edge background images. Requires 'Do not use padding' to be active. Warning: This may affect existing ticket designs!", 'event-tickets-with-ticket-scanner'), 'type'=>"checkbox", 'def'=>""];
+ $options[] = ['key'=>'wcTicketPDFBackgroundColor', 'label'=>__("Ticket background color", 'event-tickets-with-ticket-scanner'), 'desc'=>__("This color will be used as the background color for the ticket PDF. Useful when you don't have a background image or as a fallback. Leave empty or white (#FFFFFF) for no background color.", 'event-tickets-with-ticket-scanner'), 'type'=>"color", 'def'=>"#FFFFFF"];
$options[] = ['key'=>'wcTicketPDFisRTL', 'label'=>__("BETA Use RTL for PDF", 'event-tickets-with-ticket-scanner'), 'desc'=>__("This feature is in Beta. This means, good results are not guaranteed, still optimizing this. If active, the PDF will be generated with RTL option active.", 'event-tickets-with-ticket-scanner'), 'type'=>"checkbox", 'def'=>"", '_doc_video'=>'https://youtu.be/7xmNgRmcrH0'];
$options[] = ['key'=>'wcTicketSizeWidth', 'label'=>__('Size in mm for the width', 'event-tickets-with-ticket-scanner'), 'desc'=>__('Will be used to set the width of the PDF. If empty or zero or lower than 20, the default of 210 will be used.', 'event-tickets-with-ticket-scanner'), 'type'=>'number', 'def'=>210, "additional"=>["min"=>20], '_doc_video'=>'https://youtu.be/c2XtUY2l1OM'];
$options[] = ['key'=>'wcTicketSizeHeight', 'label'=>__('Size in mm for the height', 'event-tickets-with-ticket-scanner'), 'desc'=>__('Will be used to set the height of the PDF. If empty or zero or lower than 20, the default of 297 will be used.', 'event-tickets-with-ticket-scanner'), 'type'=>'number', 'def'=>297, "additional"=>["min"=>20], '_doc_video'=>'https://youtu.be/c2XtUY2l1OM'];
@@ -340,6 +342,8 @@
$options[] = ['key'=>'h16a', 'label'=>__("Ticket Designer Test", 'event-tickets-with-ticket-scanner'), 'desc'=>"", 'type'=>"heading"];
$options[] = ['key'=>'wcTicketPDFZeroMarginTest', 'label'=>__("Do not use padding within the <b>test</b> PDF ticket", 'event-tickets-with-ticket-scanner'), 'desc'=>__("If active, then the PDF content will start directly from the beginning of the paper. You need to add your own padding and margin within the template.", 'event-tickets-with-ticket-scanner'), 'type'=>"checkbox", 'def'=>"", '_doc_video'=>'https://youtu.be/jewIPLsu5nw'];
+ $options[] = ['key'=>'wcTicketPDFFullBleedTest', 'label'=>__("Full bleed mode for <b>test</b> (no margins at all)", 'event-tickets-with-ticket-scanner'), 'desc'=>__("If active, removes ALL margins, paddings and cell spacings from the test PDF. Use this for edge-to-edge background images.", 'event-tickets-with-ticket-scanner'), 'type'=>"checkbox", 'def'=>""];
+ $options[] = ['key'=>'wcTicketPDFBackgroundColorTest', 'label'=>__("Ticket background color for <b>test</b>", 'event-tickets-with-ticket-scanner'), 'desc'=>__("This color will be used as the background color for the test ticket PDF. Useful when you don't have a background image.", 'event-tickets-with-ticket-scanner'), 'type'=>"color", 'def'=>"#FFFFFF"];
$options[] = ['key'=>'wcTicketPDFisRTLTest', 'label'=>__("BETA Use RTL for PDF <b>test</b>", 'event-tickets-with-ticket-scanner'), 'desc'=>__("This feature is in Beta. This means, good results are not guaranteed, still optimizing this. If active, the PDF will be generated with RTL option active.", 'event-tickets-with-ticket-scanner'), 'type'=>"checkbox", 'def'=>""];
$options[] = ['key'=>'wcTicketSizeWidthTest', 'label'=>__('Size in mm for the width of the <b>test</b>', 'event-tickets-with-ticket-scanner'), 'desc'=>__('Will be used to set the width of the PDF. If empty or zero, the default of 80 will be used.', 'event-tickets-with-ticket-scanner'), 'type'=>'number', 'def'=>210, "additional"=>["min"=>20], '_doc_video'=>'https://youtu.be/ylgo0rvn9SA'];
$options[] = ['key'=>'wcTicketSizeHeightTest', 'label'=>__('Size in mm for the height of the <b>test</b>', 'event-tickets-with-ticket-scanner'), 'desc'=>__('Will be used to set the height of the PDF. If empty or zero, the default of 120 will be used.', 'event-tickets-with-ticket-scanner'), 'type'=>'number', 'def'=>297, "additional"=>["min"=>20], '_doc_video'=>'https://youtu.be/ylgo0rvn9SA'];
--- a/event-tickets-with-ticket-scanner/sasoEventtickets_PDF.php
+++ b/event-tickets-with-ticket-scanner/sasoEventtickets_PDF.php
@@ -11,6 +11,7 @@
private $isRTL = false;
private $languageArray = null;
private $background_image = null;
+ private $background_color = null;
private $fontSize = 10;
private $fontFamily = "dejavusans";
@@ -18,6 +19,7 @@
private $size_width = 210;
private $size_height = 297;
public $marginsZero = false;
+ public $fullBleed = false;
private $attach_pdfs = [];
@@ -196,6 +198,18 @@
$this->background_image = $background_image;
}
+ /**
+ * Set the background color for the ticket PDF.
+ * @param string|null $color Hex color value (e.g., '#FF0000' or 'FF0000')
+ */
+ public function setBackgroundColor(?string $color = null): void {
+ if ($color !== null && $color !== '' && strtoupper($color) !== '#FFFFFF' && strtoupper($color) !== 'FFFFFF') {
+ $this->background_color = ltrim($color, '#');
+ } else {
+ $this->background_color = null;
+ }
+ }
+
public function setFontSize($number=10) {
$this->fontSize = intval($number);
}
@@ -415,6 +429,14 @@
if ($this->marginsZero) {
$pdf->SetMargins(0, 0, 0);
}
+ // Full bleed mode: remove ALL margins, paddings and spacings for edge-to-edge printing
+ if ($this->fullBleed) {
+ $pdf->SetMargins(0, 0, 0);
+ $pdf->SetAutoPageBreak(false, 0);
+ $pdf->SetCellPadding(0);
+ $pdf->SetHeaderMargin(0);
+ $pdf->SetFooterMargin(0);
+ }
//$pdf->SetMargins(PDF_MARGIN_LEFT, 17, 10);
//$pdf->SetHeaderMargin(10);
//$pdf->SetFooterMargin(10);
@@ -439,6 +461,21 @@
// Print text using writeHTMLCell()
$pdf->AddPage();
+ // Full bleed: start at exact position 0,0
+ if ($this->fullBleed) {
+ $pdf->SetXY(0, 0);
+ }
+
+ // background color (fills entire page as fallback/base)
+ if ($this->background_color !== null) {
+ $hex = $this->background_color;
+ $r = hexdec(substr($hex, 0, 2));
+ $g = hexdec(substr($hex, 2, 2));
+ $b = hexdec(substr($hex, 4, 2));
+ $pdf->SetFillColor($r, $g, $b);
+ $pdf->Rect(0, 0, $this->size_width, $this->size_height, 'F');
+ }
+
// background image
if ($this->background_image != null) {
//$w_image = $this->orientation == "L" ? $this->size_height : $this->size_width;
--- a/event-tickets-with-ticket-scanner/sasoEventtickets_Ticket.php
+++ b/event-tickets-with-ticket-scanner/sasoEventtickets_Ticket.php
@@ -245,24 +245,38 @@
}
function rest_permission_callback(WP_REST_Request $web_request) {
- // check ip brute force attack?????
-
$ret = false;
- // check if request contains authtoken var
+
+ // Path 1: Authtoken authentication (for scanner team without WP accounts)
if ($web_request->has_param($this->MAIN->getAuthtokenHandler()::$authtoken_param)) {
$authHandler = $this->MAIN->getAuthtokenHandler();
$this->authtoken = $web_request->get_param($authHandler::$authtoken_param);
$ret = $authHandler->checkAccessForAuthtoken($this->authtoken);
} else {
- $allowed_role = $this->MAIN->getOptions()->getOptionValue('wcTicketScannerAllowedRoles');
- if (!$this->onlyLoggedInScannerAllowed && $allowed_role == "-") return true;
+ // Path 2: WordPress user authentication (must be logged in)
+ if (!is_user_logged_in()) return false;
+
$user = wp_get_current_user();
$user_roles = (array) $user->roles;
- if ($this->onlyLoggedInScannerAllowed && in_array("administrator", $user_roles)) return true;
- if ($allowed_role != "-") {
- if (in_array($allowed_role, $user_roles)) $ret = true;
+
+ // Administrators always have access
+ if (in_array("administrator", $user_roles)) return true;
+
+ if ($this->onlyLoggedInScannerAllowed) {
+ // Strict mode: only administrators (already checked above)
+ $ret = false;
+ } else {
+ $allowed_role = $this->MAIN->getOptions()->getOptionValue('wcTicketScannerAllowedRoles');
+ if ($allowed_role == "-") {
+ // No specific role required - any logged-in user can access
+ $ret = true;
+ } else {
+ // Specific role required
+ $ret = in_array($allowed_role, $user_roles);
+ }
}
}
+
$ret = apply_filters( $this->MAIN->_add_filter_prefix.'ticket_rest_permission_callback', $ret, $web_request );
return $ret;
}
@@ -1667,6 +1681,23 @@
}
$pdf->marginsZero = $marginZero;
+ // Full bleed mode for edge-to-edge printing
+ $fullBleed = false;
+ if ($ticket_template != null) {
+ $fullBleed = $ticket_template['metaObj']['wcTicketPDFFullBleed'] == true || intval($ticket_template['metaObj']['wcTicketPDFFullBleed']) == 1;
+ } else {
+ if (SASO_EVENTTICKETS::issetRPara('testDesigner')) {
+ if ($this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketPDFFullBleedTest')) {
+ $fullBleed = true;
+ }
+ } else {
+ if ($this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketPDFFullBleed')) {
+ $fullBleed = true;
+ }
+ }
+ }
+ $pdf->fullBleed = $fullBleed;
+
$width = 210;
$height = 297;
$qr_code_size = 0; // takes default then
@@ -1807,6 +1838,15 @@
}
}
+ // Background color (as fallback when no image or to fill gaps)
+ $bgColorOption = SASO_EVENTTICKETS::issetRPara('testDesigner')
+ ? 'wcTicketPDFBackgroundColorTest'
+ : 'wcTicketPDFBackgroundColor';
+ $wcTicketPDFBackgroundColor = $this->MAIN->getOptions()->getOptionValue($bgColorOption);
+ if (!empty($wcTicketPDFBackgroundColor)) {
+ $pdf->setBackgroundColor($wcTicketPDFBackgroundColor);
+ }
+
$wcTicketTicketAttachPDFOnTicket = $this->MAIN->getAdmin()->getOptionValue('wcTicketTicketAttachPDFOnTicket');
if (!empty($wcTicketTicketAttachPDFOnTicket)) {
$mediaData = SASO_EVENTTICKETS::getMediaData($wcTicketTicketAttachPDFOnTicket);
--- a/event-tickets-with-ticket-scanner/sasoEventtickets_TicketBadge.php
+++ b/event-tickets-with-ticket-scanner/sasoEventtickets_TicketBadge.php
@@ -202,6 +202,13 @@
}
$product_id = intval($metaObj['woocommerce']['product_id']);
+
+ // Background color (as fallback when no image or to fill gaps)
+ $wcTicketPDFBackgroundColor = $this->MAIN->getOptions()->getOptionValue('wcTicketPDFBackgroundColor');
+ if (!empty($wcTicketPDFBackgroundColor)) {
+ $pdf->setBackgroundColor($wcTicketPDFBackgroundColor);
+ }
+
$wcTicketBadgeBG = $this->MAIN->getAdmin()->getOptionValue('wcTicketBadgeBG');
$wcTicketBadgeBG = apply_filters( $this->MAIN->_add_filter_prefix.'wcTicketBadgeBG', $wcTicketBadgeBG, $product_id);
if (!empty($wcTicketBadgeBG) && intval($wcTicketBadgeBG) >0) {
@@ -217,7 +224,6 @@
}
}
-
$pdf->addPart($html);
$qrTicketPDFPadding = intval($this->MAIN->getOptions()->getOptionValue('qrTicketPDFPadding'));
$pdf->setQRCodeContent(["text"=>$qr_content, "style"=>["vpadding"=>$qrTicketPDFPadding, "hpadding"=>$qrTicketPDFPadding]]);