added additional plugins
This commit is contained in:
parent
c85895d306
commit
00e60ec1b7
132 changed files with 27514 additions and 0 deletions
|
|
@ -0,0 +1,516 @@
|
|||
<?php
|
||||
/**
|
||||
* Ticket Tailor Event Manager
|
||||
*
|
||||
* Handles event caching, syncing, and retrieval
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Ticket_Tailor_Event_Manager {
|
||||
|
||||
/**
|
||||
* API Client
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
private $table;
|
||||
|
||||
/**
|
||||
* Cache duration (in seconds)
|
||||
*/
|
||||
private $cache_duration;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($api) {
|
||||
global $wpdb;
|
||||
|
||||
$this->api = $api;
|
||||
$this->table = $wpdb->prefix . 'ticket_tailor_events';
|
||||
$this->cache_duration = get_option('ticket_tailor_cache_duration', 3600); // 1 hour default
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all events (from cache or API) - PERFORMANCE FIX: Added limit parameter
|
||||
*/
|
||||
public function get_events($force_refresh = false, $limit = null) {
|
||||
if ($force_refresh) {
|
||||
return $this->sync_all_events();
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// Check if cache is still valid
|
||||
$cache_valid_time = gmdate('Y-m-d H:i:s', time() - $this->cache_duration);
|
||||
|
||||
// Build query with optional limit
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT event_data FROM {$this->table} WHERE last_synced > %s ORDER BY last_synced DESC",
|
||||
$cache_valid_time
|
||||
);
|
||||
|
||||
// PERFORMANCE FIX: Add LIMIT if specified
|
||||
if ($limit !== null && is_numeric($limit)) {
|
||||
$query .= $wpdb->prepare(" LIMIT %d", absint($limit));
|
||||
}
|
||||
|
||||
$cached_events = $wpdb->get_results($query);
|
||||
|
||||
if (!empty($cached_events)) {
|
||||
$events = array();
|
||||
foreach ($cached_events as $row) {
|
||||
$event_data = json_decode($row->event_data, true);
|
||||
if ($event_data) {
|
||||
$events[] = $event_data;
|
||||
}
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
// Cache is expired or empty, fetch from API
|
||||
$all_events = $this->sync_all_events();
|
||||
|
||||
// Apply limit to synced events if specified
|
||||
if ($limit !== null && is_array($all_events)) {
|
||||
return array_slice($all_events, 0, absint($limit));
|
||||
}
|
||||
|
||||
return $all_events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single event
|
||||
*/
|
||||
public function get_event($event_id, $force_refresh = false) {
|
||||
$event_id = sanitize_text_field($event_id);
|
||||
|
||||
if (!$force_refresh) {
|
||||
global $wpdb;
|
||||
|
||||
$cache_valid_time = gmdate('Y-m-d H:i:s', time() - $this->cache_duration);
|
||||
|
||||
$cached = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT event_data FROM {$this->table} WHERE event_id = %s AND last_synced > %s",
|
||||
$event_id,
|
||||
$cache_valid_time
|
||||
)
|
||||
);
|
||||
|
||||
if ($cached) {
|
||||
$event_data = json_decode($cached, true);
|
||||
if ($event_data) {
|
||||
return $event_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
$event = $this->api->get_event($event_id);
|
||||
|
||||
if (is_wp_error($event)) {
|
||||
return $event;
|
||||
}
|
||||
|
||||
// Cache it
|
||||
$this->cache_event($event);
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all events from API - ENTERPRISE: Race condition protection
|
||||
*/
|
||||
public function sync_all_events() {
|
||||
if (!$this->api->is_configured()) {
|
||||
return new WP_Error('no_api_key', __('API key not configured', 'ticket-tailor'));
|
||||
}
|
||||
|
||||
// ENTERPRISE: Acquire lock to prevent concurrent syncs
|
||||
$lock_key = 'ticket_tailor_sync_events_lock';
|
||||
$lock_acquired = $this->acquire_sync_lock($lock_key);
|
||||
|
||||
if (!$lock_acquired) {
|
||||
return new WP_Error(
|
||||
'sync_in_progress',
|
||||
__('Event sync already in progress. Please wait.', 'ticket-tailor'),
|
||||
array('lock_key' => $lock_key)
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$events = $this->api->get_all_paginated('events');
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
$this->release_sync_lock($lock_key);
|
||||
return $events;
|
||||
}
|
||||
|
||||
// ENTERPRISE: Use transaction for atomic cache updates
|
||||
global $wpdb;
|
||||
$wpdb->query('START TRANSACTION');
|
||||
|
||||
try {
|
||||
// Cache all events
|
||||
$cached_count = 0;
|
||||
foreach ($events as $event) {
|
||||
if ($this->cache_event($event)) {
|
||||
$cached_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update last sync time
|
||||
update_option('ticket_tailor_last_event_sync', time());
|
||||
|
||||
$wpdb->query('COMMIT');
|
||||
|
||||
error_log(sprintf(
|
||||
'Ticket Tailor: Successfully synced %d events (%d cached)',
|
||||
count($events),
|
||||
$cached_count
|
||||
));
|
||||
|
||||
} catch (Exception $e) {
|
||||
$wpdb->query('ROLLBACK');
|
||||
$this->release_sync_lock($lock_key);
|
||||
|
||||
return new WP_Error(
|
||||
'sync_transaction_failed',
|
||||
sprintf('Event sync transaction failed: %s', $e->getMessage())
|
||||
);
|
||||
}
|
||||
|
||||
$this->release_sync_lock($lock_key);
|
||||
return $events;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->release_sync_lock($lock_key);
|
||||
|
||||
return new WP_Error(
|
||||
'sync_failed',
|
||||
sprintf('Event sync failed: %s', $e->getMessage())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire sync lock - ENTERPRISE: Prevent concurrent syncs
|
||||
*/
|
||||
private function acquire_sync_lock($lock_key, $timeout = 300) {
|
||||
// Try to set transient with 5-minute expiry
|
||||
// If it already exists, returns false
|
||||
$acquired = set_transient($lock_key, time(), $timeout);
|
||||
|
||||
if (!$acquired) {
|
||||
// Check if lock is stale (older than timeout)
|
||||
$lock_time = get_transient($lock_key);
|
||||
if ($lock_time && (time() - $lock_time) > $timeout) {
|
||||
// Stale lock, force release and reacquire
|
||||
delete_transient($lock_key);
|
||||
return set_transient($lock_key, time(), $timeout);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release sync lock - ENTERPRISE
|
||||
*/
|
||||
private function release_sync_lock($lock_key) {
|
||||
return delete_transient($lock_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache a single event
|
||||
*/
|
||||
private function cache_event($event) {
|
||||
if (empty($event['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$event_id = sanitize_text_field($event['id']);
|
||||
$event_data = wp_json_encode($event);
|
||||
|
||||
// Check if exists
|
||||
$exists = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT id FROM {$this->table} WHERE event_id = %s",
|
||||
$event_id
|
||||
)
|
||||
);
|
||||
|
||||
if ($exists) {
|
||||
// Update existing
|
||||
return $wpdb->update(
|
||||
$this->table,
|
||||
array(
|
||||
'event_data' => $event_data,
|
||||
'last_synced' => current_time('mysql', 1),
|
||||
),
|
||||
array('event_id' => $event_id),
|
||||
array('%s', '%s'),
|
||||
array('%s')
|
||||
);
|
||||
} else {
|
||||
// Insert new
|
||||
return $wpdb->insert(
|
||||
$this->table,
|
||||
array(
|
||||
'event_id' => $event_id,
|
||||
'event_data' => $event_data,
|
||||
'last_synced' => current_time('mysql', 1),
|
||||
),
|
||||
array('%s', '%s', '%s')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upcoming events
|
||||
*/
|
||||
public function get_upcoming_events($limit = 10) {
|
||||
$events = $this->get_events();
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
return $events;
|
||||
}
|
||||
|
||||
// Filter and sort by start date
|
||||
$upcoming = array_filter($events, function($event) {
|
||||
if (!isset($event['start']['iso'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start_time = strtotime($event['start']['iso']);
|
||||
return $start_time >= time();
|
||||
});
|
||||
|
||||
// Sort by start date
|
||||
usort($upcoming, function($a, $b) {
|
||||
$time_a = strtotime($a['start']['iso'] ?? 0);
|
||||
$time_b = strtotime($b['start']['iso'] ?? 0);
|
||||
return $time_a - $time_b;
|
||||
});
|
||||
|
||||
return array_slice($upcoming, 0, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get past events
|
||||
*/
|
||||
public function get_past_events($limit = 10) {
|
||||
$events = $this->get_events();
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
return $events;
|
||||
}
|
||||
|
||||
// Filter and sort by start date
|
||||
$past = array_filter($events, function($event) {
|
||||
if (!isset($event['start']['iso'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start_time = strtotime($event['start']['iso']);
|
||||
return $start_time < time();
|
||||
});
|
||||
|
||||
// Sort by start date (newest first)
|
||||
usort($past, function($a, $b) {
|
||||
$time_a = strtotime($a['start']['iso'] ?? 0);
|
||||
$time_b = strtotime($b['start']['iso'] ?? 0);
|
||||
return $time_b - $time_a;
|
||||
});
|
||||
|
||||
return array_slice($past, 0, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search events
|
||||
*/
|
||||
public function search_events($search_term) {
|
||||
$events = $this->get_events();
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
return $events;
|
||||
}
|
||||
|
||||
$search_term = strtolower($search_term);
|
||||
|
||||
return array_filter($events, function($event) use ($search_term) {
|
||||
$name = strtolower($event['name'] ?? '');
|
||||
$description = strtolower($event['description'] ?? '');
|
||||
|
||||
return strpos($name, $search_term) !== false ||
|
||||
strpos($description, $search_term) !== false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get events by category
|
||||
*/
|
||||
public function get_events_by_category($category) {
|
||||
$events = $this->get_events();
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
return $events;
|
||||
}
|
||||
|
||||
$category = strtolower($category);
|
||||
|
||||
return array_filter($events, function($event) use ($category) {
|
||||
$event_category = strtolower($event['category'] ?? '');
|
||||
return $event_category === $category;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event ticket types
|
||||
*/
|
||||
public function get_event_tickets($event_id) {
|
||||
$event_id = sanitize_text_field($event_id);
|
||||
|
||||
// Try to get from transient first
|
||||
$cache_key = 'tt_tickets_' . $event_id;
|
||||
$cached = get_transient($cache_key);
|
||||
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
$tickets = $this->api->get_ticket_types($event_id);
|
||||
|
||||
if (is_wp_error($tickets)) {
|
||||
return $tickets;
|
||||
}
|
||||
|
||||
// Cache for 15 minutes (tickets change less frequently)
|
||||
set_transient($cache_key, $tickets, 900);
|
||||
|
||||
return $tickets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear event cache - SECURITY FIX: Use prepared statements
|
||||
*/
|
||||
public function clear_cache($event_id = null) {
|
||||
global $wpdb;
|
||||
|
||||
if ($event_id) {
|
||||
$event_id = sanitize_text_field($event_id);
|
||||
$wpdb->delete($this->table, array('event_id' => $event_id), array('%s'));
|
||||
|
||||
// Also clear ticket cache
|
||||
delete_transient('tt_tickets_' . $event_id);
|
||||
} else {
|
||||
// SECURITY FIX: Validate table name before TRUNCATE
|
||||
if (preg_match('/^[a-zA-Z0-9_]+$/', $this->table)) {
|
||||
$wpdb->query("TRUNCATE TABLE `{$this->table}`");
|
||||
}
|
||||
|
||||
// Clear all ticket transients - SECURITY FIX: Use prepared statement
|
||||
$wpdb->query($wpdb->prepare(
|
||||
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
|
||||
$wpdb->esc_like('_transient_tt_tickets_') . '%'
|
||||
));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
*/
|
||||
public function get_cache_stats() {
|
||||
global $wpdb;
|
||||
|
||||
$total_events = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table}");
|
||||
|
||||
$cache_valid_time = gmdate('Y-m-d H:i:s', time() - $this->cache_duration);
|
||||
$valid_events = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$this->table} WHERE last_synced > %s",
|
||||
$cache_valid_time
|
||||
)
|
||||
);
|
||||
|
||||
$last_sync = get_option('ticket_tailor_last_event_sync', 0);
|
||||
|
||||
return array(
|
||||
'total_cached' => (int) $total_events,
|
||||
'valid_cached' => (int) $valid_events,
|
||||
'expired_cached' => (int) $total_events - (int) $valid_events,
|
||||
'last_sync' => $last_sync,
|
||||
'cache_duration' => $this->cache_duration,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event statistics efficiently - PERFORMANCE OPTIMIZATION
|
||||
* Uses aggregated database queries instead of loading all events
|
||||
*/
|
||||
public function get_event_statistics() {
|
||||
global $wpdb;
|
||||
|
||||
$cache_key = 'tt_event_stats';
|
||||
$cached = wp_cache_get($cache_key);
|
||||
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$cache_valid_time = gmdate('Y-m-d H:i:s', time() - $this->cache_duration);
|
||||
|
||||
// Get total events count
|
||||
$total_events = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$this->table} WHERE last_synced > %s",
|
||||
$cache_valid_time
|
||||
)
|
||||
);
|
||||
|
||||
// Get upcoming events count (parse JSON to check dates)
|
||||
$all_events = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT event_data FROM {$this->table} WHERE last_synced > %s",
|
||||
$cache_valid_time
|
||||
)
|
||||
);
|
||||
|
||||
$upcoming_count = 0;
|
||||
$now = time();
|
||||
|
||||
foreach ($all_events as $row) {
|
||||
$event = json_decode($row->event_data, true);
|
||||
if (!empty($event['start']['iso'])) {
|
||||
$start_time = strtotime($event['start']['iso']);
|
||||
if ($start_time >= $now) {
|
||||
$upcoming_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stats = array(
|
||||
'total_events' => (int) $total_events,
|
||||
'upcoming_events' => $upcoming_count,
|
||||
);
|
||||
|
||||
// Cache for 5 minutes
|
||||
wp_cache_set($cache_key, $stats, '', 300);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue