api = $api; $this->events = $events; $this->orders = $orders; add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); add_action('admin_notices', array($this, 'admin_notices')); // Only add admin action handler if not doing plugin operations if (!defined('WP_UNINSTALL_PLUGIN') && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('activate', 'deactivate', 'delete', 'activate-selected', 'deactivate-selected')))) { add_action('admin_init', array($this, 'handle_admin_actions')); } add_action('wp_dashboard_setup', array($this, 'add_dashboard_widgets')); // Hook to wp_head for frontend styles with higher priority add_action('wp_head', array($this, 'output_custom_styles'), 999); // Also hook to admin_head for preview in admin add_action('admin_head', array($this, 'output_custom_styles'), 999); // SECURITY FIX: Add security headers for admin pages add_action('admin_init', array($this, 'add_security_headers')); // Custom admin footer text add_filter('admin_footer_text', array($this, 'custom_admin_footer_text')); add_filter('update_footer', array($this, 'custom_admin_footer_version'), 11); } /** * Add security headers - SECURITY ENHANCEMENT: Added CSP */ public function add_security_headers() { if (is_admin()) { header('X-Frame-Options: SAMEORIGIN'); header('X-Content-Type-Options: nosniff'); header('X-XSS-Protection: 1; mode=block'); header('Referrer-Policy: strict-origin-when-cross-origin'); // SECURITY ENHANCEMENT: Content Security Policy $csp = array( "default-src 'self'", "script-src 'self' https://cdn.tickettailor.com 'unsafe-inline' 'unsafe-eval'", "style-src 'self' 'unsafe-inline'", "img-src 'self' https: data:", "font-src 'self' data:", "connect-src 'self' https://api.tickettailor.com", "frame-src 'self' https://www.tickettailor.com https://tickettailor.com", "object-src 'none'", "base-uri 'self'", "form-action 'self'", "frame-ancestors 'self'", ); header('Content-Security-Policy: ' . implode('; ', $csp)); // Also add report-only CSP for monitoring $csp_report = array_merge($csp, array("report-uri /wp-admin/admin-ajax.php?action=tt_csp_report")); header('Content-Security-Policy-Report-Only: ' . implode('; ', $csp_report)); } } /** * Add admin menu pages - SECURITY ENHANCEMENT: Capability-based access */ public function add_admin_menu() { // Main menu add_menu_page( __('Ticket Tailor', 'ticket-tailor'), __('Ticket Tailor', 'ticket-tailor'), 'view_ticket_tailor_dashboard', 'ticket-tailor', array($this, 'render_dashboard'), 'dashicons-tickets-alt', 30 ); // Dashboard (rename main menu item) add_submenu_page( 'ticket-tailor', __('Dashboard', 'ticket-tailor'), __('Dashboard', 'ticket-tailor'), 'view_ticket_tailor_dashboard', 'ticket-tailor', array($this, 'render_dashboard') ); // Events add_submenu_page( 'ticket-tailor', __('Events', 'ticket-tailor'), __('Events', 'ticket-tailor'), 'view_ticket_tailor_events', 'ticket-tailor-events', array($this, 'render_events_page') ); // Orders add_submenu_page( 'ticket-tailor', __('Orders', 'ticket-tailor'), __('Orders', 'ticket-tailor'), 'view_ticket_tailor_orders', 'ticket-tailor-orders', array($this, 'render_orders_page') ); // Settings add_submenu_page( 'ticket-tailor', __('Settings', 'ticket-tailor'), __('Settings', 'ticket-tailor'), 'configure_ticket_tailor_settings', 'ticket-tailor-settings', array($this, 'render_settings_page') ); // Help add_submenu_page( 'ticket-tailor', __('Help', 'ticket-tailor'), __('Help', 'ticket-tailor'), 'view_ticket_tailor_dashboard', 'ticket-tailor-help', array($this, 'render_help_page') ); } /** * Enqueue admin assets */ public function enqueue_admin_assets($hook) { // Only load on our admin pages if (strpos($hook, 'ticket-tailor') === false) { return; } wp_enqueue_style( 'ticket-tailor-admin', TICKET_TAILOR_PLUGIN_URL . 'assets/css/admin.css', array(), TICKET_TAILOR_VERSION ); // Enqueue colour picker on settings page if (strpos($hook, 'ticket-tailor-settings') !== false) { wp_enqueue_style('wp-color-picker'); wp_enqueue_script('wp-color-picker'); } wp_enqueue_script( 'ticket-tailor-admin', TICKET_TAILOR_PLUGIN_URL . 'assets/js/admin.js', array('jquery', 'wp-color-picker'), TICKET_TAILOR_VERSION, true ); wp_localize_script('ticket-tailor-admin', 'ticketTailorAdmin', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ticket_tailor_admin'), )); } /** * Output custom styles to frontend and admin - SECURITY FIX: Validate colors */ public function output_custom_styles() { // Get saved colour settings - SECURITY FIX: Validate before output $border_colour = $this->validate_hex_color(get_option('ticket_tailor_border_color', '#e1e1e1'), '#e1e1e1'); $border_radius = absint(get_option('ticket_tailor_border_radius', '10')); $button_bg = $this->validate_hex_color(get_option('ticket_tailor_button_bg', '#2271b1'), '#2271b1'); $button_hover = $this->validate_hex_color(get_option('ticket_tailor_button_hover', '#135e96'), '#135e96'); $button_text = $this->validate_hex_color(get_option('ticket_tailor_button_text', '#ffffff'), '#ffffff'); $text_colour = $this->validate_hex_color(get_option('ticket_tailor_text_color', '#333333'), '#333333'); // Additional validation for border radius if ($border_radius < 0) $border_radius = 0; if ($border_radius > 50) $border_radius = 50; ?> api->is_configured() && $this->is_ticket_tailor_page()) { ?>


