- Added explicit checks to prevent authentication redirects on registration page - Added ensure_registration_page_public() method with priority 1 to run before other auth checks - Included registration-pending and training-login pages in public pages list - Added fallback function in main plugin file to remove auth hooks on registration page This ensures that users can access /trainer/registration/ without being logged in, as intended for new trainer signups.
471 lines
No EOL
14 KiB
PHP
471 lines
No EOL
14 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 meta relationships since TEC doesn't use post_parent for attendees
|
|
$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 = %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 meta relationships to sum revenue - check multiple possible price fields
|
|
$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 = %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 array $args Query arguments including:
|
|
* - 'status' (string): Event status filter ('all', 'publish', 'future', 'draft', 'pending', 'private')
|
|
* - 'search' (string): Search term for event names
|
|
* - 'orderby' (string): Column to sort by ('date', 'name', 'status', 'capacity', 'sold', 'revenue')
|
|
* - 'order' (string): Sort order ('ASC' or 'DESC')
|
|
* - 'page' (int): Current page number
|
|
* - 'per_page' (int): Number of events per page
|
|
* - 'date_from' (string): Start date filter (Y-m-d format)
|
|
* - 'date_to' (string): End date filter (Y-m-d format)
|
|
* @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' => ''
|
|
);
|
|
|
|
$args = wp_parse_args( $args, $defaults );
|
|
|
|
// Use direct database approach since TEC interferes with WP_Query
|
|
return $this->get_events_table_data_direct( $args );
|
|
}
|
|
|
|
/**
|
|
* Get events table data using direct database queries (bypassing TEC query interference)
|
|
*/
|
|
private function get_events_table_data_direct( $args ) {
|
|
global $wpdb;
|
|
|
|
$events_data = [];
|
|
$valid_statuses = array( 'publish', 'future', 'draft', 'pending', 'private' );
|
|
|
|
// Build WHERE clauses
|
|
$where_clauses = array(
|
|
'p.post_type = %s',
|
|
'p.post_author = %d'
|
|
);
|
|
$where_values = array( 'tribe_events', $this->user_id );
|
|
|
|
// 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'] ) . '%';
|
|
}
|
|
|
|
// 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 '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'
|
|
WHERE $where_sql";
|
|
|
|
$total_items = $wpdb->get_var( $wpdb->prepare( $count_sql, $where_values ) );
|
|
|
|
// Main query with joins for all needed data
|
|
$sql = "SELECT
|
|
p.ID,
|
|
p.post_title,
|
|
p.post_status,
|
|
p.post_date,
|
|
COALESCE(pm_start.meta_value, p.post_date) as event_date,
|
|
COALESCE(
|
|
(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
|
|
) as sold,
|
|
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'
|
|
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)
|
|
$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' => $this->user_id,
|
|
'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
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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
|