monorepo/native/wordpress/ticket-tailor-wp-max/includes/class-event-manager.php

516 lines
15 KiB
PHP

<?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;
}
}