' . esc_html__('settings', 'ticket-tailor') . '' ); ?>


' . esc_html__('API key', 'ticket-tailor') . '' ); ?>

403, 'back_link' => true, ) ); } switch ($action) { case 'sync_events': $result = $this->events->sync_all_events(); if (is_wp_error($result)) { $this->add_notice('error', $result->get_error_message()); } else { $this->add_notice('success', sprintf( /* translators: %d: number of events synced */ __('Successfully synced %d events', 'ticket-tailor'), count($result) )); } wp_safe_redirect(remove_query_arg(array('action', '_wpnonce'))); exit; case 'sync_orders': $result = $this->orders->sync_all_orders(); if (is_wp_error($result)) { $this->add_notice('error', $result->get_error_message()); } else { $this->add_notice('success', sprintf( /* translators: %d: number of orders synced */ __('Successfully synced %d orders', 'ticket-tailor'), count($result) )); } wp_safe_redirect(remove_query_arg(array('action', '_wpnonce'))); exit; case 'clear_cache': delete_transient('ticket_tailor_events_cache'); delete_transient('ticket_tailor_orders_cache'); $this->add_notice('success', __('Cache cleared successfully', 'ticket-tailor')); wp_safe_redirect(remove_query_arg(array('action', '_wpnonce'))); exit; } } /** * Add dashboard widgets */ public function add_dashboard_widgets() { wp_add_dashboard_widget( 'ticket_tailor_summary', __('Ticket Tailor Summary', 'ticket-tailor'), array($this, 'render_dashboard_widget') ); } /** * Render dashboard widget */ public function render_dashboard_widget() { $events = $this->events->get_upcoming_events(5); $orders = $this->orders->get_recent_orders(5); ?>

api->is_configured()) { ?>

' . esc_html__('settings', 'ticket-tailor') . '' ); ?>

events->get_event_statistics(); $order_stats = $this->orders->get_order_statistics(); $total_events = $event_stats['total_events'] ?? 0; $upcoming_count = $event_stats['upcoming_events'] ?? 0; $total_orders = $order_stats['total_orders'] ?? 0; $total_revenue = $order_stats['total_revenue'] ?? 0; ?>
format_amount($total_revenue)); ?>

events->get_upcoming_events(5); if (is_wp_error($upcoming_events) || empty($upcoming_events)) { echo '

' . esc_html__('No upcoming events found.', 'ticket-tailor') . '

'; } else { ?>
events->get_events(); ?>


render_stored_notices(); ?>

get_error_message()); ?>


orders->get_orders($args); $events = $this->events->get_events(); ?>


render_stored_notices(); ?>

get_error_message()); ?>

#
format_amount($order['total'] ?? 0)); ?>
save_general_settings(); } elseif ($current_tab === 'style') { $this->save_style_settings(); } } // Handle cache clearing if (isset($_POST['ticket_tailor_clear_cache'])) { check_admin_referer('ticket_tailor_clear_cache'); delete_transient('ticket_tailor_events_cache'); delete_transient('ticket_tailor_orders_cache'); add_settings_error('ticket_tailor', 'cache_cleared', __('Cache cleared successfully!', 'ticket-tailor'), 'success'); } // Get current settings $api_key = get_option('ticket_tailor_api_key', ''); $cache_duration = get_option('ticket_tailor_cache_duration', 3600); $currency = get_option('ticket_tailor_currency', 'USD'); $debug_mode = get_option('ticket_tailor_debug_mode', false); $webhook_url = ticket_tailor()->webhooks->get_webhook_url(); // Style settings - including the new text colour (keeping database option names for compatibility) $text_colour = get_option('ticket_tailor_text_color', '#333333'); $border_colour = get_option('ticket_tailor_border_color', '#e1e1e1'); $border_radius = get_option('ticket_tailor_border_radius', '10'); $button_bg = get_option('ticket_tailor_button_bg', '#2271b1'); $button_hover = get_option('ticket_tailor_button_hover', '#135e96'); $button_text = get_option('ticket_tailor_button_text', '#ffffff'); // Get WooCommerce status $wc_status = ticket_tailor()->get_woocommerce_status(); ?>


