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(); ?>
orders->get_orders($args);
$events = $this->events->get_events();
?>
render_stored_notices(); ?>
|
|
|
|
|
|
|
| # |
|
|
|
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();
?>
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;
}
}
?>