From 13a5c3370152c9c3820277498df5c62ba053840e Mon Sep 17 00:00:00 2001 From: Julien Rosset Date: Mon, 17 Jul 2023 13:00:26 +0200 Subject: [PATCH] Add PHPMailerEnhanced class --- src/PHPMailerEnhanced/PHPMailerEnhanced.php | 172 ++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 src/PHPMailerEnhanced/PHPMailerEnhanced.php diff --git a/src/PHPMailerEnhanced/PHPMailerEnhanced.php b/src/PHPMailerEnhanced/PHPMailerEnhanced.php new file mode 100644 index 0000000..c267a5e --- /dev/null +++ b/src/PHPMailerEnhanced/PHPMailerEnhanced.php @@ -0,0 +1,172 @@ +CharSet = PHPMailer::CHARSET_UTF8; + } + + /** + * @inheritDoc + */ + public function setFrom ($address, $name = '', $auto = true): bool { + static::splitAddressName($address, $name); + return parent::setFrom($address, $name, $auto); + } + /** + * @inheritDoc + */ + protected function addAnAddress ($kind, $address, $name = ''): bool { + static::splitAddressName($address, $name); + return parent::addAnAddress($kind, $address, $name); + } + + /** + * Casse une adresse contenant un nom + * + * @param string $address [in/out] L'adresse mail d'origine avec potentiellement un nom (format : "{name} <{email}>") + * @param string $name [in/out] Le nom de l'adresse + * @param boolean $replaceName Remplace le nom fourni si un trouvé dans l'adresse mail + * + * @return bool Est-ce qu'un nom a été trouvé dans l'adresse mail ? + */ + protected static function splitAddressName (string &$address, string &$name = '', bool $replaceName = true): bool { + if (preg_match('/^\\s*(?[^<]+?)\s*<(?
[^>]+@[^>]+)>\\s*$/i', $address, $match) !== 1) { + return false; + } + + $address = $match['address']; + if ($replaceName && $name !== '') { + $name = $match['name']; + } + + return true; + } + + /** + * Process local files to embed then in mail + * + * INFO: automatically called by {@see static::send()} + * + * @param string $localDirectory Le répertoire des images (défaut = répertoire exécution actuel) + * + * @return void + * + * @throws Exception If failed to embed one of the files + */ + public function processFiles (string $localDirectory = '.'): void { + //region Ensure the trailing directory separator in local directory path + if (preg_match('#' . preg_quote(DIRECTORY_SEPARATOR, '#') . '$#', $localDirectory) !== 1) { + $localDirectory .= DIRECTORY_SEPARATOR; + } + //endregion + + //region Read the HTML mail body + switch ($this->ContentType) { + case PHPMailer::CONTENT_TYPE_TEXT_HTML: + // Mail in HTML directly → use body + $bodyProperty = 'Body'; + break; + + case PHPMailer::CONTENT_TYPE_PLAINTEXT: + // Mail NOT in HTML → try alternative body + $bodyProperty = 'AltBody'; + break; + + default: + return; + } + $body = $this->$bodyProperty; + + // If HTML body is empty, stop here + if (empty($body)) { + return; + } + //endregion + //region Generate an unique “cid“ prefix + $cidNumber = 1; + do { + $cidPrefix = uniqid() . '_'; + } while ($this->cidExists($cidPrefix . $cidNumber)); + $embeddedImages = []; + //endregion + + //region Search all link (“src” or “href“ attribute) + $body = preg_replace_callback( + '#(?<=src="|href=")[^"]+(?=")#i', + function (array $match) use ($localDirectory, $cidPrefix, &$cidNumber, &$embeddedImages): string { + $link = $match[0]; + + //region Check the link has no protocol (http, cid, etc.) or it's “file” + if (preg_match('#^(?[a-z][a-z\d+.-]*):(?.+)$#i', $link, $match) === 1) { + if (($match['protocol'] ?? '') !== 'file') { + return $link; + } + else { + $link = $match['path']; + } + } + //endregion + + //region Try to find a valid file + // Search a file relative to local directory + $path = $localDirectory . $link; + if (!file_exists($path) || !is_file($path) || !is_readable($path)) { + // Search an absolute file + $path = $link; + if (!file_exists($path) || !is_file($path) || !is_readable($path)) { + return $link; + } + } + //endregion + + //region Associate a “cid“ to the file + $cid = $cidPrefix . ($cidNumber++); + $embeddedImages[$cid] = $path; + //endregion + + return 'cid:' . $cid; + }, + $body + ); + //region Check there is at least one file to embed + if (count($embeddedImages) == 0) { + return; + } + //endregion + //endregion + + //region Write back the mail body + $this->$bodyProperty = $body; + //endregion + //region Embed the files in the mail + foreach ($embeddedImages as $cid => $path) { + $this->addEmbeddedImage($path, $cid); + } + //endregion + } + /** + * {@inheritDoc} + * + * @param string $localDirectory Le répertoire des images embarquées + * + * @throws Exception If failed to embed one of the files + */ + public function send (string $localDirectory = '.'): bool { + $this->processFiles($localDirectory); + return parent::send(); + } +}