get_all_trainer_user_ids(); if (empty($trainer_users)) { return 0; } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_author IN ($user_ids_placeholder) AND post_status IN ('publish', 'future', 'draft', 'pending', 'private')", array_merge([Tribe__Events__Main::POSTTYPE], $trainer_users) ) ); // Cache for 15 minutes wp_cache_set($cache_key, $count, 'hvac_master_dashboard', 900); return (int) $count; } /** * Get the number of upcoming events for ALL trainers. * * @return int */ public function get_upcoming_events_count() { global $wpdb; // Check cache first $cache_key = 'hvac_master_upcoming_events_count'; $cached_count = wp_cache_get($cache_key, 'hvac_master_dashboard'); if ($cached_count !== false) { return (int) $cached_count; } $today = date( 'Y-m-d H:i:s' ); $trainer_users = $this->get_all_trainer_user_ids(); if (empty($trainer_users)) { return 0; } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventStartDate' WHERE p.post_type = %s AND p.post_author IN ($user_ids_placeholder) AND p.post_status IN ('publish', 'future') AND (pm.meta_value >= %s OR pm.meta_value IS NULL)", array_merge([Tribe__Events__Main::POSTTYPE], $trainer_users, [$today]) ) ); // Cache for 15 minutes wp_cache_set($cache_key, $count, 'hvac_master_dashboard', 900); return (int) $count; } /** * Get the number of past events for ALL trainers. * * @return int */ public function get_past_events_count() { global $wpdb; // Check cache first $cache_key = 'hvac_master_past_events_count'; $cached_count = wp_cache_get($cache_key, 'hvac_master_dashboard'); if ($cached_count !== false) { return (int) $cached_count; } $today = date( 'Y-m-d H:i:s' ); $trainer_users = $this->get_all_trainer_user_ids(); if (empty($trainer_users)) { return 0; } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_EventEndDate' WHERE p.post_type = %s AND p.post_author IN ($user_ids_placeholder) AND p.post_status IN ('publish', 'private') AND pm.meta_value < %s", array_merge([Tribe__Events__Main::POSTTYPE], $trainer_users, [$today]) ) ); // Cache for 15 minutes wp_cache_set($cache_key, $count, 'hvac_master_dashboard', 900); return (int) $count; } /** * Get the total number of tickets sold across ALL trainers' events. * * @return int */ public function get_total_tickets_sold() { global $wpdb; // Check cache first $cache_key = 'hvac_master_total_tickets_sold'; $cached_count = wp_cache_get($cache_key, 'hvac_master_dashboard'); if ($cached_count !== false) { return (int) $cached_count; } $trainer_users = $this->get_all_trainer_user_ids(); if (empty($trainer_users)) { return 0; } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); // Count TEC Commerce attendees $tec_commerce_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tec_tickets_commerce_event' WHERE p.post_type = %s AND pm.meta_value IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_author IN ($user_ids_placeholder) AND post_status IN ('publish', 'private') )", array_merge(['tec_tc_attendee', 'tribe_events'], $trainer_users) ) ); // Count legacy Tribe PayPal attendees $tribe_tpp_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tribe_tpp_event' WHERE p.post_type = %s AND pm.meta_value IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_author IN ($user_ids_placeholder) AND post_status IN ('publish', 'private') )", array_merge(['tribe_tpp_attendees', 'tribe_events'], $trainer_users) ) ); // Note: RSVP attendees are not counted as "tickets sold" since they are free registrations $total_count = (int) ($tec_commerce_count + $tribe_tpp_count); // Cache for 15 minutes wp_cache_set($cache_key, $total_count, 'hvac_master_dashboard', 900); return $total_count; } /** * Get the total revenue generated across ALL trainers' events. * * @return float */ public function get_total_revenue() { global $wpdb; // Check cache first $cache_key = 'hvac_master_total_revenue'; $cached_revenue = wp_cache_get($cache_key, 'hvac_master_dashboard'); if ($cached_revenue !== false) { return (float) $cached_revenue; } $trainer_users = $this->get_all_trainer_user_ids(); if (empty($trainer_users)) { return 0.00; } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); // Get TEC Commerce revenue $tec_commerce_revenue = $wpdb->get_var( $wpdb->prepare( "SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_event ON p.ID = pm_event.post_id AND pm_event.meta_key = '_tec_tickets_commerce_event' INNER JOIN {$wpdb->postmeta} pm_price ON p.ID = pm_price.post_id AND pm_price.meta_key = '_tec_tickets_commerce_price_paid' WHERE p.post_type = %s AND pm_event.meta_value IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_author IN ($user_ids_placeholder) AND post_status IN ('publish', 'private') )", array_merge(['tec_tc_attendee', 'tribe_events'], $trainer_users) ) ); // Get legacy Tribe PayPal revenue $tribe_tpp_revenue = $wpdb->get_var( $wpdb->prepare( "SELECT SUM( CASE WHEN pm_price1.meta_value IS NOT NULL THEN CAST(pm_price1.meta_value AS DECIMAL(10,2)) WHEN pm_price2.meta_value IS NOT NULL THEN CAST(pm_price2.meta_value AS DECIMAL(10,2)) WHEN pm_price3.meta_value IS NOT NULL THEN CAST(pm_price3.meta_value AS DECIMAL(10,2)) ELSE 0 END ) FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm_event ON p.ID = pm_event.post_id AND pm_event.meta_key = '_tribe_tpp_event' LEFT JOIN {$wpdb->postmeta} pm_price1 ON p.ID = pm_price1.post_id AND pm_price1.meta_key = '_tribe_tpp_ticket_price' LEFT JOIN {$wpdb->postmeta} pm_price2 ON p.ID = pm_price2.post_id AND pm_price2.meta_key = '_paid_price' LEFT JOIN {$wpdb->postmeta} pm_price3 ON p.ID = pm_price3.post_id AND pm_price3.meta_key = '_tribe_tpp_price' WHERE p.post_type = %s AND pm_event.meta_value IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_author IN ($user_ids_placeholder) AND post_status IN ('publish', 'private') )", array_merge(['tribe_tpp_attendees', 'tribe_events'], $trainer_users) ) ); // Note: RSVP attendees typically don't have revenue (free tickets) $total_revenue = (float) (($tec_commerce_revenue ?: 0.00) + ($tribe_tpp_revenue ?: 0.00)); // Cache for 15 minutes wp_cache_set($cache_key, $total_revenue, 'hvac_master_dashboard', 900); return $total_revenue; } /** * Clear all cached master dashboard data * Should be called when events, tickets, or trainer data changes * * @return void */ public static function clear_cache() { $cache_keys = [ 'hvac_master_total_events_count', 'hvac_master_upcoming_events_count', 'hvac_master_past_events_count', 'hvac_master_total_tickets_sold', 'hvac_master_total_revenue' ]; foreach ($cache_keys as $key) { wp_cache_delete($key, 'hvac_master_dashboard'); } } /** * Get trainer statistics - count and individual performance data * * @return array */ public function get_trainer_statistics() { global $wpdb; $trainer_users = $this->get_all_trainer_user_ids(); if (empty($trainer_users)) { return ['total_trainers' => 0, 'trainer_data' => []]; } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); // Get detailed data for each trainer $trainer_data = $wpdb->get_results( $wpdb->prepare( "SELECT u.ID as trainer_id, u.display_name, u.user_email, COUNT(DISTINCT p.ID) as total_events, COUNT(DISTINCT CASE WHEN pm_start.meta_value >= %s THEN p.ID END) as upcoming_events, COUNT(DISTINCT CASE WHEN pm_end.meta_value < %s THEN p.ID END) as past_events, COALESCE(attendee_stats.total_attendees, 0) as total_attendees, COALESCE(revenue_stats.total_revenue, 0) as total_revenue FROM {$wpdb->users} u LEFT JOIN {$wpdb->posts} p ON u.ID = p.post_author AND p.post_type = %s AND p.post_status IN ('publish', 'future', 'draft', 'pending', 'private') LEFT JOIN {$wpdb->postmeta} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '_EventStartDate' LEFT JOIN {$wpdb->postmeta} pm_end ON p.ID = pm_end.post_id AND pm_end.meta_key = '_EventEndDate' LEFT JOIN ( SELECT trainer_events.post_author, ( SELECT COUNT(*) FROM {$wpdb->posts} tec_attendees INNER JOIN {$wpdb->postmeta} tec_event ON tec_attendees.ID = tec_event.post_id AND tec_event.meta_key = '_tec_tickets_commerce_event' WHERE tec_attendees.post_type = 'tec_tc_attendee' AND tec_event.meta_value = trainer_events.ID ) + ( SELECT COUNT(*) FROM {$wpdb->posts} tpp_attendees INNER JOIN {$wpdb->postmeta} tpp_event ON tpp_attendees.ID = tpp_event.post_id AND tpp_event.meta_key = '_tribe_tpp_event' WHERE tpp_attendees.post_type = 'tribe_tpp_attendees' AND tpp_event.meta_value = trainer_events.ID ) + 0 /* RSVP attendees not counted as tickets sold (free registrations) */ ) as total_attendees FROM {$wpdb->posts} trainer_events WHERE trainer_events.post_type = 'tribe_events' AND trainer_events.post_author IN ($user_ids_placeholder) GROUP BY trainer_events.post_author ) attendee_stats ON u.ID = attendee_stats.post_author LEFT JOIN ( SELECT trainer_events.post_author, ( COALESCE((SELECT SUM(CAST(tec_price.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} tec_attendees INNER JOIN {$wpdb->postmeta} tec_event ON tec_attendees.ID = tec_event.post_id AND tec_event.meta_key = '_tec_tickets_commerce_event' INNER JOIN {$wpdb->postmeta} tec_price ON tec_attendees.ID = tec_price.post_id AND tec_price.meta_key = '_tec_tickets_commerce_price_paid' WHERE tec_attendees.post_type = 'tec_tc_attendee' AND tec_event.meta_value = trainer_events.ID), 0) + COALESCE((SELECT SUM( CASE WHEN pm_price1.meta_value IS NOT NULL THEN CAST(pm_price1.meta_value AS DECIMAL(10,2)) WHEN pm_price2.meta_value IS NOT NULL THEN CAST(pm_price2.meta_value AS DECIMAL(10,2)) WHEN pm_price3.meta_value IS NOT NULL THEN CAST(pm_price3.meta_value AS DECIMAL(10,2)) ELSE 0 END ) FROM {$wpdb->posts} tpp_attendees INNER JOIN {$wpdb->postmeta} tpp_event ON tpp_attendees.ID = tpp_event.post_id AND tpp_event.meta_key = '_tribe_tpp_event' LEFT JOIN {$wpdb->postmeta} pm_price1 ON tpp_attendees.ID = pm_price1.post_id AND pm_price1.meta_key = '_tribe_tpp_ticket_price' LEFT JOIN {$wpdb->postmeta} pm_price2 ON tpp_attendees.ID = pm_price2.post_id AND pm_price2.meta_key = '_paid_price' LEFT JOIN {$wpdb->postmeta} pm_price3 ON tpp_attendees.ID = pm_price3.post_id AND pm_price3.meta_key = '_tribe_tpp_price' WHERE tpp_attendees.post_type = 'tribe_tpp_attendees' AND tpp_event.meta_value = trainer_events.ID), 0) ) as total_revenue FROM {$wpdb->posts} trainer_events WHERE trainer_events.post_type = 'tribe_events' AND trainer_events.post_author IN ($user_ids_placeholder) GROUP BY trainer_events.post_author ) revenue_stats ON u.ID = revenue_stats.post_author WHERE u.ID IN ($user_ids_placeholder) GROUP BY u.ID ORDER BY total_revenue DESC", array_merge([ date('Y-m-d H:i:s'), // for upcoming events date('Y-m-d H:i:s'), // for past events Tribe__Events__Main::POSTTYPE ], $trainer_users, $trainer_users, $trainer_users) ) ); return [ 'total_trainers' => count($trainer_users), 'trainer_data' => $trainer_data ]; } /** * Get the data needed for the events table on the master dashboard. * Shows ALL events from ALL trainers with additional trainer information. * * @param array $args Query arguments * @return array Contains 'events' array and 'pagination' data */ public function get_events_table_data( $args = array() ) { // Default arguments $defaults = array( 'status' => 'all', 'search' => '', 'orderby' => 'date', 'order' => 'DESC', 'page' => 1, 'per_page' => 10, 'date_from' => '', 'date_to' => '', 'trainer_id' => '' // New filter for specific trainer ); $args = wp_parse_args( $args, $defaults ); return $this->get_events_table_data_direct( $args ); } /** * Get events table data using direct database queries (shows ALL trainer events) */ private function get_events_table_data_direct( $args ) { global $wpdb; $events_data = []; $valid_statuses = array( 'publish', 'future', 'draft', 'pending', 'private' ); $trainer_users = $this->get_all_trainer_user_ids(); if (empty($trainer_users)) { return [ 'events' => [], 'pagination' => [ 'total_items' => 0, 'total_pages' => 0, 'current_page' => 1, 'per_page' => $args['per_page'], 'has_prev' => false, 'has_next' => false ] ]; } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); // Build WHERE clauses $where_clauses = array( 'p.post_type = %s', "p.post_author IN ($user_ids_placeholder)" ); $where_values = array_merge([Tribe__Events__Main::POSTTYPE], $trainer_users); // Status filter if ( 'all' === $args['status'] || ! in_array( $args['status'], $valid_statuses, true ) ) { $status_placeholders = implode( ',', array_fill( 0, count( $valid_statuses ), '%s' ) ); $where_clauses[] = "p.post_status IN ($status_placeholders)"; $where_values = array_merge( $where_values, $valid_statuses ); } else { $where_clauses[] = 'p.post_status = %s'; $where_values[] = $args['status']; } // Search filter if ( ! empty( $args['search'] ) ) { $where_clauses[] = 'p.post_title LIKE %s'; $where_values[] = '%' . $wpdb->esc_like( $args['search'] ) . '%'; } // Trainer filter if ( ! empty( $args['trainer_id'] ) && is_numeric( $args['trainer_id'] ) ) { $where_clauses[] = 'p.post_author = %d'; $where_values[] = (int) $args['trainer_id']; } // Date range filters if ( ! empty( $args['date_from'] ) ) { $where_clauses[] = "pm_start.meta_value >= %s"; $where_values[] = $args['date_from'] . ' 00:00:00'; } if ( ! empty( $args['date_to'] ) ) { $where_clauses[] = "pm_start.meta_value <= %s"; $where_values[] = $args['date_to'] . ' 23:59:59'; } // Build ORDER BY clause $order_column = 'p.post_date'; switch ( $args['orderby'] ) { case 'name': $order_column = 'p.post_title'; break; case 'status': $order_column = 'p.post_status'; break; case 'date': $order_column = 'COALESCE(pm_start.meta_value, p.post_date)'; break; case 'trainer': $order_column = 'u.display_name'; break; case 'capacity': $order_column = 'capacity'; break; case 'sold': $order_column = 'sold'; break; case 'revenue': $order_column = 'revenue'; break; } $order_dir = ( strtoupper( $args['order'] ) === 'ASC' ) ? 'ASC' : 'DESC'; // Calculate offset for pagination $offset = ( $args['page'] - 1 ) * $args['per_page']; // Build the complete SQL query $where_sql = implode( ' AND ', $where_clauses ); // First, get total count for pagination $count_sql = "SELECT COUNT(DISTINCT p.ID) FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '_EventStartDate' LEFT JOIN {$wpdb->users} u ON p.post_author = u.ID WHERE $where_sql"; $total_items = $wpdb->get_var( $wpdb->prepare( $count_sql, $where_values ) ); // Main query with joins for all needed data including trainer information $sql = "SELECT p.ID, p.post_title, p.post_status, p.post_date, p.post_author, u.display_name as trainer_name, u.user_email as trainer_email, COALESCE(pm_start.meta_value, p.post_date) as event_date, ( (SELECT COUNT(*) FROM {$wpdb->posts} attendees INNER JOIN {$wpdb->postmeta} pm_event ON attendees.ID = pm_event.post_id AND pm_event.meta_key = '_tec_tickets_commerce_event' WHERE attendees.post_type = 'tec_tc_attendee' AND pm_event.meta_value = p.ID) + (SELECT COUNT(*) FROM {$wpdb->posts} attendees INNER JOIN {$wpdb->postmeta} pm_event ON attendees.ID = pm_event.post_id AND pm_event.meta_key = '_tribe_tpp_event' WHERE attendees.post_type = 'tribe_tpp_attendees' AND pm_event.meta_value = p.ID) + 0 /* RSVP attendees not counted as tickets sold (free registrations) */ ) as sold, ( COALESCE((SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} attendees INNER JOIN {$wpdb->postmeta} pm_event ON attendees.ID = pm_event.post_id AND pm_event.meta_key = '_tec_tickets_commerce_event' INNER JOIN {$wpdb->postmeta} pm_price ON attendees.ID = pm_price.post_id AND pm_price.meta_key = '_tec_tickets_commerce_price_paid' WHERE attendees.post_type = 'tec_tc_attendee' AND pm_event.meta_value = p.ID), 0) + COALESCE((SELECT SUM( CASE WHEN pm_price1.meta_value IS NOT NULL THEN CAST(pm_price1.meta_value AS DECIMAL(10,2)) WHEN pm_price2.meta_value IS NOT NULL THEN CAST(pm_price2.meta_value AS DECIMAL(10,2)) WHEN pm_price3.meta_value IS NOT NULL THEN CAST(pm_price3.meta_value AS DECIMAL(10,2)) ELSE 0 END ) FROM {$wpdb->posts} attendees INNER JOIN {$wpdb->postmeta} pm_event ON attendees.ID = pm_event.post_id AND pm_event.meta_key = '_tribe_tpp_event' LEFT JOIN {$wpdb->postmeta} pm_price1 ON attendees.ID = pm_price1.post_id AND pm_price1.meta_key = '_tribe_tpp_ticket_price' LEFT JOIN {$wpdb->postmeta} pm_price2 ON attendees.ID = pm_price2.post_id AND pm_price2.meta_key = '_paid_price' LEFT JOIN {$wpdb->postmeta} pm_price3 ON attendees.ID = pm_price3.post_id AND pm_price3.meta_key = '_tribe_tpp_price' WHERE attendees.post_type = 'tribe_tpp_attendees' AND pm_event.meta_value = p.ID), 0) ) as revenue, 50 as capacity FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '_EventStartDate' LEFT JOIN {$wpdb->users} u ON p.post_author = u.ID WHERE $where_sql ORDER BY $order_column $order_dir LIMIT %d OFFSET %d"; $query_values = array_merge( $where_values, array( $args['per_page'], $offset ) ); $events = $wpdb->get_results( $wpdb->prepare( $sql, $query_values ) ); if ( ! empty( $events ) ) { foreach ( $events as $event ) { $event_id = $event->ID; $start_date_ts = $event->event_date ? strtotime( $event->event_date ) : strtotime( $event->post_date ); // Build event data array (matching template expectations with trainer info) $events_data[] = array( 'id' => $event_id, 'name' => $event->post_title, 'status' => $event->post_status, 'start_date_ts' => $start_date_ts, 'link' => get_permalink( $event_id ), 'organizer_id' => $event->post_author, 'trainer_name' => $event->trainer_name, 'trainer_email' => $event->trainer_email, 'capacity' => (int) $event->capacity, 'sold' => (int) $event->sold, 'revenue' => (float) $event->revenue, ); } } // Calculate pagination data $total_pages = ceil( $total_items / $args['per_page'] ); return array( 'events' => $events_data, 'pagination' => array( 'total_items' => $total_items, 'total_pages' => $total_pages, 'current_page' => $args['page'], 'per_page' => $args['per_page'], 'has_prev' => $args['page'] > 1, 'has_next' => $args['page'] < $total_pages ) ); } /** * Get all user IDs who have hvac_trainer or hvac_master_trainer role * * @return array Array of user IDs */ private function get_all_trainer_user_ids() { $trainer_users = get_users(array( 'role__in' => array('hvac_trainer', 'hvac_master_trainer'), 'fields' => 'ID' )); return array_map('intval', $trainer_users); } /** * Get summary statistics for Google Sheets integration * * @return array */ public function get_google_sheets_summary_data() { return [ 'events_summary' => $this->get_events_summary_for_sheets(), 'attendees_summary' => $this->get_attendees_summary_for_sheets(), 'trainers_summary' => $this->get_trainer_statistics(), 'ticket_purchases_summary' => $this->get_ticket_purchases_summary_for_sheets() ]; } /** * Get events summary data formatted for Google Sheets */ private function get_events_summary_for_sheets() { // This will be implemented when we create the Google Sheets integration return [ 'total_events' => $this->get_total_events_count(), 'upcoming_events' => $this->get_upcoming_events_count(), 'past_events' => $this->get_past_events_count(), 'total_revenue' => $this->get_total_revenue() ]; } /** * Get attendees summary data formatted for Google Sheets */ private function get_attendees_summary_for_sheets() { // This will be implemented when we create the Google Sheets integration return [ 'total_attendees' => $this->get_total_tickets_sold() ]; } /** * Get ticket purchases summary data formatted for Google Sheets */ private function get_ticket_purchases_summary_for_sheets() { // This will be implemented when we create the Google Sheets integration return [ 'total_tickets_sold' => $this->get_total_tickets_sold(), 'total_revenue' => $this->get_total_revenue() ]; } /** * Get completed events count * * @return int */ public function get_completed_events_count() { return $this->get_past_events_count(); } /** * Get active trainers count * * @return int */ public function get_active_trainers_count() { return count($this->get_all_trainer_user_ids()); } /** * Get trainer performance data for Google Sheets * * @return array */ public function get_trainer_performance_data() { $trainer_stats = $this->get_trainer_statistics(); $performance_data = array(); foreach ($trainer_stats['trainer_data'] as $trainer) { $performance_data[] = array( 'name' => $trainer->display_name, 'events' => $trainer->total_events, 'tickets' => $trainer->total_attendees, 'revenue' => $trainer->total_revenue ); } return $performance_data; } /** * Get all events data formatted for Google Sheets * * @return array */ public function get_all_events_data() { $events_table_data = $this->get_events_table_data(array( 'per_page' => 999, // Get all events 'orderby' => 'date', 'order' => 'DESC' )); $formatted_events = array(); foreach ($events_table_data['events'] as $event) { $formatted_events[] = array( 'title' => $event['name'], 'trainer_name' => $event['trainer_name'], 'date' => date('M j, Y', $event['start_date_ts']), 'status' => ucfirst($event['status']), 'tickets' => $event['sold'], 'revenue' => $event['revenue'] ); } return $formatted_events; } /** * Get monthly revenue data for analytics * * @return array */ public function get_monthly_revenue_data() { global $wpdb; $trainer_users = $this->get_all_trainer_user_ids(); if (empty($trainer_users)) { return array(); } $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); // Get events grouped by month for the last 12 months $months_data = $wpdb->get_results( $wpdb->prepare( "SELECT DATE_FORMAT(pm_start.meta_value, '%%Y-%%m') as month, COUNT(DISTINCT p.ID) as event_count FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '_EventStartDate' WHERE p.post_type = %s AND p.post_author IN ($user_ids_placeholder) AND p.post_status = 'publish' AND pm_start.meta_value >= DATE_SUB(NOW(), INTERVAL 12 MONTH) GROUP BY DATE_FORMAT(pm_start.meta_value, '%%Y-%%m') ORDER BY month DESC", array_merge([Tribe__Events__Main::POSTTYPE], $trainer_users) ) ); // Calculate revenue for each month $monthly_data = array(); foreach ($months_data as $month_data) { $month_revenue = $this->get_month_revenue($month_data->month, $trainer_users); $monthly_data[] = array( 'month' => date('M Y', strtotime($month_data->month . '-01')), 'events' => $month_data->event_count, 'revenue' => $month_revenue ); } return $monthly_data; } /** * Get revenue for a specific month * * @param string $month Format: Y-m * @param array $trainer_users * @return float */ private function get_month_revenue($month, $trainer_users) { global $wpdb; $user_ids_placeholder = implode(',', array_fill(0, count($trainer_users), '%d')); // Get revenue for all events in this month $revenue = $wpdb->get_var( $wpdb->prepare( "SELECT SUM( CASE WHEN pm_price1.meta_value IS NOT NULL THEN CAST(pm_price1.meta_value AS DECIMAL(10,2)) WHEN pm_price2.meta_value IS NOT NULL THEN CAST(pm_price2.meta_value AS DECIMAL(10,2)) WHEN pm_price3.meta_value IS NOT NULL THEN CAST(pm_price3.meta_value AS DECIMAL(10,2)) ELSE 0 END ) FROM {$wpdb->posts} attendees INNER JOIN {$wpdb->postmeta} pm_event ON attendees.ID = pm_event.post_id AND pm_event.meta_key = '_tribe_tpp_event' INNER JOIN {$wpdb->posts} events ON pm_event.meta_value = events.ID LEFT JOIN {$wpdb->postmeta} pm_start ON events.ID = pm_start.post_id AND pm_start.meta_key = '_EventStartDate' LEFT JOIN {$wpdb->postmeta} pm_price1 ON attendees.ID = pm_price1.post_id AND pm_price1.meta_key = '_tribe_tpp_ticket_price' LEFT JOIN {$wpdb->postmeta} pm_price2 ON attendees.ID = pm_price2.post_id AND pm_price2.meta_key = '_paid_price' LEFT JOIN {$wpdb->postmeta} pm_price3 ON attendees.ID = pm_price3.post_id AND pm_price3.meta_key = '_tribe_tpp_price' WHERE attendees.post_type = 'tribe_tpp_attendees' AND events.post_author IN ($user_ids_placeholder) AND DATE_FORMAT(pm_start.meta_value, '%%Y-%%m') = %s", array_merge($trainer_users, [$month]) ) ); return (float) ($revenue ?: 0.00); } /** * AJAX handler for trainers table */ public function ajax_get_trainers_table() { // Check nonce if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'hvac_master_dashboard_nonce' ) ) { wp_die( 'Security check failed' ); } // Check permissions if ( ! current_user_can( 'view_master_dashboard' ) && ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Insufficient permissions' ) ); } // Get parameters $args = array( 'status' => isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : 'all', 'search' => isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '', 'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1, 'per_page' => isset( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 10, 'orderby' => isset( $_POST['orderby'] ) ? sanitize_text_field( $_POST['orderby'] ) : 'display_name', 'order' => isset( $_POST['order'] ) ? sanitize_text_field( $_POST['order'] ) : 'ASC', ); // Get trainer table data $data = $this->get_trainers_table_data( $args ); wp_send_json_success( $data ); } /** * Get trainers table data with filtering and pagination * * @param array $args Query arguments * @return array */ public function get_trainers_table_data( $args = array() ) { // Default arguments $defaults = array( 'status' => 'all', 'search' => '', 'page' => 1, 'per_page' => 10, 'orderby' => 'display_name', 'order' => 'ASC', ); $args = wp_parse_args( $args, $defaults ); // Load trainer status class if ( ! class_exists( 'HVAC_Trainer_Status' ) ) { require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-trainer-status.php'; } // Build user query args $user_args = array( 'role__in' => array( 'hvac_trainer', 'hvac_master_trainer' ), 'number' => $args['per_page'], 'paged' => $args['page'], 'orderby' => $args['orderby'], 'order' => $args['order'], ); // Add search if ( ! empty( $args['search'] ) ) { $user_args['search'] = '*' . $args['search'] . '*'; $user_args['search_columns'] = array( 'user_login', 'user_email', 'display_name' ); } // Handle status filter if ( $args['status'] !== 'all' ) { if ( in_array( $args['status'], array( 'active', 'inactive' ), true ) ) { // For dynamic statuses, we need to filter after query $user_args['number'] = -1; // Get all users $user_args['paged'] = 1; } else { // For static statuses, use meta query $user_args['meta_query'] = array( array( 'key' => 'account_status', 'value' => $args['status'], 'compare' => '=', ), ); } } // Query users $user_query = new WP_User_Query( $user_args ); $users = $user_query->get_results(); $total_users = $user_query->get_total(); // Filter by dynamic status if needed if ( $args['status'] !== 'all' && in_array( $args['status'], array( 'active', 'inactive' ), true ) ) { $filtered_users = array(); foreach ( $users as $user ) { $user_status = HVAC_Trainer_Status::get_trainer_status( $user->ID ); if ( $user_status === $args['status'] ) { $filtered_users[] = $user; } } $total_users = count( $filtered_users ); // Apply pagination manually $offset = ( $args['page'] - 1 ) * $args['per_page']; $users = array_slice( $filtered_users, $offset, $args['per_page'] ); } // Build trainer data $trainers_data = array(); $all_statuses = HVAC_Trainer_Status::get_all_statuses(); foreach ( $users as $user ) { $status = HVAC_Trainer_Status::get_trainer_status( $user->ID ); $last_event_date = HVAC_Trainer_Status::get_last_event_date( $user->ID ); $trainers_data[] = array( 'id' => $user->ID, 'name' => $user->display_name, 'email' => $user->user_email, 'status' => $status, 'status_label' => isset( $all_statuses[$status] ) ? $all_statuses[$status] : ucfirst( $status ), 'registration_date' => date( 'M j, Y', strtotime( $user->user_registered ) ), 'last_event_date' => $last_event_date ? date( 'M j, Y', strtotime( $last_event_date ) ) : null, 'total_events' => HVAC_Trainer_Status::get_trainer_event_count( $user->ID ), 'revenue' => HVAC_Trainer_Status::get_trainer_revenue( $user->ID ), ); } // Calculate pagination $total_pages = ceil( $total_users / $args['per_page'] ); return array( 'trainers' => $trainers_data, 'pagination' => array( 'total_items' => $total_users, 'total_pages' => $total_pages, 'current_page' => $args['page'], 'per_page' => $args['per_page'], 'has_prev' => $args['page'] > 1, 'has_next' => $args['page'] < $total_pages, ), ); } }