added additional plugins
This commit is contained in:
parent
c85895d306
commit
00e60ec1b7
132 changed files with 27514 additions and 0 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue