== INSTRUCTIONS == composer require symfony/mailer then backup smtp.php in inc/mailhandlers/ === BEGIN PHP CODE ===
Please make sure IN_MYBB is defined."); } use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; use Symfony\Component\Mime\Email; use Symfony\Component\Mime\Address; if(!defined('MYBB_SSL')) { define('MYBB_SSL', 1); } if(!defined('MYBB_TLS')) { define('MYBB_TLS', 2); } class SmtpMail extends MailHandler { /** * SMTP host. * * @var string */ public $host = ''; /** * SMTP port. * * @var int */ public $port = 25; /** * SMTP username. * * @var string */ public $username = ''; /** * SMTP password. * * @var string */ public $password = ''; /** * Whether to use implicit SSL (port 465). * * @var bool */ public $use_ssl = false; /** * Whether to use STARTTLS (port 587). * * @var bool */ public $use_tls = false; /** * Connection timeout in seconds. * * @var int */ public $timeout = 5; /** * The last error message. * * @var string */ public $last_error = ''; /** * Plain text message body, stored separately so Symfony can * build multipart messages natively rather than consuming * MailHandler's raw MIME string. * * @var string */ public $message_text = ''; function __construct() { global $mybb; if(empty($mybb->settings['smtp_host'])) { $this->host = @ini_get('SMTP') ?: 'localhost'; } else { $this->host = $mybb->settings['smtp_host']; } switch((int)$mybb->settings['secure_smtp']) { case MYBB_SSL: $this->use_ssl = true; $this->port = 465; break; case MYBB_TLS: $this->use_tls = true; $this->port = 587; break; default: $this->port = 25; break; } // Explicit port setting overrides the protocol default if(!empty($mybb->settings['smtp_port'])) { $this->port = (int)$mybb->settings['smtp_port']; } elseif(empty($mybb->settings['smtp_port']) && @ini_get('smtp_port')) { $this->port = (int)@ini_get('smtp_port'); } $this->username = $mybb->settings['smtp_user'] ?? ''; $this->password = $mybb->settings['smtp_pass'] ?? ''; } /** * Override MailHandler's raw MIME multipart construction. * Symfony Mailer handles multipart natively, so we store the * HTML and plain text parts separately and skip the raw boundary * building that MailHandler would otherwise do. * * @param string $message HTML message body * @param string $message_text Plain text alternative */ function set_html_headers($message, $message_text = '') { $this->message = $message; $this->message_text = $message_text ?: strip_tags($message); // Intentionally do not call parent — we do not want the raw // MIME boundary construction; Symfony handles this for us. } /** * Build and return a configured EsmtpTransport instance. * * @return EsmtpTransport */ protected function build_transport() { $transport = new EsmtpTransport( $this->host, $this->port, $this->use_ssl // true = implicit SSL (port 465), false = plain or STARTTLS ); if($this->use_tls) { $transport->getStream()->setStreamOptions([ 'ssl' => [ 'verify_peer' => true, 'verify_peer_name' => true, 'allow_self_signed' => false, ], ]); } if(!empty($this->username)) { $transport->setUsername($this->username); } if(!empty($this->password)) { $transport->setPassword($this->password); } return $transport; } /** * Sends the email via Symfony Mailer. * * @return bool True on success, false on failure. */ function send() { try { $transport = $this->build_transport(); $mailer = new Mailer($transport); $email = new Email(); // From — from_named is already formatted as "Name" by MailHandler if(!empty($this->from_named) && $this->from_named !== $this->from) { $email->from(Address::create($this->from_named)); } else { $email->from($this->from); } // Reply-To / Return-Path if(!empty($this->return_email)) { $email->replyTo($this->return_email); } // To — MailHandler stores comma-separated addresses in $this->to $recipients = array_map('trim', explode(',', $this->to)); foreach($recipients as $recipient) { if(!empty($recipient)) { $email->addTo($recipient); } } $email->subject($this->orig_subject ?: $this->subject); // Body — use parse_format set by MailHandler::build_message() if($this->parse_format === 'html' || $this->parse_format === 'both') { $email->html($this->message); if(!empty($this->message_text)) { $email->text($this->message_text); } } else { $email->text($this->message); } // Additional headers — skip anything Symfony sets itself if(!empty($this->headers)) { $skip = [ 'from', 'reply-to', 'return-path', 'message-id', 'content-type', 'content-transfer-encoding', 'mime-version', 'x-priority', 'x-mailer', ]; $raw_headers = explode("\n", trim($this->headers)); foreach($raw_headers as $header_line) { $header_line = trim($header_line); if(empty($header_line)) { continue; } $parts = explode(':', $header_line, 2); if(count($parts) !== 2) { continue; } $name = trim($parts[0]); $value = trim($parts[1]); if(in_array(strtolower($name), $skip)) { continue; } $email->getHeaders()->addTextHeader($name, $value); } } $mailer->send($email); return true; } catch(\Symfony\Component\Mailer\Exception\TransportExceptionInterface $e) { $this->set_error($e->getMessage()); $this->fatal_error("Symfony Mailer transport error: " . $e->getMessage()); return false; } catch(\Exception $e) { $this->set_error($e->getMessage()); $this->fatal_error("Mailer error: " . $e->getMessage()); return false; } } /** * Get the last error message. * * @return string */ function get_error() { return $this->last_error ?: 'N/A'; } /** * Set the last error message. * * @param string $error */ function set_error($error) { $this->last_error = $error; } }