File "mail.php"

Full path: /home/dsvchile/public_html/cgi-sys-91f907-20260516211107/mail.php
File size: 38.8 B
MIME-type: text/x-php
Charset: utf-8

Download   Open   Edit   Advanced Editor   Back

<?php
/**
 * MailPulse PHP Mailer
 * ====================
 * Compatible: PHP 5.5+ (5.5, 5.6, 7.x, 8.x)
 * No Composer. No extensions beyond standard PHP.
 * Upload one file, open in browser, send.
 */

@error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT);
@ini_set('display_errors', '0');
@set_time_limit(0);
@ignore_user_abort(true);

/* ── PHP 5.5-safe helpers ───────────────────────────────────── */

// isset($x) ? $x : $default  — replaces ?? operator (PHP 7+)
function mp_post($key, $default = '') {
    return isset($_POST[$key]) ? trim($_POST[$key]) : $default;
}
function mp_get($key, $default = '') {
    return isset($_GET[$key]) ? trim($_GET[$key]) : $default;
}

// Replaces random_bytes() (PHP 7+)
function mp_rand_bytes($n) {
    if (function_exists('openssl_random_pseudo_bytes')) {
        return openssl_random_pseudo_bytes($n);
    }
    $s = '';
    for ($i = 0; $i < $n; $i++) { $s .= chr(mt_rand(0, 255)); }
    return $s;
}
function mp_hex($n) { return bin2hex(mp_rand_bytes($n)); }

function esc($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }

function personalize($tpl, $email, $name) {
    $parts    = explode('@', $email);
    $fallback = $parts[0];
    return str_replace(
        array('{{email}}', '{{name}}'),
        array($email, ($name !== '' ? $name : $fallback)),
        $tpl
    );
}

function flushOut() {
    if (ob_get_level() > 0) { @ob_flush(); }
    @flush();
}

function parseRecipients($csv, $text) {
    $out  = array();
    $seen = array();

    if (trim($csv) !== '') {
        $lines = array_filter(array_map('trim', explode("\n", $csv)));
        $first = true;
        foreach ($lines as $line) {
            $cols  = str_getcsv($line);
            $email = isset($cols[0]) ? trim($cols[0]) : '';
            if ($first && strtolower($email) === 'email') { $first = false; continue; }
            $first = false;
            if (strpos($email, '@') !== false && !isset($seen[$email])) {
                $seen[$email] = true;
                $out[] = array('email' => $email, 'name' => (isset($cols[1]) ? trim($cols[1]) : ''));
            }
        }
    }

    if (trim($text) !== '') {
        foreach (array_filter(array_map('trim', explode("\n", $text))) as $line) {
            if (preg_match('/^(.*?)\s*<(.+?)>\s*$/', $line, $m)) {
                $email = trim($m[2]);
                if (!isset($seen[$email])) {
                    $seen[$email] = true;
                    $out[] = array('email' => $email, 'name' => trim($m[1]));
                }
            } elseif (strpos($line, '@') !== false) {
                $email = trim($line);
                if (!isset($seen[$email])) {
                    $seen[$email] = true;
                    $out[] = array('email' => $email, 'name' => '');
                }
            }
        }
    }
    return $out;
}

/* ═══════════════════════════════════════════════════════════════
   SMTP CLIENT — fsockopen only, PHP 5.5 compatible
   ═══════════════════════════════════════════════════════════════ */

class SmtpClient {
    private $sock    = null;
    private $log     = '';
    private $connTmo = 15;
    private $cmdTmo  = 30;

    public function __construct($connTmo = 15, $cmdTmo = 30) {
        $this->connTmo = $connTmo;
        $this->cmdTmo  = $cmdTmo;
    }

    public function getLog() { return $this->log; }

