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