- Update dashboard data class to check multiple meta field formats - Add attendee counting to calculate stats when meta fields missing - Add revenue calculation based on ticket prices and attendee count - Add fallback methods for direct database queries - Fix event summary data to update dashboard stats on view - Handle check-in status from multiple possible field formats 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
404 lines
No EOL
13 KiB
PHP
404 lines
No EOL
13 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Community Events Dashboard Data Handler
|
|
*
|
|
* Retrieves and calculates data needed for the Trainer Dashboard.
|
|
*
|
|
* @package HVAC Community Events
|
|
* @subpackage Includes
|
|
* @author Roo
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Dashboard_Data
|
|
*
|
|
* Handles fetching and processing data for the trainer dashboard.
|
|
*/
|
|
class HVAC_Dashboard_Data {
|
|
|
|
/**
|
|
* The ID of the trainer user.
|
|
*
|
|
* @var int
|
|
*/
|
|
private int $user_id;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param int $user_id The ID of the trainer user.
|
|
*/
|
|
public function __construct( int $user_id ) {
|
|
$this->user_id = $user_id;
|
|
}
|
|
|
|
/**
|
|
* Get the total number of events created by the trainer.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_total_events_count() : int {
|
|
$args = array(
|
|
'post_type' => Tribe__Events__Main::POSTTYPE,
|
|
'author' => $this->user_id, // Query by post author, not organizer
|
|
'post_status' => array( 'publish', 'future', 'draft', 'pending', 'private' ),
|
|
'posts_per_page' => -1,
|
|
'fields' => 'ids', // Only need the count
|
|
);
|
|
$query = new WP_Query( $args );
|
|
return (int) $query->found_posts;
|
|
}
|
|
|
|
/**
|
|
* Get the number of upcoming events for the trainer.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_upcoming_events_count() : int {
|
|
$today = date( 'Y-m-d H:i:s' );
|
|
$args = array(
|
|
'post_type' => Tribe__Events__Main::POSTTYPE,
|
|
// 'author' => $this->user_id, // Query by organizer instead
|
|
'post_status' => array( 'publish', 'future' ), // Only published or scheduled future events
|
|
'posts_per_page' => -1,
|
|
'fields' => 'ids', // Only need the count
|
|
'meta_query' => array(
|
|
'relation' => 'AND', // Combine organizer and date query
|
|
array(
|
|
'key' => '_EventOrganizerID',
|
|
'value' => $this->user_id,
|
|
'compare' => '=',
|
|
'type' => 'NUMERIC', // Specify numeric comparison
|
|
),
|
|
array(
|
|
'key' => '_EventStartDate',
|
|
'value' => $today,
|
|
'compare' => '>=',
|
|
'type' => 'DATETIME',
|
|
),
|
|
),
|
|
'orderby' => 'event_date',
|
|
'order' => 'ASC',
|
|
);
|
|
$query = new WP_Query( $args );
|
|
return (int) $query->found_posts;
|
|
}
|
|
|
|
/**
|
|
* Get the number of past events for the trainer.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_past_events_count() : int {
|
|
$today = date( 'Y-m-d H:i:s' );
|
|
$args = array(
|
|
'post_type' => Tribe__Events__Main::POSTTYPE,
|
|
// 'author' => $this->user_id, // Query by organizer instead
|
|
'post_status' => array( 'publish', 'private' ), // Count published or private past events
|
|
'posts_per_page' => -1,
|
|
'fields' => 'ids', // Only need the count
|
|
'meta_query' => array(
|
|
'relation' => 'AND', // Combine organizer and date query
|
|
array(
|
|
'key' => '_EventOrganizerID',
|
|
'value' => $this->user_id,
|
|
'compare' => '=',
|
|
'type' => 'NUMERIC', // Specify numeric comparison
|
|
),
|
|
array(
|
|
'key' => '_EventEndDate', // Use end date to determine if it's truly past
|
|
'value' => $today,
|
|
'compare' => '<',
|
|
'type' => 'DATETIME',
|
|
),
|
|
),
|
|
);
|
|
$query = new WP_Query( $args );
|
|
return (int) $query->found_posts;
|
|
}
|
|
|
|
/**
|
|
* Get the total number of tickets sold across all the trainer's events.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function get_total_tickets_sold() : int {
|
|
$total_tickets = 0;
|
|
$args = array(
|
|
'post_type' => Tribe__Events__Main::POSTTYPE,
|
|
// 'author' => $this->user_id, // Query by organizer instead
|
|
'post_status' => array( 'publish', 'future', 'draft', 'pending', 'private' ), // Include all statuses for historical data
|
|
'posts_per_page' => -1,
|
|
'fields' => 'ids', // Only need the IDs
|
|
'meta_key' => '_EventOrganizerID',
|
|
'meta_value' => $this->user_id,
|
|
'meta_compare' => '=', // Explicitly set compare
|
|
'meta_type' => 'NUMERIC', // Specify numeric comparison
|
|
);
|
|
$event_ids = get_posts( $args );
|
|
|
|
if ( ! empty( $event_ids ) ) {
|
|
foreach ( $event_ids as $event_id ) {
|
|
// Check both meta keys that might store sold count
|
|
$sold = get_post_meta( $event_id, '_tribe_tickets_sold', true );
|
|
if (!is_numeric($sold)) {
|
|
// Try alternative meta key used in the test script
|
|
$sold = get_post_meta( $event_id, '_tribe_ticket_sold_count', true );
|
|
}
|
|
|
|
if ( is_numeric( $sold ) ) {
|
|
$total_tickets += (int) $sold;
|
|
} else {
|
|
// If no sold count metadata found, count attendees directly
|
|
$attendees_count = $this->count_event_attendees($event_id);
|
|
if ($attendees_count > 0) {
|
|
$total_tickets += $attendees_count;
|
|
// Update the meta for future reference
|
|
update_post_meta($event_id, '_tribe_tickets_sold', $attendees_count);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $total_tickets;
|
|
}
|
|
|
|
/**
|
|
* Get the total revenue generated across all the trainer's events.
|
|
*
|
|
* @return float
|
|
*/
|
|
public function get_total_revenue() : float {
|
|
$total_revenue = 0.0;
|
|
$args = array(
|
|
'post_type' => Tribe__Events__Main::POSTTYPE,
|
|
// 'author' => $this->user_id, // Query by organizer instead
|
|
'post_status' => array( 'publish', 'future', 'draft', 'pending', 'private' ), // Include all statuses for historical data
|
|
'posts_per_page' => -1,
|
|
'fields' => 'ids', // Only need the IDs
|
|
'meta_key' => '_EventOrganizerID',
|
|
'meta_value' => $this->user_id,
|
|
);
|
|
$event_ids = get_posts( $args );
|
|
|
|
if ( ! empty( $event_ids ) ) {
|
|
foreach ( $event_ids as $event_id ) {
|
|
// Event Tickets Plus often stores total revenue in '_tribe_revenue_total' meta
|
|
$revenue = get_post_meta( $event_id, '_tribe_revenue_total', true );
|
|
if ( is_numeric( $revenue ) ) {
|
|
$total_revenue += (float) $revenue;
|
|
} else {
|
|
// Calculate revenue from attendees if meta not found
|
|
$event_revenue = $this->calculate_event_revenue($event_id);
|
|
if ($event_revenue > 0) {
|
|
$total_revenue += $event_revenue;
|
|
// Update the meta for future reference
|
|
update_post_meta($event_id, '_tribe_revenue_total', $event_revenue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $total_revenue;
|
|
}
|
|
|
|
/**
|
|
* Get the annual revenue target set by the trainer.
|
|
*
|
|
* @return float|null Returns the target as a float, or null if not set.
|
|
*/
|
|
public function get_annual_revenue_target() : ?float {
|
|
$target = get_user_meta( $this->user_id, 'annual_revenue_target', true );
|
|
return ! empty( $target ) && is_numeric( $target ) ? (float) $target : null;
|
|
}
|
|
|
|
/**
|
|
* Get the data needed for the events table on the dashboard.
|
|
*
|
|
* @param string $filter_status The status to filter events by ('all', 'publish', 'future', 'draft', 'pending', 'private'). Defaults to 'all'.
|
|
* @return array An array of event data arrays/objects, each containing keys like: id, status, name, link, date, organizer, capacity, sold, revenue.
|
|
*/
|
|
public function get_events_table_data( string $filter_status = 'all' ) : array {
|
|
$events_data = [];
|
|
$valid_statuses = array( 'publish', 'future', 'draft', 'pending', 'private' );
|
|
$post_status = ( 'all' === $filter_status || ! in_array( $filter_status, $valid_statuses, true ) )
|
|
? $valid_statuses
|
|
: array( $filter_status );
|
|
|
|
$args = array(
|
|
'post_type' => Tribe__Events__Main::POSTTYPE,
|
|
'author' => $this->user_id, // Query by post author
|
|
'post_status' => $post_status,
|
|
'posts_per_page' => -1,
|
|
'orderby' => 'meta_value', // Order by start date
|
|
'meta_key' => '_EventStartDate', // Specify the meta key for ordering
|
|
'order' => 'DESC', // Show most recent first
|
|
);
|
|
|
|
$query = new WP_Query( $args );
|
|
|
|
if ( $query->have_posts() ) {
|
|
while ( $query->have_posts() ) {
|
|
$query->the_post();
|
|
$event_id = get_the_ID();
|
|
|
|
// Get Capacity - Sum capacity of all tickets for this event
|
|
$total_capacity = 0;
|
|
if ( function_exists( 'tribe_get_tickets' ) ) {
|
|
$tickets = tribe_get_tickets( $event_id );
|
|
if ( $tickets ) {
|
|
foreach ( $tickets as $ticket ) {
|
|
$capacity = $ticket->capacity();
|
|
// -1 often means unlimited capacity for Tribe Tickets
|
|
if ( $capacity === -1 ) {
|
|
$total_capacity = -1; // Mark as unlimited
|
|
break; // No need to sum further if one is unlimited
|
|
}
|
|
if ( is_numeric( $capacity ) ) {
|
|
$total_capacity += $capacity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get sold and revenue counts, checking for both standard and alternative meta fields
|
|
$sold = get_post_meta( $event_id, '_tribe_tickets_sold', true );
|
|
if (!is_numeric($sold)) {
|
|
$sold = get_post_meta( $event_id, '_tribe_ticket_sold_count', true );
|
|
|
|
// If still no valid count, calculate from attendees
|
|
if (!is_numeric($sold)) {
|
|
$sold = $this->count_event_attendees($event_id);
|
|
if ($sold > 0) {
|
|
update_post_meta($event_id, '_tribe_tickets_sold', $sold);
|
|
}
|
|
}
|
|
}
|
|
|
|
$revenue = get_post_meta( $event_id, '_tribe_revenue_total', true );
|
|
if (!is_numeric($revenue)) {
|
|
$revenue = $this->calculate_event_revenue($event_id);
|
|
if ($revenue > 0) {
|
|
update_post_meta($event_id, '_tribe_revenue_total', $revenue);
|
|
}
|
|
}
|
|
|
|
$events_data[] = array(
|
|
'id' => $event_id,
|
|
'status' => get_post_status( $event_id ),
|
|
'name' => get_the_title(),
|
|
// Return raw data instead of calling TEC functions here
|
|
'link' => get_permalink( $event_id ), // Use standard WP permalink
|
|
'start_date_ts' => strtotime( get_post_meta( $event_id, '_EventStartDate', true ) ), // Return timestamp
|
|
'organizer_id' => (int) get_post_meta( $event_id, '_EventOrganizerID', true ), // Return organizer ID
|
|
'capacity' => ( $total_capacity === -1 ) ? 'Unlimited' : (int) $total_capacity,
|
|
'sold' => is_numeric( $sold ) ? (int) $sold : 0,
|
|
'revenue' => is_numeric( $revenue ) ? (float) $revenue : 0.0,
|
|
);
|
|
}
|
|
wp_reset_postdata(); // Restore original Post Data
|
|
}
|
|
|
|
return $events_data;
|
|
}
|
|
|
|
/**
|
|
* Count the number of attendees for an event by querying attendee posts
|
|
*
|
|
* @param int $event_id Event ID to count attendees for
|
|
* @return int Number of attendees found
|
|
*/
|
|
private function count_event_attendees(int $event_id) : int {
|
|
$attendee_count = 0;
|
|
|
|
// Check if Event Tickets is active
|
|
if (class_exists('Tribe__Tickets__Tickets_Handler') && method_exists(Tribe__Tickets__Tickets_Handler::instance(), 'get_attendees_by_id')) {
|
|
$attendees = Tribe__Tickets__Tickets_Handler::instance()->get_attendees_by_id($event_id);
|
|
if (is_array($attendees)) {
|
|
$attendee_count = count($attendees);
|
|
}
|
|
} else {
|
|
// Fallback to direct query if Event Tickets not available
|
|
$attendees_query = new WP_Query([
|
|
'post_type' => 'tribe_tpp_attendees',
|
|
'posts_per_page' => -1,
|
|
'fields' => 'ids',
|
|
'meta_query' => [
|
|
[
|
|
'key' => '_tribe_tpp_event',
|
|
'value' => $event_id,
|
|
'compare' => '=',
|
|
],
|
|
],
|
|
]);
|
|
$attendee_count = $attendees_query->found_posts;
|
|
}
|
|
|
|
return $attendee_count;
|
|
}
|
|
|
|
/**
|
|
* Calculate total revenue for an event by summing ticket prices
|
|
*
|
|
* @param int $event_id Event ID to calculate revenue for
|
|
* @return float Total revenue calculated
|
|
*/
|
|
private function calculate_event_revenue(int $event_id) : float {
|
|
$total_revenue = 0.0;
|
|
|
|
// Check if Event Tickets is active
|
|
if (class_exists('Tribe__Tickets__Tickets_Handler') && method_exists(Tribe__Tickets__Tickets_Handler::instance(), 'get_attendees_by_id')) {
|
|
$attendees = Tribe__Tickets__Tickets_Handler::instance()->get_attendees_by_id($event_id);
|
|
|
|
if (is_array($attendees)) {
|
|
foreach ($attendees as $attendee) {
|
|
// Extract price - structure might vary based on ticket provider
|
|
$price = 0;
|
|
if (isset($attendee['price']) && is_numeric($attendee['price'])) {
|
|
$price = (float)$attendee['price'];
|
|
} elseif (isset($attendee['price_paid']) && is_numeric($attendee['price_paid'])) {
|
|
$price = (float)$attendee['price_paid'];
|
|
}
|
|
|
|
$total_revenue += $price;
|
|
}
|
|
}
|
|
} else {
|
|
// Fallback to direct calculation if Event Tickets not available
|
|
// First get all tickets for this event to get prices
|
|
$tickets_query = new WP_Query([
|
|
'post_type' => 'tribe_tpp_tickets',
|
|
'posts_per_page' => -1,
|
|
'meta_query' => [
|
|
[
|
|
'key' => '_tribe_tpp_for_event',
|
|
'value' => $event_id,
|
|
'compare' => '=',
|
|
],
|
|
],
|
|
]);
|
|
|
|
if ($tickets_query->have_posts()) {
|
|
while ($tickets_query->have_posts()) {
|
|
$tickets_query->the_post();
|
|
$ticket_id = get_the_ID();
|
|
$price = get_post_meta($ticket_id, '_price', true);
|
|
$sold = get_post_meta($ticket_id, '_tribe_tpp_sold', true);
|
|
|
|
if (is_numeric($price) && is_numeric($sold)) {
|
|
$total_revenue += ((float)$price * (int)$sold);
|
|
}
|
|
}
|
|
wp_reset_postdata();
|
|
}
|
|
}
|
|
|
|
return $total_revenue;
|
|
}
|
|
} // End class HVAC_Dashboard_Data
|