added additional plugins

This commit is contained in:
Rodolfo Martinez 2025-12-12 19:05:48 -05:00
parent c85895d306
commit 00e60ec1b7
132 changed files with 27514 additions and 0 deletions

View file

@ -0,0 +1,490 @@
<?php
/**
* Ticket Tailor Order Manager
*
* Handles order caching, syncing, and retrieval
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
class Ticket_Tailor_Order_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_orders';
$this->cache_duration = get_option('ticket_tailor_cache_duration', 3600); // 1 hour default
}
/**
* Get all orders - PERFORMANCE FIX: Improved query building
*/
public function get_orders($args = array(), $force_refresh = false) {
if ($force_refresh) {
return $this->sync_all_orders();
}
global $wpdb;
$cache_valid_time = gmdate('Y-m-d H:i:s', time() - $this->cache_duration);
$query = "SELECT order_data FROM {$this->table} WHERE last_synced > %s";
$query_args = array($cache_valid_time);
// Filter by event if specified
if (!empty($args['event_id'])) {
$query .= " AND event_id = %s";
$query_args[] = sanitize_text_field($args['event_id']);
}
$query .= " ORDER BY last_synced DESC";
// PERFORMANCE FIX: Always apply a reasonable default limit if not specified
if (!empty($args['limit'])) {
$query .= " LIMIT %d";
$query_args[] = absint($args['limit']);
} else {
// Default limit of 1000 to prevent loading unlimited orders
$query .= " LIMIT 1000";
}
$cached_orders = $wpdb->get_results(
$wpdb->prepare($query, $query_args)
);
if (!empty($cached_orders)) {
$orders = array();
foreach ($cached_orders as $row) {
$order_data = json_decode($row->order_data, true);
if ($order_data) {
$orders[] = $order_data;
}
}
return $orders;
}
// Cache is expired or empty, fetch from API
$all_orders = $this->sync_all_orders();
// Apply limit to synced orders if specified
if (!empty($args['limit']) && is_array($all_orders)) {
return array_slice($all_orders, 0, absint($args['limit']));
}
// Apply default limit
if (is_array($all_orders)) {
return array_slice($all_orders, 0, 1000);
}
return $all_orders;
}
/**
* Get single order
*/
public function get_order($order_id, $force_refresh = false) {
$order_id = sanitize_text_field($order_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 order_data FROM {$this->table} WHERE order_id = %s AND last_synced > %s",
$order_id,
$cache_valid_time
)
);
if ($cached) {
$order_data = json_decode($cached, true);
if ($order_data) {
return $order_data;
}
}
}
// Fetch from API
$order = $this->api->get_order($order_id);
if (is_wp_error($order)) {
return $order;
}
// Cache it
$this->cache_order($order);
return $order;
}
/**
* Sync all orders from API - ENTERPRISE: Race condition protection
*/
public function sync_all_orders() {
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_orders_lock';
$lock_acquired = $this->acquire_sync_lock($lock_key);
if (!$lock_acquired) {
return new WP_Error(
'sync_in_progress',
__('Order sync already in progress. Please wait.', 'ticket-tailor'),
array('lock_key' => $lock_key)
);
}
try {
$orders = $this->api->get_all_paginated('orders');
if (is_wp_error($orders)) {
$this->release_sync_lock($lock_key);
return $orders;
}
// ENTERPRISE: Use transaction for atomic cache updates
global $wpdb;
$wpdb->query('START TRANSACTION');
try {
// Cache all orders
$cached_count = 0;
foreach ($orders as $order) {
if ($this->cache_order($order)) {
$cached_count++;
}
}
// Update last sync time
update_option('ticket_tailor_last_order_sync', time());
$wpdb->query('COMMIT');
error_log(sprintf(
'Ticket Tailor: Successfully synced %d orders (%d cached)',
count($orders),
$cached_count
));
} catch (Exception $e) {
$wpdb->query('ROLLBACK');
$this->release_sync_lock($lock_key);
return new WP_Error(
'sync_transaction_failed',
sprintf('Order sync transaction failed: %s', $e->getMessage())
);
}
$this->release_sync_lock($lock_key);
return $orders;
} catch (Exception $e) {
$this->release_sync_lock($lock_key);
return new WP_Error(
'sync_failed',
sprintf('Order sync failed: %s', $e->getMessage())
);
}
}
/**
* Acquire sync lock - ENTERPRISE: Prevent concurrent syncs
*/
private function acquire_sync_lock($lock_key, $timeout = 300) {
$acquired = set_transient($lock_key, time(), $timeout);
if (!$acquired) {
$lock_time = get_transient($lock_key);
if ($lock_time && (time() - $lock_time) > $timeout) {
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 order
*/
private function cache_order($order) {
if (empty($order['id'])) {
return false;
}
global $wpdb;
$order_id = sanitize_text_field($order['id']);
$event_id = !empty($order['event_id']) ? sanitize_text_field($order['event_id']) : '';
$order_data = wp_json_encode($order);
// Check if exists
$exists = $wpdb->get_var(
$wpdb->prepare(
"SELECT id FROM {$this->table} WHERE order_id = %s",
$order_id
)
);
if ($exists) {
// Update existing
return $wpdb->update(
$this->table,
array(
'event_id' => $event_id,
'order_data' => $order_data,
'last_synced' => current_time('mysql', 1),
),
array('order_id' => $order_id),
array('%s', '%s', '%s'),
array('%s')
);
} else {
// Insert new
return $wpdb->insert(
$this->table,
array(
'order_id' => $order_id,
'event_id' => $event_id,
'order_data' => $order_data,
'last_synced' => current_time('mysql', 1),
),
array('%s', '%s', '%s', '%s')
);
}
}
/**
* Get orders for a specific event
*/
public function get_event_orders($event_id) {
return $this->get_orders(array('event_id' => $event_id));
}
/**
* Get order statistics
*/
public function get_order_stats($event_id = null) {
$orders = $event_id ? $this->get_event_orders($event_id) : $this->get_orders();
if (is_wp_error($orders)) {
return $orders;
}
$stats = array(
'total_orders' => count($orders),
'total_revenue' => 0,
'total_tickets' => 0,
'by_status' => array(),
);
foreach ($orders as $order) {
// Count tickets
if (isset($order['total_quantity'])) {
$stats['total_tickets'] += (int) $order['total_quantity'];
}
// Sum revenue
if (isset($order['total'])) {
$stats['total_revenue'] += (int) $order['total'];
}
// Count by status
$status = $order['status'] ?? 'unknown';
if (!isset($stats['by_status'][$status])) {
$stats['by_status'][$status] = 0;
}
$stats['by_status'][$status]++;
}
// Format revenue (convert from cents to dollars/pounds)
$currency = get_option('ticket_tailor_currency', 'USD');
$stats['total_revenue_formatted'] = $this->format_currency($stats['total_revenue'], $currency);
return $stats;
}
/**
* Format currency
*/
private function format_currency($amount_cents, $currency = 'USD') {
$amount = $amount_cents / 100;
$symbols = array(
'USD' => '$',
'GBP' => '£',
'EUR' => '€',
'CAD' => 'C$',
'AUD' => 'A$',
);
$symbol = $symbols[$currency] ?? $currency . ' ';
return $symbol . number_format($amount, 2);
}
/**
* Search orders
*/
public function search_orders($search_term) {
$orders = $this->get_orders();
if (is_wp_error($orders)) {
return $orders;
}
$search_term = strtolower($search_term);
return array_filter($orders, function($order) use ($search_term) {
$email = strtolower($order['email'] ?? '');
$name = strtolower($order['customer_name'] ?? '');
$order_id = strtolower($order['id'] ?? '');
return strpos($email, $search_term) !== false ||
strpos($name, $search_term) !== false ||
strpos($order_id, $search_term) !== false;
});
}
/**
* Get recent orders
*/
public function get_recent_orders($limit = 10) {
return $this->get_orders(array('limit' => $limit));
}
/**
* Clear order cache - SECURITY FIX: Validate table name
*/
public function clear_cache($order_id = null) {
global $wpdb;
if ($order_id) {
$order_id = sanitize_text_field($order_id);
$wpdb->delete($this->table, array('order_id' => $order_id), array('%s'));
} else {
// SECURITY FIX: Validate table name before TRUNCATE
if (preg_match('/^[a-zA-Z0-9_]+$/', $this->table)) {
$wpdb->query("TRUNCATE TABLE `{$this->table}`");
}
}
return true;
}
/**
* Get cache statistics
*/
public function get_cache_stats() {
global $wpdb;
$total_orders = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table}");
$cache_valid_time = gmdate('Y-m-d H:i:s', time() - $this->cache_duration);
$valid_orders = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$this->table} WHERE last_synced > %s",
$cache_valid_time
)
);
$last_sync = get_option('ticket_tailor_last_order_sync', 0);
return array(
'total_cached' => (int) $total_orders,
'valid_cached' => (int) $valid_orders,
'expired_cached' => (int) $total_orders - (int) $valid_orders,
'last_sync' => $last_sync,
'cache_duration' => $this->cache_duration,
);
}
/**
* Get order statistics efficiently - PERFORMANCE OPTIMIZATION
* Uses aggregated database queries instead of loading all orders
*/
public function get_order_statistics() {
global $wpdb;
$cache_key = 'tt_order_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 orders count
$total_orders = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$this->table} WHERE last_synced > %s",
$cache_valid_time
)
);
// Get all order data to calculate revenue (we need to parse JSON)
$orders_data = $wpdb->get_results(
$wpdb->prepare(
"SELECT order_data FROM {$this->table} WHERE last_synced > %s",
$cache_valid_time
)
);
$total_revenue = 0;
foreach ($orders_data as $row) {
$order = json_decode($row->order_data, true);
if (!empty($order['total'])) {
$total_revenue += floatval($order['total']);
}
}
$stats = array(
'total_orders' => (int) $total_orders,
'total_revenue' => $total_revenue,
);
// Cache for 5 minutes
wp_cache_set($cache_key, $stats, '', 300);
return $stats;
}
}