Mailer.class.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. <?php
  2. /**
  3. * A class to build and send multi part mails (with HTML content and embedded
  4. * attachments). All mails are assumed to be in UTF-8 encoding.
  5. *
  6. * Attachments are handled in memory so this shouldn't be used to send huge
  7. * files, but then again mail shouldn't be used to send huge files either.
  8. *
  9. * @author Andreas Gohr <andi@splitbrain.org>
  10. */
  11. use dokuwiki\Extension\Event;
  12. /**
  13. * Mail Handling
  14. */
  15. class Mailer {
  16. protected $headers = array();
  17. protected $attach = array();
  18. protected $html = '';
  19. protected $text = '';
  20. protected $boundary = '';
  21. protected $partid = '';
  22. protected $sendparam = null;
  23. protected $allowhtml = true;
  24. protected $replacements = array('text'=> array(), 'html' => array());
  25. /**
  26. * Constructor
  27. *
  28. * Initializes the boundary strings, part counters and token replacements
  29. */
  30. public function __construct() {
  31. global $conf;
  32. /* @var Input $INPUT */
  33. global $INPUT;
  34. $server = parse_url(DOKU_URL, PHP_URL_HOST);
  35. if(strpos($server,'.') === false) $server .= '.localhost';
  36. $this->partid = substr(md5(uniqid(mt_rand(), true)),0, 8).'@'.$server;
  37. $this->boundary = '__________'.md5(uniqid(mt_rand(), true));
  38. $listid = implode('.', array_reverse(explode('/', DOKU_BASE))).$server;
  39. $listid = strtolower(trim($listid, '.'));
  40. $messageid = uniqid(mt_rand(), true) . "@$server";
  41. $this->allowhtml = (bool)$conf['htmlmail'];
  42. // add some default headers for mailfiltering FS#2247
  43. if(!empty($conf['mailreturnpath'])) {
  44. $this->setHeader('Return-Path', $conf['mailreturnpath']);
  45. }
  46. $this->setHeader('X-Mailer', 'DokuWiki');
  47. $this->setHeader('X-DokuWiki-User', $INPUT->server->str('REMOTE_USER'));
  48. $this->setHeader('X-DokuWiki-Title', $conf['title']);
  49. $this->setHeader('X-DokuWiki-Server', $server);
  50. $this->setHeader('X-Auto-Response-Suppress', 'OOF');
  51. $this->setHeader('List-Id', $conf['title'].' <'.$listid.'>');
  52. $this->setHeader('Date', date('r'), false);
  53. $this->setHeader('Message-Id', "<$messageid>");
  54. $this->prepareTokenReplacements();
  55. }
  56. /**
  57. * Attach a file
  58. *
  59. * @param string $path Path to the file to attach
  60. * @param string $mime Mimetype of the attached file
  61. * @param string $name The filename to use
  62. * @param string $embed Unique key to reference this file from the HTML part
  63. */
  64. public function attachFile($path, $mime, $name = '', $embed = '') {
  65. if(!$name) {
  66. $name = \dokuwiki\Utf8\PhpString::basename($path);
  67. }
  68. $this->attach[] = array(
  69. 'data' => file_get_contents($path),
  70. 'mime' => $mime,
  71. 'name' => $name,
  72. 'embed' => $embed
  73. );
  74. }
  75. /**
  76. * Attach a file
  77. *
  78. * @param string $data The file contents to attach
  79. * @param string $mime Mimetype of the attached file
  80. * @param string $name The filename to use
  81. * @param string $embed Unique key to reference this file from the HTML part
  82. */
  83. public function attachContent($data, $mime, $name = '', $embed = '') {
  84. if(!$name) {
  85. list(, $ext) = explode('/', $mime);
  86. $name = count($this->attach).".$ext";
  87. }
  88. $this->attach[] = array(
  89. 'data' => $data,
  90. 'mime' => $mime,
  91. 'name' => $name,
  92. 'embed' => $embed
  93. );
  94. }
  95. /**
  96. * Callback function to automatically embed images referenced in HTML templates
  97. *
  98. * @param array $matches
  99. * @return string placeholder
  100. */
  101. protected function autoEmbedCallBack($matches) {
  102. static $embeds = 0;
  103. $embeds++;
  104. // get file and mime type
  105. $media = cleanID($matches[1]);
  106. list(, $mime) = mimetype($media);
  107. $file = mediaFN($media);
  108. if(!file_exists($file)) return $matches[0]; //bad reference, keep as is
  109. // attach it and set placeholder
  110. $this->attachFile($file, $mime, '', 'autoembed'.$embeds);
  111. return '%%autoembed'.$embeds.'%%';
  112. }
  113. /**
  114. * Add an arbitrary header to the mail
  115. *
  116. * If an empy value is passed, the header is removed
  117. *
  118. * @param string $header the header name (no trailing colon!)
  119. * @param string|string[] $value the value of the header
  120. * @param bool $clean remove all non-ASCII chars and line feeds?
  121. */
  122. public function setHeader($header, $value, $clean = true) {
  123. $header = str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $header)))); // streamline casing
  124. if($clean) {
  125. $header = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@]+/', '', $header);
  126. $value = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@<>]+/', '', $value);
  127. }
  128. // empty value deletes
  129. if(is_array($value)){
  130. $value = array_map('trim', $value);
  131. $value = array_filter($value);
  132. if(!$value) $value = '';
  133. }else{
  134. $value = trim($value);
  135. }
  136. if($value === '') {
  137. if(isset($this->headers[$header])) unset($this->headers[$header]);
  138. } else {
  139. $this->headers[$header] = $value;
  140. }
  141. }
  142. /**
  143. * Set additional parameters to be passed to sendmail
  144. *
  145. * Whatever is set here is directly passed to PHP's mail() command as last
  146. * parameter. Depending on the PHP setup this might break mailing alltogether
  147. *
  148. * @param string $param
  149. */
  150. public function setParameters($param) {
  151. $this->sendparam = $param;
  152. }
  153. /**
  154. * Set the text and HTML body and apply replacements
  155. *
  156. * This function applies a whole bunch of default replacements in addition
  157. * to the ones specified as parameters
  158. *
  159. * If you pass the HTML part or HTML replacements yourself you have to make
  160. * sure you encode all HTML special chars correctly
  161. *
  162. * @param string $text plain text body
  163. * @param array $textrep replacements to apply on the text part
  164. * @param array $htmlrep replacements to apply on the HTML part, null to use $textrep (urls wrapped in <a> tags)
  165. * @param string $html the HTML body, leave null to create it from $text
  166. * @param bool $wrap wrap the HTML in the default header/Footer
  167. */
  168. public function setBody($text, $textrep = null, $htmlrep = null, $html = null, $wrap = true) {
  169. $htmlrep = (array)$htmlrep;
  170. $textrep = (array)$textrep;
  171. // create HTML from text if not given
  172. if($html === null) {
  173. $html = $text;
  174. $html = hsc($html);
  175. $html = preg_replace('/^----+$/m', '<hr >', $html);
  176. $html = nl2br($html);
  177. }
  178. if($wrap) {
  179. $wrapper = rawLocale('mailwrap', 'html');
  180. $html = preg_replace('/\n-- <br \/>.*$/s', '', $html); //strip signature
  181. $html = str_replace('@EMAILSIGNATURE@', '', $html); //strip @EMAILSIGNATURE@
  182. $html = str_replace('@HTMLBODY@', $html, $wrapper);
  183. }
  184. if(strpos($text, '@EMAILSIGNATURE@') === false) {
  185. $text .= '@EMAILSIGNATURE@';
  186. }
  187. // copy over all replacements missing for HTML (autolink URLs)
  188. foreach($textrep as $key => $value) {
  189. if(isset($htmlrep[$key])) continue;
  190. if(media_isexternal($value)) {
  191. $htmlrep[$key] = '<a href="'.hsc($value).'">'.hsc($value).'</a>';
  192. } else {
  193. $htmlrep[$key] = hsc($value);
  194. }
  195. }
  196. // embed media from templates
  197. $html = preg_replace_callback(
  198. '/@MEDIA\(([^\)]+)\)@/',
  199. array($this, 'autoEmbedCallBack'), $html
  200. );
  201. // add default token replacements
  202. $trep = array_merge($this->replacements['text'], (array)$textrep);
  203. $hrep = array_merge($this->replacements['html'], (array)$htmlrep);
  204. // Apply replacements
  205. foreach($trep as $key => $substitution) {
  206. $text = str_replace('@'.strtoupper($key).'@', $substitution, $text);
  207. }
  208. foreach($hrep as $key => $substitution) {
  209. $html = str_replace('@'.strtoupper($key).'@', $substitution, $html);
  210. }
  211. $this->setHTML($html);
  212. $this->setText($text);
  213. }
  214. /**
  215. * Set the HTML part of the mail
  216. *
  217. * Placeholders can be used to reference embedded attachments
  218. *
  219. * You probably want to use setBody() instead
  220. *
  221. * @param string $html
  222. */
  223. public function setHTML($html) {
  224. $this->html = $html;
  225. }
  226. /**
  227. * Set the plain text part of the mail
  228. *
  229. * You probably want to use setBody() instead
  230. *
  231. * @param string $text
  232. */
  233. public function setText($text) {
  234. $this->text = $text;
  235. }
  236. /**
  237. * Add the To: recipients
  238. *
  239. * @see cleanAddress
  240. * @param string|string[] $address Multiple adresses separated by commas or as array
  241. */
  242. public function to($address) {
  243. $this->setHeader('To', $address, false);
  244. }
  245. /**
  246. * Add the Cc: recipients
  247. *
  248. * @see cleanAddress
  249. * @param string|string[] $address Multiple adresses separated by commas or as array
  250. */
  251. public function cc($address) {
  252. $this->setHeader('Cc', $address, false);
  253. }
  254. /**
  255. * Add the Bcc: recipients
  256. *
  257. * @see cleanAddress
  258. * @param string|string[] $address Multiple adresses separated by commas or as array
  259. */
  260. public function bcc($address) {
  261. $this->setHeader('Bcc', $address, false);
  262. }
  263. /**
  264. * Add the From: address
  265. *
  266. * This is set to $conf['mailfrom'] when not specified so you shouldn't need
  267. * to call this function
  268. *
  269. * @see cleanAddress
  270. * @param string $address from address
  271. */
  272. public function from($address) {
  273. $this->setHeader('From', $address, false);
  274. }
  275. /**
  276. * Add the mail's Subject: header
  277. *
  278. * @param string $subject the mail subject
  279. */
  280. public function subject($subject) {
  281. $this->headers['Subject'] = $subject;
  282. }
  283. /**
  284. * Return a clean name which can be safely used in mail address
  285. * fields. That means the name will be enclosed in '"' if it includes
  286. * a '"' or a ','. Also a '"' will be escaped as '\"'.
  287. *
  288. * @param string $name the name to clean-up
  289. * @see cleanAddress
  290. */
  291. public function getCleanName($name) {
  292. $name = trim($name, " \t\"");
  293. $name = str_replace('"', '\"', $name, $count);
  294. if ($count > 0 || strpos($name, ',') !== false) {
  295. $name = '"'.$name.'"';
  296. }
  297. return $name;
  298. }
  299. /**
  300. * Sets an email address header with correct encoding
  301. *
  302. * Unicode characters will be deaccented and encoded base64
  303. * for headers. Addresses may not contain Non-ASCII data!
  304. *
  305. * If @$addresses is a string then it will be split into multiple
  306. * addresses. Addresses must be separated by a comma. If the display
  307. * name includes a comma then it MUST be properly enclosed by '"' to
  308. * prevent spliting at the wrong point.
  309. *
  310. * Example:
  311. * cc("föö <foo@bar.com>, me@somewhere.com","TBcc");
  312. * to("foo, Dr." <foo@bar.com>, me@somewhere.com");
  313. *
  314. * @param string|string[] $addresses Multiple adresses separated by commas or as array
  315. * @return false|string the prepared header (can contain multiple lines)
  316. */
  317. public function cleanAddress($addresses) {
  318. $headers = '';
  319. if(!is_array($addresses)){
  320. $count = preg_match_all('/\s*(?:("[^"]*"[^,]+),*)|([^,]+)\s*,*/', $addresses, $matches, PREG_SET_ORDER);
  321. $addresses = array();
  322. if ($count !== false && is_array($matches)) {
  323. foreach ($matches as $match) {
  324. array_push($addresses, rtrim($match[0], ','));
  325. }
  326. }
  327. }
  328. foreach($addresses as $part) {
  329. $part = preg_replace('/[\r\n\0]+/', ' ', $part); // remove attack vectors
  330. $part = trim($part);
  331. // parse address
  332. if(preg_match('#(.*?)<(.*?)>#', $part, $matches)) {
  333. $text = trim($matches[1]);
  334. $addr = $matches[2];
  335. } else {
  336. $text = '';
  337. $addr = $part;
  338. }
  339. // skip empty ones
  340. if(empty($addr)) {
  341. continue;
  342. }
  343. // FIXME: is there a way to encode the localpart of a emailaddress?
  344. if(!\dokuwiki\Utf8\Clean::isASCII($addr)) {
  345. msg(hsc("E-Mail address <$addr> is not ASCII"), -1, __LINE__, __FILE__, MSG_ADMINS_ONLY);
  346. continue;
  347. }
  348. if(!mail_isvalid($addr)) {
  349. msg(hsc("E-Mail address <$addr> is not valid"), -1, __LINE__, __FILE__, MSG_ADMINS_ONLY);
  350. continue;
  351. }
  352. // text was given
  353. if(!empty($text) && !isWindows()) { // No named recipients for To: in Windows (see FS#652)
  354. // add address quotes
  355. $addr = "<$addr>";
  356. if(defined('MAILHEADER_ASCIIONLY')) {
  357. $text = \dokuwiki\Utf8\Clean::deaccent($text);
  358. $text = \dokuwiki\Utf8\Clean::strip($text);
  359. }
  360. if(strpos($text, ',') !== false || !\dokuwiki\Utf8\Clean::isASCII($text)) {
  361. $text = '=?UTF-8?B?'.base64_encode($text).'?=';
  362. }
  363. } else {
  364. $text = '';
  365. }
  366. // add to header comma seperated
  367. if($headers != '') {
  368. $headers .= ', ';
  369. }
  370. $headers .= $text.' '.$addr;
  371. }
  372. $headers = trim($headers);
  373. if(empty($headers)) return false;
  374. return $headers;
  375. }
  376. /**
  377. * Prepare the mime multiparts for all attachments
  378. *
  379. * Replaces placeholders in the HTML with the correct CIDs
  380. *
  381. * @return string mime multiparts
  382. */
  383. protected function prepareAttachments() {
  384. $mime = '';
  385. $part = 1;
  386. // embedded attachments
  387. foreach($this->attach as $media) {
  388. $media['name'] = str_replace(':', '_', cleanID($media['name'], true));
  389. // create content id
  390. $cid = 'part'.$part.'.'.$this->partid;
  391. // replace wildcards
  392. if($media['embed']) {
  393. $this->html = str_replace('%%'.$media['embed'].'%%', 'cid:'.$cid, $this->html);
  394. }
  395. $mime .= '--'.$this->boundary.MAILHEADER_EOL;
  396. $mime .= $this->wrappedHeaderLine('Content-Type', $media['mime'].'; id="'.$cid.'"');
  397. $mime .= $this->wrappedHeaderLine('Content-Transfer-Encoding', 'base64');
  398. $mime .= $this->wrappedHeaderLine('Content-ID',"<$cid>");
  399. if($media['embed']) {
  400. $mime .= $this->wrappedHeaderLine('Content-Disposition', 'inline; filename='.$media['name']);
  401. } else {
  402. $mime .= $this->wrappedHeaderLine('Content-Disposition', 'attachment; filename='.$media['name']);
  403. }
  404. $mime .= MAILHEADER_EOL; //end of headers
  405. $mime .= chunk_split(base64_encode($media['data']), 74, MAILHEADER_EOL);
  406. $part++;
  407. }
  408. return $mime;
  409. }
  410. /**
  411. * Build the body and handles multi part mails
  412. *
  413. * Needs to be called before prepareHeaders!
  414. *
  415. * @return string the prepared mail body, false on errors
  416. */
  417. protected function prepareBody() {
  418. // no HTML mails allowed? remove HTML body
  419. if(!$this->allowhtml) {
  420. $this->html = '';
  421. }
  422. // check for body
  423. if(!$this->text && !$this->html) {
  424. return false;
  425. }
  426. // add general headers
  427. $this->headers['MIME-Version'] = '1.0';
  428. $body = '';
  429. if(!$this->html && !count($this->attach)) { // we can send a simple single part message
  430. $this->headers['Content-Type'] = 'text/plain; charset=UTF-8';
  431. $this->headers['Content-Transfer-Encoding'] = 'base64';
  432. $body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL);
  433. } else { // multi part it is
  434. $body .= "This is a multi-part message in MIME format.".MAILHEADER_EOL;
  435. // prepare the attachments
  436. $attachments = $this->prepareAttachments();
  437. // do we have alternative text content?
  438. if($this->text && $this->html) {
  439. $this->headers['Content-Type'] = 'multipart/alternative;'.MAILHEADER_EOL.
  440. ' boundary="'.$this->boundary.'XX"';
  441. $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
  442. $body .= 'Content-Type: text/plain; charset=UTF-8'.MAILHEADER_EOL;
  443. $body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
  444. $body .= MAILHEADER_EOL;
  445. $body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL);
  446. $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
  447. $body .= 'Content-Type: multipart/related;'.MAILHEADER_EOL.
  448. ' boundary="'.$this->boundary.'";'.MAILHEADER_EOL.
  449. ' type="text/html"'.MAILHEADER_EOL;
  450. $body .= MAILHEADER_EOL;
  451. }
  452. $body .= '--'.$this->boundary.MAILHEADER_EOL;
  453. $body .= 'Content-Type: text/html; charset=UTF-8'.MAILHEADER_EOL;
  454. $body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
  455. $body .= MAILHEADER_EOL;
  456. $body .= chunk_split(base64_encode($this->html), 72, MAILHEADER_EOL);
  457. $body .= MAILHEADER_EOL;
  458. $body .= $attachments;
  459. $body .= '--'.$this->boundary.'--'.MAILHEADER_EOL;
  460. // close open multipart/alternative boundary
  461. if($this->text && $this->html) {
  462. $body .= '--'.$this->boundary.'XX--'.MAILHEADER_EOL;
  463. }
  464. }
  465. return $body;
  466. }
  467. /**
  468. * Cleanup and encode the headers array
  469. */
  470. protected function cleanHeaders() {
  471. global $conf;
  472. // clean up addresses
  473. if(empty($this->headers['From'])) $this->from($conf['mailfrom']);
  474. $addrs = array('To', 'From', 'Cc', 'Bcc', 'Reply-To', 'Sender');
  475. foreach($addrs as $addr) {
  476. if(isset($this->headers[$addr])) {
  477. $this->headers[$addr] = $this->cleanAddress($this->headers[$addr]);
  478. }
  479. }
  480. if(isset($this->headers['Subject'])) {
  481. // add prefix to subject
  482. if(empty($conf['mailprefix'])) {
  483. if(\dokuwiki\Utf8\PhpString::strlen($conf['title']) < 20) {
  484. $prefix = '['.$conf['title'].']';
  485. } else {
  486. $prefix = '['.\dokuwiki\Utf8\PhpString::substr($conf['title'], 0, 20).'...]';
  487. }
  488. } else {
  489. $prefix = '['.$conf['mailprefix'].']';
  490. }
  491. $len = strlen($prefix);
  492. if(substr($this->headers['Subject'], 0, $len) != $prefix) {
  493. $this->headers['Subject'] = $prefix.' '.$this->headers['Subject'];
  494. }
  495. // encode subject
  496. if(defined('MAILHEADER_ASCIIONLY')) {
  497. $this->headers['Subject'] = \dokuwiki\Utf8\Clean::deaccent($this->headers['Subject']);
  498. $this->headers['Subject'] = \dokuwiki\Utf8\Clean::strip($this->headers['Subject']);
  499. }
  500. if(!\dokuwiki\Utf8\Clean::isASCII($this->headers['Subject'])) {
  501. $this->headers['Subject'] = '=?UTF-8?B?'.base64_encode($this->headers['Subject']).'?=';
  502. }
  503. }
  504. }
  505. /**
  506. * Returns a complete, EOL terminated header line, wraps it if necessary
  507. *
  508. * @param string $key
  509. * @param string $val
  510. * @return string line
  511. */
  512. protected function wrappedHeaderLine($key, $val){
  513. return wordwrap("$key: $val", 78, MAILHEADER_EOL.' ').MAILHEADER_EOL;
  514. }
  515. /**
  516. * Create a string from the headers array
  517. *
  518. * @returns string the headers
  519. */
  520. protected function prepareHeaders() {
  521. $headers = '';
  522. foreach($this->headers as $key => $val) {
  523. if ($val === '' || $val === null) continue;
  524. $headers .= $this->wrappedHeaderLine($key, $val);
  525. }
  526. return $headers;
  527. }
  528. /**
  529. * return a full email with all headers
  530. *
  531. * This is mainly intended for debugging and testing but could also be
  532. * used for MHT exports
  533. *
  534. * @return string the mail, false on errors
  535. */
  536. public function dump() {
  537. $this->cleanHeaders();
  538. $body = $this->prepareBody();
  539. if($body === false) return false;
  540. $headers = $this->prepareHeaders();
  541. return $headers.MAILHEADER_EOL.$body;
  542. }
  543. /**
  544. * Prepare default token replacement strings
  545. *
  546. * Populates the '$replacements' property.
  547. * Should be called by the class constructor
  548. */
  549. protected function prepareTokenReplacements() {
  550. global $INFO;
  551. global $conf;
  552. /* @var Input $INPUT */
  553. global $INPUT;
  554. global $lang;
  555. $ip = clientIP();
  556. $cip = gethostsbyaddrs($ip);
  557. $name = $INFO['userinfo']['name'] ?? '';
  558. $mail = $INFO['userinfo']['mail'] ?? '';
  559. $this->replacements['text'] = array(
  560. 'DATE' => dformat(),
  561. 'BROWSER' => $INPUT->server->str('HTTP_USER_AGENT'),
  562. 'IPADDRESS' => $ip,
  563. 'HOSTNAME' => $cip,
  564. 'TITLE' => $conf['title'],
  565. 'DOKUWIKIURL' => DOKU_URL,
  566. 'USER' => $INPUT->server->str('REMOTE_USER'),
  567. 'NAME' => $name,
  568. 'MAIL' => $mail
  569. );
  570. $signature = str_replace(
  571. '@DOKUWIKIURL@',
  572. $this->replacements['text']['DOKUWIKIURL'],
  573. $lang['email_signature_text']
  574. );
  575. $this->replacements['text']['EMAILSIGNATURE'] = "\n-- \n" . $signature . "\n";
  576. $this->replacements['html'] = array(
  577. 'DATE' => '<i>' . hsc(dformat()) . '</i>',
  578. 'BROWSER' => hsc($INPUT->server->str('HTTP_USER_AGENT')),
  579. 'IPADDRESS' => '<code>' . hsc($ip) . '</code>',
  580. 'HOSTNAME' => '<code>' . hsc($cip) . '</code>',
  581. 'TITLE' => hsc($conf['title']),
  582. 'DOKUWIKIURL' => '<a href="' . DOKU_URL . '">' . DOKU_URL . '</a>',
  583. 'USER' => hsc($INPUT->server->str('REMOTE_USER')),
  584. 'NAME' => hsc($name),
  585. 'MAIL' => '<a href="mailto:"' . hsc($mail) . '">' .
  586. hsc($mail) . '</a>'
  587. );
  588. $signature = $lang['email_signature_text'];
  589. if(!empty($lang['email_signature_html'])) {
  590. $signature = $lang['email_signature_html'];
  591. }
  592. $signature = str_replace(
  593. array(
  594. '@DOKUWIKIURL@',
  595. "\n"
  596. ),
  597. array(
  598. $this->replacements['html']['DOKUWIKIURL'],
  599. '<br />'
  600. ),
  601. $signature
  602. );
  603. $this->replacements['html']['EMAILSIGNATURE'] = $signature;
  604. }
  605. /**
  606. * Send the mail
  607. *
  608. * Call this after all data was set
  609. *
  610. * @triggers MAIL_MESSAGE_SEND
  611. * @return bool true if the mail was successfully passed to the MTA
  612. */
  613. public function send() {
  614. global $lang;
  615. $success = false;
  616. // prepare hook data
  617. $data = array(
  618. // pass the whole mail class to plugin
  619. 'mail' => $this,
  620. // pass references for backward compatibility
  621. 'to' => &$this->headers['To'],
  622. 'cc' => &$this->headers['Cc'],
  623. 'bcc' => &$this->headers['Bcc'],
  624. 'from' => &$this->headers['From'],
  625. 'subject' => &$this->headers['Subject'],
  626. 'body' => &$this->text,
  627. 'params' => &$this->sendparam,
  628. 'headers' => '', // plugins shouldn't use this
  629. // signal if we mailed successfully to AFTER event
  630. 'success' => &$success,
  631. );
  632. // do our thing if BEFORE hook approves
  633. $evt = new Event('MAIL_MESSAGE_SEND', $data);
  634. if($evt->advise_before(true)) {
  635. // clean up before using the headers
  636. $this->cleanHeaders();
  637. // any recipients?
  638. if(trim($this->headers['To']) === '' &&
  639. trim($this->headers['Cc']) === '' &&
  640. trim($this->headers['Bcc']) === ''
  641. ) return false;
  642. // The To: header is special
  643. if(array_key_exists('To', $this->headers)) {
  644. $to = (string)$this->headers['To'];
  645. unset($this->headers['To']);
  646. } else {
  647. $to = '';
  648. }
  649. // so is the subject
  650. if(array_key_exists('Subject', $this->headers)) {
  651. $subject = (string)$this->headers['Subject'];
  652. unset($this->headers['Subject']);
  653. } else {
  654. $subject = '';
  655. }
  656. // make the body
  657. $body = $this->prepareBody();
  658. if($body === false) return false;
  659. // cook the headers
  660. $headers = $this->prepareHeaders();
  661. // add any headers set by legacy plugins
  662. if(trim($data['headers'])) {
  663. $headers .= MAILHEADER_EOL.trim($data['headers']);
  664. }
  665. if(!function_exists('mail')){
  666. $emsg = $lang['email_fail'] . $subject;
  667. error_log($emsg);
  668. msg(hsc($emsg), -1, __LINE__, __FILE__, MSG_MANAGERS_ONLY);
  669. $evt->advise_after();
  670. return false;
  671. }
  672. // send the thing
  673. if($to === '') $to = '(undisclosed-recipients)'; // #1422
  674. if($this->sendparam === null) {
  675. $success = @mail($to, $subject, $body, $headers);
  676. } else {
  677. $success = @mail($to, $subject, $body, $headers, $this->sendparam);
  678. }
  679. }
  680. // any AFTER actions?
  681. $evt->advise_after();
  682. return $success;
  683. }
  684. }