' . esc_html__('Ticket Tailor API Settings', 'ticket-tailor') . '' ); ?>


>

px


📅

📍


api->test_connection(); if (!is_wp_error($test)) { // Try to sync events automatically $sync_result = $this->events->sync_all_events(); if (!is_wp_error($sync_result) && !empty($sync_result)) { add_settings_error('ticket_tailor', 'settings_saved', sprintf( /* translators: %d: number of events synced */ __('Settings saved and successfully synced %d events!', 'ticket-tailor'), count($sync_result) ), 'success' ); } else { if (is_wp_error($sync_result)) { add_settings_error('ticket_tailor', 'settings_saved', __('Settings saved and API connection successful, but event sync failed. Try the "Sync Events" button.', 'ticket-tailor'), 'warning' ); } else { add_settings_error('ticket_tailor', 'settings_saved', __('Settings saved and API connection successful, but no events found.', 'ticket-tailor'), 'warning' ); } } } else { add_settings_error('ticket_tailor', 'settings_saved', __('Settings saved but API connection failed. Please verify your API key.', 'ticket-tailor'), 'warning' ); } } else { add_settings_error('ticket_tailor', 'settings_saved', __('Settings saved.', 'ticket-tailor'), 'success'); } } /** * Save style settings - SECURITY FIX: Added strict hex color validation */ private function save_style_settings() { // Sanitize and save colour values - SECURITY FIX: Strict validation $text_colour = isset($_POST['text_colour']) ? $this->validate_hex_color($_POST['text_colour'], '#333333') : '#333333'; $border_colour = isset($_POST['border_colour']) ? $this->validate_hex_color($_POST['border_colour'], '#e1e1e1') : '#e1e1e1'; $border_radius = isset($_POST['border_radius']) ? absint($_POST['border_radius']) : 10; $button_bg = isset($_POST['button_bg']) ? $this->validate_hex_color($_POST['button_bg'], '#2271b1') : '#2271b1'; $button_hover = isset($_POST['button_hover']) ? $this->validate_hex_color($_POST['button_hover'], '#135e96') : '#135e96'; $button_text = isset($_POST['button_text']) ? $this->validate_hex_color($_POST['button_text'], '#ffffff') : '#ffffff'; // Validate border radius range if ($border_radius < 0) $border_radius = 0; if ($border_radius > 50) $border_radius = 50; // Save all settings (keeping database option names for backward compatibility) update_option('ticket_tailor_text_color', $text_colour); update_option('ticket_tailor_border_color', $border_colour); update_option('ticket_tailor_border_radius', $border_radius); update_option('ticket_tailor_button_bg', $button_bg); update_option('ticket_tailor_button_hover', $button_hover); update_option('ticket_tailor_button_text', $button_text); add_settings_error('ticket_tailor', 'settings_saved', __('Style settings saved successfully!', 'ticket-tailor'), 'success'); // Clear any caches that might prevent styles from updating if (function_exists('wp_cache_flush')) { wp_cache_flush(); } } /** * Validate hex color - SECURITY FIX: Strict hex color validation */ private function validate_hex_color($color, $default = '#000000') { // Remove any whitespace $color = trim($color); // Must start with # and be 4 or 7 characters (#RGB or #RRGGBB) if (preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $color)) { return $color; } // Invalid color, return default return $default; } /** * Render help page */ public function render_help_page() { ?>

[tt-widget url="YOUR_EVENT_URL"]

[tt-events limit="10" layout="grid" columns="3"]

[tt-event id="EVENT_ID"]

'$', 'GBP' => '£', 'EUR' => '€', 'CAD' => 'C$', 'AUD' => 'A$', ); $symbol = isset($symbols[$currency]) ? $symbols[$currency] : '$'; return $symbol . number_format($amount, 2); } /** * Check if we're on a Ticket Tailor admin page */ private function is_ticket_tailor_page() { $screen = get_current_screen(); return ($screen && strpos($screen->id, 'ticket-tailor') !== false); } /** * Add admin notice */ private function add_notice($type, $message) { set_transient('ticket_tailor_notice_' . get_current_user_id(), array( 'type' => $type, 'message' => $message ), 30); } /** * Render stored notices */ private function render_stored_notices() { $notice = get_transient('ticket_tailor_notice_' . get_current_user_id()); if ($notice) { ?>

is_ticket_tailor_page()) { $footer_text = sprintf( __('Ticket Tailor v3.1 by %s', 'ticket-tailor'), 'SSP Media' ); } return $footer_text; } /** * Customize admin footer version */ public function custom_admin_footer_version($footer_version) { // Only change on Ticket Tailor admin pages if ($this->is_ticket_tailor_page()) { $footer_version = ''; // Remove version text on the right side } return $footer_version; } } ?>