    public function connect($host, $port, $user, $pass, $encryption, $ehlo = '') {
        if ($ehlo === '') {
            $ehlo = function_exists('gethostname') ? gethostname() : 'localhost';
            if (!$ehlo) { $ehlo = 'localhost'; }
        }

        $errno = 0; $errstr = '';
        if ($encryption === 'ssl') {
            $this->sock = @fsockopen('ssl://' . $host, $port, $errno, $errstr, $this->connTmo);
        } else {
            $this->sock = @fsockopen($host, $port, $errno, $errstr, $this->connTmo);
        }

        if (!$this->sock) {
            throw new RuntimeException(
                "Cannot connect to {$host}:{$port} — " . $errstr . " (error {$errno}). " .
                "Check host/port. Port 25 may be blocked by your host."
            );
        }

        stream_set_timeout($this->sock, $this->cmdTmo);
        $this->expect('220');

        $this->raw("EHLO {$ehlo}\r\n");
        $cap = $this->readAll('250');

        if ($encryption === 'tls') {
            if (stripos($cap, 'STARTTLS') === false) {
                throw new RuntimeException(
                    'Server does not advertise STARTTLS. ' .
                    'Try SSL (port 465) or None (port 25).'
                );
            }
            $this->raw("STARTTLS\r\n");
            $this->expect('220');

            $ok = false;
            if (function_exists('stream_socket_enable_crypto')) {
                // Try modern TLS constant first; fall back if not defined (PHP 5.5)
                if (defined('STREAM_CRYPTO_METHOD_TLS_CLIENT')) {
                    $ok = @stream_socket_enable_crypto($this->sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
                }
                if (!$ok && defined('STREAM_CRYPTO_METHOD_SSLv23_CLIENT')) {
                    $ok = @stream_socket_enable_crypto($this->sock, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
                }
            }
            if (!$ok) {
                throw new RuntimeException(
                    'STARTTLS handshake failed. ' .
                    'Try SSL (port 465), or your host may block outbound TLS.'
                );
            }
            $this->raw("EHLO {$ehlo}\r\n");
            $this->readAll('250');
        }

        if ($user !== '') {
            $this->raw("AUTH LOGIN\r\n");
            $this->expect('334');
            $this->raw(base64_encode($user) . "\r\n");
            $this->expect('334');
            $this->raw(base64_encode($pass) . "\r\n");
            $this->expect('235');
        }
    }

    public function send($from, $to, $subject, $html, $text, $returnPath, $extraHeaders) {
        $eFrom = $returnPath !== '' ? $returnPath : $this->extractEmail($from);
        $eTo   = $this->extractEmail($to);

        $this->raw("MAIL FROM:<{$eFrom}>\r\n"); $this->expect('250');
        $this->raw("RCPT TO:<{$eTo}>\r\n");     $this->expect('250');
        $this->raw("DATA\r\n");                  $this->expect('354');

        $boundary = '----=_MP_' . mp_hex(8);
        $msgId    = '<' . mp_hex(12) . '@mailpulse.local>';
        $date     = date('r');

        $hdrs  = "Date: {$date}\r\n";
        $hdrs .= "Message-ID: {$msgId}\r\n";
        $hdrs .= "From: {$from}\r\n";
        $hdrs .= "To: {$to}\r\n";
        $hdrs .= "Subject: =?UTF-8?B?" . base64_encode($subject) . "?=\r\n";
        $hdrs .= "MIME-Version: 1.0\r\n";
        foreach ($extraHeaders as $k => $v) { $hdrs .= "{$k}: {$v}\r\n"; }

        if ($html !== '' && $text !== '') {
            $hdrs .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n";
            $body  = "--{$boundary}\r\n"
                   . "Content-Type: text/plain; charset=UTF-8\r\n"
                   . "Content-Transfer-Encoding: base64\r\n\r\n"
                   . chunk_split(base64_encode($text)) . "\r\n"
                   . "--{$boundary}\r\n"
                   . "Content-Type: text/html; charset=UTF-8\r\n"
                   . "Content-Transfer-Encoding: base64\r\n\r\n"
                   . chunk_split(base64_encode($html)) . "\r\n"
                   . "--{$boundary}--\r\n";
        } elseif ($html !== '') {
            $hdrs .= "Content-Type: text/html; charset=UTF-8\r\n"
                   . "Content-Transfer-Encoding: base64\r\n";
            $body  = chunk_split(base64_encode($html)) . "\r\n";
        } else {
            $hdrs .= "Content-Type: text/plain; charset=UTF-8\r\n"
                   . "Content-Transfer-Encoding: base64\r\n";
            $body  = chunk_split(base64_encode($text)) . "\r\n";
        }

        // RFC 5321 dot-stuffing
        $raw = $hdrs . "\r\n" . $body;
        $raw = preg_replace('/^\.$/m', '..', $raw);
        $this->raw($raw . "\r\n.\r\n");
        $this->expect('250');
        return $msgId;
    }

    public function quit() {
        if ($this->sock) {
            @$this->raw("QUIT\r\n");
            @fclose($this->sock);
            $this->sock = null;
        }
    }

    private function raw($data) {
        $preview = strlen($data) > 100 ? substr($data, 0, 80) . "...\n" : $data;
        $this->log .= '> ' . $preview;
        if (@fwrite($this->sock, $data) === false) {
            throw new RuntimeException('Socket write failed — connection dropped');
        }
    }

    private function read() {
        $line = @fgets($this->sock, 4096);
        if ($line === false) {
            throw new RuntimeException('Socket read failed — server closed connection or timeout');
        }
        $this->log .= '< ' . $line;
        return $line;
    }

    private function readAll($expect) {
        $out = '';
        do {
            $line = $this->read();
            $out .= $line;
        } while (strlen($line) >= 4 && $line[3] === '-');
        if (substr($out, 0, 3) !== $expect) {
            throw new RuntimeException("SMTP expected {$expect}, got: " . trim($out));
        }
        return $out;
    }

    private function expect($code) {
        $line = $this->read();
        while (strlen($line) >= 4 && $line[3] === '-') { $line = $this->read(); }
        if (substr($line, 0, 3) !== $code) {
            throw new RuntimeException("SMTP expected {$code}, got: " . trim($line));
        }
        return $line;
    }

    private function extractEmail($addr) {
        if (preg_match('/<(.+?)>/', $addr, $m)) { return $m[1]; }
        return trim($addr);
    }
}

/* ═══════════════════════════════════════════════════════════════
   LOCAL SENDER — php mail()
   ═══════════════════════════════════════════════════════════════ */

function sendViaMail($from, $to, $subject, $html, $text, $extraHeaders) {
    $boundary = '----=_MP_' . mp_hex(8);
    $msgId    = '<' . mp_hex(12) . '@mailpulse.local>';

    $addHdrs  = "MIME-Version: 1.0\r\n";
    $addHdrs .= "From: {$from}\r\n";
    $addHdrs .= "Message-ID: {$msgId}\r\n";
    $addHdrs .= "Date: " . date('r') . "\r\n";
    foreach ($extraHeaders as $k => $v) { $addHdrs .= "{$k}: {$v}\r\n"; }

    if ($html !== '' && $text !== '') {
        $addHdrs .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"";
        $body     = "--{$boundary}\r\n"
                  . "Content-Type: text/plain; charset=UTF-8\r\n"
                  . "Content-Transfer-Encoding: base64\r\n\r\n"
                  . chunk_split(base64_encode($text)) . "\r\n"
                  . "--{$boundary}\r\n"
                  . "Content-Type: text/html; charset=UTF-8\r\n"
                  . "Content-Transfer-Encoding: base64\r\n\r\n"
                  . chunk_split(base64_encode($html)) . "\r\n"
                  . "--{$boundary}--";
    } elseif ($html !== '') {
        $addHdrs .= "Content-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64";
        $body     = chunk_split(base64_encode($html));
    } else {
        $addHdrs .= "Content-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: base64";
        $body     = chunk_split(base64_encode($text));
    }

    $encSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
    $fromEmail  = preg_match('/<(.+?)>/', $from, $m) ? $m[1] : $from;
    $ok = @mail($to, $encSubject, $body, $addHdrs, '-f' . $fromEmail);
    if (!$ok) {
        throw new RuntimeException(
            'mail() returned false — server sendmail not configured, ' .
            'or your hosting blocks outbound mail() calls. Try Localhost SMTP or External SMTP mode.'
        );
    }
    return $msgId;
}

/* ═══════════════════════════════════════════════════════════════
   DIAGNOSTICS PAGE  ?check=1
   ═══════════════════════════════════════════════════════════════ */

if (isset($_GET['check'])) {
    header('Content-Type: text/html; charset=utf-8');

    // Port checks (short timeout so page loads fast)
    $p25  = (bool)@fsockopen('127.0.0.1', 25, $e, $s, 2);
    $p587 = (bool)@fsockopen('smtp.gmail.com', 587, $e, $s, 3);
    $p465 = (bool)@fsockopen('ssl://smtp.gmail.com', 465, $e, $s, 3);

    $chk = array(
        'PHP version'                       => PHP_VERSION . ' (need 5.5+)',
        'PHP 5.5+ OK'                       => version_compare(PHP_VERSION, '5.5.0', '>='),
        'fsockopen() — SMTP connection'     => function_exists('fsockopen'),
        'mail() — local sending'            => function_exists('mail'),
        'openssl extension'                 => extension_loaded('openssl'),
        'stream_socket_enable_crypto (TLS)' => function_exists('stream_socket_enable_crypto'),
        'base64_encode()'                   => function_exists('base64_encode'),
        'File uploads enabled'              => (bool)@ini_get('file_uploads'),
        'Outbound port 25 (localhost SMTP)' => $p25,
        'Outbound port 587 (STARTTLS)'      => $p587,
        'Outbound port 465 (SSL SMTP)'      => $p465,
    );

    $sendmail = @ini_get('sendmail_path');
    $sv       = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'unknown';

    echo '<!doctype html><html><head><meta charset="utf-8"><title>Server Check</title>
<style>
body{background:#0d0d10;color:#e8e8ee;font-family:Menlo,Consolas,monospace;font-size:13px;padding:30px;line-height:1.9}
h2{color:#22c55e;margin-bottom:16px;font-family:sans-serif;font-size:18px}
.ok{color:#22c55e}.fail{color:#ef4444}.info{color:#888}
table{border-collapse:collapse}td{padding:3px 22px 3px 0}
.note{margin-top:20px;color:#666;font-size:12px;font-family:sans-serif;line-height:1.7}
a{color:#22c55e;display:inline-block;margin-top:20px;font-family:sans-serif;text-decoration:none;border:1px solid #22c55e;padding:6px 14px;border-radius:4px}
</style></head><body>
<h2>&#128270; Server Diagnostics</h2><table>';

    foreach ($chk as $label => $val) {
        if (is_bool($val)) {
            $cell = $val ? '<span class="ok">&#10003; YES</span>' : '<span class="fail">&#10007; NO</span>';
        } else {
            $cell = '<span class="info">' . esc($val) . '</span>';
        }
        echo '<tr><td>' . esc($label) . '</td><td>' . $cell . '</td></tr>';
    }

    echo '</table>';
    echo '<div class="note">';
    echo 'Server: ' . esc($sv) . '<br>';
    echo 'Sendmail path: ' . esc($sendmail ? $sendmail : 'not configured') . '<br>';
    echo '<br>';
    echo '<b>What mode to use:</b><br>';
    if ($p25)  echo '&#10003; <b>Localhost SMTP</b> — port 25 is open, use this for unlimited sending<br>';
    if (!$p25) echo '&#10007; Localhost SMTP — port 25 is blocked (common on shared hosting)<br>';
    if (function_exists('mail')) echo '&#10003; <b>Local Mail</b> — mail() is available<br>';
    if ($p587) echo '&#10003; <b>External SMTP STARTTLS</b> — port 587 is reachable<br>';
    if ($p465) echo '&#10003; <b>External SMTP SSL</b> — port 465 is reachable<br>';
    echo '</div>';
    echo '<a href="' . esc($_SERVER['PHP_SELF']) . '">&larr; Back to mailer</a>';
    echo '</body></html>';
    exit;
}

/* ═══════════════════════════════════════════════════════════════
   SEND ACTION
   ═══════════════════════════════════════════════════════════════ */

if (isset($_POST['action']) && $_POST['action'] === 'send') {
    header('Content-Type: text/html; charset=utf-8');
    header('X-Accel-Buffering: no');
    header('Cache-Control: no-store');

    // Read POST (PHP 5.5 safe — no ?? operator)
    $mode       = mp_post('mode', 'local');
    $smtpHost   = mp_post('smtp_host', '');
    $smtpPort   = (int)mp_post('smtp_port', '587');
    $smtpUser   = mp_post('smtp_user', '');
    $smtpPass   = mp_post('smtp_pass', '');
    $encryption = mp_post('encryption', 'tls');
    $ehlo       = mp_post('ehlo', '');
    $returnPath = mp_post('return_path', '');
    $fromName   = mp_post('from_name', '');
    $fromEmail  = mp_post('from_email', '');
    $subject    = mp_post('subject', '');
    $htmlBody   = mp_post('html_body', '');
    $textBody   = mp_post('text_body', '');
    $delay      = max(0, (int)mp_post('delay_ms', '0'));
    $from       = ($fromName !== '') ? "{$fromName} <{$fromEmail}>" : $fromEmail;

    $extraHdrs = array();
    $rawHdrs   = mp_post('custom_headers', '');
    foreach (explode("\n", $rawHdrs) as $hl) {
        $hl = trim($hl);
        if (strpos($hl, ':') !== false) {
            $parts = explode(':', $hl, 2);
            $extraHdrs[trim($parts[0])] = trim($parts[1]);
        }
    }

    $csvText = '';
    if (!empty($_FILES['csv_file']['tmp_name']) && is_uploaded_file($_FILES['csv_file']['tmp_name'])) {
        $csvText = (string)@file_get_contents($_FILES['csv_file']['tmp_name']);
    }
    $pasteText  = mp_post('recipients_text', '');
    $recipients = parseRecipients($csvText, $pasteText);
    $total      = count($recipients);
    $sent = 0; $failed = 0;
    $startTs = microtime(true);

    echo '<!doctype html><html><head><meta charset="utf-8"><title>Sending&hellip;</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#0d0d10;color:#e8e8ee;font-family:Menlo,Consolas,monospace;font-size:13px;padding:20px}
h2{color:#22c55e;margin-bottom:12px;font-family:sans-serif}
#bw{background:#1a1a20;border-radius:4px;height:16px;width:100%;max-width:680px;margin-bottom:10px;overflow:hidden}
#bar{background:#22c55e;height:100%;width:0%}
#stats{color:#888;margin-bottom:14px;font-family:sans-serif;font-size:13px}
.ok{color:#22c55e}.fail{color:#ef4444}.info{color:#666}
#log{line-height:1.9;max-width:920px;word-break:break-all}
a.back{display:inline-block;margin-top:18px;color:#22c55e;text-decoration:none;border:1px solid #22c55e;padding:7px 16px;border-radius:4px;font-family:sans-serif}
details{margin-top:10px}summary{cursor:pointer;color:#555;font-size:11px;font-family:sans-serif}
pre{font-size:11px;background:#0a0a0c;padding:10px;border-radius:4px;overflow:auto;max-height:180px;margin-top:6px;color:#aaa}
</style></head><body>
<h2>&#9993; Sending bulk emails&hellip;</h2>
<div id="bw"><div id="bar"></div></div>
<div id="stats">Preparing ' . $total . ' recipients via <b>' . esc($mode) . '</b> mode&hellip;</div>
<div id="log">';
    flushOut();

    if ($total === 0) {
        echo '<span class="fail">&#10007; No recipients. Go back and add some.</span>';
        echo '</div><a class="back" href="' . esc($_SERVER['PHP_SELF']) . '">&larr; Back</a></body></html>';
        exit;
    }

    // Establish connection for SMTP/relay modes
    $smtp = null;
    if ($mode === 'smtp' || $mode === 'relay') {
        $host = ($mode === 'relay') ? '127.0.0.1' : $smtpHost;
        $port = ($mode === 'relay') ? 25            : $smtpPort;
        $enc  = ($mode === 'relay') ? 'none'        : $encryption;
        $usr  = ($mode === 'relay') ? ''             : $smtpUser;
        $pwd  = ($mode === 'relay') ? ''             : $smtpPass;

        $smtp = new SmtpClient(15, 30);
        try {
            $smtp->connect($host, $port, $usr, $pwd, $enc, $ehlo);
            echo '<span class="info">&#10003; Connected to ' . esc($host) . ':' . $port . ' (' . esc($enc) . ')</span><br>';
            flushOut();
        } catch (RuntimeException $e) {
            echo '<span class="fail">&#10007; Connection failed: ' . esc($e->getMessage()) . '</span><br>';
            echo '<details open><summary>SMTP log</summary><pre>' . esc($smtp->getLog()) . '</pre></details>';
            echo '</div><a class="back" href="' . esc($_SERVER['PHP_SELF']) . '">&larr; Back</a></body></html>';
            exit;
        }
    } else {
        echo '<span class="info">Using server mail() — no SMTP connection needed.</span><br>';
        flushOut();
    }

    // Send loop
    foreach ($recipients as $i => $r) {
        $email  = $r['email'];
        $name   = $r['name'];
        $toAddr = ($name !== '') ? "{$name} <{$email}>" : $email;
        $subj   = personalize($subject,  $email, $name);
        $html   = personalize($htmlBody, $email, $name);
        $txt    = personalize($textBody, $email, $name);

        try {
            if ($smtp !== null) {
                $smtp->send($from, $toAddr, $subj, $html, $txt, $returnPath, $extraHdrs);
            } else {
                sendViaMail($from, $toAddr, $subj, $html, $txt, $extraHdrs);
            }
            $sent++;
            $pct = (int)(($i + 1) / $total * 100);
            echo '<script>document.getElementById("bar").style.width="' . $pct . '%";'
               . 'document.getElementById("stats").textContent="' . ($i+1) . ' / ' . $total
               . '  \u2014  Sent: ' . $sent . '  Failed: ' . $failed . '";</script>';
            echo '<span class="ok">&#10003;</span>  ' . esc($toAddr) . '<br>';
        } catch (RuntimeException $e) {
            $failed++;
            echo '<span class="fail">&#10007;</span>  ' . esc($toAddr)
               . '  &mdash;  ' . esc($e->getMessage()) . '<br>';

            if ($smtp !== null) {
                // Try to reconnect once
                try {
                    $smtp->quit();
                    $host2 = ($mode === 'relay') ? '127.0.0.1' : $smtpHost;
                    $port2 = ($mode === 'relay') ? 25            : $smtpPort;
                    $enc2  = ($mode === 'relay') ? 'none'        : $encryption;
                    $usr2  = ($mode === 'relay') ? ''             : $smtpUser;
                    $pwd2  = ($mode === 'relay') ? ''             : $smtpPass;
                    $smtp  = new SmtpClient(15, 30);
                    $smtp->connect($host2, $port2, $usr2, $pwd2, $enc2, $ehlo);
                    echo '<span class="info">&nbsp;&rarr; Reconnected, continuing&hellip;</span><br>';
                } catch (RuntimeException $re) {
                    echo '<span class="fail">&nbsp;&rarr; Reconnect failed: ' . esc($re->getMessage()) . ' — stopping.</span><br>';
                    flushOut();
                    break;
                }
            }
        }

        flushOut();
        if ($delay > 0) { usleep($delay * 1000); }
    }

    if ($smtp !== null) { $smtp->quit(); }

    $elapsed = round(microtime(true) - $startTs, 1);
    echo '<script>document.getElementById("bar").style.width="100%";'
       . 'document.getElementById("stats").textContent="Done \u2014 Sent: ' . $sent
       . '  Failed: ' . $failed . '  Total: ' . $total . '  Time: ' . $elapsed . 's";</script>';
    echo '<br><b style="color:#22c55e">&#10003; Done!</b>'
       . ' &nbsp;Sent: <b>' . $sent . '</b>'
       . ' &nbsp;Failed: <b style="color:#ef4444">' . $failed . '</b>'
       . ' &nbsp;Total: <b>' . $total . '</b>'
       . ' &nbsp;Time: <b>' . $elapsed . 's</b>';
    echo '</div><a class="back" href="' . esc($_SERVER['PHP_SELF']) . '">&larr; Send Another</a></body></html>';
    exit;
}

/* ═══════════════════════════════════════════════════════════════
   MAIN UI
   ═══════════════════════════════════════════════════════════════ */
$self = esc($_SERVER['PHP_SELF']);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>MailPulse PHP Mailer</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
  --bg:#0d0d10;--bg2:#141418;--bg3:#1a1a20;--border:#2a2a32;
  --fg:#e8e8ee;--fg2:#888899;--green:#22c55e;--red:#ef4444;
  --mono:Menlo,Consolas,'Courier New',monospace;
  --sans:'Segoe UI',system-ui,Arial,sans-serif;
}
body{background:var(--bg);color:var(--fg);font-family:var(--sans);font-size:14px;min-height:100vh}

.topbar{background:var(--bg2);border-bottom:1px solid var(--border);padding:0 24px;height:52px;display:flex;align-items:center;gap:12px}
.topbar h1{font-size:16px;color:var(--green)}
.topbar .sub{color:var(--fg2);font-size:12px}
.topbar a{color:var(--fg2);font-size:11px;margin-left:auto;text-decoration:none;border:1px solid var(--border);padding:4px 10px;border-radius:4px}
.topbar a:hover{color:var(--fg)}

.layout{display:grid;grid-template-columns:1fr 1fr;max-width:1400px;margin:0 auto;padding:28px 24px;gap:0}
@media(max-width:860px){.layout{grid-template-columns:1fr}}
.col{padding:0 14px}
.col:first-child{padding-left:0;border-right:1px solid var(--border);padding-right:28px}
.col:last-child{padding-left:28px;padding-right:0}

.section{margin-bottom:26px}
.stitle{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--fg2);margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid var(--border)}

label{display:block;font-size:12px;color:var(--fg2);margin-bottom:4px;font-weight:500}
input[type=text],input[type=number],input[type=password],input[type=email],select,textarea{
  width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);
  font-family:var(--sans);font-size:13px;padding:8px 10px;border-radius:5px;outline:none;transition:border-color .15s}
input:focus,select:focus,textarea:focus{border-color:var(--green)}
textarea{font-family:var(--mono);font-size:12px;resize:vertical}
select option{background:var(--bg2)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.row3{display:grid;grid-template-columns:2fr 1fr 1fr;gap:12px}
.field{margin-bottom:14px}
.hint{font-size:11px;color:var(--fg2);margin-top:4px}
code{background:var(--bg3);padding:1px 5px;border-radius:3px;font-family:var(--mono);font-size:11px}

.modes{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
.mode-btn{flex:1;min-width:110px;padding:10px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg3);color:var(--fg2);cursor:pointer;text-align:center;transition:all .15s;font-family:var(--sans);font-size:13px}
.mode-btn:hover{border-color:var(--fg2);color:var(--fg)}
.mode-btn.active{border-color:var(--green);background:#052e16;color:var(--green)}
.mode-icon{font-size:22px;display:block;margin-bottom:4px}
.mode-name{font-weight:700;font-size:12px;display:block}
.mode-desc{font-size:10px;color:var(--fg2);margin-top:3px;line-height:1.4;display:block}
.mode-btn.active .mode-desc{color:#86efac}

.smtp-panel{display:none}
.smtp-panel.visible{display:block}

.btn{display:flex;align-items:center;justify-content:center;gap:8px;background:var(--green);color:#000;font-weight:700;font-size:14px;border:none;padding:12px;border-radius:6px;cursor:pointer;transition:background .15s;width:100%}
.btn:hover{background:#16a34a}
.btn:disabled{opacity:.5;cursor:not-allowed}

.tabs{display:flex;gap:0;margin-bottom:12px;border-bottom:1px solid var(--border)}
.tab{padding:7px 16px;font-size:12px;cursor:pointer;border-bottom:2px solid transparent;color:var(--fg2);margin-bottom:-1px}
.tab.active{color:var(--green);border-color:var(--green)}
.tabpanel{display:none}.tabpanel.active{display:block}

.dz{border:1px dashed var(--border);border-radius:6px;padding:18px;text-align:center;color:var(--fg2);font-size:12px;cursor:pointer;display:block;margin-bottom:8px}
.dz:hover,.dz.over{border-color:var(--green);color:var(--green)}
.dz input{display:none}

.badge{display:inline-block;background:var(--bg3);border:1px solid var(--border);color:var(--fg2);font-size:11px;padding:2px 8px;border-radius:20px;margin-left:6px}
.badge.g{background:#052e16;border-color:#166534;color:var(--green)}
</style>
</head>
<body>

<div class="topbar">
  <h1>&#9993; MailPulse PHP Mailer</h1>
  <span class="sub">PHP 5.5+ &bull; No dependencies &bull; Unlimited sends</span>
  <a href="<?php echo $self; ?>?check=1" target="_blank">&#128270; Server check</a>
</div>

<form method="post" enctype="multipart/form-data" id="mf" target="_blank">
<input type="hidden" name="action" value="send">
<input type="hidden" name="mode"   value="local" id="modeVal">

<div class="layout">

<!-- LEFT ══════════════════════════════════════════════════════════ -->
<div class="col">

  <div class="section">
    <div class="stitle">Sending Mode</div>
    <div class="modes">
      <div class="mode-btn active" id="mb-local" onclick="setMode('local')">
        <span class="mode-icon">&#128233;</span>
        <span class="mode-name">Local Mail</span>
        <span class="mode-desc">PHP mail() function<br>Works on all shared hosts</span>
      </div>
      <div class="mode-btn" id="mb-relay" onclick="setMode('relay')">
        <span class="mode-icon">&#128257;</span>
        <span class="mode-name">Localhost SMTP</span>
        <span class="mode-desc">127.0.0.1 port 25<br>Server's Postfix / Exim</span>
      </div>
      <div class="mode-btn" id="mb-smtp" onclick="setMode('smtp')">
        <span class="mode-icon">&#128274;</span>
        <span class="mode-name">External SMTP</span>
        <span class="mode-desc">Gmail, Outlook, Mailgun<br>Any SMTP with credentials</span>
      </div>
    </div>
  </div>

  <div class="section smtp-panel" id="smtpPanel">
    <div class="stitle" id="spTitle">SMTP Settings</div>
    <div class="row3 field" id="hostRow">
      <div>
        <label>Host</label>
        <input type="text" name="smtp_host" id="smtpHost" placeholder="smtp.gmail.com">
      </div>
      <div>
        <label>Port</label>
        <input type="number" name="smtp_port" id="smtpPort" value="587" min="1" max="65535">
      </div>
      <div>
        <label>Encryption</label>
        <select name="encryption" id="encSel">
          <option value="tls">STARTTLS</option>
          <option value="ssl">SSL</option>
          <option value="none">None</option>
        </select>
      </div>
    </div>
    <div class="row2 field" id="authRow">
      <div>
        <label>Username</label>
        <input type="text" name="smtp_user" placeholder="you@gmail.com">
      </div>
      <div>
        <label>Password / App password</label>
        <input type="password" name="smtp_pass">
      </div>
    </div>
    <div class="row2 field">
      <div>
        <label>EHLO Hostname <small>(optional)</small></label>
        <input type="text" name="ehlo" placeholder="mta.yourdomain.com">
      </div>
      <div>
        <label>Return-Path <small>(optional)</small></label>
        <input type="text" name="return_path" placeholder="bounce@yourdomain.com">
      </div>
    </div>
  </div>

  <div class="section">
    <div class="stitle">Sender</div>
    <div class="row2 field">
      <div><label>From Name</label><input type="text" name="from_name" placeholder="Your Name"></div>
      <div><label>From Email</label><input type="email" name="from_email" placeholder="you@domain.com" required></div>
    </div>
    <div class="field">
      <label>Subject</label>
      <input type="text" name="subject" placeholder="Hello {{name}}!" required>
      <p class="hint">Use <code>{{name}}</code> and <code>{{email}}</code> for per-recipient personalisation.</p>
    </div>
  </div>

  <div class="section">
    <div class="stitle">Advanced</div>
    <div class="field">
      <label>Delay between emails (ms) &mdash; 0 = send as fast as possible</label>
      <input type="number" name="delay_ms" value="0" min="0" max="60000" style="width:180px">
    </div>
    <div class="field">
      <label>Custom Headers <small>(one per line &mdash; Header-Name: value)</small></label>
      <textarea name="custom_headers" rows="3" placeholder="X-Campaign: launch-2026&#10;X-Mailer: MailPulse"></textarea>
    </div>
  </div>

</div><!-- /left -->

<!-- RIGHT ═════════════════════════════════════════════════════════ -->
<div class="col">

  <div class="section">
    <div class="stitle">Message Body</div>
    <div class="tabs" id="bt">
      <div class="tab active" data-tp="html">HTML</div>
      <div class="tab" data-tp="text">Plain Text</div>
      <div class="tab" data-tp="prev">Preview</div>
    </div>
    <div class="tabpanel active" id="tp-html">
      <textarea name="html_body" id="htmlBody" rows="14"
        placeholder="&lt;h1&gt;Hello {{name}},&lt;/h1&gt;&#10;&lt;p&gt;Your email: {{email}}&lt;/p&gt;&#10;&lt;p&gt;Your message here.&lt;/p&gt;"></textarea>
      <p class="hint">Inline CSS works best across email clients.</p>
    </div>
    <div class="tabpanel" id="tp-text">
      <textarea name="text_body" rows="14" placeholder="Hello {{name}},&#10;&#10;Your email is {{email}}."></textarea>
      <p class="hint">Optional plain-text fallback sent alongside HTML.</p>
    </div>
    <div class="tabpanel" id="tp-prev">
      <iframe id="pf" style="width:100%;height:280px;background:#fff;border:1px solid var(--border);border-radius:4px"></iframe>
      <p class="hint" style="margin-top:5px">Preview: name=&ldquo;John&rdquo;, email=&ldquo;john@example.com&rdquo;</p>
    </div>
  </div>

  <div class="section">
    <div class="stitle">Recipients <span class="badge" id="rb">0</span></div>
    <div class="tabs" id="rt">
      <div class="tab active" data-tp="paste">Paste list</div>
      <div class="tab" data-tp="upload">CSV file</div>
    </div>
    <div class="tabpanel active" id="tp-paste">
      <textarea name="recipients_text" id="recipTxt" rows="9"
        placeholder="alice@example.com&#10;Bob Smith &lt;bob@example.com&gt;&#10;charlie@example.com"></textarea>
      <p class="hint">One per line: <code>email</code> or <code>Name &lt;email&gt;</code></p>
    </div>
    <div class="tabpanel" id="tp-upload">
      <label class="dz" id="dz">
        <input type="file" name="csv_file" id="cf" accept=".csv,.txt">
        <div id="dzTxt">&#128196; Click to choose CSV &mdash; or drag &amp; drop<br>
          <span style="font-size:11px">Columns: email, name &nbsp;(header row is optional)</span></div>
      </label>
      <p class="hint">Example row: <code>alice@example.com, Alice Smith</code></p>
    </div>
  </div>

  <button type="submit" class="btn" id="sb">&#9993; &nbsp;Send Bulk Emails</button>
  <p class="hint" style="text-align:center;margin-top:8px">Opens a new tab with live progress per email.</p>

</div><!-- /right -->
</div><!-- /layout -->
</form>

<script>
function setMode(m) {
  document.getElementById('modeVal').value = m;
  var ids = ['local','relay','smtp'];
  for (var i=0; i<ids.length; i++) {
    var el = document.getElementById('mb-'+ids[i]);
    if (el) { el.className = 'mode-btn' + (ids[i]===m ? ' active' : ''); }
  }
  var panel = document.getElementById('smtpPanel');
  var hr    = document.getElementById('hostRow');
  var ar    = document.getElementById('authRow');
  var title = document.getElementById('spTitle');
  if (m === 'local') {
    panel.className = 'section smtp-panel';
  } else if (m === 'relay') {
    panel.className = 'section smtp-panel visible';
    hr.style.display = 'none';
    ar.style.display = 'none';
    title.textContent = 'Localhost SMTP \u2014 connects to 127.0.0.1:25 (no auth)';
  } else {
    panel.className = 'section smtp-panel visible';
    hr.style.display = '';
    ar.style.display = '';
    title.textContent = 'External SMTP Settings';
  }
}

function initTabs(wrapperId) {
  var wrap = document.getElementById(wrapperId);
  if (!wrap) return;
  var tabs = wrap.querySelectorAll('.tab');
  for (var i=0; i<tabs.length; i++) {
    (function(tab){
      tab.addEventListener('click', function() {
        var key = tab.getAttribute('data-tp');
        for (var j=0; j<tabs.length; j++) { tabs[j].className = 'tab'; }
        tab.className = 'tab active';
        var section = wrap.parentNode;
        var panels  = section.querySelectorAll('.tabpanel');
        for (var k=0; k<panels.length; k++) {
          panels[k].className = panels[k].id === 'tp-'+key ? 'tabpanel active' : 'tabpanel';
        }
        if (key === 'prev') { updatePreview(); }
      });
    })(tabs[i]);
  }
}
initTabs('bt');
initTabs('rt');

function updatePreview() {
  var html = (document.getElementById('htmlBody').value || '')
    .replace(/\{\{name\}\}/g, 'John')
    .replace(/\{\{email\}\}/g, 'john@example.com');
  var doc = document.getElementById('pf').contentDocument;
  doc.open(); doc.write(html); doc.close();
}

function countR() {
  var lines = (document.getElementById('recipTxt').value || '').split('\n');
  var n = 0;
  for (var i=0; i<lines.length; i++) { if (lines[i].indexOf('@') !== -1) n++; }
  var b = document.getElementById('rb');
  b.textContent = n;
  b.className   = 'badge' + (n ? ' g' : '');
}
document.getElementById('recipTxt').addEventListener('input', countR);

var dz = document.getElementById('dz');
var fi = document.getElementById('cf');
var dt = document.getElementById('dzTxt');
fi.addEventListener('change', function(){ if(fi.files.length) dt.textContent = '\u2713 ' + fi.files[0].name; });
dz.addEventListener('dragover',  function(e){ e.preventDefault(); dz.className='dz over'; });
dz.addEventListener('dragleave', function(){  dz.className='dz'; });
dz.addEventListener('drop', function(e){
  e.preventDefault(); dz.className='dz';
  var f = e.dataTransfer.files[0];
  if (!f) return;
  try { var dt2=new DataTransfer(); dt2.items.add(f); fi.files=dt2.files; } catch(err){}
  dt.textContent = '\u2713 ' + f.name;
});

document.getElementById('mf').addEventListener('submit', function(){
  var b = document.getElementById('sb');
  b.disabled = true;
  b.textContent = 'Sending\u2026 (check the new tab)';
  setTimeout(function(){ b.disabled=false; b.innerHTML='\u2709 Send Bulk Emails'; }, 5000);
});
</script>
</body>
</html>