- Add trainer status system (pending, approved, active, inactive, disabled) - Create access control system based on trainer status - Refactor Master Dashboard with enhanced trainer table - Add status column and filtering - Implement search and pagination - Add bulk status update functionality - Create status pages for pending and disabled trainers - Implement approval workflow with email notifications - Add email template management to settings page - Include comprehensive test suite (unit, integration, E2E) This allows Master Trainers to manage trainer accounts, approve new registrations, and control access based on account status. Trainers must be approved before accessing dashboard features. Co-Authored-By: Claude <noreply@anthropic.com>
314 lines
No EOL
10 KiB
PHP
314 lines
No EOL
10 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Community Events - Trainer Status Management
|
|
*
|
|
* Handles trainer account status logic and transitions
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Trainer_Status
|
|
*
|
|
* Manages trainer account statuses and their transitions
|
|
*/
|
|
class HVAC_Trainer_Status {
|
|
|
|
/**
|
|
* Status constants
|
|
*/
|
|
const STATUS_PENDING = 'pending';
|
|
const STATUS_APPROVED = 'approved';
|
|
const STATUS_ACTIVE = 'active';
|
|
const STATUS_INACTIVE = 'inactive';
|
|
const STATUS_DISABLED = 'disabled';
|
|
|
|
/**
|
|
* Days before a trainer is considered inactive
|
|
*/
|
|
const INACTIVE_DAYS_THRESHOLD = 180;
|
|
|
|
/**
|
|
* Get all available statuses with labels
|
|
*
|
|
* @return array
|
|
*/
|
|
public static function get_all_statuses() {
|
|
return array(
|
|
self::STATUS_PENDING => __( 'Pending', 'hvac-community-events' ),
|
|
self::STATUS_APPROVED => __( 'Approved', 'hvac-community-events' ),
|
|
self::STATUS_ACTIVE => __( 'Active', 'hvac-community-events' ),
|
|
self::STATUS_INACTIVE => __( 'Inactive', 'hvac-community-events' ),
|
|
self::STATUS_DISABLED => __( 'Disabled', 'hvac-community-events' ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get trainer's current status
|
|
*
|
|
* @param int $user_id User ID
|
|
* @return string Status value
|
|
*/
|
|
public static function get_trainer_status( $user_id ) {
|
|
// Get stored status
|
|
$stored_status = get_user_meta( $user_id, 'account_status', true );
|
|
|
|
// If disabled, return immediately
|
|
if ( $stored_status === self::STATUS_DISABLED ) {
|
|
return self::STATUS_DISABLED;
|
|
}
|
|
|
|
// If pending, return immediately
|
|
if ( $stored_status === self::STATUS_PENDING ) {
|
|
return self::STATUS_PENDING;
|
|
}
|
|
|
|
// For approved users, check if they're active or inactive
|
|
if ( $stored_status === self::STATUS_APPROVED ||
|
|
$stored_status === self::STATUS_ACTIVE ||
|
|
$stored_status === self::STATUS_INACTIVE ||
|
|
empty( $stored_status ) ) {
|
|
|
|
// Check last event date to determine active/inactive
|
|
$last_event_date = self::get_last_event_date( $user_id );
|
|
|
|
if ( $last_event_date ) {
|
|
$days_since_event = ( time() - strtotime( $last_event_date ) ) / DAY_IN_SECONDS;
|
|
|
|
if ( $days_since_event <= self::INACTIVE_DAYS_THRESHOLD ) {
|
|
return self::STATUS_ACTIVE;
|
|
} else {
|
|
return self::STATUS_INACTIVE;
|
|
}
|
|
} else {
|
|
// No events created yet, but approved
|
|
return ( $stored_status === self::STATUS_APPROVED ) ? self::STATUS_APPROVED : self::STATUS_INACTIVE;
|
|
}
|
|
}
|
|
|
|
// Default to pending for safety
|
|
return self::STATUS_PENDING;
|
|
}
|
|
|
|
/**
|
|
* Set trainer status
|
|
*
|
|
* @param int $user_id User ID
|
|
* @param string $status New status
|
|
* @return bool Success
|
|
*/
|
|
public static function set_trainer_status( $user_id, $status ) {
|
|
$valid_statuses = array_keys( self::get_all_statuses() );
|
|
|
|
if ( ! in_array( $status, $valid_statuses, true ) ) {
|
|
return false;
|
|
}
|
|
|
|
$old_status = get_user_meta( $user_id, 'account_status', true );
|
|
|
|
// Update the status
|
|
update_user_meta( $user_id, 'account_status', $status );
|
|
|
|
// If transitioning to approved, set approval date
|
|
if ( $status === self::STATUS_APPROVED && $old_status !== self::STATUS_APPROVED ) {
|
|
update_user_meta( $user_id, 'approval_date', current_time( 'mysql' ) );
|
|
}
|
|
|
|
// Trigger status change action
|
|
do_action( 'hvac_trainer_status_changed', $user_id, $status, $old_status );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the date of the trainer's last event
|
|
*
|
|
* @param int $user_id User ID
|
|
* @return string|false Date string or false if no events
|
|
*/
|
|
public static function get_last_event_date( $user_id ) {
|
|
global $wpdb;
|
|
|
|
// Get the most recent event by this trainer
|
|
$last_event_date = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT pm.meta_value
|
|
FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
WHERE p.post_author = %d
|
|
AND p.post_type = %s
|
|
AND p.post_status IN ('publish', 'private')
|
|
AND pm.meta_key = '_EventStartDate'
|
|
ORDER BY pm.meta_value DESC
|
|
LIMIT 1",
|
|
$user_id,
|
|
class_exists( 'Tribe__Events__Main' ) ? Tribe__Events__Main::POSTTYPE : 'tribe_events'
|
|
) );
|
|
|
|
// Store the last event date for quick access
|
|
if ( $last_event_date ) {
|
|
update_user_meta( $user_id, 'last_event_date', $last_event_date );
|
|
}
|
|
|
|
return $last_event_date;
|
|
}
|
|
|
|
/**
|
|
* Check if a trainer can access protected pages
|
|
*
|
|
* @param int $user_id User ID
|
|
* @return bool
|
|
*/
|
|
public static function can_access_trainer_pages( $user_id ) {
|
|
$status = self::get_trainer_status( $user_id );
|
|
|
|
// Only active and inactive trainers can access protected pages
|
|
return in_array( $status, array( self::STATUS_ACTIVE, self::STATUS_INACTIVE ), true );
|
|
}
|
|
|
|
/**
|
|
* Get trainers by status
|
|
*
|
|
* @param string|array $status Status or array of statuses
|
|
* @param array $args Additional query arguments
|
|
* @return array Array of user objects
|
|
*/
|
|
public static function get_trainers_by_status( $status, $args = array() ) {
|
|
$default_args = array(
|
|
'role__in' => array( 'hvac_trainer', 'hvac_master_trainer' ),
|
|
'meta_query' => array(),
|
|
);
|
|
|
|
$args = wp_parse_args( $args, $default_args );
|
|
|
|
// Handle dynamic statuses (active/inactive)
|
|
if ( is_string( $status ) && in_array( $status, array( self::STATUS_ACTIVE, self::STATUS_INACTIVE ), true ) ) {
|
|
// For active/inactive, we need to check last event dates
|
|
$users = get_users( $args );
|
|
$filtered_users = array();
|
|
|
|
foreach ( $users as $user ) {
|
|
if ( self::get_trainer_status( $user->ID ) === $status ) {
|
|
$filtered_users[] = $user;
|
|
}
|
|
}
|
|
|
|
return $filtered_users;
|
|
}
|
|
|
|
// For static statuses (pending, approved, disabled)
|
|
$statuses = (array) $status;
|
|
$args['meta_query'][] = array(
|
|
'key' => 'account_status',
|
|
'value' => $statuses,
|
|
'compare' => 'IN',
|
|
);
|
|
|
|
return get_users( $args );
|
|
}
|
|
|
|
/**
|
|
* Get trainer registration date
|
|
*
|
|
* @param int $user_id User ID
|
|
* @return string Formatted date
|
|
*/
|
|
public static function get_registration_date( $user_id ) {
|
|
$user = get_userdata( $user_id );
|
|
|
|
if ( ! $user ) {
|
|
return '';
|
|
}
|
|
|
|
return date( 'Y-m-d', strtotime( $user->user_registered ) );
|
|
}
|
|
|
|
/**
|
|
* Get trainer's total revenue
|
|
*
|
|
* @param int $user_id User ID
|
|
* @return float
|
|
*/
|
|
public static function get_trainer_revenue( $user_id ) {
|
|
global $wpdb;
|
|
|
|
// This query matches the one in HVAC_Master_Dashboard_Data
|
|
$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_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 = %d",
|
|
$user_id
|
|
) );
|
|
|
|
return (float) ( $revenue ?: 0.00 );
|
|
}
|
|
|
|
/**
|
|
* Get total events count for a trainer
|
|
*
|
|
* @param int $user_id User ID
|
|
* @return int
|
|
*/
|
|
public static function get_trainer_event_count( $user_id ) {
|
|
global $wpdb;
|
|
|
|
$count = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*)
|
|
FROM {$wpdb->posts}
|
|
WHERE post_author = %d
|
|
AND post_type = %s
|
|
AND post_status IN ('publish', 'future', 'draft', 'pending', 'private')",
|
|
$user_id,
|
|
class_exists( 'Tribe__Events__Main' ) ? Tribe__Events__Main::POSTTYPE : 'tribe_events'
|
|
) );
|
|
|
|
return (int) $count;
|
|
}
|
|
|
|
/**
|
|
* Bulk update trainer statuses
|
|
*
|
|
* @param array $user_ids Array of user IDs
|
|
* @param string $new_status New status to set
|
|
* @return array Results array with success/failure counts
|
|
*/
|
|
public static function bulk_update_status( $user_ids, $new_status ) {
|
|
$results = array(
|
|
'success' => 0,
|
|
'failed' => 0,
|
|
'errors' => array(),
|
|
);
|
|
|
|
foreach ( $user_ids as $user_id ) {
|
|
if ( self::set_trainer_status( $user_id, $new_status ) ) {
|
|
$results['success']++;
|
|
} else {
|
|
$results['failed']++;
|
|
$results['errors'][] = sprintf(
|
|
__( 'Failed to update status for user ID %d', 'hvac-community-events' ),
|
|
$user_id
|
|
);
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
} |