1436 lines
67 KiB
PHP
1436 lines
67 KiB
PHP
<?php
|
|
/**
|
|
* Ticket Tailor Admin
|
|
*
|
|
* Handles all admin interfaces and dashboard
|
|
* Version: 3.1 - Canadian/UK English Spelling
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class Ticket_Tailor_Admin {
|
|
|
|
/**
|
|
* API Client
|
|
*/
|
|
private $api;
|
|
|
|
/**
|
|
* Event Manager
|
|
*/
|
|
private $events;
|
|
|
|
/**
|
|
* Order Manager
|
|
*/
|
|
private $orders;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct($api, $events, $orders) {
|
|
$this->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;
|
|
|
|
?>
|
|
<style id="ticket-tailor-custom-styles" type="text/css">
|
|
/* Ticket Tailor Custom Styles - Applied with high specificity */
|
|
|
|
/* Card border and radius */
|
|
.tt-event-card,
|
|
.tt-event-listing .tt-event-card,
|
|
.tt-event-listing.tt-layout-grid .tt-event-card,
|
|
.tt-event-listing.tt-layout-list .tt-event-card {
|
|
border-color: <?php echo esc_attr($border_colour); ?> !important;
|
|
border-radius: <?php echo esc_attr($border_radius); ?>px !important;
|
|
overflow: hidden; /* Ensure border radius clips content */
|
|
}
|
|
|
|
/* Card image border radius (top corners) */
|
|
.tt-event-card .tt-event-image,
|
|
.tt-event-listing .tt-event-card .tt-event-image {
|
|
border-radius: <?php echo esc_attr($border_radius); ?>px <?php echo esc_attr($border_radius); ?>px 0 0 !important;
|
|
}
|
|
|
|
/* Text colour for event content */
|
|
.tt-event-card .tt-event-title,
|
|
.tt-event-card .tt-event-excerpt,
|
|
.tt-event-card .tt-event-description,
|
|
.tt-event-content h3,
|
|
.tt-event-content p {
|
|
color: <?php echo esc_attr($text_colour); ?> !important;
|
|
}
|
|
|
|
/* Button styles */
|
|
.tt-event-button,
|
|
.tt-button,
|
|
.tt-event-listing .tt-event-button,
|
|
.tt-event-card .tt-event-button,
|
|
a.tt-event-button {
|
|
background: <?php echo esc_attr($button_bg); ?> !important;
|
|
background-color: <?php echo esc_attr($button_bg); ?> !important;
|
|
color: <?php echo esc_attr($button_text); ?> !important;
|
|
border-color: <?php echo esc_attr($button_bg); ?> !important;
|
|
text-decoration: none !important;
|
|
}
|
|
|
|
/* Button hover states */
|
|
.tt-event-button:hover,
|
|
.tt-button:hover,
|
|
.tt-event-listing .tt-event-button:hover,
|
|
.tt-event-card .tt-event-button:hover,
|
|
a.tt-event-button:hover,
|
|
.tt-event-button:focus,
|
|
.tt-button:focus,
|
|
a.tt-event-button:focus {
|
|
background: <?php echo esc_attr($button_hover); ?> !important;
|
|
background-color: <?php echo esc_attr($button_hover); ?> !important;
|
|
color: <?php echo esc_attr($button_text); ?> !important;
|
|
border-color: <?php echo esc_attr($button_hover); ?> !important;
|
|
opacity: 1 !important;
|
|
}
|
|
|
|
/* Ensure button text colour persists */
|
|
.tt-event-button span,
|
|
.tt-button span,
|
|
a.tt-event-button span {
|
|
color: <?php echo esc_attr($button_text); ?> !important;
|
|
}
|
|
|
|
/* Widget button styles */
|
|
.tt-widget-container .tt-event-button,
|
|
.tt-widget-container .tt-button {
|
|
background: <?php echo esc_attr($button_bg); ?> !important;
|
|
color: <?php echo esc_attr($button_text); ?> !important;
|
|
}
|
|
|
|
.tt-widget-container .tt-event-button:hover,
|
|
.tt-widget-container .tt-button:hover {
|
|
background: <?php echo esc_attr($button_hover); ?> !important;
|
|
color: <?php echo esc_attr($button_text); ?> !important;
|
|
}
|
|
|
|
/* Single event page buttons */
|
|
.tt-single-event .tt-button,
|
|
.tt-single-event .tt-button-primary {
|
|
background: <?php echo esc_attr($button_bg); ?> !important;
|
|
color: <?php echo esc_attr($button_text); ?> !important;
|
|
}
|
|
|
|
.tt-single-event .tt-button:hover,
|
|
.tt-single-event .tt-button-primary:hover {
|
|
background: <?php echo esc_attr($button_hover); ?> !important;
|
|
color: <?php echo esc_attr($button_text); ?> !important;
|
|
}
|
|
</style>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Admin notices
|
|
*/
|
|
public function admin_notices() {
|
|
// API key not configured
|
|
if (!$this->api->is_configured() && $this->is_ticket_tailor_page()) {
|
|
?>
|
|
<div class="notice notice-warning">
|
|
<p>
|
|
<strong><?php esc_html_e('Ticket Tailor API Key Required', 'ticket-tailor'); ?></strong><br>
|
|
<?php
|
|
printf(
|
|
/* translators: %s: settings page URL */
|
|
esc_html__('Please configure your API key in the %s to start using Ticket Tailor.', 'ticket-tailor'),
|
|
'<a href="' . esc_url(admin_url('admin.php?page=ticket-tailor-settings')) . '">' . esc_html__('settings', 'ticket-tailor') . '</a>'
|
|
);
|
|
?>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
// Display welcome notice
|
|
if (get_transient('ticket_tailor_welcome_notice')) {
|
|
?>
|
|
<div class="notice notice-success is-dismissible">
|
|
<p>
|
|
<strong><?php esc_html_e('Welcome to Ticket Tailor!', 'ticket-tailor'); ?></strong><br>
|
|
<?php
|
|
printf(
|
|
/* translators: %s: settings page URL */
|
|
esc_html__('Get started by configuring your %s.', 'ticket-tailor'),
|
|
'<a href="' . esc_url(admin_url('admin.php?page=ticket-tailor-settings')) . '">' . esc_html__('API key', 'ticket-tailor') . '</a>'
|
|
);
|
|
?>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
delete_transient('ticket_tailor_welcome_notice');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle admin actions - FULLY FIXED VERSION
|
|
* No security errors during deactivation
|
|
*/
|
|
public function handle_admin_actions() {
|
|
// Extra safety check - should never run during plugin operations due to constructor check
|
|
if (defined('WP_UNINSTALL_PLUGIN') ||
|
|
(isset($_REQUEST['action']) && in_array($_REQUEST['action'], array('activate', 'deactivate', 'delete', 'activate-selected', 'deactivate-selected'))) ||
|
|
(isset($_REQUEST['action2']) && in_array($_REQUEST['action2'], array('activate', 'deactivate', 'delete', 'activate-selected', 'deactivate-selected')))) {
|
|
return;
|
|
}
|
|
|
|
// Check if we're on the plugins page - if so, exit early
|
|
global $pagenow;
|
|
if ($pagenow === 'plugins.php') {
|
|
return;
|
|
}
|
|
|
|
// Don't run on non-admin pages
|
|
if (!is_admin()) {
|
|
return;
|
|
}
|
|
|
|
// Only process our specific actions
|
|
if (!isset($_GET['action']) || !isset($_GET['page'])) {
|
|
return;
|
|
}
|
|
|
|
// Check if this is a Ticket Tailor page
|
|
if (strpos($_GET['page'], 'ticket-tailor') === false) {
|
|
return;
|
|
}
|
|
|
|
// Check user capabilities
|
|
if (!current_user_can('manage_options')) {
|
|
return;
|
|
}
|
|
|
|
$action = sanitize_key($_GET['action']);
|
|
|
|
// Only process our known actions
|
|
$valid_actions = array('sync_events', 'sync_orders', 'clear_cache');
|
|
if (!in_array($action, $valid_actions)) {
|
|
return;
|
|
}
|
|
|
|
// Verify nonce for security - but only for our specific actions
|
|
if (!isset($_GET['_wpnonce']) || !wp_verify_nonce($_GET['_wpnonce'], 'ticket_tailor_action')) {
|
|
wp_die(
|
|
esc_html__('Security check failed. Please try again.', 'ticket-tailor'),
|
|
esc_html__('Security Error', 'ticket-tailor'),
|
|
array(
|
|
'response' => 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);
|
|
|
|
?>
|
|
<div class="tt-dashboard-widget">
|
|
<h4><?php esc_html_e('Upcoming Events', 'ticket-tailor'); ?></h4>
|
|
<?php if (is_wp_error($events) || empty($events)) : ?>
|
|
<p><?php esc_html_e('No upcoming events found.', 'ticket-tailor'); ?></p>
|
|
<?php else : ?>
|
|
<ul>
|
|
<?php foreach ($events as $event) : ?>
|
|
<li>
|
|
<strong><?php echo esc_html($event['name']); ?></strong><br>
|
|
<span class="description">
|
|
<?php
|
|
if (!empty($event['start']['iso'])) {
|
|
echo esc_html(date_i18n(get_option('date_format'), strtotime($event['start']['iso'])));
|
|
}
|
|
?>
|
|
</span>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
|
|
<h4><?php esc_html_e('Recent Orders', 'ticket-tailor'); ?></h4>
|
|
<?php if (is_wp_error($orders) || empty($orders)) : ?>
|
|
<p><?php esc_html_e('No recent orders found.', 'ticket-tailor'); ?></p>
|
|
<?php else : ?>
|
|
<ul>
|
|
<?php foreach ($orders as $order) : ?>
|
|
<li>
|
|
<strong>#<?php echo esc_html($order['reference'] ?? 'N/A'); ?></strong><br>
|
|
<span class="description">
|
|
<?php echo esc_html($this->format_amount($order['total'] ?? 0)); ?>
|
|
</span>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
|
|
<p class="tt-dashboard-links">
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=ticket-tailor')); ?>" class="button">
|
|
<?php esc_html_e('View Full Dashboard', 'ticket-tailor'); ?>
|
|
</a>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render main dashboard - FIXED VERSION WITH CORRECT CARD STRUCTURE
|
|
*/
|
|
public function render_dashboard() {
|
|
?>
|
|
<div class="wrap ticket-tailor-admin">
|
|
<h1><?php esc_html_e('Ticket Tailor Dashboard', 'ticket-tailor'); ?></h1>
|
|
|
|
<?php
|
|
if (!$this->api->is_configured()) {
|
|
?>
|
|
<div class="notice notice-warning">
|
|
<p>
|
|
<?php
|
|
printf(
|
|
/* translators: %s: settings page link */
|
|
esc_html__('Please configure your API key in the %s to start using Ticket Tailor.', 'ticket-tailor'),
|
|
'<a href="' . esc_url(admin_url('admin.php?page=ticket-tailor-settings')) . '">' . esc_html__('settings', 'ticket-tailor') . '</a>'
|
|
);
|
|
?>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
return;
|
|
}
|
|
?>
|
|
|
|
<div class="tt-dashboard-grid">
|
|
<!-- Stats Cards - PERFORMANCE OPTIMIZED -->
|
|
<div class="tt-stats-grid">
|
|
<?php
|
|
// PERFORMANCE FIX: Use aggregated statistics instead of loading all data
|
|
$event_stats = $this->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;
|
|
?>
|
|
|
|
<div class="tt-stat-card">
|
|
<div class="tt-stat-value"><?php echo esc_html($total_events); ?></div>
|
|
<div class="tt-stat-label"><?php esc_html_e('Total Events', 'ticket-tailor'); ?></div>
|
|
</div>
|
|
|
|
<div class="tt-stat-card">
|
|
<div class="tt-stat-value"><?php echo esc_html($upcoming_count); ?></div>
|
|
<div class="tt-stat-label"><?php esc_html_e('Upcoming Events', 'ticket-tailor'); ?></div>
|
|
</div>
|
|
|
|
<div class="tt-stat-card">
|
|
<div class="tt-stat-value"><?php echo esc_html($total_orders); ?></div>
|
|
<div class="tt-stat-label"><?php esc_html_e('Total Orders', 'ticket-tailor'); ?></div>
|
|
</div>
|
|
|
|
<div class="tt-stat-card">
|
|
<div class="tt-stat-value"><?php echo esc_html($this->format_amount($total_revenue)); ?></div>
|
|
<div class="tt-stat-label"><?php esc_html_e('Total Revenue', 'ticket-tailor'); ?></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="tt-quick-actions">
|
|
<h2><?php esc_html_e('Quick Actions', 'ticket-tailor'); ?></h2>
|
|
<div class="button-group">
|
|
<a href="<?php echo esc_url(wp_nonce_url(add_query_arg('action', 'sync_events'), 'ticket_tailor_action')); ?>" class="button button-primary">
|
|
<?php esc_html_e('Sync Events', 'ticket-tailor'); ?>
|
|
</a>
|
|
<a href="<?php echo esc_url(wp_nonce_url(add_query_arg('action', 'sync_orders'), 'ticket_tailor_action')); ?>" class="button">
|
|
<?php esc_html_e('Sync Orders', 'ticket-tailor'); ?>
|
|
</a>
|
|
<a href="<?php echo esc_url(admin_url('admin.php?page=ticket-tailor-settings')); ?>" class="button">
|
|
<?php esc_html_e('Settings', 'ticket-tailor'); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Events -->
|
|
<div class="tt-dashboard-section">
|
|
<h2><?php esc_html_e('Upcoming Events', 'ticket-tailor'); ?></h2>
|
|
<?php
|
|
$upcoming_events = $this->events->get_upcoming_events(5);
|
|
if (is_wp_error($upcoming_events) || empty($upcoming_events)) {
|
|
echo '<p>' . esc_html__('No upcoming events found.', 'ticket-tailor') . '</p>';
|
|
} else {
|
|
?>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php esc_html_e('Event', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Date', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Venue', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Status', 'ticket-tailor'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($upcoming_events as $event) : ?>
|
|
<tr>
|
|
<td><strong><?php echo esc_html($event['name']); ?></strong></td>
|
|
<td>
|
|
<?php
|
|
if (!empty($event['start']['iso'])) {
|
|
echo esc_html(date_i18n(get_option('date_format'), strtotime($event['start']['iso'])));
|
|
}
|
|
?>
|
|
</td>
|
|
<td><?php echo esc_html($event['venue']['name'] ?? 'TBA'); ?></td>
|
|
<td>
|
|
<span class="tt-status-badge tt-status-<?php echo esc_attr($event['status'] ?? 'draft'); ?>">
|
|
<?php echo esc_html(ucfirst($event['status'] ?? 'draft')); ?>
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php
|
|
}
|
|
?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render events page
|
|
*/
|
|
public function render_events_page() {
|
|
$events = $this->events->get_events();
|
|
?>
|
|
<div class="wrap ticket-tailor-admin">
|
|
<h1 class="wp-heading-inline"><?php esc_html_e('Events', 'ticket-tailor'); ?></h1>
|
|
|
|
<a href="<?php echo esc_url(wp_nonce_url(add_query_arg('action', 'sync_events'), 'ticket_tailor_action')); ?>" class="page-title-action">
|
|
<?php esc_html_e('Sync Events', 'ticket-tailor'); ?>
|
|
</a>
|
|
|
|
<hr class="wp-header-end">
|
|
|
|
<?php $this->render_stored_notices(); ?>
|
|
|
|
<?php if (is_wp_error($events)) : ?>
|
|
<div class="notice notice-error">
|
|
<p><?php echo esc_html($events->get_error_message()); ?></p>
|
|
</div>
|
|
<?php elseif (empty($events)) : ?>
|
|
<p><?php esc_html_e('No events found. Try syncing with Ticket Tailor.', 'ticket-tailor'); ?></p>
|
|
<?php else : ?>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php esc_html_e('Event', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Date', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Venue', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Tickets Sold', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Status', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Actions', 'ticket-tailor'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($events as $event) : ?>
|
|
<tr>
|
|
<td>
|
|
<strong><?php echo esc_html($event['name']); ?></strong>
|
|
<?php if (!empty($event['description'])) : ?>
|
|
<br><span class="description">
|
|
<?php echo esc_html(wp_trim_words($event['description'], 10)); ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<?php
|
|
if (!empty($event['start']['iso'])) {
|
|
echo esc_html(date_i18n(
|
|
get_option('date_format') . ' ' . get_option('time_format'),
|
|
strtotime($event['start']['iso'])
|
|
));
|
|
}
|
|
?>
|
|
</td>
|
|
<td><?php echo esc_html($event['venue']['name'] ?? 'TBA'); ?></td>
|
|
<td><?php echo esc_html($event['tickets_sold'] ?? 0); ?></td>
|
|
<td>
|
|
<span class="tt-status-badge tt-status-<?php echo esc_attr($event['status'] ?? 'draft'); ?>">
|
|
<?php echo esc_html(ucfirst($event['status'] ?? 'draft')); ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<?php if (!empty($event['url'])) : ?>
|
|
<a href="<?php echo esc_url($event['url']); ?>" target="_blank" class="button button-small">
|
|
<?php esc_html_e('View', 'ticket-tailor'); ?>
|
|
</a>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render orders page
|
|
*/
|
|
public function render_orders_page() {
|
|
$event_filter = isset($_GET['event_id']) ? sanitize_text_field(wp_unslash($_GET['event_id'])) : '';
|
|
|
|
$args = array();
|
|
if ($event_filter) {
|
|
$args['event_id'] = $event_filter;
|
|
}
|
|
|
|
$orders = $this->orders->get_orders($args);
|
|
$events = $this->events->get_events();
|
|
?>
|
|
<div class="wrap ticket-tailor-admin">
|
|
<h1 class="wp-heading-inline"><?php esc_html_e('Orders', 'ticket-tailor'); ?></h1>
|
|
|
|
<a href="<?php echo esc_url(wp_nonce_url(add_query_arg('action', 'sync_orders'), 'ticket_tailor_action')); ?>" class="page-title-action">
|
|
<?php esc_html_e('Sync Orders', 'ticket-tailor'); ?>
|
|
</a>
|
|
|
|
<hr class="wp-header-end">
|
|
|
|
<?php $this->render_stored_notices(); ?>
|
|
|
|
<!-- Event Filter -->
|
|
<?php if (!is_wp_error($events) && !empty($events)) : ?>
|
|
<div class="tablenav top">
|
|
<form method="get" style="display: inline-block;">
|
|
<input type="hidden" name="page" value="ticket-tailor-orders">
|
|
<select name="event_id" onchange="this.form.submit()">
|
|
<option value=""><?php esc_html_e('All Events', 'ticket-tailor'); ?></option>
|
|
<?php foreach ($events as $event) : ?>
|
|
<option value="<?php echo esc_attr($event['id']); ?>" <?php selected($event_filter, $event['id']); ?>>
|
|
<?php echo esc_html($event['name']); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</form>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if (is_wp_error($orders)) : ?>
|
|
<div class="notice notice-error">
|
|
<p><?php echo esc_html($orders->get_error_message()); ?></p>
|
|
</div>
|
|
<?php elseif (empty($orders)) : ?>
|
|
<p><?php esc_html_e('No orders found. Orders will appear here once tickets are sold.', 'ticket-tailor'); ?></p>
|
|
<?php else : ?>
|
|
<table class="wp-list-table widefat fixed striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?php esc_html_e('Order #', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Customer', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Event', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Tickets', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Total', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Status', 'ticket-tailor'); ?></th>
|
|
<th><?php esc_html_e('Date', 'ticket-tailor'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($orders as $order) : ?>
|
|
<tr>
|
|
<td><strong>#<?php echo esc_html($order['reference'] ?? 'N/A'); ?></strong></td>
|
|
<td>
|
|
<?php echo esc_html($order['email'] ?? 'Guest'); ?>
|
|
<?php if (!empty($order['name'])) : ?>
|
|
<br><small><?php echo esc_html($order['name']); ?></small>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?php echo esc_html($order['event_name'] ?? 'Unknown Event'); ?></td>
|
|
<td><?php echo esc_html($order['quantity'] ?? 0); ?></td>
|
|
<td><?php echo esc_html($this->format_amount($order['total'] ?? 0)); ?></td>
|
|
<td>
|
|
<span class="tt-status-badge tt-status-<?php echo esc_attr($order['status'] ?? 'unknown'); ?>">
|
|
<?php echo esc_html($order['status'] ?? 'unknown'); ?>
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<?php
|
|
if (!empty($order['created_at'])) {
|
|
echo esc_html(date_i18n(get_option('date_format'), strtotime($order['created_at'])));
|
|
}
|
|
?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render settings page with tabs - ENHANCED VERSION
|
|
*/
|
|
public function render_settings_page() {
|
|
// Get current tab
|
|
$current_tab = isset($_GET['tab']) ? sanitize_key($_GET['tab']) : 'general';
|
|
|
|
// Handle form submission
|
|
if (isset($_POST['ticket_tailor_settings_submit'])) {
|
|
check_admin_referer('ticket_tailor_settings');
|
|
|
|
if ($current_tab === 'general') {
|
|
$this->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();
|
|
?>
|
|
<div class="wrap ticket-tailor-admin">
|
|
<h1><?php esc_html_e('Ticket Tailor Settings', 'ticket-tailor'); ?></h1>
|
|
|
|
<?php settings_errors('ticket_tailor'); ?>
|
|
|
|
<?php if ($wc_status['active']) : ?>
|
|
<div class="notice notice-info">
|
|
<p>
|
|
<strong><?php esc_html_e('WooCommerce Detected', 'ticket-tailor'); ?></strong><br>
|
|
<?php
|
|
printf(
|
|
/* translators: %s: WooCommerce version */
|
|
esc_html__('WooCommerce %s is active.', 'ticket-tailor'),
|
|
esc_html($wc_status['version'])
|
|
);
|
|
?>
|
|
<?php if ($wc_status['hpos_enabled']) : ?>
|
|
<span style="color: #46b450;">✓ <?php esc_html_e('High-Performance Order Storage (HPOS) is enabled and fully compatible.', 'ticket-tailor'); ?></span>
|
|
<?php else : ?>
|
|
<span><?php esc_html_e('Legacy order storage is active. This plugin is compatible with both modes.', 'ticket-tailor'); ?></span>
|
|
<?php endif; ?>
|
|
</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Tab Navigation -->
|
|
<h2 class="nav-tab-wrapper">
|
|
<a href="?page=ticket-tailor-settings&tab=general" class="nav-tab <?php echo $current_tab === 'general' ? 'nav-tab-active' : ''; ?>">
|
|
<?php esc_html_e('General Settings', 'ticket-tailor'); ?>
|
|
</a>
|
|
<a href="?page=ticket-tailor-settings&tab=style" class="nav-tab <?php echo $current_tab === 'style' ? 'nav-tab-active' : ''; ?>">
|
|
<?php esc_html_e('Style', 'ticket-tailor'); ?>
|
|
</a>
|
|
</h2>
|
|
|
|
<form method="post" action="">
|
|
<?php wp_nonce_field('ticket_tailor_settings'); ?>
|
|
|
|
<?php if ($current_tab === 'general') : ?>
|
|
<!-- General Settings Tab -->
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="api_key"><?php esc_html_e('API Key', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="text"
|
|
id="api_key"
|
|
name="api_key"
|
|
value="<?php echo esc_attr($api_key); ?>"
|
|
class="regular-text"
|
|
autocomplete="off">
|
|
<p class="description">
|
|
<?php
|
|
printf(
|
|
/* translators: %s: API settings URL */
|
|
esc_html__('Get your API key from %s', 'ticket-tailor'),
|
|
'<a href="https://app.tickettailor.com/box-office/api" target="_blank">' . esc_html__('Ticket Tailor API Settings', 'ticket-tailor') . '</a>'
|
|
);
|
|
?>
|
|
<br>
|
|
<strong><?php esc_html_e('Events will automatically sync when you save a new API key.', 'ticket-tailor'); ?></strong>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="cache_duration"><?php esc_html_e('Cache Duration', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="number"
|
|
id="cache_duration"
|
|
name="cache_duration"
|
|
value="<?php echo esc_attr($cache_duration); ?>"
|
|
min="0"
|
|
max="86400"
|
|
step="60"
|
|
class="small-text">
|
|
<span><?php esc_html_e('seconds', 'ticket-tailor'); ?></span>
|
|
<p class="description">
|
|
<?php esc_html_e('How long to cache event data (0 to disable caching).', 'ticket-tailor'); ?><br>
|
|
<?php esc_html_e('Suggested values: 3600 (1 hour), 7200 (2 hours), 21600 (6 hours)', 'ticket-tailor'); ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="currency"><?php esc_html_e('Currency', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<select id="currency" name="currency">
|
|
<option value="USD" <?php selected($currency, 'USD'); ?>>USD - US Dollar</option>
|
|
<option value="GBP" <?php selected($currency, 'GBP'); ?>>GBP - British Pound</option>
|
|
<option value="EUR" <?php selected($currency, 'EUR'); ?>>EUR - Euro</option>
|
|
<option value="CAD" <?php selected($currency, 'CAD'); ?>>CAD - Canadian Dollar</option>
|
|
<option value="AUD" <?php selected($currency, 'AUD'); ?>>AUD - Australian Dollar</option>
|
|
</select>
|
|
<p class="description"><?php esc_html_e('Default currency for displaying prices.', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="debug_mode"><?php esc_html_e('Debug Mode', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="checkbox"
|
|
id="debug_mode"
|
|
name="debug_mode"
|
|
value="1"
|
|
<?php checked($debug_mode, true); ?>>
|
|
<label for="debug_mode"><?php esc_html_e('Enable debug logging', 'ticket-tailor'); ?></label>
|
|
<p class="description"><?php esc_html_e('Log API requests and errors for troubleshooting.', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<?php esc_html_e('Webhook URL', 'ticket-tailor'); ?>
|
|
</th>
|
|
<td>
|
|
<code><?php echo esc_html($webhook_url); ?></code>
|
|
<p class="description">
|
|
<?php esc_html_e('Configure this URL in your Ticket Tailor webhook settings to receive real-time updates.', 'ticket-tailor'); ?>
|
|
<a href="https://help.tickettailor.com/en/articles/webhooks" target="_blank"><?php esc_html_e('Learn more', 'ticket-tailor'); ?></a>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<?php elseif ($current_tab === 'style') : ?>
|
|
<!-- ENHANCED Style Settings Tab with Text Colour and Live Preview -->
|
|
<table class="form-table">
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="text_colour"><?php esc_html_e('Text Colour', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="text"
|
|
id="text_colour"
|
|
name="text_colour"
|
|
value="<?php echo esc_attr($text_colour); ?>"
|
|
class="tt-colour-picker"
|
|
data-default-colour="#333333">
|
|
<p class="description"><?php esc_html_e('Choose the text colour for event titles and descriptions.', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="border_colour"><?php esc_html_e('Card Border Colour', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="text"
|
|
id="border_colour"
|
|
name="border_colour"
|
|
value="<?php echo esc_attr($border_colour); ?>"
|
|
class="tt-colour-picker"
|
|
data-default-colour="#e1e1e1">
|
|
<p class="description"><?php esc_html_e('Choose the border colour for event cards.', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="border_radius"><?php esc_html_e('Card Border Radius', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="number"
|
|
id="border_radius"
|
|
name="border_radius"
|
|
value="<?php echo esc_attr($border_radius); ?>"
|
|
min="0"
|
|
max="50"
|
|
class="small-text">
|
|
<span>px</span>
|
|
<p class="description"><?php esc_html_e('Border roundness for event cards (0-50px). Default: 10px', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="button_bg"><?php esc_html_e('Button Background Colour', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="text"
|
|
id="button_bg"
|
|
name="button_bg"
|
|
value="<?php echo esc_attr($button_bg); ?>"
|
|
class="tt-colour-picker"
|
|
data-default-colour="#2271b1">
|
|
<p class="description"><?php esc_html_e('Choose the background colour for "Get Tickets" buttons.', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="button_hover"><?php esc_html_e('Button Hover Colour', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="text"
|
|
id="button_hover"
|
|
name="button_hover"
|
|
value="<?php echo esc_attr($button_hover); ?>"
|
|
class="tt-colour-picker"
|
|
data-default-colour="#135e96">
|
|
<p class="description"><?php esc_html_e('Button colour when hovering. Usually darker than the main button colour.', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th scope="row">
|
|
<label for="button_text"><?php esc_html_e('Button Text Colour', 'ticket-tailor'); ?></label>
|
|
</th>
|
|
<td>
|
|
<input type="text"
|
|
id="button_text"
|
|
name="button_text"
|
|
value="<?php echo esc_attr($button_text); ?>"
|
|
class="tt-colour-picker"
|
|
data-default-colour="#ffffff">
|
|
<p class="description"><?php esc_html_e('Text colour for "Get Tickets" buttons.', 'ticket-tailor'); ?></p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<!-- Live Preview Section -->
|
|
<hr style="margin: 2rem 0;">
|
|
<h3><?php esc_html_e('Live Preview', 'ticket-tailor'); ?></h3>
|
|
<div style="background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 2rem;">
|
|
<div class="tt-preview-card" style="max-width: 350px; background: white; border: 1px solid <?php echo esc_attr($border_colour); ?>; border-radius: <?php echo esc_attr($border_radius); ?>px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
|
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); height: 180px; display: flex; align-items: center; justify-content: center; color: white; font-size: 14px; text-transform: uppercase; letter-spacing: 1px;">
|
|
<?php esc_html_e('Event Image Preview', 'ticket-tailor'); ?>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<h3 class="preview-title" style="color: <?php echo esc_attr($text_colour); ?>; margin: 0 0 10px; font-size: 1.25rem;"><?php esc_html_e('Sample Event Title', 'ticket-tailor'); ?></h3>
|
|
<p class="preview-description" style="color: <?php echo esc_attr($text_colour); ?>; opacity: 0.8; margin: 0 0 8px; font-size: 0.9rem;">
|
|
<span style="display: inline-block; margin-right: 5px;">📅</span>
|
|
<?php esc_html_e('January 15, 2025', 'ticket-tailor'); ?>
|
|
</p>
|
|
<p class="preview-venue" style="color: <?php echo esc_attr($text_colour); ?>; opacity: 0.8; margin: 0 0 15px; font-size: 0.9rem;">
|
|
<span style="display: inline-block; margin-right: 5px;">📍</span>
|
|
<?php esc_html_e('Event Venue, City', 'ticket-tailor'); ?>
|
|
</p>
|
|
<a href="#" onclick="return false;" class="tt-preview-button" style="display: inline-block; padding: 10px 20px; background: <?php echo esc_attr($button_bg); ?>; color: <?php echo esc_attr($button_text); ?>; text-decoration: none; border-radius: 4px; font-weight: 600; transition: all 0.3s ease;">
|
|
<?php esc_html_e('Get Tickets', 'ticket-tailor'); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
jQuery(document).ready(function($) {
|
|
// Initialize colour pickers with real-time preview
|
|
$('.tt-colour-picker').wpColorPicker({
|
|
change: function(event, ui) {
|
|
var colour = ui.color.toString();
|
|
var field = $(this).attr('id');
|
|
|
|
// Update preview in real-time
|
|
if (field === 'text_colour') {
|
|
$('.preview-title, .preview-description, .preview-venue').css('color', colour);
|
|
} else if (field === 'border_colour') {
|
|
$('.tt-preview-card').css('border-color', colour);
|
|
} else if (field === 'button_bg') {
|
|
$('.tt-preview-button').css('background-color', colour);
|
|
} else if (field === 'button_text') {
|
|
$('.tt-preview-button').css('color', colour);
|
|
}
|
|
},
|
|
clear: function() {
|
|
var field = $(this).attr('id');
|
|
var defaultColour = $(this).data('default-colour');
|
|
|
|
if (field === 'text_colour') {
|
|
$('.preview-title, .preview-description, .preview-venue').css('color', defaultColour);
|
|
} else if (field === 'border_colour') {
|
|
$('.tt-preview-card').css('border-color', defaultColour);
|
|
} else if (field === 'button_bg') {
|
|
$('.tt-preview-button').css('background-color', defaultColour);
|
|
} else if (field === 'button_text') {
|
|
$('.tt-preview-button').css('color', defaultColour);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Update border radius preview in real-time
|
|
$('#border_radius').on('input', function() {
|
|
var radius = $(this).val();
|
|
$('.tt-preview-card').css('border-radius', radius + 'px');
|
|
});
|
|
|
|
// Button hover effect for preview
|
|
var originalBg = '<?php echo esc_js($button_bg); ?>';
|
|
var hoverBg = '<?php echo esc_js($button_hover); ?>';
|
|
|
|
$('.tt-preview-button').hover(
|
|
function() {
|
|
var currentHover = $('#button_hover').val() || hoverBg;
|
|
$(this).css('background-color', currentHover);
|
|
},
|
|
function() {
|
|
var currentBg = $('#button_bg').val() || originalBg;
|
|
$(this).css('background-color', currentBg);
|
|
}
|
|
);
|
|
|
|
// Update hover colour when changed
|
|
$('#button_hover').on('change', function() {
|
|
hoverBg = $(this).val();
|
|
});
|
|
|
|
$('#button_bg').on('change', function() {
|
|
originalBg = $(this).val();
|
|
});
|
|
});
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<?php submit_button(__('Save Settings', 'ticket-tailor'), 'primary', 'ticket_tailor_settings_submit'); ?>
|
|
</form>
|
|
|
|
<?php if ($current_tab === 'general') : ?>
|
|
<hr>
|
|
|
|
<h2><?php esc_html_e('Cache Management', 'ticket-tailor'); ?></h2>
|
|
<p><?php esc_html_e('Clear cached data to force a fresh sync from Ticket Tailor.', 'ticket-tailor'); ?></p>
|
|
|
|
<form method="post" action="">
|
|
<?php wp_nonce_field('ticket_tailor_clear_cache'); ?>
|
|
<input type="hidden" name="action" value="clear_cache">
|
|
<?php submit_button(__('Clear Cache', 'ticket-tailor'), 'secondary', 'ticket_tailor_clear_cache', false); ?>
|
|
</form>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Save general settings
|
|
*/
|
|
private function save_general_settings() {
|
|
$api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : '';
|
|
$cache_duration = isset($_POST['cache_duration']) ? absint($_POST['cache_duration']) : 3600;
|
|
$currency = isset($_POST['currency']) ? sanitize_text_field(wp_unslash($_POST['currency'])) : 'USD';
|
|
$debug_mode = isset($_POST['debug_mode']) ? true : false;
|
|
|
|
// Check if API key changed
|
|
$old_api_key = get_option('ticket_tailor_api_key', '');
|
|
$api_key_changed = ($old_api_key !== $api_key);
|
|
|
|
update_option('ticket_tailor_api_key', $api_key);
|
|
update_option('ticket_tailor_cache_duration', $cache_duration);
|
|
update_option('ticket_tailor_currency', $currency);
|
|
update_option('ticket_tailor_debug_mode', $debug_mode);
|
|
|
|
// If API key changed and not empty, test connection and sync
|
|
if ($api_key_changed && !empty($api_key)) {
|
|
// Test API connection
|
|
$test = $this->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() {
|
|
?>
|
|
<div class="wrap ticket-tailor-admin">
|
|
<h1><?php esc_html_e('Ticket Tailor Help', 'ticket-tailor'); ?></h1>
|
|
|
|
<div class="tt-help-grid">
|
|
<div class="tt-help-section">
|
|
<h2><?php esc_html_e('Getting Started', 'ticket-tailor'); ?></h2>
|
|
<ol>
|
|
<li><?php esc_html_e('Get your API key from Ticket Tailor', 'ticket-tailor'); ?></li>
|
|
<li><?php esc_html_e('Enter it in the Settings page', 'ticket-tailor'); ?></li>
|
|
<li><?php esc_html_e('Click "Sync Events" to import your events', 'ticket-tailor'); ?></li>
|
|
<li><?php esc_html_e('Use shortcodes or blocks to display events', 'ticket-tailor'); ?></li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="tt-help-section">
|
|
<h2><?php esc_html_e('Shortcodes', 'ticket-tailor'); ?></h2>
|
|
|
|
<h3><?php esc_html_e('Event Widget', 'ticket-tailor'); ?></h3>
|
|
<code>[tt-widget url="YOUR_EVENT_URL"]</code>
|
|
|
|
<h3><?php esc_html_e('Event Listing', 'ticket-tailor'); ?></h3>
|
|
<code>[tt-events limit="10" layout="grid" columns="3"]</code>
|
|
|
|
<h3><?php esc_html_e('Single Event', 'ticket-tailor'); ?></h3>
|
|
<code>[tt-event id="EVENT_ID"]</code>
|
|
</div>
|
|
|
|
<div class="tt-help-section">
|
|
<h2><?php esc_html_e('Webhooks', 'ticket-tailor'); ?></h2>
|
|
<p><?php esc_html_e('For real-time updates, configure webhooks in Ticket Tailor:', 'ticket-tailor'); ?></p>
|
|
<ol>
|
|
<li><?php esc_html_e('Go to your Ticket Tailor settings', 'ticket-tailor'); ?></li>
|
|
<li><?php esc_html_e('Add a new webhook', 'ticket-tailor'); ?></li>
|
|
<li><?php esc_html_e('Use the URL shown in Settings', 'ticket-tailor'); ?></li>
|
|
<li><?php esc_html_e('Select events to monitor', 'ticket-tailor'); ?></li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="tt-help-section">
|
|
<h2><?php esc_html_e('Support', 'ticket-tailor'); ?></h2>
|
|
<p><?php esc_html_e('Need help? Here are some resources:', 'ticket-tailor'); ?></p>
|
|
<ul>
|
|
<li><a href="https://help.tickettailor.com" target="_blank"><?php esc_html_e('Ticket Tailor Help Center', 'ticket-tailor'); ?></a></li>
|
|
<li><a href="https://wordpress.org/support/plugin/ticket-tailor" target="_blank"><?php esc_html_e('WordPress Support Forum', 'ticket-tailor'); ?></a></li>
|
|
<li><a href="mailto:support@tickettailor.com"><?php esc_html_e('Email Support', 'ticket-tailor'); ?></a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Helper method to format amounts
|
|
*/
|
|
private function format_amount($amount) {
|
|
$currency = get_option('ticket_tailor_currency', 'USD');
|
|
$symbols = array(
|
|
'USD' => '$',
|
|
'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) {
|
|
?>
|
|
<div class="notice notice-<?php echo esc_attr($notice['type']); ?> is-dismissible">
|
|
<p><?php echo esc_html($notice['message']); ?></p>
|
|
</div>
|
|
<?php
|
|
delete_transient('ticket_tailor_notice_' . get_current_user_id());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Customize admin footer text
|
|
*/
|
|
public function custom_admin_footer_text($footer_text) {
|
|
// Only change on Ticket Tailor admin pages
|
|
if ($this->is_ticket_tailor_page()) {
|
|
$footer_text = sprintf(
|
|
__('Ticket Tailor v3.1 by %s', 'ticket-tailor'),
|
|
'<a href="https://sspmedia.ca/wordpress/" target="_blank">SSP Media</a>'
|
|
);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
?>
|