upskill-event-manager/wordpress-dev/wordpress/wp-content/plugins/hvac-community-events/includes/class-hvac-dashboard-data.php
bengizmo 0b64980286 fix: Resolve dashboard data display and certificate generation issues
Fixed critical issues where dashboard showed zero tickets sold/revenue and
Generate Certificates page couldn't recognize events. Replaced TEC-interfering
queries with direct database queries and added proper navigation header.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-23 00:39:42 -03:00

420 lines
No EOL
12 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 $user_id;
/**
* Constructor.
*
* @param int $user_id The ID of the trainer user.
*/
public function __construct( $user_id ) {
$this->user_id = (int) $user_id;
}
/**
* Get the total number of events created by the trainer.
*
* @return int
*/
public function get_total_events_count() {
global $wpdb;
// Use direct database query to avoid TEC query hijacking
$count = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts}
WHERE post_type = %s
AND post_author = %d
AND post_status IN ('publish', 'future', 'draft', 'pending', 'private')",
Tribe__Events__Main::POSTTYPE,
$this->user_id
) );
return (int) $count;
}
/**
* Get the number of upcoming events for the trainer.
*
* @return int
*/
public function get_upcoming_events_count() {
global $wpdb;
$today = date( 'Y-m-d H:i:s' );
// Use direct database query to avoid TEC query hijacking
$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 = %d
AND p.post_status IN ('publish', 'future')
AND (pm.meta_value >= %s OR pm.meta_value IS NULL)",
Tribe__Events__Main::POSTTYPE,
$this->user_id,
$today
) );
return (int) $count;
}
/**
* Get the number of past events for the trainer.
*
* @return int
*/
public function get_past_events_count() {
global $wpdb;
$today = date( 'Y-m-d H:i:s' );
// Use direct database query to avoid TEC query hijacking
$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 = %d
AND p.post_status IN ('publish', 'private')
AND pm.meta_value < %s",
Tribe__Events__Main::POSTTYPE,
$this->user_id,
$today
) );
return (int) $count;
}
/**
* Get the total number of tickets sold across all the trainer's events.
*
* @return int
*/
public function get_total_tickets_sold() {
global $wpdb;
// Use direct database query to count attendees for user's events
$count = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts} p
WHERE p.post_type = %s
AND p.post_parent IN (
SELECT ID FROM {$wpdb->posts}
WHERE post_type = %s
AND post_author = %d
AND post_status IN ('publish', 'private')
)",
'tribe_tpp_attendees',
'tribe_events',
$this->user_id
) );
return (int) $count;
}
/**
* Get the total revenue generated across all the trainer's events.
*
* @return float
*/
public function get_total_revenue() {
global $wpdb;
// Use direct database query to sum revenue from attendees for user's events
$revenue = $wpdb->get_var( $wpdb->prepare(
"SELECT SUM(CAST(pm.meta_value AS DECIMAL(10,2))) FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tribe_tpp_ticket_price'
WHERE p.post_type = %s
AND p.post_parent IN (
SELECT ID FROM {$wpdb->posts}
WHERE post_type = %s
AND post_author = %d
AND post_status IN ('publish', 'private')
)",
'tribe_tpp_attendees',
'tribe_events',
$this->user_id
) );
return (float) ($revenue ?: 0.00);
}
/**
* 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() {
$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( $filter_status = 'all' ) {
// Use direct database approach since TEC interferes with WP_Query
return $this->get_events_table_data_direct( $filter_status );
}
/**
* Get events table data using direct database queries (bypassing TEC query interference)
*/
private function get_events_table_data_direct( $filter_status = 'all' ) {
global $wpdb;
$events_data = [];
$valid_statuses = array( 'publish', 'future', 'draft', 'pending', 'private' );
// Build status filter for SQL
if ( 'all' === $filter_status || ! in_array( $filter_status, $valid_statuses, true ) ) {
$status_placeholders = implode( ',', array_fill( 0, count( $valid_statuses ), '%s' ) );
$status_values = $valid_statuses;
} else {
$status_placeholders = '%s';
$status_values = array( $filter_status );
}
// Use direct database query to get events (bypassing TEC query modifications)
$sql = "SELECT ID, post_title, post_status, post_date
FROM {$wpdb->posts}
WHERE post_type = %s
AND post_author = %d
AND post_status IN ($status_placeholders)
ORDER BY post_date DESC";
$query_params = array_merge(
array( 'tribe_events', $this->user_id ),
$status_values
);
$events = $wpdb->get_results( $wpdb->prepare( $sql, $query_params ) );
if ( ! empty( $events ) ) {
foreach ( $events as $event ) {
$event_id = $event->ID;
// Get event start date
$start_date = get_post_meta( $event_id, '_EventStartDate', true );
$start_date_ts = $start_date ? strtotime( $start_date ) : time();
// Get sold count from attendees
$sold = $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->posts}
WHERE post_type = 'tribe_tpp_attendees'
AND post_parent = %d",
$event_id
) );
// Get revenue from attendees
$revenue = $wpdb->get_var( $wpdb->prepare(
"SELECT SUM(CAST(pm.meta_value AS DECIMAL(10,2)))
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_tribe_tpp_ticket_price'
WHERE p.post_type = 'tribe_tpp_attendees'
AND p.post_parent = %d",
$event_id
) );
// Build event data array (matching template expectations)
$events_data[] = array(
'id' => $event_id,
'name' => $event->post_title, // Template expects 'name'
'status' => $event->post_status,
'start_date_ts' => $start_date_ts, // Template expects this
'link' => get_permalink( $event_id ),
'organizer_id' => $this->user_id, // Template expects this
'capacity' => 50, // Default capacity
'sold' => (int) ($sold ?: 0),
'revenue' => (float) ($revenue ?: 0.00),
);
}
}
return $events_data;
if ( ! empty( $events ) ) {
foreach ( $events as $event ) {
$event_id = $event->ID;
$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( $event_id ) {
$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( $event_id ) {
$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