1bb01c27cSAndreas Gohr<?php 2d4f83172SAndreas Gohr 3bb01c27cSAndreas Gohr/** 41d045709SAndreas Gohr * A class to build and send multi part mails (with HTML content and embedded 51d045709SAndreas Gohr * attachments). All mails are assumed to be in UTF-8 encoding. 61d045709SAndreas Gohr * 71d045709SAndreas Gohr * Attachments are handled in memory so this shouldn't be used to send huge 81d045709SAndreas Gohr * files, but then again mail shouldn't be used to send huge files either. 91d045709SAndreas Gohr * 10bb01c27cSAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 11bb01c27cSAndreas Gohr */ 12d4f83172SAndreas Gohr 13*73dc0a89SAndreas Gohruse dokuwiki\MailUtils; 1424870174SAndreas Gohruse dokuwiki\Utf8\PhpString; 1524870174SAndreas Gohruse dokuwiki\Utf8\Clean; 16e1d9dcc8SAndreas Gohruse dokuwiki\Extension\Event; 17e1d9dcc8SAndreas Gohr 18a89c75afSAndreas Gohr/** 19a89c75afSAndreas Gohr * Mail Handling 20a89c75afSAndreas Gohr */ 218c7c53b0SAndreas Gohrclass Mailer 228c7c53b0SAndreas Gohr{ 2324870174SAndreas Gohr protected $headers = []; 2424870174SAndreas Gohr protected $attach = []; 25f41c79d7SAndreas Gohr protected $html = ''; 26f41c79d7SAndreas Gohr protected $text = ''; 27bb01c27cSAndreas Gohr 28f41c79d7SAndreas Gohr protected $boundary = ''; 29f41c79d7SAndreas Gohr protected $partid = ''; 3024870174SAndreas Gohr protected $sendparam; 31bb01c27cSAndreas Gohr 32f41c79d7SAndreas Gohr protected $allowhtml = true; 331d045709SAndreas Gohr 3424870174SAndreas Gohr protected $replacements = ['text' => [], 'html' => []]; 359ea45836SChristopher Smith 361d045709SAndreas Gohr /** 371d045709SAndreas Gohr * Constructor 381d045709SAndreas Gohr * 399ea45836SChristopher Smith * Initializes the boundary strings, part counters and token replacements 401d045709SAndreas Gohr */ 41d868eb89SAndreas Gohr public function __construct() 42d868eb89SAndreas Gohr { 439f3eca0bSAndreas Gohr global $conf; 44585bf44eSChristopher Smith /* @var Input $INPUT */ 45585bf44eSChristopher Smith global $INPUT; 469f3eca0bSAndreas Gohr 479f3eca0bSAndreas Gohr $server = parse_url(DOKU_URL, PHP_URL_HOST); 48093fe67eSAndreas Gohr if (!str_contains($server, '.')) $server .= '.localhost'; 491d045709SAndreas Gohr 5024870174SAndreas Gohr $this->partid = substr(md5(uniqid(random_int(0, mt_getrandmax()), true)), 0, 8) . '@' . $server; 5124870174SAndreas Gohr $this->boundary = '__________' . md5(uniqid(random_int(0, mt_getrandmax()), true)); 529f3eca0bSAndreas Gohr 53749c0023SAndreas Gohr $listid = implode('.', array_reverse(explode('/', DOKU_BASE))) . $server; 549f3eca0bSAndreas Gohr $listid = strtolower(trim($listid, '.')); 5524870174SAndreas Gohr 5624870174SAndreas Gohr $messageid = uniqid(random_int(0, mt_getrandmax()), true) . "@$server"; 579f3eca0bSAndreas Gohr 582398a2b5SAndreas Gohr $this->allowhtml = (bool)$conf['htmlmail']; 592398a2b5SAndreas Gohr 609f3eca0bSAndreas Gohr // add some default headers for mailfiltering FS#2247 615f43dcf4SLukas Rademacher if (!empty($conf['mailreturnpath'])) { 625f43dcf4SLukas Rademacher $this->setHeader('Return-Path', $conf['mailreturnpath']); 635f43dcf4SLukas Rademacher } 646a1f928fSAndreas Gohr $this->setHeader('X-Mailer', 'DokuWiki'); 65585bf44eSChristopher Smith $this->setHeader('X-DokuWiki-User', $INPUT->server->str('REMOTE_USER')); 669f3eca0bSAndreas Gohr $this->setHeader('X-DokuWiki-Title', $conf['title']); 679f3eca0bSAndreas Gohr $this->setHeader('X-DokuWiki-Server', $server); 689f3eca0bSAndreas Gohr $this->setHeader('X-Auto-Response-Suppress', 'OOF'); 699f3eca0bSAndreas Gohr $this->setHeader('List-Id', $conf['title'] . ' <' . $listid . '>'); 70d6e04b60SAndreas Gohr $this->setHeader('Date', date('r'), false); 717c7659d2SPhilipp Specht $this->setHeader('Message-Id', "<$messageid>"); 729ea45836SChristopher Smith 739ea45836SChristopher Smith $this->prepareTokenReplacements(); 74bb01c27cSAndreas Gohr } 75bb01c27cSAndreas Gohr 76bb01c27cSAndreas Gohr /** 77*73dc0a89SAndreas Gohr * Resolve the @MAIL@/@USER@/@NAME@ placeholders in $conf['mailfrom'] and derive $conf['mailfromnobody']. 78*73dc0a89SAndreas Gohr * 79*73dc0a89SAndreas Gohr * Called once during init. The "nobody" variant is the address used when the resolved mailfrom would be 80*73dc0a89SAndreas Gohr * user-dependent (e.g. for subscriptions which must look like they come from a generic sender, not the actor). 81*73dc0a89SAndreas Gohr * 82*73dc0a89SAndreas Gohr * @todo Resolve lazily on first Mailer instantiation instead of eagerly at init time, so the explicit init.php 83*73dc0a89SAndreas Gohr * call can go away and this method makes more sense here 84*73dc0a89SAndreas Gohr * 85*73dc0a89SAndreas Gohr * 86*73dc0a89SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 87*73dc0a89SAndreas Gohr */ 88*73dc0a89SAndreas Gohr public static function configInit(): void 89*73dc0a89SAndreas Gohr { 90*73dc0a89SAndreas Gohr global $conf; 91*73dc0a89SAndreas Gohr global $USERINFO; 92*73dc0a89SAndreas Gohr /** @var \dokuwiki\Input\Input $INPUT */ 93*73dc0a89SAndreas Gohr global $INPUT; 94*73dc0a89SAndreas Gohr 95*73dc0a89SAndreas Gohr // auto constructed address 96*73dc0a89SAndreas Gohr $host = @parse_url(DOKU_URL, PHP_URL_HOST); 97*73dc0a89SAndreas Gohr if (!$host) $host = 'example.com'; 98*73dc0a89SAndreas Gohr $noreply = 'noreply@' . $host; 99*73dc0a89SAndreas Gohr 100*73dc0a89SAndreas Gohr $replace = []; 101*73dc0a89SAndreas Gohr if (!empty($USERINFO['mail'])) { 102*73dc0a89SAndreas Gohr $replace['@MAIL@'] = $USERINFO['mail']; 103*73dc0a89SAndreas Gohr } else { 104*73dc0a89SAndreas Gohr $replace['@MAIL@'] = $noreply; 105*73dc0a89SAndreas Gohr } 106*73dc0a89SAndreas Gohr 107*73dc0a89SAndreas Gohr // use 'noreply' if no user 108*73dc0a89SAndreas Gohr $replace['@USER@'] = $INPUT->server->str('REMOTE_USER', 'noreply', true); 109*73dc0a89SAndreas Gohr 110*73dc0a89SAndreas Gohr if (!empty($USERINFO['name'])) { 111*73dc0a89SAndreas Gohr $replace['@NAME@'] = $USERINFO['name']; 112*73dc0a89SAndreas Gohr } else { 113*73dc0a89SAndreas Gohr $replace['@NAME@'] = ''; 114*73dc0a89SAndreas Gohr } 115*73dc0a89SAndreas Gohr 116*73dc0a89SAndreas Gohr // apply replacements 117*73dc0a89SAndreas Gohr $from = str_replace( 118*73dc0a89SAndreas Gohr array_keys($replace), 119*73dc0a89SAndreas Gohr array_values($replace), 120*73dc0a89SAndreas Gohr $conf['mailfrom'] 121*73dc0a89SAndreas Gohr ); 122*73dc0a89SAndreas Gohr 123*73dc0a89SAndreas Gohr // any replacements done? set different mailfromnone 124*73dc0a89SAndreas Gohr if ($from != $conf['mailfrom']) { 125*73dc0a89SAndreas Gohr $conf['mailfromnobody'] = $noreply; 126*73dc0a89SAndreas Gohr } else { 127*73dc0a89SAndreas Gohr $conf['mailfromnobody'] = $from; 128*73dc0a89SAndreas Gohr } 129*73dc0a89SAndreas Gohr $conf['mailfrom'] = $from; 130*73dc0a89SAndreas Gohr } 131*73dc0a89SAndreas Gohr 132*73dc0a89SAndreas Gohr /** 133bb01c27cSAndreas Gohr * Attach a file 134bb01c27cSAndreas Gohr * 135a89c75afSAndreas Gohr * @param string $path Path to the file to attach 136a89c75afSAndreas Gohr * @param string $mime Mimetype of the attached file 137a89c75afSAndreas Gohr * @param string $name The filename to use 138a89c75afSAndreas Gohr * @param string $embed Unique key to reference this file from the HTML part 139bb01c27cSAndreas Gohr */ 140d868eb89SAndreas Gohr public function attachFile($path, $mime, $name = '', $embed = '') 141d868eb89SAndreas Gohr { 142bb01c27cSAndreas Gohr if (!$name) { 14324870174SAndreas Gohr $name = PhpString::basename($path); 144bb01c27cSAndreas Gohr } 145bb01c27cSAndreas Gohr 14624870174SAndreas Gohr $this->attach[] = [ 147bb01c27cSAndreas Gohr 'data' => file_get_contents($path), 148bb01c27cSAndreas Gohr 'mime' => $mime, 149bb01c27cSAndreas Gohr 'name' => $name, 150bb01c27cSAndreas Gohr 'embed' => $embed 15124870174SAndreas Gohr ]; 152bb01c27cSAndreas Gohr } 153bb01c27cSAndreas Gohr 154bb01c27cSAndreas Gohr /** 155bb01c27cSAndreas Gohr * Attach a file 156bb01c27cSAndreas Gohr * 157a89c75afSAndreas Gohr * @param string $data The file contents to attach 158a89c75afSAndreas Gohr * @param string $mime Mimetype of the attached file 159a89c75afSAndreas Gohr * @param string $name The filename to use 160a89c75afSAndreas Gohr * @param string $embed Unique key to reference this file from the HTML part 161bb01c27cSAndreas Gohr */ 162d868eb89SAndreas Gohr public function attachContent($data, $mime, $name = '', $embed = '') 163d868eb89SAndreas Gohr { 164bb01c27cSAndreas Gohr if (!$name) { 16524870174SAndreas Gohr [, $ext] = explode('/', $mime); 166bb01c27cSAndreas Gohr $name = count($this->attach) . ".$ext"; 167bb01c27cSAndreas Gohr } 168bb01c27cSAndreas Gohr 16924870174SAndreas Gohr $this->attach[] = [ 170bb01c27cSAndreas Gohr 'data' => $data, 171bb01c27cSAndreas Gohr 'mime' => $mime, 172bb01c27cSAndreas Gohr 'name' => $name, 173bb01c27cSAndreas Gohr 'embed' => $embed 17424870174SAndreas Gohr ]; 175bb01c27cSAndreas Gohr } 176bb01c27cSAndreas Gohr 177bb01c27cSAndreas Gohr /** 178850dbf1fSAndreas Gohr * Callback function to automatically embed images referenced in HTML templates 17942ea7f44SGerrit Uitslag * 18042ea7f44SGerrit Uitslag * @param array $matches 18142ea7f44SGerrit Uitslag * @return string placeholder 182850dbf1fSAndreas Gohr */ 183d868eb89SAndreas Gohr protected function autoEmbedCallBack($matches) 184d868eb89SAndreas Gohr { 185850dbf1fSAndreas Gohr static $embeds = 0; 186850dbf1fSAndreas Gohr $embeds++; 187850dbf1fSAndreas Gohr 188850dbf1fSAndreas Gohr // get file and mime type 189850dbf1fSAndreas Gohr $media = cleanID($matches[1]); 19024870174SAndreas Gohr [, $mime] = mimetype($media); 191850dbf1fSAndreas Gohr $file = mediaFN($media); 192850dbf1fSAndreas Gohr if (!file_exists($file)) return $matches[0]; //bad reference, keep as is 193850dbf1fSAndreas Gohr 194850dbf1fSAndreas Gohr // attach it and set placeholder 195850dbf1fSAndreas Gohr $this->attachFile($file, $mime, '', 'autoembed' . $embeds); 196850dbf1fSAndreas Gohr return '%%autoembed' . $embeds . '%%'; 197850dbf1fSAndreas Gohr } 198850dbf1fSAndreas Gohr 199850dbf1fSAndreas Gohr /** 2001d045709SAndreas Gohr * Add an arbitrary header to the mail 2011d045709SAndreas Gohr * 202a36fc348SAndreas Gohr * If an empy value is passed, the header is removed 203a36fc348SAndreas Gohr * 2041d045709SAndreas Gohr * @param string $header the header name (no trailing colon!) 20559bc3b48SGerrit Uitslag * @param string|string[] $value the value of the header 2061d045709SAndreas Gohr * @param bool $clean remove all non-ASCII chars and line feeds? 2071d045709SAndreas Gohr */ 208d868eb89SAndreas Gohr public function setHeader($header, $value, $clean = true) 209d868eb89SAndreas Gohr { 2109f3eca0bSAndreas Gohr $header = str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $header)))); // streamline casing 2111d045709SAndreas Gohr if ($clean) { 212578b2c23SAndreas Gohr $header = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@]+/', '', $header); 213578b2c23SAndreas Gohr $value = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@<>]+/', '', $value); 2141d045709SAndreas Gohr } 215a36fc348SAndreas Gohr 216a36fc348SAndreas Gohr // empty value deletes 217b6c97c70SAndreas Gohr if (is_array($value)) { 218093fe67eSAndreas Gohr $value = array_map(trim(...), $value); 219b6c97c70SAndreas Gohr $value = array_filter($value); 220b6c97c70SAndreas Gohr if (!$value) $value = ''; 221b6c97c70SAndreas Gohr } else { 222a36fc348SAndreas Gohr $value = trim($value); 223b6c97c70SAndreas Gohr } 224a36fc348SAndreas Gohr if ($value === '') { 225a36fc348SAndreas Gohr if (isset($this->headers[$header])) unset($this->headers[$header]); 226a36fc348SAndreas Gohr } else { 2271d045709SAndreas Gohr $this->headers[$header] = $value; 2281d045709SAndreas Gohr } 229a36fc348SAndreas Gohr } 2301d045709SAndreas Gohr 2311d045709SAndreas Gohr /** 2321d045709SAndreas Gohr * Set additional parameters to be passed to sendmail 2331d045709SAndreas Gohr * 2341d045709SAndreas Gohr * Whatever is set here is directly passed to PHP's mail() command as last 2351d045709SAndreas Gohr * parameter. Depending on the PHP setup this might break mailing alltogether 23642ea7f44SGerrit Uitslag * 23742ea7f44SGerrit Uitslag * @param string $param 2381d045709SAndreas Gohr */ 239d868eb89SAndreas Gohr public function setParameters($param) 240d868eb89SAndreas Gohr { 2411d045709SAndreas Gohr $this->sendparam = $param; 2421d045709SAndreas Gohr } 2431d045709SAndreas Gohr 2441d045709SAndreas Gohr /** 245abbf0890SAndreas Gohr * Set the text and HTML body and apply replacements 246abbf0890SAndreas Gohr * 247abbf0890SAndreas Gohr * This function applies a whole bunch of default replacements in addition 24804dcb5b2SChristopher Smith * to the ones specified as parameters 249abbf0890SAndreas Gohr * 250abbf0890SAndreas Gohr * If you pass the HTML part or HTML replacements yourself you have to make 251abbf0890SAndreas Gohr * sure you encode all HTML special chars correctly 252abbf0890SAndreas Gohr * 253abbf0890SAndreas Gohr * @param string $text plain text body 254abbf0890SAndreas Gohr * @param array $textrep replacements to apply on the text part 25564159a61SAndreas Gohr * @param array $htmlrep replacements to apply on the HTML part, null to use $textrep (urls wrapped in <a> tags) 25659bc3b48SGerrit Uitslag * @param string $html the HTML body, leave null to create it from $text 257f08086ecSAndreas Gohr * @param bool $wrap wrap the HTML in the default header/Footer 258abbf0890SAndreas Gohr */ 259d868eb89SAndreas Gohr public function setBody($text, $textrep = null, $htmlrep = null, $html = null, $wrap = true) 260d868eb89SAndreas Gohr { 261585bf44eSChristopher Smith 26276efd6d0SAndreas Gohr $htmlrep = (array)$htmlrep; 26376efd6d0SAndreas Gohr $textrep = (array)$textrep; 264abbf0890SAndreas Gohr 265abbf0890SAndreas Gohr // create HTML from text if not given 266749c0023SAndreas Gohr if ($html === null) { 267ba9c057bSAndreas Gohr $html = $text; 268ba9c057bSAndreas Gohr $html = hsc($html); 269ba2c2f17Sfurun $html = preg_replace('/^----+$/m', '<hr >', $html); 270ba9c057bSAndreas Gohr $html = nl2br($html); 271abbf0890SAndreas Gohr } 272f08086ecSAndreas Gohr if ($wrap) { 273749c0023SAndreas Gohr $wrapper = rawLocale('mailwrap', 'html'); 2743819cafdSfurun $html = preg_replace('/\n-- <br \/>.*$/s', '', $html); //strip signature 2753819cafdSfurun $html = str_replace('@EMAILSIGNATURE@', '', $html); //strip @EMAILSIGNATURE@ 276749c0023SAndreas Gohr $html = str_replace('@HTMLBODY@', $html, $wrapper); 277f08086ecSAndreas Gohr } 278f08086ecSAndreas Gohr 279093fe67eSAndreas Gohr if (!str_contains($text, '@EMAILSIGNATURE@')) { 2809ea45836SChristopher Smith $text .= '@EMAILSIGNATURE@'; 2813819cafdSfurun } 282ba2c2f17Sfurun 28376efd6d0SAndreas Gohr // copy over all replacements missing for HTML (autolink URLs) 28476efd6d0SAndreas Gohr foreach ($textrep as $key => $value) { 28576efd6d0SAndreas Gohr if (isset($htmlrep[$key])) continue; 2863e7e6067SKlap-in if (media_isexternal($value)) { 28776efd6d0SAndreas Gohr $htmlrep[$key] = '<a href="' . hsc($value) . '">' . hsc($value) . '</a>'; 28876efd6d0SAndreas Gohr } else { 28976efd6d0SAndreas Gohr $htmlrep[$key] = hsc($value); 29076efd6d0SAndreas Gohr } 291abbf0890SAndreas Gohr } 292abbf0890SAndreas Gohr 293850dbf1fSAndreas Gohr // embed media from templates 294a89c75afSAndreas Gohr $html = preg_replace_callback( 295a89c75afSAndreas Gohr '/@MEDIA\(([^\)]+)\)@/', 296093fe67eSAndreas Gohr $this->autoEmbedCallBack(...), 297dccd6b2bSAndreas Gohr $html 298a89c75afSAndreas Gohr ); 299850dbf1fSAndreas Gohr 3009ea45836SChristopher Smith // add default token replacements 30124870174SAndreas Gohr $trep = array_merge($this->replacements['text'], $textrep); 30224870174SAndreas Gohr $hrep = array_merge($this->replacements['html'], $htmlrep); 303abbf0890SAndreas Gohr 304abbf0890SAndreas Gohr // Apply replacements 305abbf0890SAndreas Gohr foreach ($trep as $key => $substitution) { 306abbf0890SAndreas Gohr $text = str_replace('@' . strtoupper($key) . '@', $substitution, $text); 307abbf0890SAndreas Gohr } 308abbf0890SAndreas Gohr foreach ($hrep as $key => $substitution) { 309abbf0890SAndreas Gohr $html = str_replace('@' . strtoupper($key) . '@', $substitution, $html); 310abbf0890SAndreas Gohr } 311abbf0890SAndreas Gohr 312abbf0890SAndreas Gohr $this->setHTML($html); 313abbf0890SAndreas Gohr $this->setText($text); 314abbf0890SAndreas Gohr } 315abbf0890SAndreas Gohr 316abbf0890SAndreas Gohr /** 317bb01c27cSAndreas Gohr * Set the HTML part of the mail 318bb01c27cSAndreas Gohr * 319bb01c27cSAndreas Gohr * Placeholders can be used to reference embedded attachments 320abbf0890SAndreas Gohr * 321abbf0890SAndreas Gohr * You probably want to use setBody() instead 32242ea7f44SGerrit Uitslag * 32342ea7f44SGerrit Uitslag * @param string $html 324bb01c27cSAndreas Gohr */ 325d868eb89SAndreas Gohr public function setHTML($html) 326d868eb89SAndreas Gohr { 327bb01c27cSAndreas Gohr $this->html = $html; 328bb01c27cSAndreas Gohr } 329bb01c27cSAndreas Gohr 330bb01c27cSAndreas Gohr /** 331bb01c27cSAndreas Gohr * Set the plain text part of the mail 332abbf0890SAndreas Gohr * 333abbf0890SAndreas Gohr * You probably want to use setBody() instead 33442ea7f44SGerrit Uitslag * 33542ea7f44SGerrit Uitslag * @param string $text 336bb01c27cSAndreas Gohr */ 337d868eb89SAndreas Gohr public function setText($text) 338d868eb89SAndreas Gohr { 339bb01c27cSAndreas Gohr $this->text = $text; 340bb01c27cSAndreas Gohr } 341bb01c27cSAndreas Gohr 342bb01c27cSAndreas Gohr /** 343a36fc348SAndreas Gohr * Add the To: recipients 344a36fc348SAndreas Gohr * 3458c253612SGerrit Uitslag * @see cleanAddress 34659bc3b48SGerrit Uitslag * @param string|string[] $address Multiple adresses separated by commas or as array 347a36fc348SAndreas Gohr */ 348d868eb89SAndreas Gohr public function to($address) 349d868eb89SAndreas Gohr { 350a36fc348SAndreas Gohr $this->setHeader('To', $address, false); 351a36fc348SAndreas Gohr } 352a36fc348SAndreas Gohr 353a36fc348SAndreas Gohr /** 354a36fc348SAndreas Gohr * Add the Cc: recipients 355a36fc348SAndreas Gohr * 3568c253612SGerrit Uitslag * @see cleanAddress 35759bc3b48SGerrit Uitslag * @param string|string[] $address Multiple adresses separated by commas or as array 358a36fc348SAndreas Gohr */ 359d868eb89SAndreas Gohr public function cc($address) 360d868eb89SAndreas Gohr { 361a36fc348SAndreas Gohr $this->setHeader('Cc', $address, false); 362a36fc348SAndreas Gohr } 363a36fc348SAndreas Gohr 364a36fc348SAndreas Gohr /** 365a36fc348SAndreas Gohr * Add the Bcc: recipients 366a36fc348SAndreas Gohr * 3678c253612SGerrit Uitslag * @see cleanAddress 36859bc3b48SGerrit Uitslag * @param string|string[] $address Multiple adresses separated by commas or as array 369a36fc348SAndreas Gohr */ 370d868eb89SAndreas Gohr public function bcc($address) 371d868eb89SAndreas Gohr { 372a36fc348SAndreas Gohr $this->setHeader('Bcc', $address, false); 373a36fc348SAndreas Gohr } 374a36fc348SAndreas Gohr 375a36fc348SAndreas Gohr /** 376a36fc348SAndreas Gohr * Add the From: address 377a36fc348SAndreas Gohr * 378a36fc348SAndreas Gohr * This is set to $conf['mailfrom'] when not specified so you shouldn't need 379a36fc348SAndreas Gohr * to call this function 380a36fc348SAndreas Gohr * 3818c253612SGerrit Uitslag * @see cleanAddress 382a36fc348SAndreas Gohr * @param string $address from address 383a36fc348SAndreas Gohr */ 384d868eb89SAndreas Gohr public function from($address) 385d868eb89SAndreas Gohr { 386a36fc348SAndreas Gohr $this->setHeader('From', $address, false); 387a36fc348SAndreas Gohr } 388a36fc348SAndreas Gohr 389a36fc348SAndreas Gohr /** 390a36fc348SAndreas Gohr * Add the mail's Subject: header 391a36fc348SAndreas Gohr * 392a36fc348SAndreas Gohr * @param string $subject the mail subject 393a36fc348SAndreas Gohr */ 394d868eb89SAndreas Gohr public function subject($subject) 395d868eb89SAndreas Gohr { 396a36fc348SAndreas Gohr $this->headers['Subject'] = $subject; 397a36fc348SAndreas Gohr } 398a36fc348SAndreas Gohr 399a36fc348SAndreas Gohr /** 400102cdbd7SLarsGit223 * Return a clean name which can be safely used in mail address 401102cdbd7SLarsGit223 * fields. That means the name will be enclosed in '"' if it includes 402102cdbd7SLarsGit223 * a '"' or a ','. Also a '"' will be escaped as '\"'. 403102cdbd7SLarsGit223 * 404102cdbd7SLarsGit223 * @param string $name the name to clean-up 405102cdbd7SLarsGit223 * @see cleanAddress 406102cdbd7SLarsGit223 */ 407d868eb89SAndreas Gohr public function getCleanName($name) 408d868eb89SAndreas Gohr { 4092b58f049SAndreas Gohr $name = trim($name, " \t\""); 410102cdbd7SLarsGit223 $name = str_replace('"', '\"', $name, $count); 411093fe67eSAndreas Gohr if ($count > 0 || str_contains($name, ',')) { 412102cdbd7SLarsGit223 $name = '"' . $name . '"'; 413102cdbd7SLarsGit223 } 414102cdbd7SLarsGit223 return $name; 415102cdbd7SLarsGit223 } 416102cdbd7SLarsGit223 417102cdbd7SLarsGit223 /** 4181d045709SAndreas Gohr * Sets an email address header with correct encoding 419bb01c27cSAndreas Gohr * 420bb01c27cSAndreas Gohr * Unicode characters will be deaccented and encoded base64 421bb01c27cSAndreas Gohr * for headers. Addresses may not contain Non-ASCII data! 422bb01c27cSAndreas Gohr * 423102cdbd7SLarsGit223 * If @$addresses is a string then it will be split into multiple 424102cdbd7SLarsGit223 * addresses. Addresses must be separated by a comma. If the display 425102cdbd7SLarsGit223 * name includes a comma then it MUST be properly enclosed by '"' to 426102cdbd7SLarsGit223 * prevent spliting at the wrong point. 427102cdbd7SLarsGit223 * 428bb01c27cSAndreas Gohr * Example: 4298c253612SGerrit Uitslag * cc("föö <foo@bar.com>, me@somewhere.com","TBcc"); 430102cdbd7SLarsGit223 * to("foo, Dr." <foo@bar.com>, me@somewhere.com"); 431bb01c27cSAndreas Gohr * 43242ea7f44SGerrit Uitslag * @param string|string[] $addresses Multiple adresses separated by commas or as array 43342ea7f44SGerrit Uitslag * @return false|string the prepared header (can contain multiple lines) 434bb01c27cSAndreas Gohr */ 435d868eb89SAndreas Gohr public function cleanAddress($addresses) 436d868eb89SAndreas Gohr { 437bb01c27cSAndreas Gohr $headers = ''; 438b6c97c70SAndreas Gohr if (!is_array($addresses)) { 439d31a1599SLarsGit223 $count = preg_match_all('/\s*(?:("[^"]*"[^,]+),*)|([^,]+)\s*,*/', $addresses, $matches, PREG_SET_ORDER); 44024870174SAndreas Gohr $addresses = []; 441743792d0SLarsGit223 if ($count !== false && is_array($matches)) { 442102cdbd7SLarsGit223 foreach ($matches as $match) { 44324870174SAndreas Gohr $addresses[] = rtrim($match[0], ','); 444102cdbd7SLarsGit223 } 445b6c97c70SAndreas Gohr } 446b6c97c70SAndreas Gohr } 447b6c97c70SAndreas Gohr 448b6c97c70SAndreas Gohr foreach ($addresses as $part) { 449b6c97c70SAndreas Gohr $part = preg_replace('/[\r\n\0]+/', ' ', $part); // remove attack vectors 450bb01c27cSAndreas Gohr $part = trim($part); 451bb01c27cSAndreas Gohr 452bb01c27cSAndreas Gohr // parse address 453bb01c27cSAndreas Gohr if (preg_match('#(.*?)<(.*?)>#', $part, $matches)) { 454bb01c27cSAndreas Gohr $text = trim($matches[1]); 455bb01c27cSAndreas Gohr $addr = $matches[2]; 456bb01c27cSAndreas Gohr } else { 45710da1f74SAndreas Gohr $text = ''; 458bb01c27cSAndreas Gohr $addr = $part; 459bb01c27cSAndreas Gohr } 460bb01c27cSAndreas Gohr // skip empty ones 461bb01c27cSAndreas Gohr if (empty($addr)) { 462bb01c27cSAndreas Gohr continue; 463bb01c27cSAndreas Gohr } 464bb01c27cSAndreas Gohr 465bb01c27cSAndreas Gohr // FIXME: is there a way to encode the localpart of a emailaddress? 46624870174SAndreas Gohr if (!Clean::isASCII($addr)) { 4674772cf38SAndreas Gohr msg(hsc("E-Mail address <$addr> is not ASCII"), -1, __LINE__, __FILE__, MSG_ADMINS_ONLY); 468bb01c27cSAndreas Gohr continue; 469bb01c27cSAndreas Gohr } 470bb01c27cSAndreas Gohr 471*73dc0a89SAndreas Gohr if (!MailUtils::isValid($addr)) { 4724772cf38SAndreas Gohr msg(hsc("E-Mail address <$addr> is not valid"), -1, __LINE__, __FILE__, MSG_ADMINS_ONLY); 473bb01c27cSAndreas Gohr continue; 474bb01c27cSAndreas Gohr } 475bb01c27cSAndreas Gohr 476bb01c27cSAndreas Gohr // text was given 47730085ef3SYurii K if (!empty($text) && !isWindows()) { // No named recipients for To: in Windows (see FS#652) 478bb01c27cSAndreas Gohr // add address quotes 479bb01c27cSAndreas Gohr $addr = "<$addr>"; 480bb01c27cSAndreas Gohr 481bb01c27cSAndreas Gohr if (defined('MAILHEADER_ASCIIONLY')) { 48224870174SAndreas Gohr $text = Clean::deaccent($text); 48324870174SAndreas Gohr $text = Clean::strip($text); 484bb01c27cSAndreas Gohr } 485bb01c27cSAndreas Gohr 486093fe67eSAndreas Gohr if (str_contains($text, ',') || !Clean::isASCII($text)) { 487bb01c27cSAndreas Gohr $text = '=?UTF-8?B?' . base64_encode($text) . '?='; 488bb01c27cSAndreas Gohr } 489bb01c27cSAndreas Gohr } else { 490bb01c27cSAndreas Gohr $text = ''; 491bb01c27cSAndreas Gohr } 492bb01c27cSAndreas Gohr 493bb01c27cSAndreas Gohr // add to header comma seperated 494bb01c27cSAndreas Gohr if ($headers != '') { 495bb01c27cSAndreas Gohr $headers .= ', '; 496bb01c27cSAndreas Gohr } 497bb01c27cSAndreas Gohr $headers .= $text . ' ' . $addr; 498bb01c27cSAndreas Gohr } 499bb01c27cSAndreas Gohr 500b6c97c70SAndreas Gohr $headers = trim($headers); 501bb01c27cSAndreas Gohr if (empty($headers)) return false; 502bb01c27cSAndreas Gohr 503bb01c27cSAndreas Gohr return $headers; 504bb01c27cSAndreas Gohr } 505bb01c27cSAndreas Gohr 506bb01c27cSAndreas Gohr 507bb01c27cSAndreas Gohr /** 508bb01c27cSAndreas Gohr * Prepare the mime multiparts for all attachments 509bb01c27cSAndreas Gohr * 510bb01c27cSAndreas Gohr * Replaces placeholders in the HTML with the correct CIDs 51142ea7f44SGerrit Uitslag * 51242ea7f44SGerrit Uitslag * @return string mime multiparts 513bb01c27cSAndreas Gohr */ 514d868eb89SAndreas Gohr protected function prepareAttachments() 515d868eb89SAndreas Gohr { 516bb01c27cSAndreas Gohr $mime = ''; 517bb01c27cSAndreas Gohr $part = 1; 518bb01c27cSAndreas Gohr // embedded attachments 519bb01c27cSAndreas Gohr foreach ($this->attach as $media) { 520ce9d2cc8SAndreas Gohr $media['name'] = str_replace(':', '_', cleanID($media['name'], true)); 521ce9d2cc8SAndreas Gohr 522bb01c27cSAndreas Gohr // create content id 523bb01c27cSAndreas Gohr $cid = 'part' . $part . '.' . $this->partid; 524bb01c27cSAndreas Gohr 525bb01c27cSAndreas Gohr // replace wildcards 526bb01c27cSAndreas Gohr if ($media['embed']) { 527bb01c27cSAndreas Gohr $this->html = str_replace('%%' . $media['embed'] . '%%', 'cid:' . $cid, $this->html); 528bb01c27cSAndreas Gohr } 529bb01c27cSAndreas Gohr 530bb01c27cSAndreas Gohr $mime .= '--' . $this->boundary . MAILHEADER_EOL; 5311d8036c2SAndreas Gohr $mime .= $this->wrappedHeaderLine('Content-Type', $media['mime'] . '; id="' . $cid . '"'); 5321d8036c2SAndreas Gohr $mime .= $this->wrappedHeaderLine('Content-Transfer-Encoding', 'base64'); 5331d8036c2SAndreas Gohr $mime .= $this->wrappedHeaderLine('Content-ID', "<$cid>"); 534bb01c27cSAndreas Gohr if ($media['embed']) { 5351d8036c2SAndreas Gohr $mime .= $this->wrappedHeaderLine('Content-Disposition', 'inline; filename=' . $media['name']); 536bb01c27cSAndreas Gohr } else { 5371d8036c2SAndreas Gohr $mime .= $this->wrappedHeaderLine('Content-Disposition', 'attachment; filename=' . $media['name']); 538bb01c27cSAndreas Gohr } 539bb01c27cSAndreas Gohr $mime .= MAILHEADER_EOL; //end of headers 540bb01c27cSAndreas Gohr $mime .= chunk_split(base64_encode($media['data']), 74, MAILHEADER_EOL); 541bb01c27cSAndreas Gohr 542bb01c27cSAndreas Gohr $part++; 543bb01c27cSAndreas Gohr } 544bb01c27cSAndreas Gohr return $mime; 545bb01c27cSAndreas Gohr } 546bb01c27cSAndreas Gohr 5471d045709SAndreas Gohr /** 5481d045709SAndreas Gohr * Build the body and handles multi part mails 5491d045709SAndreas Gohr * 5501d045709SAndreas Gohr * Needs to be called before prepareHeaders! 5511d045709SAndreas Gohr * 5521d045709SAndreas Gohr * @return string the prepared mail body, false on errors 5531d045709SAndreas Gohr */ 554d868eb89SAndreas Gohr protected function prepareBody() 555d868eb89SAndreas Gohr { 5561d045709SAndreas Gohr 5572398a2b5SAndreas Gohr // no HTML mails allowed? remove HTML body 5582398a2b5SAndreas Gohr if (!$this->allowhtml) { 5592398a2b5SAndreas Gohr $this->html = ''; 5602398a2b5SAndreas Gohr } 5612398a2b5SAndreas Gohr 562bb01c27cSAndreas Gohr // check for body 563bb01c27cSAndreas Gohr if (!$this->text && !$this->html) { 564bb01c27cSAndreas Gohr return false; 565bb01c27cSAndreas Gohr } 566bb01c27cSAndreas Gohr 567bb01c27cSAndreas Gohr // add general headers 568bb01c27cSAndreas Gohr $this->headers['MIME-Version'] = '1.0'; 569bb01c27cSAndreas Gohr 5701d045709SAndreas Gohr $body = ''; 5711d045709SAndreas Gohr 572bb01c27cSAndreas Gohr if (!$this->html && !count($this->attach)) { // we can send a simple single part message 573bb01c27cSAndreas Gohr $this->headers['Content-Type'] = 'text/plain; charset=UTF-8'; 574bb01c27cSAndreas Gohr $this->headers['Content-Transfer-Encoding'] = 'base64'; 575be3cc6abSAndreas Gohr $body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL); 576bb01c27cSAndreas Gohr } else { // multi part it is 5771d045709SAndreas Gohr $body .= "This is a multi-part message in MIME format." . MAILHEADER_EOL; 578bb01c27cSAndreas Gohr 579bb01c27cSAndreas Gohr // prepare the attachments 580bb01c27cSAndreas Gohr $attachments = $this->prepareAttachments(); 581bb01c27cSAndreas Gohr 582bb01c27cSAndreas Gohr // do we have alternative text content? 583bb01c27cSAndreas Gohr if ($this->text && $this->html) { 584a36fc348SAndreas Gohr $this->headers['Content-Type'] = 'multipart/alternative;' . MAILHEADER_EOL . 585a36fc348SAndreas Gohr ' boundary="' . $this->boundary . 'XX"'; 586bb01c27cSAndreas Gohr $body .= '--' . $this->boundary . 'XX' . MAILHEADER_EOL; 5871d045709SAndreas Gohr $body .= 'Content-Type: text/plain; charset=UTF-8' . MAILHEADER_EOL; 5881d045709SAndreas Gohr $body .= 'Content-Transfer-Encoding: base64' . MAILHEADER_EOL; 589bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 590be3cc6abSAndreas Gohr $body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL); 591bb01c27cSAndreas Gohr $body .= '--' . $this->boundary . 'XX' . MAILHEADER_EOL; 592a36fc348SAndreas Gohr $body .= 'Content-Type: multipart/related;' . MAILHEADER_EOL . 593d6e04b60SAndreas Gohr ' boundary="' . $this->boundary . '";' . MAILHEADER_EOL . 594d6e04b60SAndreas Gohr ' type="text/html"' . MAILHEADER_EOL; 595bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 596bb01c27cSAndreas Gohr } 597bb01c27cSAndreas Gohr 5981d045709SAndreas Gohr $body .= '--' . $this->boundary . MAILHEADER_EOL; 5991d045709SAndreas Gohr $body .= 'Content-Type: text/html; charset=UTF-8' . MAILHEADER_EOL; 6001d045709SAndreas Gohr $body .= 'Content-Transfer-Encoding: base64' . MAILHEADER_EOL; 601bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 602be3cc6abSAndreas Gohr $body .= chunk_split(base64_encode($this->html), 72, MAILHEADER_EOL); 603bb01c27cSAndreas Gohr $body .= MAILHEADER_EOL; 604bb01c27cSAndreas Gohr $body .= $attachments; 605bb01c27cSAndreas Gohr $body .= '--' . $this->boundary . '--' . MAILHEADER_EOL; 606bb01c27cSAndreas Gohr 607bb01c27cSAndreas Gohr // close open multipart/alternative boundary 608bb01c27cSAndreas Gohr if ($this->text && $this->html) { 609bb01c27cSAndreas Gohr $body .= '--' . $this->boundary . 'XX--' . MAILHEADER_EOL; 610bb01c27cSAndreas Gohr } 611bb01c27cSAndreas Gohr } 612bb01c27cSAndreas Gohr 613bb01c27cSAndreas Gohr return $body; 614bb01c27cSAndreas Gohr } 615bb01c27cSAndreas Gohr 616bb01c27cSAndreas Gohr /** 617a36fc348SAndreas Gohr * Cleanup and encode the headers array 618a36fc348SAndreas Gohr */ 619d868eb89SAndreas Gohr protected function cleanHeaders() 620d868eb89SAndreas Gohr { 621a36fc348SAndreas Gohr global $conf; 622a36fc348SAndreas Gohr 623a36fc348SAndreas Gohr // clean up addresses 624a36fc348SAndreas Gohr if (empty($this->headers['From'])) $this->from($conf['mailfrom']); 62524870174SAndreas Gohr $addrs = ['To', 'From', 'Cc', 'Bcc', 'Reply-To', 'Sender']; 626a36fc348SAndreas Gohr foreach ($addrs as $addr) { 627a36fc348SAndreas Gohr if (isset($this->headers[$addr])) { 628a36fc348SAndreas Gohr $this->headers[$addr] = $this->cleanAddress($this->headers[$addr]); 629a36fc348SAndreas Gohr } 630a36fc348SAndreas Gohr } 631a36fc348SAndreas Gohr 63245992a63SAndreas Gohr if (isset($this->headers['Subject'])) { 633a36fc348SAndreas Gohr // add prefix to subject 63454f30755SAndreas Gohr if (empty($conf['mailprefix'])) { 63524870174SAndreas Gohr if (PhpString::strlen($conf['title']) < 20) { 63654f30755SAndreas Gohr $prefix = '[' . $conf['title'] . ']'; 63754f30755SAndreas Gohr } else { 63824870174SAndreas Gohr $prefix = '[' . PhpString::substr($conf['title'], 0, 20) . '...]'; 6398a215f09SAndreas Gohr } 6408a215f09SAndreas Gohr } else { 641a36fc348SAndreas Gohr $prefix = '[' . $conf['mailprefix'] . ']'; 64254f30755SAndreas Gohr } 6436c16a3a9Sfiwswe if (!str_starts_with($this->headers['Subject'], $prefix)) { 64445992a63SAndreas Gohr $this->headers['Subject'] = $prefix . ' ' . $this->headers['Subject']; 645a36fc348SAndreas Gohr } 646a36fc348SAndreas Gohr 647a36fc348SAndreas Gohr // encode subject 648a36fc348SAndreas Gohr if (defined('MAILHEADER_ASCIIONLY')) { 64924870174SAndreas Gohr $this->headers['Subject'] = Clean::deaccent($this->headers['Subject']); 65024870174SAndreas Gohr $this->headers['Subject'] = Clean::strip($this->headers['Subject']); 651a36fc348SAndreas Gohr } 65224870174SAndreas Gohr if (!Clean::isASCII($this->headers['Subject'])) { 65345992a63SAndreas Gohr $this->headers['Subject'] = '=?UTF-8?B?' . base64_encode($this->headers['Subject']) . '?='; 654a36fc348SAndreas Gohr } 655a36fc348SAndreas Gohr } 656a36fc348SAndreas Gohr } 6571d8036c2SAndreas Gohr 6581d8036c2SAndreas Gohr /** 6591d8036c2SAndreas Gohr * Returns a complete, EOL terminated header line, wraps it if necessary 6601d8036c2SAndreas Gohr * 66142ea7f44SGerrit Uitslag * @param string $key 66242ea7f44SGerrit Uitslag * @param string $val 66342ea7f44SGerrit Uitslag * @return string line 6641d8036c2SAndreas Gohr */ 665d868eb89SAndreas Gohr protected function wrappedHeaderLine($key, $val) 666d868eb89SAndreas Gohr { 6671d8036c2SAndreas Gohr return wordwrap("$key: $val", 78, MAILHEADER_EOL . ' ') . MAILHEADER_EOL; 668a36fc348SAndreas Gohr } 669a36fc348SAndreas Gohr 670a36fc348SAndreas Gohr /** 671bb01c27cSAndreas Gohr * Create a string from the headers array 6721d045709SAndreas Gohr * 6731d045709SAndreas Gohr * @returns string the headers 674bb01c27cSAndreas Gohr */ 675d868eb89SAndreas Gohr protected function prepareHeaders() 676d868eb89SAndreas Gohr { 677bb01c27cSAndreas Gohr $headers = ''; 678bb01c27cSAndreas Gohr foreach ($this->headers as $key => $val) { 679749c0023SAndreas Gohr if ($val === '' || $val === null) continue; 6801d8036c2SAndreas Gohr $headers .= $this->wrappedHeaderLine($key, $val); 681bb01c27cSAndreas Gohr } 682bb01c27cSAndreas Gohr return $headers; 683bb01c27cSAndreas Gohr } 684bb01c27cSAndreas Gohr 685bb01c27cSAndreas Gohr /** 686bb01c27cSAndreas Gohr * return a full email with all headers 687bb01c27cSAndreas Gohr * 6881d045709SAndreas Gohr * This is mainly intended for debugging and testing but could also be 6891d045709SAndreas Gohr * used for MHT exports 6901d045709SAndreas Gohr * 6911d045709SAndreas Gohr * @return string the mail, false on errors 692bb01c27cSAndreas Gohr */ 693d868eb89SAndreas Gohr public function dump() 694d868eb89SAndreas Gohr { 695a36fc348SAndreas Gohr $this->cleanHeaders(); 696bb01c27cSAndreas Gohr $body = $this->prepareBody(); 6974d18e936SAndreas Gohr if ($body === false) return false; 6981d045709SAndreas Gohr $headers = $this->prepareHeaders(); 699bb01c27cSAndreas Gohr 700bb01c27cSAndreas Gohr return $headers . MAILHEADER_EOL . $body; 701bb01c27cSAndreas Gohr } 7021d045709SAndreas Gohr 7031d045709SAndreas Gohr /** 7049ea45836SChristopher Smith * Prepare default token replacement strings 7059ea45836SChristopher Smith * 7069ea45836SChristopher Smith * Populates the '$replacements' property. 7079ea45836SChristopher Smith * Should be called by the class constructor 7089ea45836SChristopher Smith */ 709d868eb89SAndreas Gohr protected function prepareTokenReplacements() 710d868eb89SAndreas Gohr { 7119ea45836SChristopher Smith global $INFO; 7129ea45836SChristopher Smith global $conf; 7139ea45836SChristopher Smith /* @var Input $INPUT */ 7149ea45836SChristopher Smith global $INPUT; 7159ea45836SChristopher Smith global $lang; 7169ea45836SChristopher Smith 7179ea45836SChristopher Smith $ip = clientIP(); 7189ea45836SChristopher Smith $cip = gethostsbyaddrs($ip); 7198fa268b3SAndreas Gohr $name = $INFO['userinfo']['name'] ?? ''; 7208fa268b3SAndreas Gohr $mail = $INFO['userinfo']['mail'] ?? ''; 7219ea45836SChristopher Smith 72224870174SAndreas Gohr $this->replacements['text'] = [ 7239ea45836SChristopher Smith 'DATE' => dformat(), 7249ea45836SChristopher Smith 'BROWSER' => $INPUT->server->str('HTTP_USER_AGENT'), 7259ea45836SChristopher Smith 'IPADDRESS' => $ip, 7269ea45836SChristopher Smith 'HOSTNAME' => $cip, 7279ea45836SChristopher Smith 'TITLE' => $conf['title'], 7289ea45836SChristopher Smith 'DOKUWIKIURL' => DOKU_URL, 7299ea45836SChristopher Smith 'USER' => $INPUT->server->str('REMOTE_USER'), 73068491db9SPhy 'NAME' => $name, 73168491db9SPhy 'MAIL' => $mail 73224870174SAndreas Gohr ]; 73324870174SAndreas Gohr 73464159a61SAndreas Gohr $signature = str_replace( 73564159a61SAndreas Gohr '@DOKUWIKIURL@', 73664159a61SAndreas Gohr $this->replacements['text']['DOKUWIKIURL'], 73764159a61SAndreas Gohr $lang['email_signature_text'] 73864159a61SAndreas Gohr ); 739774514c9SGerrit Uitslag $this->replacements['text']['EMAILSIGNATURE'] = "\n-- \n" . $signature . "\n"; 7409ea45836SChristopher Smith 74124870174SAndreas Gohr $this->replacements['html'] = [ 7429ea45836SChristopher Smith 'DATE' => '<i>' . hsc(dformat()) . '</i>', 7439ea45836SChristopher Smith 'BROWSER' => hsc($INPUT->server->str('HTTP_USER_AGENT')), 7449ea45836SChristopher Smith 'IPADDRESS' => '<code>' . hsc($ip) . '</code>', 7459ea45836SChristopher Smith 'HOSTNAME' => '<code>' . hsc($cip) . '</code>', 7469ea45836SChristopher Smith 'TITLE' => hsc($conf['title']), 7479ea45836SChristopher Smith 'DOKUWIKIURL' => '<a href="' . DOKU_URL . '">' . DOKU_URL . '</a>', 7489ea45836SChristopher Smith 'USER' => hsc($INPUT->server->str('REMOTE_USER')), 74968491db9SPhy 'NAME' => hsc($name), 75024870174SAndreas Gohr 'MAIL' => '<a href="mailto:"' . hsc($mail) . '">' . hsc($mail) . '</a>' 75124870174SAndreas Gohr ]; 752774514c9SGerrit Uitslag $signature = $lang['email_signature_text']; 753774514c9SGerrit Uitslag if (!empty($lang['email_signature_html'])) { 754774514c9SGerrit Uitslag $signature = $lang['email_signature_html']; 755774514c9SGerrit Uitslag } 756774514c9SGerrit Uitslag $signature = str_replace( 75724870174SAndreas Gohr ['@DOKUWIKIURL@', "\n"], 75824870174SAndreas Gohr [$this->replacements['html']['DOKUWIKIURL'], '<br />'], 759774514c9SGerrit Uitslag $signature 760774514c9SGerrit Uitslag ); 761774514c9SGerrit Uitslag $this->replacements['html']['EMAILSIGNATURE'] = $signature; 7629ea45836SChristopher Smith } 7639ea45836SChristopher Smith 7649ea45836SChristopher Smith /** 7651d045709SAndreas Gohr * Send the mail 7661d045709SAndreas Gohr * 7671d045709SAndreas Gohr * Call this after all data was set 7681d045709SAndreas Gohr * 76928d2ad80SAndreas Gohr * @triggers MAIL_MESSAGE_SEND 7701d045709SAndreas Gohr * @return bool true if the mail was successfully passed to the MTA 7711d045709SAndreas Gohr */ 772d868eb89SAndreas Gohr public function send() 773d868eb89SAndreas Gohr { 7743f6872b1SMyron Turner global $lang; 77528d2ad80SAndreas Gohr $success = false; 776a36fc348SAndreas Gohr 77728d2ad80SAndreas Gohr // prepare hook data 77824870174SAndreas Gohr $data = [ 77928d2ad80SAndreas Gohr // pass the whole mail class to plugin 78028d2ad80SAndreas Gohr 'mail' => $this, 78128d2ad80SAndreas Gohr // pass references for backward compatibility 78228d2ad80SAndreas Gohr 'to' => &$this->headers['To'], 78328d2ad80SAndreas Gohr 'cc' => &$this->headers['Cc'], 78428d2ad80SAndreas Gohr 'bcc' => &$this->headers['Bcc'], 78528d2ad80SAndreas Gohr 'from' => &$this->headers['From'], 78628d2ad80SAndreas Gohr 'subject' => &$this->headers['Subject'], 78728d2ad80SAndreas Gohr 'body' => &$this->text, 788a89c75afSAndreas Gohr 'params' => &$this->sendparam, 78928d2ad80SAndreas Gohr 'headers' => '', // plugins shouldn't use this 79028d2ad80SAndreas Gohr // signal if we mailed successfully to AFTER event 79128d2ad80SAndreas Gohr 'success' => &$success, 79224870174SAndreas Gohr ]; 79328d2ad80SAndreas Gohr 79428d2ad80SAndreas Gohr // do our thing if BEFORE hook approves 795e1d9dcc8SAndreas Gohr $evt = new Event('MAIL_MESSAGE_SEND', $data); 79628d2ad80SAndreas Gohr if ($evt->advise_before(true)) { 79728d2ad80SAndreas Gohr // clean up before using the headers 798a36fc348SAndreas Gohr $this->cleanHeaders(); 799a36fc348SAndreas Gohr 8001d045709SAndreas Gohr // any recipients? 8017d34963bSAndreas Gohr if ( 8027d34963bSAndreas Gohr trim($this->headers['To']) === '' && 8031d045709SAndreas Gohr trim($this->headers['Cc']) === '' && 804a89c75afSAndreas Gohr trim($this->headers['Bcc']) === '' 805a89c75afSAndreas Gohr ) return false; 8061d045709SAndreas Gohr 8071d045709SAndreas Gohr // The To: header is special 8086be717dbSMichael Hamann if (array_key_exists('To', $this->headers)) { 8096be717dbSMichael Hamann $to = (string)$this->headers['To']; 8101d045709SAndreas Gohr unset($this->headers['To']); 8111d045709SAndreas Gohr } else { 8121d045709SAndreas Gohr $to = ''; 8131d045709SAndreas Gohr } 8141d045709SAndreas Gohr 8151d045709SAndreas Gohr // so is the subject 8166be717dbSMichael Hamann if (array_key_exists('Subject', $this->headers)) { 8176be717dbSMichael Hamann $subject = (string)$this->headers['Subject']; 8181d045709SAndreas Gohr unset($this->headers['Subject']); 8191d045709SAndreas Gohr } else { 8201d045709SAndreas Gohr $subject = ''; 8211d045709SAndreas Gohr } 8221d045709SAndreas Gohr 8231d045709SAndreas Gohr // make the body 8241d045709SAndreas Gohr $body = $this->prepareBody(); 8254c89a7f6SAndreas Gohr if ($body === false) return false; 8261d045709SAndreas Gohr 8271d045709SAndreas Gohr // cook the headers 8281d045709SAndreas Gohr $headers = $this->prepareHeaders(); 82928d2ad80SAndreas Gohr // add any headers set by legacy plugins 83028d2ad80SAndreas Gohr if (trim($data['headers'])) { 83128d2ad80SAndreas Gohr $headers .= MAILHEADER_EOL . trim($data['headers']); 83228d2ad80SAndreas Gohr } 8331d045709SAndreas Gohr 8343f6872b1SMyron Turner if (!function_exists('mail')) { 8353f6872b1SMyron Turner $emsg = $lang['email_fail'] . $subject; 8363f6872b1SMyron Turner error_log($emsg); 8373f6872b1SMyron Turner msg(hsc($emsg), -1, __LINE__, __FILE__, MSG_MANAGERS_ONLY); 8383f6872b1SMyron Turner $evt->advise_after(); 8393f6872b1SMyron Turner return false; 8403f6872b1SMyron Turner } 8413f6872b1SMyron Turner 8421d045709SAndreas Gohr // send the thing 843bfa6d256SAndreas Gohr if ($to === '') $to = '(undisclosed-recipients)'; // #1422 844749c0023SAndreas Gohr if ($this->sendparam === null) { 84528d2ad80SAndreas Gohr $success = @mail($to, $subject, $body, $headers); 8461d045709SAndreas Gohr } else { 84728d2ad80SAndreas Gohr $success = @mail($to, $subject, $body, $headers, $this->sendparam); 8481d045709SAndreas Gohr } 8491d045709SAndreas Gohr } 85028d2ad80SAndreas Gohr // any AFTER actions? 85128d2ad80SAndreas Gohr $evt->advise_after(); 85228d2ad80SAndreas Gohr return $success; 85328d2ad80SAndreas Gohr } 854bb01c27cSAndreas Gohr} 855