feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles

- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

View File

@@ -48,7 +48,7 @@ This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/o
PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file:
```json
"phpmailer/phpmailer": "^6.10.0"
"phpmailer/phpmailer": "^6.11.1"
```
or run

View File

@@ -1 +1 @@
6.10.0
6.11.1

View File

@@ -49,14 +49,15 @@
},
"suggest": {
"decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
"ext-imap": "Needed to support advanced email address parsing according to RFC822",
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
},
"autoload": {
"psr-4": {
@@ -71,6 +72,7 @@
"license": "LGPL-2.1-only",
"scripts": {
"check": "./vendor/bin/phpcs",
"style": "./vendor/bin/phpcbf",
"test": "./vendor/bin/phpunit --no-coverage",
"coverage": "./vendor/bin/phpunit",
"lint": [

View File

@@ -9,7 +9,7 @@
*/
$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.';
$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP está afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP ha sido afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.';
$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.';
$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.';
@@ -18,7 +18,7 @@ $PHPMAILER_LANG['execute'] = 'Imposible ejecutar: ';
$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: ';
$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: ';
$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: ';
$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: ';
$PHPMAILER_LANG['from_failed'] = 'La siguiente dirección de remitente falló: ';
$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.';
$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: ';
$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido';
@@ -34,3 +34,5 @@ $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.';
$PHPMAILER_LANG['smtp_detail'] = 'Detalle: ';
$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: ';
$PHPMAILER_LANG['imap_recommended'] = 'No se recomienda usar el analizador de direcciones simplificado. Instala la extensión IMAP de PHP para un análisis RFC822 más completo.';
$PHPMAILER_LANG['deprecated_argument'] = 'El argumento $useimap ha quedado obsoleto';

View File

@@ -561,9 +561,9 @@ class PHPMailer
* string $body the email body
* string $from email address of sender
* string $extra extra information of possible use
* "smtp_transaction_id' => last smtp transaction id
* 'smtp_transaction_id' => last smtp transaction id
*
* @var string
* @var callable|callable-string
*/
public $action_function = '';
@@ -711,7 +711,7 @@ class PHPMailer
*
* @var array
*/
protected $language = [];
protected static $language = [];
/**
* The number of errors encountered.
@@ -768,7 +768,7 @@ class PHPMailer
*
* @var string
*/
const VERSION = '6.10.0';
const VERSION = '6.11.1';
/**
* Error severity: message only, continue processing.
@@ -1102,7 +1102,7 @@ class PHPMailer
//At-sign is missing.
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$kind,
$address
);
@@ -1187,7 +1187,7 @@ class PHPMailer
if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
$error_message = sprintf(
'%s: %s',
$this->lang('Invalid recipient kind'),
self::lang('Invalid recipient kind'),
$kind
);
$this->setError($error_message);
@@ -1201,7 +1201,7 @@ class PHPMailer
if (!static::validateAddress($address)) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$kind,
$address
);
@@ -1220,12 +1220,16 @@ class PHPMailer
return true;
}
} elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
$this->ReplyTo[strtolower($address)] = [$address, $name];
} else {
foreach ($this->ReplyTo as $replyTo) {
if (0 === strcasecmp($replyTo[0], $address)) {
return false;
}
}
$this->ReplyTo[] = [$address, $name];
return true;
}
return false;
}
@@ -1238,15 +1242,18 @@ class PHPMailer
* @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
*
* @param string $addrstr The address list string
* @param bool $useimap Whether to use the IMAP extension to parse the list
* @param null $useimap Deprecated argument since 6.11.0.
* @param string $charset The charset to use when decoding the address list string.
*
* @return array
*/
public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591)
public static function parseAddresses($addrstr, $useimap = null, $charset = self::CHARSET_ISO88591)
{
if ($useimap !== null) {
trigger_error(self::lang('deprecated_argument'), E_USER_DEPRECATED);
}
$addresses = [];
if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
if (function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available
$list = imap_rfc822_parse_adrlist($addrstr, '');
// Clear any potential IMAP errors to get rid of notices being thrown at end of script.
@@ -1256,20 +1263,13 @@ class PHPMailer
'.SYNTAX-ERROR.' !== $address->host &&
static::validateAddress($address->mailbox . '@' . $address->host)
) {
//Decode the name part if it's present and encoded
//Decode the name part if it's present and maybe encoded
if (
property_exists($address, 'personal') &&
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
defined('MB_CASE_UPPER') &&
preg_match('/^=\?.*\?=$/s', $address->personal)
property_exists($address, 'personal')
&& is_string($address->personal)
&& $address->personal !== ''
) {
$origCharset = mb_internal_encoding();
mb_internal_encoding($charset);
//Undo any RFC2047-encoded spaces-as-underscores
$address->personal = str_replace('_', '=20', $address->personal);
//Decode the name
$address->personal = mb_decode_mimeheader($address->personal);
mb_internal_encoding($origCharset);
$address->personal = static::decodeHeader($address->personal, $charset);
}
$addresses[] = [
@@ -1280,40 +1280,51 @@ class PHPMailer
}
} else {
//Use this simpler parser
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
//Is there a separate name part?
if (strpos($address, '<') === false) {
//No separate name, just use the whole thing
if (static::validateAddress($address)) {
$addresses[] = [
'name' => '',
'address' => $address,
];
}
} else {
list($name, $email) = explode('<', $address);
$email = trim(str_replace('>', '', $email));
$name = trim($name);
if (static::validateAddress($email)) {
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
//If this name is encoded, decode it
if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) {
$origCharset = mb_internal_encoding();
mb_internal_encoding($charset);
//Undo any RFC2047-encoded spaces-as-underscores
$name = str_replace('_', '=20', $name);
//Decode the name
$name = mb_decode_mimeheader($name);
mb_internal_encoding($origCharset);
}
$addresses[] = [
//Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '),
'address' => $email,
];
}
$addresses = static::parseSimplerAddresses($addrstr, $charset);
}
return $addresses;
}
/**
* Parse a string containing one or more RFC822-style comma-separated email addresses
* with the form "display name <address>" into an array of name/address pairs.
* Uses a simpler parser that does not require the IMAP extension but doesnt support
* the full RFC822 spec. For full RFC822 support, use the PHP IMAP extension.
*
* @param string $addrstr The address list string
* @param string $charset The charset to use when decoding the address list string.
*
* @return array
*/
protected static function parseSimplerAddresses($addrstr, $charset)
{
// Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing
trigger_error(self::lang('imap_recommended'), E_USER_NOTICE);
$addresses = [];
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
//Is there a separate name part?
if (strpos($address, '<') === false) {
//No separate name, just use the whole thing
if (static::validateAddress($address)) {
$addresses[] = [
'name' => '',
'address' => $address,
];
}
} else {
$parsed = static::parseEmailString($address);
$email = $parsed['email'];
if (static::validateAddress($email)) {
$name = static::decodeHeader($parsed['name'], $charset);
$addresses[] = [
//Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '),
'address' => $email,
];
}
}
}
@@ -1321,6 +1332,42 @@ class PHPMailer
return $addresses;
}
/**
* Parse a string containing an email address with an optional name
* and divide it into a name and email address.
*
* @param string $input The email with name.
*
* @return array{name: string, email: string}
*/
private static function parseEmailString($input)
{
$input = trim((string)$input);
if ($input === '') {
return ['name' => '', 'email' => ''];
}
$pattern = '/^\s*(?:(?:"([^"]*)"|\'([^\']*)\'|([^<]*?))\s*)?<\s*([^>]+)\s*>\s*$/';
if (preg_match($pattern, $input, $matches)) {
$name = '';
// Double quotes including special scenarios.
if (isset($matches[1]) && $matches[1] !== '') {
$name = $matches[1];
// Single quotes including special scenarios.
} elseif (isset($matches[2]) && $matches[2] !== '') {
$name = $matches[2];
// Simplest scenario, name and email are in the format "Name <email>".
} elseif (isset($matches[3])) {
$name = trim($matches[3]);
}
return ['name' => $name, 'email' => trim($matches[4])];
}
return ['name' => '', 'email' => $input];
}
/**
* Set the From and FromName properties.
*
@@ -1334,6 +1381,10 @@ class PHPMailer
*/
public function setFrom($address, $name = '', $auto = true)
{
if (is_null($name)) {
//Helps avoid a deprecation warning in the preg_replace() below
$name = '';
}
$address = trim((string)$address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
//Don't validate now addresses with IDN. Will be done in send().
@@ -1345,7 +1396,7 @@ class PHPMailer
) {
$error_message = sprintf(
'%s (From): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$address
);
$this->setError($error_message);
@@ -1601,7 +1652,7 @@ class PHPMailer
&& ini_get('mail.add_x_header') === '1'
&& stripos(PHP_OS, 'WIN') === 0
) {
trigger_error($this->lang('buggy_php'), E_USER_WARNING);
trigger_error(self::lang('buggy_php'), E_USER_WARNING);
}
try {
@@ -1631,7 +1682,7 @@ class PHPMailer
call_user_func_array([$this, 'addAnAddress'], $params);
}
if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
throw new Exception(self::lang('provide_address'), self::STOP_CRITICAL);
}
//Validate From, Sender, and ConfirmReadingTo addresses
@@ -1648,7 +1699,7 @@ class PHPMailer
if (!static::validateAddress($this->{$address_kind})) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$address_kind,
$this->{$address_kind}
);
@@ -1670,7 +1721,7 @@ class PHPMailer
$this->setMessageType();
//Refuse to send an empty message unless we are specifically allowing it
if (!$this->AllowEmpty && empty($this->Body)) {
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
//Trim subject consistently
@@ -1809,8 +1860,10 @@ class PHPMailer
} else {
$sendmailFmt = '%s -oi -f%s -t';
}
} elseif ($this->Mailer === 'qmail') {
$sendmailFmt = '%s';
} else {
//allow sendmail to choose a default envelope sender. It may
//Allow sendmail to choose a default envelope sender. It may
//seem preferable to force it to use the From header as with
//SMTP, but that introduces new problems (see
//<https://github.com/PHPMailer/PHPMailer/issues/2298>), and
@@ -1828,33 +1881,35 @@ class PHPMailer
foreach ($this->SingleToArray as $toAddr) {
$mail = @popen($sendmail, 'w');
if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
$this->edebug("To: {$toAddr}");
fwrite($mail, 'To: ' . $toAddr . "\n");
fwrite($mail, $header);
fwrite($mail, $body);
$result = pclose($mail);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
$this->doCallback(
($result === 0),
[[$addrinfo['address'], $addrinfo['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
$addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
foreach ($addrinfo as $addr) {
$this->doCallback(
($result === 0),
[[$addr['address'], $addr['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
}
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
} else {
$mail = @popen($sendmail, 'w');
if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
fwrite($mail, $header);
fwrite($mail, $body);
@@ -1871,7 +1926,7 @@ class PHPMailer
);
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
@@ -2010,17 +2065,19 @@ class PHPMailer
if ($this->SingleTo && count($toArr) > 1) {
foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
$this->doCallback(
$result,
[[$addrinfo['address'], $addrinfo['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
$addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
foreach ($addrinfo as $addr) {
$this->doCallback(
$result,
[[$addr['address'], $addr['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
}
}
} else {
$result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
@@ -2030,7 +2087,7 @@ class PHPMailer
ini_set('sendmail_from', $old_from);
}
if (!$result) {
throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
throw new Exception(self::lang('instantiate'), self::STOP_CRITICAL);
}
return true;
@@ -2116,12 +2173,12 @@ class PHPMailer
$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
$bad_rcpt = [];
if (!$this->smtpConnect($this->SMTPOptions)) {
throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
throw new Exception(self::lang('smtp_connect_failed'), self::STOP_CRITICAL);
}
//If we have recipient addresses that need Unicode support,
//but the server doesn't support it, stop here
if ($this->UseSMTPUTF8 && !$this->smtp->getServerExt('SMTPUTF8')) {
throw new Exception($this->lang('no_smtputf8'), self::STOP_CRITICAL);
throw new Exception(self::lang('no_smtputf8'), self::STOP_CRITICAL);
}
//Sender already validated in preSend()
if ('' === $this->Sender) {
@@ -2133,7 +2190,7 @@ class PHPMailer
$this->smtp->xclient($this->SMTPXClient);
}
if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
$this->setError(self::lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
}
@@ -2155,7 +2212,7 @@ class PHPMailer
//Only send the DATA command if we have viable recipients
if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
throw new Exception(self::lang('data_not_accepted'), self::STOP_CRITICAL);
}
$smtp_transaction_id = $this->smtp->getLastTransactionID();
@@ -2186,7 +2243,7 @@ class PHPMailer
foreach ($bad_rcpt as $bad) {
$errstr .= $bad['to'] . ': ' . $bad['error'];
}
throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
throw new Exception(self::lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
}
return true;
@@ -2240,7 +2297,7 @@ class PHPMailer
$hostinfo
)
) {
$this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
$this->edebug(self::lang('invalid_hostentry') . ' ' . trim($hostentry));
//Not a valid host entry
continue;
}
@@ -2252,7 +2309,7 @@ class PHPMailer
//Check the host name is a valid name or IP address before trying to use it
if (!static::isValidHost($hostinfo[2])) {
$this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
$this->edebug(self::lang('invalid_host') . ' ' . $hostinfo[2]);
continue;
}
$prefix = '';
@@ -2272,7 +2329,7 @@ class PHPMailer
if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
//Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
if (!$sslext) {
throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
throw new Exception(self::lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
}
}
$host = $hostinfo[2];
@@ -2324,7 +2381,7 @@ class PHPMailer
$this->oauth
)
) {
throw new Exception($this->lang('authenticate'));
throw new Exception(self::lang('authenticate'));
}
return true;
@@ -2374,7 +2431,7 @@ class PHPMailer
*
* @return bool Returns true if the requested language was loaded, false otherwise.
*/
public function setLanguage($langcode = 'en', $lang_path = '')
public static function setLanguage($langcode = 'en', $lang_path = '')
{
//Backwards compatibility for renamed language codes
$renamed_langcodes = [
@@ -2423,6 +2480,9 @@ class PHPMailer
'smtp_error' => 'SMTP server error: ',
'variable_set' => 'Cannot set or reset variable: ',
'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses',
'imap_recommended' => 'Using simplified address parser is not recommended. ' .
'Install the PHP IMAP extension for full RFC822 parsing.',
'deprecated_argument' => 'Argument $useimap is deprecated',
];
if (empty($lang_path)) {
//Calculate an absolute path so it can work if CWD is not here
@@ -2489,7 +2549,7 @@ class PHPMailer
}
}
}
$this->language = $PHPMAILER_LANG;
self::$language = $PHPMAILER_LANG;
return $foundlang; //Returns false if language not found
}
@@ -2501,11 +2561,11 @@ class PHPMailer
*/
public function getTranslations()
{
if (empty($this->language)) {
$this->setLanguage(); // Set the default language.
if (empty(self::$language)) {
self::setLanguage(); // Set the default language.
}
return $this->language;
return self::$language;
}
/**
@@ -2928,10 +2988,6 @@ class PHPMailer
//Create unique IDs and preset boundaries
$this->setBoundaries();
if ($this->sign_key_file) {
$body .= $this->getMailMIME() . static::$LE;
}
$this->setWordWrap();
$bodyEncoding = $this->Encoding;
@@ -2963,6 +3019,12 @@ class PHPMailer
if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
}
if ($this->sign_key_file) {
$this->Encoding = $bodyEncoding;
$body .= $this->getMailMIME() . static::$LE;
}
//Use this as a preamble in all multipart message types
$mimepre = '';
switch ($this->message_type) {
@@ -3144,12 +3206,12 @@ class PHPMailer
if ($this->isError()) {
$body = '';
if ($this->exceptions) {
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
} elseif ($this->sign_key_file) {
try {
if (!defined('PKCS7_TEXT')) {
throw new Exception($this->lang('extension_missing') . 'openssl');
throw new Exception(self::lang('extension_missing') . 'openssl');
}
$file = tempnam(sys_get_temp_dir(), 'srcsign');
@@ -3187,7 +3249,7 @@ class PHPMailer
$body = $parts[1];
} else {
@unlink($signed);
throw new Exception($this->lang('signing') . openssl_error_string());
throw new Exception(self::lang('signing') . openssl_error_string());
}
} catch (Exception $exc) {
$body = '';
@@ -3332,7 +3394,7 @@ class PHPMailer
) {
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@@ -3345,7 +3407,7 @@ class PHPMailer
$name = $filename;
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
$this->attachment[] = [
@@ -3506,11 +3568,11 @@ class PHPMailer
{
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = file_get_contents($path);
if (false === $file_buffer) {
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = $this->encodeString($file_buffer, $encoding);
@@ -3563,9 +3625,9 @@ class PHPMailer
$encoded = $this->encodeQP($str);
break;
default:
$this->setError($this->lang('encoding') . $encoding);
$this->setError(self::lang('encoding') . $encoding);
if ($this->exceptions) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
break;
}
@@ -3671,6 +3733,42 @@ class PHPMailer
return trim(static::normalizeBreaks($encoded));
}
/**
* Decode an RFC2047-encoded header value
* Attempts multiple strategies so it works even when the mbstring extension is disabled.
*
* @param string $value The header value to decode
* @param string $charset The target charset to convert to, defaults to ISO-8859-1 for BC
*
* @return string The decoded header value
*/
public static function decodeHeader($value, $charset = self::CHARSET_ISO88591)
{
if (!is_string($value) || $value === '') {
return '';
}
// Detect the presence of any RFC2047 encoded-words
$hasEncodedWord = (bool) preg_match('/=\?.*\?=/s', $value);
if ($hasEncodedWord && defined('MB_CASE_UPPER')) {
$origCharset = mb_internal_encoding();
// Always decode to UTF-8 to provide a consistent, modern output encoding.
mb_internal_encoding($charset);
if (PHP_VERSION_ID < 80300) {
// Undo any RFC2047-encoded spaces-as-underscores.
$value = str_replace('_', '=20', $value);
} else {
// PHP 8.3+ already interprets underscores as spaces. Remove additional
// linear whitespace between adjacent encoded words to avoid double spacing.
$value = preg_replace('/(\?=)\s+(=\?)/', '$1$2', $value);
}
// Decode the header value
$value = mb_decode_mimeheader($value);
mb_internal_encoding($origCharset);
}
return $value;
}
/**
* Check if a string contains multi-byte characters.
*
@@ -3840,7 +3938,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@@ -3899,7 +3997,7 @@ class PHPMailer
) {
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@@ -3908,7 +4006,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
@@ -3974,7 +4072,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@@ -4231,7 +4329,7 @@ class PHPMailer
}
if (strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) {
throw new Exception($this->lang('invalid_header'));
throw new Exception(self::lang('invalid_header'));
}
return false;
@@ -4255,15 +4353,15 @@ class PHPMailer
if ('smtp' === $this->Mailer && null !== $this->smtp) {
$lasterror = $this->smtp->getError();
if (!empty($lasterror['error'])) {
$msg .= ' ' . $this->lang('smtp_error') . $lasterror['error'];
$msg .= ' ' . self::lang('smtp_error') . $lasterror['error'];
if (!empty($lasterror['detail'])) {
$msg .= ' ' . $this->lang('smtp_detail') . $lasterror['detail'];
$msg .= ' ' . self::lang('smtp_detail') . $lasterror['detail'];
}
if (!empty($lasterror['smtp_code'])) {
$msg .= ' ' . $this->lang('smtp_code') . $lasterror['smtp_code'];
$msg .= ' ' . self::lang('smtp_code') . $lasterror['smtp_code'];
}
if (!empty($lasterror['smtp_code_ex'])) {
$msg .= ' ' . $this->lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
$msg .= ' ' . self::lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
}
}
}
@@ -4388,21 +4486,21 @@ class PHPMailer
*
* @return string
*/
protected function lang($key)
protected static function lang($key)
{
if (count($this->language) < 1) {
$this->setLanguage(); //Set the default language
if (count(self::$language) < 1) {
self::setLanguage(); //Set the default language
}
if (array_key_exists($key, $this->language)) {
if (array_key_exists($key, self::$language)) {
if ('smtp_connect_failed' === $key) {
//Include a link to troubleshooting docs on SMTP connection failure.
//This is by far the biggest cause of support questions
//but it's usually not PHPMailer's fault.
return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
return self::$language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
}
return $this->language[$key];
return self::$language[$key];
}
//Return the key as a fallback
@@ -4417,7 +4515,7 @@ class PHPMailer
*/
private function getSmtpErrorMessage($base_key)
{
$message = $this->lang($base_key);
$message = self::lang($base_key);
$error = $this->smtp->getError();
if (!empty($error['error'])) {
$message .= ' ' . $error['error'];
@@ -4461,7 +4559,7 @@ class PHPMailer
//Ensure name is not empty, and that neither name nor value contain line breaks
if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) {
throw new Exception($this->lang('invalid_header'));
throw new Exception(self::lang('invalid_header'));
}
return false;
@@ -4854,7 +4952,7 @@ class PHPMailer
return true;
}
$this->setError($this->lang('variable_set') . $name);
$this->setError(self::lang('variable_set') . $name);
return false;
}
@@ -4992,7 +5090,7 @@ class PHPMailer
{
if (!defined('PKCS7_TEXT')) {
if ($this->exceptions) {
throw new Exception($this->lang('extension_missing') . 'openssl');
throw new Exception(self::lang('extension_missing') . 'openssl');
}
return '';

View File

@@ -46,7 +46,7 @@ class POP3
*
* @var string
*/
const VERSION = '6.10.0';
const VERSION = '6.11.1';
/**
* Default POP3 port number.

View File

@@ -35,7 +35,7 @@ class SMTP
*
* @var string
*/
const VERSION = '6.10.0';
const VERSION = '6.11.1';
/**
* SMTP line break constant.
@@ -205,6 +205,7 @@ class SMTP
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
'Gsmtp' => '/[\d]{3} 2\.0\.0 OK (.*) - gsmtp/',
];
/**
@@ -633,10 +634,41 @@ class SMTP
return false;
}
$oauth = $OAuth->getOauth64();
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
return false;
/*
* An SMTP command line can have a maximum length of 512 bytes, including the command name,
* so the base64-encoded OAUTH token has a maximum length of:
* 512 - 13 (AUTH XOAUTH2) - 2 (CRLF) = 497 bytes
* If the token is longer than that, the command and the token must be sent separately as described in
* https://www.rfc-editor.org/rfc/rfc4954#section-4
*/
if ($oauth === '') {
//Sending an empty auth token is legitimate, but it must be encoded as '='
//to indicate it's not a 2-part command
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 =', 235)) {
return false;
}
} elseif (strlen($oauth) <= 497) {
//Authenticate using a token in the initial-response part
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
return false;
}
} else {
//The token is too long, so we need to send it in two parts.
//Send the auth command without a token and expect a 334
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2', 334)) {
return false;
}
//Send the token
if (!$this->sendCommand('OAuth TOKEN', $oauth, [235, 334])) {
return false;
}
//If the server answers with 334, send an empty line and wait for a 235
if (
substr($this->last_reply, 0, 3) === '334'
&& $this->sendCommand('AUTH End', '', 235)
) {
return false;
}
}
break;
default:
@@ -1309,7 +1341,16 @@ class SMTP
//stream_select returns false when the `select` system call is interrupted
//by an incoming signal, try the select again
if (stripos($message, 'interrupted system call') !== false) {
if (
stripos($message, 'interrupted system call') !== false ||
(
// on applications with a different locale than english, the message above is not found because
// it's translated. So we also check for the SOCKET_EINTR constant which is defined under
// Windows and UNIX-like platforms (if available on the platform).
defined('SOCKET_EINTR') &&
stripos($message, 'stream_select(): Unable to select [' . SOCKET_EINTR . ']') !== false
)
) {
$this->edebug(
'SMTP -> get_lines(): retrying stream_select',
self::DEBUG_LOWLEVEL