🚨 CRITICAL: Fixed deployment blockers by adding missing core directories: **Community System (CRITICAL)** - includes/community/ - Login_Handler and all community classes - templates/community/ - Community login forms **Certificate System (CRITICAL)** - includes/certificates/ - 8+ certificate classes and handlers - templates/certificates/ - Certificate reports and generation templates **Core Individual Classes (CRITICAL)** - includes/class-hvac-event-summary.php - includes/class-hvac-trainer-profile-manager.php - includes/class-hvac-master-dashboard-data.php - Plus 40+ other individual HVAC classes **Major Feature Systems (HIGH)** - includes/database/ - Training leads database tables - includes/find-trainer/ - Find trainer directory and MapGeo integration - includes/google-sheets/ - Google Sheets integration system - includes/zoho/ - Complete Zoho CRM integration - includes/communication/ - Communication templates system **Template Infrastructure** - templates/attendee/, templates/email-attendees/ - templates/event-summary/, templates/status/ - templates/template-parts/ - Shared template components **Impact:** - 70+ files added covering 10+ missing directories - Resolves ALL deployment blockers and feature breakdowns - Plugin activation should now work correctly - Multi-machine deployment fully supported 🔧 Generated with Claude Code Co-Authored-By: Ben Reed <ben@tealmaker.com>
603 lines
No EOL
21 KiB
PHP
603 lines
No EOL
21 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Community Events - Communication Schedule Manager
|
|
*
|
|
* Handles CRUD operations for communication schedules.
|
|
* Manages database interactions and schedule validation.
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @subpackage Communication
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Communication_Schedule_Manager
|
|
*
|
|
* Manages communication schedule database operations.
|
|
*/
|
|
class HVAC_Communication_Schedule_Manager {
|
|
|
|
/**
|
|
* Database table names
|
|
*/
|
|
private $schedules_table;
|
|
private $logs_table;
|
|
private $tracking_table;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
global $wpdb;
|
|
|
|
$this->schedules_table = $wpdb->prefix . 'hvac_communication_schedules';
|
|
$this->logs_table = $wpdb->prefix . 'hvac_communication_logs';
|
|
$this->tracking_table = $wpdb->prefix . 'hvac_event_communication_tracking';
|
|
}
|
|
|
|
/**
|
|
* Save a communication schedule
|
|
*
|
|
* @param array $schedule_data Schedule configuration
|
|
* @return int|false Schedule ID on success, false on failure
|
|
*/
|
|
public function save_schedule( $schedule_data ) {
|
|
global $wpdb;
|
|
|
|
$data = array(
|
|
'trainer_id' => intval( $schedule_data['trainer_id'] ),
|
|
'event_id' => ! empty( $schedule_data['event_id'] ) ? intval( $schedule_data['event_id'] ) : null,
|
|
'template_id' => intval( $schedule_data['template_id'] ),
|
|
'schedule_type' => isset( $schedule_data['schedule_type'] ) ? $schedule_data['schedule_type'] : 'time_based',
|
|
'trigger_type' => sanitize_text_field( $schedule_data['trigger_type'] ),
|
|
'trigger_value' => intval( $schedule_data['trigger_value'] ),
|
|
'trigger_unit' => sanitize_text_field( $schedule_data['trigger_unit'] ),
|
|
'status' => isset( $schedule_data['status'] ) ? sanitize_text_field( $schedule_data['status'] ) : 'active',
|
|
'target_audience' => sanitize_text_field( $schedule_data['target_audience'] ),
|
|
'custom_recipient_list' => ! empty( $schedule_data['custom_recipient_list'] ) ?
|
|
sanitize_textarea_field( $schedule_data['custom_recipient_list'] ) : null,
|
|
'conditions' => ! empty( $schedule_data['conditions'] ) ?
|
|
wp_json_encode( $schedule_data['conditions'] ) : null,
|
|
'next_run' => ! empty( $schedule_data['next_run'] ) ?
|
|
sanitize_text_field( $schedule_data['next_run'] ) : null,
|
|
'is_recurring' => isset( $schedule_data['is_recurring'] ) ?
|
|
(int) $schedule_data['is_recurring'] : 0,
|
|
'recurring_interval' => ! empty( $schedule_data['recurring_interval'] ) ?
|
|
intval( $schedule_data['recurring_interval'] ) : null,
|
|
'recurring_unit' => ! empty( $schedule_data['recurring_unit'] ) ?
|
|
sanitize_text_field( $schedule_data['recurring_unit'] ) : null,
|
|
'max_runs' => ! empty( $schedule_data['max_runs'] ) ?
|
|
intval( $schedule_data['max_runs'] ) : null
|
|
);
|
|
|
|
$formats = array(
|
|
'%d', // trainer_id
|
|
'%d', // event_id
|
|
'%d', // template_id
|
|
'%s', // schedule_type
|
|
'%s', // trigger_type
|
|
'%d', // trigger_value
|
|
'%s', // trigger_unit
|
|
'%s', // status
|
|
'%s', // target_audience
|
|
'%s', // custom_recipient_list
|
|
'%s', // conditions
|
|
'%s', // next_run
|
|
'%d', // is_recurring
|
|
'%d', // recurring_interval
|
|
'%s', // recurring_unit
|
|
'%d' // max_runs
|
|
);
|
|
|
|
$result = $wpdb->insert( $this->schedules_table, $data, $formats );
|
|
|
|
if ( $result === false ) {
|
|
if ( class_exists( 'HVAC_Logger' ) ) {
|
|
HVAC_Logger::error( 'Failed to save communication schedule: ' . $wpdb->last_error, 'Schedule Manager' );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return $wpdb->insert_id;
|
|
}
|
|
|
|
/**
|
|
* Update a communication schedule
|
|
*
|
|
* @param int $schedule_id Schedule ID
|
|
* @param array $schedule_data Updated schedule data
|
|
* @return bool Success status
|
|
*/
|
|
public function update_schedule( $schedule_id, $schedule_data ) {
|
|
global $wpdb;
|
|
|
|
$data = array();
|
|
$formats = array();
|
|
|
|
// Only update provided fields
|
|
$allowed_fields = array(
|
|
'event_id' => '%d',
|
|
'template_id' => '%d',
|
|
'schedule_type' => '%s',
|
|
'trigger_type' => '%s',
|
|
'trigger_value' => '%d',
|
|
'trigger_unit' => '%s',
|
|
'status' => '%s',
|
|
'target_audience' => '%s',
|
|
'custom_recipient_list' => '%s',
|
|
'conditions' => '%s',
|
|
'next_run' => '%s',
|
|
'is_recurring' => '%d',
|
|
'recurring_interval' => '%d',
|
|
'recurring_unit' => '%s',
|
|
'max_runs' => '%d'
|
|
);
|
|
|
|
foreach ( $allowed_fields as $field => $format ) {
|
|
if ( array_key_exists( $field, $schedule_data ) ) {
|
|
if ( $field === 'conditions' && ! empty( $schedule_data[$field] ) ) {
|
|
$data[$field] = wp_json_encode( $schedule_data[$field] );
|
|
} elseif ( in_array( $format, array( '%d' ) ) ) {
|
|
$data[$field] = intval( $schedule_data[$field] );
|
|
} else {
|
|
$data[$field] = sanitize_text_field( $schedule_data[$field] );
|
|
}
|
|
$formats[] = $format;
|
|
}
|
|
}
|
|
|
|
// Add modified timestamp
|
|
$data['modified_date'] = current_time( 'mysql' );
|
|
$formats[] = '%s';
|
|
|
|
$result = $wpdb->update(
|
|
$this->schedules_table,
|
|
$data,
|
|
array( 'schedule_id' => intval( $schedule_id ) ),
|
|
$formats,
|
|
array( '%d' )
|
|
);
|
|
|
|
return $result !== false;
|
|
}
|
|
|
|
/**
|
|
* Get a communication schedule by ID
|
|
*
|
|
* @param int $schedule_id Schedule ID
|
|
* @return array|null Schedule data or null if not found
|
|
*/
|
|
public function get_schedule( $schedule_id ) {
|
|
global $wpdb;
|
|
|
|
$schedule = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT * FROM {$this->schedules_table} WHERE schedule_id = %d",
|
|
intval( $schedule_id )
|
|
), ARRAY_A );
|
|
|
|
if ( $schedule && ! empty( $schedule['conditions'] ) ) {
|
|
$schedule['conditions'] = json_decode( $schedule['conditions'], true );
|
|
}
|
|
|
|
return $schedule;
|
|
}
|
|
|
|
/**
|
|
* Get schedules by trainer
|
|
*
|
|
* @param int $trainer_id Trainer user ID
|
|
* @param int $event_id Optional specific event ID
|
|
* @return array Array of schedules
|
|
*/
|
|
public function get_schedules_by_trainer( $trainer_id, $event_id = null ) {
|
|
global $wpdb;
|
|
|
|
$where_clause = "WHERE trainer_id = %d";
|
|
$params = array( intval( $trainer_id ) );
|
|
|
|
if ( $event_id ) {
|
|
$where_clause .= " AND event_id = %d";
|
|
$params[] = intval( $event_id );
|
|
}
|
|
|
|
$schedules = $wpdb->get_results( $wpdb->prepare(
|
|
"SELECT s.*,
|
|
t.post_title as template_name,
|
|
e.post_title as event_name,
|
|
e.post_status as event_status
|
|
FROM {$this->schedules_table} s
|
|
LEFT JOIN {$wpdb->posts} t ON s.template_id = t.ID
|
|
LEFT JOIN {$wpdb->posts} e ON s.event_id = e.ID
|
|
{$where_clause}
|
|
ORDER BY s.created_date DESC",
|
|
$params
|
|
), ARRAY_A );
|
|
|
|
// Process conditions field
|
|
foreach ( $schedules as &$schedule ) {
|
|
if ( ! empty( $schedule['conditions'] ) ) {
|
|
$schedule['conditions'] = json_decode( $schedule['conditions'], true );
|
|
}
|
|
}
|
|
|
|
return $schedules;
|
|
}
|
|
|
|
/**
|
|
* Get schedules by event
|
|
*
|
|
* @param int $event_id Event ID
|
|
* @return array Array of schedules
|
|
*/
|
|
public function get_schedules_by_event( $event_id ) {
|
|
global $wpdb;
|
|
|
|
$schedules = $wpdb->get_results( $wpdb->prepare(
|
|
"SELECT s.*,
|
|
t.post_title as template_name
|
|
FROM {$this->schedules_table} s
|
|
LEFT JOIN {$wpdb->posts} t ON s.template_id = t.ID
|
|
WHERE s.event_id = %d
|
|
ORDER BY s.trigger_type, s.trigger_value",
|
|
intval( $event_id )
|
|
), ARRAY_A );
|
|
|
|
// Process conditions field
|
|
foreach ( $schedules as &$schedule ) {
|
|
if ( ! empty( $schedule['conditions'] ) ) {
|
|
$schedule['conditions'] = json_decode( $schedule['conditions'], true );
|
|
}
|
|
}
|
|
|
|
return $schedules;
|
|
}
|
|
|
|
/**
|
|
* Get active schedules
|
|
*
|
|
* @return array Array of active schedules
|
|
*/
|
|
public function get_active_schedules() {
|
|
global $wpdb;
|
|
|
|
return $wpdb->get_results(
|
|
"SELECT * FROM {$this->schedules_table}
|
|
WHERE status = 'active'
|
|
ORDER BY next_run ASC",
|
|
ARRAY_A
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get due schedules
|
|
*
|
|
* @return array Array of schedules that are due for execution
|
|
*/
|
|
public function get_due_schedules() {
|
|
global $wpdb;
|
|
|
|
$current_time = current_time( 'mysql' );
|
|
|
|
$schedules = $wpdb->get_results( $wpdb->prepare(
|
|
"SELECT * FROM {$this->schedules_table}
|
|
WHERE status = 'active'
|
|
AND next_run IS NOT NULL
|
|
AND next_run <= %s
|
|
AND (max_runs IS NULL OR run_count < max_runs)
|
|
ORDER BY next_run ASC",
|
|
$current_time
|
|
), ARRAY_A );
|
|
|
|
return $schedules;
|
|
}
|
|
|
|
/**
|
|
* Delete a communication schedule
|
|
*
|
|
* @param int $schedule_id Schedule ID
|
|
* @return bool Success status
|
|
*/
|
|
public function delete_schedule( $schedule_id ) {
|
|
global $wpdb;
|
|
|
|
// Delete associated logs first (foreign key constraint)
|
|
$wpdb->delete(
|
|
$this->logs_table,
|
|
array( 'schedule_id' => intval( $schedule_id ) ),
|
|
array( '%d' )
|
|
);
|
|
|
|
// Delete the schedule
|
|
$result = $wpdb->delete(
|
|
$this->schedules_table,
|
|
array( 'schedule_id' => intval( $schedule_id ) ),
|
|
array( '%d' )
|
|
);
|
|
|
|
return $result !== false;
|
|
}
|
|
|
|
/**
|
|
* Update schedule run tracking
|
|
*
|
|
* @param int $schedule_id Schedule ID
|
|
* @return bool Success status
|
|
*/
|
|
public function update_schedule_run_tracking( $schedule_id ) {
|
|
global $wpdb;
|
|
|
|
$schedule = $this->get_schedule( $schedule_id );
|
|
if ( ! $schedule ) {
|
|
return false;
|
|
}
|
|
|
|
$data = array(
|
|
'last_run' => current_time( 'mysql' ),
|
|
'run_count' => intval( $schedule['run_count'] ) + 1
|
|
);
|
|
|
|
// Calculate next run if recurring
|
|
if ( $schedule['is_recurring'] ) {
|
|
$next_run = $this->calculate_next_recurring_run( $schedule );
|
|
if ( $next_run ) {
|
|
$data['next_run'] = $next_run;
|
|
}
|
|
} else {
|
|
// Mark as completed if not recurring
|
|
$data['status'] = 'completed';
|
|
$data['next_run'] = null;
|
|
}
|
|
|
|
return $this->update_schedule( $schedule_id, $data );
|
|
}
|
|
|
|
/**
|
|
* Calculate next recurring run time
|
|
*
|
|
* @param array $schedule Schedule data
|
|
* @return string|null Next run time or null
|
|
*/
|
|
private function calculate_next_recurring_run( $schedule ) {
|
|
if ( ! $schedule['is_recurring'] || ! $schedule['recurring_interval'] ) {
|
|
return null;
|
|
}
|
|
|
|
$interval = $schedule['recurring_interval'];
|
|
$unit = $schedule['recurring_unit'];
|
|
|
|
$current_time = current_time( 'timestamp' );
|
|
|
|
switch ( $unit ) {
|
|
case 'days':
|
|
$next_time = $current_time + ( $interval * DAY_IN_SECONDS );
|
|
break;
|
|
case 'weeks':
|
|
$next_time = $current_time + ( $interval * WEEK_IN_SECONDS );
|
|
break;
|
|
case 'months':
|
|
$next_time = strtotime( "+{$interval} months", $current_time );
|
|
break;
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
return date( 'Y-m-d H:i:s', $next_time );
|
|
}
|
|
|
|
/**
|
|
* Validate schedule data
|
|
*
|
|
* @param array $schedule_data Schedule data to validate
|
|
* @return bool|WP_Error True if valid, WP_Error if invalid
|
|
*/
|
|
public function validate_schedule_data( $schedule_data ) {
|
|
// Required fields
|
|
$required_fields = array( 'trainer_id', 'template_id', 'trigger_type', 'target_audience' );
|
|
|
|
foreach ( $required_fields as $field ) {
|
|
if ( empty( $schedule_data[$field] ) ) {
|
|
return new WP_Error( 'missing_field', sprintf( __( 'Required field missing: %s', 'hvac-community-events' ), $field ) );
|
|
}
|
|
}
|
|
|
|
// Validate trainer exists and has permission
|
|
$trainer = get_user_by( 'id', $schedule_data['trainer_id'] );
|
|
if ( ! $trainer || ! in_array( 'hvac_trainer', $trainer->roles ) ) {
|
|
return new WP_Error( 'invalid_trainer', __( 'Invalid trainer specified.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
// Validate template exists and belongs to trainer
|
|
$template = get_post( $schedule_data['template_id'] );
|
|
if ( ! $template || $template->post_type !== 'hvac_email_template' ) {
|
|
return new WP_Error( 'invalid_template', __( 'Invalid template specified.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
if ( $template->post_author != $schedule_data['trainer_id'] && ! current_user_can( 'edit_others_posts' ) ) {
|
|
return new WP_Error( 'template_permission', __( 'You do not have permission to use this template.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
// Validate event if specified
|
|
if ( ! empty( $schedule_data['event_id'] ) ) {
|
|
$event = get_post( $schedule_data['event_id'] );
|
|
if ( ! $event || $event->post_type !== 'tribe_events' ) {
|
|
return new WP_Error( 'invalid_event', __( 'Invalid event specified.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
// Check if trainer owns the event
|
|
if ( $event->post_author != $schedule_data['trainer_id'] && ! current_user_can( 'edit_others_posts' ) ) {
|
|
return new WP_Error( 'event_permission', __( 'You do not have permission to schedule communications for this event.', 'hvac-community-events' ) );
|
|
}
|
|
}
|
|
|
|
// Validate trigger settings
|
|
$valid_trigger_types = array( 'before_event', 'after_event', 'on_registration', 'custom_date' );
|
|
if ( ! in_array( $schedule_data['trigger_type'], $valid_trigger_types ) ) {
|
|
return new WP_Error( 'invalid_trigger_type', __( 'Invalid trigger type specified.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
$valid_trigger_units = array( 'minutes', 'hours', 'days', 'weeks' );
|
|
if ( ! in_array( $schedule_data['trigger_unit'], $valid_trigger_units ) ) {
|
|
return new WP_Error( 'invalid_trigger_unit', __( 'Invalid trigger unit specified.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
// Validate audience settings
|
|
$valid_audiences = array( 'all_attendees', 'confirmed_attendees', 'pending_attendees', 'custom_list' );
|
|
if ( ! in_array( $schedule_data['target_audience'], $valid_audiences ) ) {
|
|
return new WP_Error( 'invalid_audience', __( 'Invalid target audience specified.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
// Validate custom recipient list if specified
|
|
if ( $schedule_data['target_audience'] === 'custom_list' && empty( $schedule_data['custom_recipient_list'] ) ) {
|
|
return new WP_Error( 'missing_recipients', __( 'Custom recipient list is required when target audience is set to custom list.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
// Validate recurring settings
|
|
if ( ! empty( $schedule_data['is_recurring'] ) ) {
|
|
if ( empty( $schedule_data['recurring_interval'] ) || empty( $schedule_data['recurring_unit'] ) ) {
|
|
return new WP_Error( 'invalid_recurring', __( 'Recurring interval and unit are required for recurring schedules.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
$valid_recurring_units = array( 'days', 'weeks', 'months' );
|
|
if ( ! in_array( $schedule_data['recurring_unit'], $valid_recurring_units ) ) {
|
|
return new WP_Error( 'invalid_recurring_unit', __( 'Invalid recurring unit specified.', 'hvac-community-events' ) );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check for schedule conflicts
|
|
*
|
|
* @param array $schedule_data Schedule data to check
|
|
* @return bool|WP_Error True if no conflicts, WP_Error if conflicts found
|
|
*/
|
|
public function check_schedule_conflicts( $schedule_data ) {
|
|
global $wpdb;
|
|
|
|
// Check for duplicate schedules with same trigger settings
|
|
$existing = $wpdb->get_var( $wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$this->schedules_table}
|
|
WHERE trainer_id = %d
|
|
AND event_id = %d
|
|
AND template_id = %d
|
|
AND trigger_type = %s
|
|
AND trigger_value = %d
|
|
AND trigger_unit = %s
|
|
AND status = 'active'",
|
|
$schedule_data['trainer_id'],
|
|
$schedule_data['event_id'] ?? 0,
|
|
$schedule_data['template_id'],
|
|
$schedule_data['trigger_type'],
|
|
$schedule_data['trigger_value'],
|
|
$schedule_data['trigger_unit']
|
|
) );
|
|
|
|
if ( $existing > 0 ) {
|
|
return new WP_Error( 'duplicate_schedule', __( 'A schedule with identical settings already exists.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if user can edit schedule
|
|
*
|
|
* @param int $schedule_id Schedule ID
|
|
* @return bool Whether user can edit the schedule
|
|
*/
|
|
public function user_can_edit_schedule( $schedule_id ) {
|
|
$schedule = $this->get_schedule( $schedule_id );
|
|
|
|
if ( ! $schedule ) {
|
|
return false;
|
|
}
|
|
|
|
$current_user_id = get_current_user_id();
|
|
|
|
// Owner can edit
|
|
if ( $schedule['trainer_id'] == $current_user_id ) {
|
|
return true;
|
|
}
|
|
|
|
// Admins can edit others' schedules
|
|
if ( current_user_can( 'edit_others_posts' ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get available templates for scheduling
|
|
*
|
|
* @param int $trainer_id Trainer user ID
|
|
* @return array Array of available templates
|
|
*/
|
|
public function get_available_templates( $trainer_id ) {
|
|
$templates_manager = new HVAC_Communication_Templates();
|
|
return $templates_manager->get_user_templates( $trainer_id );
|
|
}
|
|
|
|
/**
|
|
* Validate template compatibility with schedule type
|
|
*
|
|
* @param int $template_id Template ID
|
|
* @param string $schedule_type Schedule type
|
|
* @return bool|WP_Error True if compatible, WP_Error if not
|
|
*/
|
|
public function validate_template_compatibility( $template_id, $schedule_type ) {
|
|
$template = get_post( $template_id );
|
|
|
|
if ( ! $template || $template->post_type !== 'hvac_email_template' ) {
|
|
return new WP_Error( 'invalid_template', __( 'Invalid template specified.', 'hvac-community-events' ) );
|
|
}
|
|
|
|
// Check for required placeholders based on schedule type
|
|
$required_placeholders = array( '{attendee_name}', '{event_title}' );
|
|
|
|
if ( $schedule_type === 'event_based' ) {
|
|
$required_placeholders[] = '{event_date}';
|
|
$required_placeholders[] = '{event_time}';
|
|
}
|
|
|
|
foreach ( $required_placeholders as $placeholder ) {
|
|
if ( strpos( $template->post_content, $placeholder ) === false ) {
|
|
return new WP_Error( 'missing_placeholder',
|
|
sprintf( __( 'Template missing required placeholder: %s', 'hvac-community-events' ), $placeholder )
|
|
);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get schedule statistics for a trainer
|
|
*
|
|
* @param int $trainer_id Trainer user ID
|
|
* @return array Statistics array
|
|
*/
|
|
public function get_trainer_schedule_stats( $trainer_id ) {
|
|
global $wpdb;
|
|
|
|
$stats = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT
|
|
COUNT(*) as total_schedules,
|
|
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_schedules,
|
|
COUNT(CASE WHEN status = 'paused' THEN 1 END) as paused_schedules,
|
|
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_schedules,
|
|
SUM(run_count) as total_executions
|
|
FROM {$this->schedules_table}
|
|
WHERE trainer_id = %d",
|
|
$trainer_id
|
|
), ARRAY_A );
|
|
|
|
return $stats;
|
|
}
|
|
} |