Failed Conditions
Push — issue1569 ( e7fa31...102cdb )
by
unknown
03:38
created

inc/Mailer.class.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

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
// end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?)
13
// think different
14
if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL', "\n");
15
#define('MAILHEADER_ASCIIONLY',1);
16
17
/**
18
 * Mail Handling
19
 */
20
class Mailer {
21
22
    protected $headers   = array();
23
    protected $attach    = array();
24
    protected $html      = '';
25
    protected $text      = '';
26
27
    protected $boundary  = '';
28
    protected $partid    = '';
29
    protected $sendparam = null;
30
31
    protected $allowhtml = true;
32
33
    protected $replacements = array('text'=> array(), 'html' => array());
34
35
    /**
36
     * Constructor
37
     *
38
     * Initializes the boundary strings, part counters and token replacements
39
     */
40
    public function __construct() {
41
        global $conf;
42
        /* @var Input $INPUT */
43
        global $INPUT;
44
45
        $server = parse_url(DOKU_URL, PHP_URL_HOST);
46
        if(strpos($server,'.') === false) $server = $server.'.localhost';
47
48
        $this->partid   = substr(md5(uniqid(rand(), true)),0, 8).'@'.$server;
49
        $this->boundary = '__________'.md5(uniqid(rand(), true));
50
51
        $listid = join('.', array_reverse(explode('/', DOKU_BASE))).$server;
52
        $listid = strtolower(trim($listid, '.'));
53
54
        $this->allowhtml = (bool)$conf['htmlmail'];
55
56
        // add some default headers for mailfiltering FS#2247
57
        if(!empty($conf['mailreturnpath'])) {
58
            $this->setHeader('Return-Path', $conf['mailreturnpath']);
59
        }
60
        $this->setHeader('X-Mailer', 'DokuWiki');
61
        $this->setHeader('X-DokuWiki-User', $INPUT->server->str('REMOTE_USER'));
62
        $this->setHeader('X-DokuWiki-Title', $conf['title']);
63
        $this->setHeader('X-DokuWiki-Server', $server);
64
        $this->setHeader('X-Auto-Response-Suppress', 'OOF');
65
        $this->setHeader('List-Id', $conf['title'].' <'.$listid.'>');
66
        $this->setHeader('Date', date('r'), false);
67
68
        $this->prepareTokenReplacements();
69
    }
70
71
    /**
72
     * Attach a file
73
     *
74
     * @param string $path  Path to the file to attach
75
     * @param string $mime  Mimetype of the attached file
76
     * @param string $name The filename to use
77
     * @param string $embed Unique key to reference this file from the HTML part
78
     */
79
    public function attachFile($path, $mime, $name = '', $embed = '') {
80
        if(!$name) {
81
            $name = utf8_basename($path);
82
        }
83
84
        $this->attach[] = array(
85
            'data'  => file_get_contents($path),
86
            'mime'  => $mime,
87
            'name'  => $name,
88
            'embed' => $embed
89
        );
90
    }
91
92
    /**
93
     * Attach a file
94
     *
95
     * @param string $data  The file contents to attach
96
     * @param string $mime  Mimetype of the attached file
97
     * @param string $name  The filename to use
98
     * @param string $embed Unique key to reference this file from the HTML part
99
     */
100
    public function attachContent($data, $mime, $name = '', $embed = '') {
101
        if(!$name) {
102
            list(, $ext) = explode('/', $mime);
103
            $name = count($this->attach).".$ext";
104
        }
105
106
        $this->attach[] = array(
107
            'data'  => $data,
108
            'mime'  => $mime,
109
            'name'  => $name,
110
            'embed' => $embed
111
        );
112
    }
113
114
    /**
115
     * Callback function to automatically embed images referenced in HTML templates
116
     *
117
     * @param array $matches
118
     * @return string placeholder
119
     */
120
    protected function autoembed_cb($matches) {
121
        static $embeds = 0;
122
        $embeds++;
123
124
        // get file and mime type
125
        $media = cleanID($matches[1]);
126
        list(, $mime) = mimetype($media);
127
        $file = mediaFN($media);
128
        if(!file_exists($file)) return $matches[0]; //bad reference, keep as is
129
130
        // attach it and set placeholder
131
        $this->attachFile($file, $mime, '', 'autoembed'.$embeds);
132
        return '%%autoembed'.$embeds.'%%';
133
    }
134
135
    /**
136
     * Add an arbitrary header to the mail
137
     *
138
     * If an empy value is passed, the header is removed
139
     *
140
     * @param string $header the header name (no trailing colon!)
141
     * @param string|string[] $value  the value of the header
142
     * @param bool   $clean  remove all non-ASCII chars and line feeds?
143
     */
144
    public function setHeader($header, $value, $clean = true) {
145
        $header = str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $header)))); // streamline casing
146
        if($clean) {
147
            $header = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@]+/', '', $header);
148
            $value  = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@<>]+/', '', $value);
149
        }
150
151
        // empty value deletes
152
        if(is_array($value)){
153
            $value = array_map('trim', $value);
154
            $value = array_filter($value);
155
            if(!$value) $value = '';
156
        }else{
157
            $value = trim($value);
158
        }
159
        if($value === '') {
160
            if(isset($this->headers[$header])) unset($this->headers[$header]);
161
        } else {
162
            $this->headers[$header] = $value;
163
        }
164
    }
165
166
    /**
167
     * Set additional parameters to be passed to sendmail
168
     *
169
     * Whatever is set here is directly passed to PHP's mail() command as last
170
     * parameter. Depending on the PHP setup this might break mailing alltogether
171
     *
172
     * @param string $param
173
     */
174
    public function setParameters($param) {
175
        $this->sendparam = $param;
176
    }
177
178
    /**
179
     * Set the text and HTML body and apply replacements
180
     *
181
     * This function applies a whole bunch of default replacements in addition
182
     * to the ones specified as parameters
183
     *
184
     * If you pass the HTML part or HTML replacements yourself you have to make
185
     * sure you encode all HTML special chars correctly
186
     *
187
     * @param string $text     plain text body
188
     * @param array  $textrep  replacements to apply on the text part
189
     * @param array  $htmlrep  replacements to apply on the HTML part, null to use $textrep (with urls wrapped in <a> tags)
190
     * @param string $html     the HTML body, leave null to create it from $text
191
     * @param bool   $wrap     wrap the HTML in the default header/Footer
192
     */
193
    public function setBody($text, $textrep = null, $htmlrep = null, $html = null, $wrap = true) {
194
195
        $htmlrep = (array)$htmlrep;
196
        $textrep = (array)$textrep;
197
198
        // create HTML from text if not given
199
        if(is_null($html)) {
200
            $html = $text;
201
            $html = hsc($html);
202
            $html = preg_replace('/^----+$/m', '<hr >', $html);
203
            $html = nl2br($html);
204
        }
205
        if($wrap) {
206
            $wrap = rawLocale('mailwrap', 'html');
207
            $html = preg_replace('/\n-- <br \/>.*$/s', '', $html); //strip signature
208
            $html = str_replace('@EMAILSIGNATURE@', '', $html); //strip @EMAILSIGNATURE@
209
            $html = str_replace('@HTMLBODY@', $html, $wrap);
210
        }
211
212
        if(strpos($text, '@EMAILSIGNATURE@') === false) {
213
            $text .= '@EMAILSIGNATURE@';
214
        }
215
216
        // copy over all replacements missing for HTML (autolink URLs)
217
        foreach($textrep as $key => $value) {
218
            if(isset($htmlrep[$key])) continue;
219
            if(media_isexternal($value)) {
220
                $htmlrep[$key] = '<a href="'.hsc($value).'">'.hsc($value).'</a>';
221
            } else {
222
                $htmlrep[$key] = hsc($value);
223
            }
224
        }
225
226
        // embed media from templates
227
        $html = preg_replace_callback(
228
            '/@MEDIA\(([^\)]+)\)@/',
229
            array($this, 'autoembed_cb'), $html
230
        );
231
232
        // add default token replacements
233
        $trep = array_merge($this->replacements['text'], (array)$textrep);
234
        $hrep = array_merge($this->replacements['html'], (array)$htmlrep);
235
236
        // Apply replacements
237
        foreach($trep as $key => $substitution) {
238
            $text = str_replace('@'.strtoupper($key).'@', $substitution, $text);
239
        }
240
        foreach($hrep as $key => $substitution) {
241
            $html = str_replace('@'.strtoupper($key).'@', $substitution, $html);
242
        }
243
244
        $this->setHTML($html);
245
        $this->setText($text);
246
    }
247
248
    /**
249
     * Set the HTML part of the mail
250
     *
251
     * Placeholders can be used to reference embedded attachments
252
     *
253
     * You probably want to use setBody() instead
254
     *
255
     * @param string $html
256
     */
257
    public function setHTML($html) {
258
        $this->html = $html;
259
    }
260
261
    /**
262
     * Set the plain text part of the mail
263
     *
264
     * You probably want to use setBody() instead
265
     *
266
     * @param string $text
267
     */
268
    public function setText($text) {
269
        $this->text = $text;
270
    }
271
272
    /**
273
     * Add the To: recipients
274
     *
275
     * @see cleanAddress
276
     * @param string|string[]  $address Multiple adresses separated by commas or as array
277
     */
278
    public function to($address) {
279
        $this->setHeader('To', $address, false);
280
    }
281
282
    /**
283
     * Add the Cc: recipients
284
     *
285
     * @see cleanAddress
286
     * @param string|string[]  $address Multiple adresses separated by commas or as array
287
     */
288
    public function cc($address) {
289
        $this->setHeader('Cc', $address, false);
290
    }
291
292
    /**
293
     * Add the Bcc: recipients
294
     *
295
     * @see cleanAddress
296
     * @param string|string[]  $address Multiple adresses separated by commas or as array
297
     */
298
    public function bcc($address) {
299
        $this->setHeader('Bcc', $address, false);
300
    }
301
302
    /**
303
     * Add the From: address
304
     *
305
     * This is set to $conf['mailfrom'] when not specified so you shouldn't need
306
     * to call this function
307
     *
308
     * @see cleanAddress
309
     * @param string  $address from address
310
     */
311
    public function from($address) {
312
        $this->setHeader('From', $address, false);
313
    }
314
315
    /**
316
     * Add the mail's Subject: header
317
     *
318
     * @param string $subject the mail subject
319
     */
320
    public function subject($subject) {
321
        $this->headers['Subject'] = $subject;
322
    }
323
324
    /**
325
     * Return a clean name which can be safely used in mail address
326
     * fields. That means the name will be enclosed in '"' if it includes
327
     * a '"' or a ','. Also a '"' will be escaped as '\"'.
328
     *
329
     * @param string $name the name to clean-up
330
     * @see cleanAddress
331
     */
332
    public function getCleanName($name) {
333
        $name = trim($name, ' \t"');
334
        $name = str_replace('"', '\"', $name, $count);
335
        if ($count > 0 || strpos($name, ',') !== false) {
336
            $name = '"'.$name.'"';
337
        }
338
        return $name;
339
    }
340
341
    /**
342
     * Sets an email address header with correct encoding
343
     *
344
     * Unicode characters will be deaccented and encoded base64
345
     * for headers. Addresses may not contain Non-ASCII data!
346
     *
347
     * If @$addresses is a string then it will be split into multiple
348
     * addresses. Addresses must be separated by a comma. If the display
349
     * name includes a comma then it MUST be properly enclosed by '"' to
350
     * prevent spliting at the wrong point.
351
     * 
352
     * Example:
353
     *   cc("föö <[email protected]>, [email protected]","TBcc");
354
     *   to("foo, Dr." <[email protected]>, [email protected]");
355
     *
356
     * @param string|string[]  $addresses Multiple adresses separated by commas or as array
357
     * @return false|string  the prepared header (can contain multiple lines)
358
     */
359
    public function cleanAddress($addresses) {
360
        $headers = '';
361
        if(!is_array($addresses)){
362
            preg_match_all('/\s*(?:("[^"]*"[^,]+),*)|([^,]+)\s*,*/', $addresses, $matches, PREG_SET_ORDER);
363
            $addresses = array();
364
            foreach ($matches as $match) {
0 ignored issues
show
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
365
                array_push($addresses, $match[0]);
366
            }
367
        }
368
369
        foreach($addresses as $part) {
370
            $part = preg_replace('/[\r\n\0]+/', ' ', $part); // remove attack vectors
371
            $part = trim($part);
372
373
            // parse address
374
            if(preg_match('#(.*?)<(.*?)>#', $part, $matches)) {
375
                $text = trim($matches[1]);
376
                $addr = $matches[2];
377
            } else {
378
                $addr = $part;
379
            }
380
            // skip empty ones
381
            if(empty($addr)) {
382
                continue;
383
            }
384
385
            // FIXME: is there a way to encode the localpart of a emailaddress?
386
            if(!utf8_isASCII($addr)) {
387
                msg(hsc("E-Mail address <$addr> is not ASCII"), -1);
388
                continue;
389
            }
390
391
            if(!mail_isvalid($addr)) {
392
                msg(hsc("E-Mail address <$addr> is not valid"), -1);
393
                continue;
394
            }
395
396
            // text was given
397
            if(!empty($text) && !isWindows()) { // No named recipients for To: in Windows (see FS#652)
398
                // add address quotes
399
                $addr = "<$addr>";
400
401
                if(defined('MAILHEADER_ASCIIONLY')) {
402
                    $text = utf8_deaccent($text);
403
                    $text = utf8_strip($text);
404
                }
405
406
                if(strpos($text, ',') !== false || !utf8_isASCII($text)) {
407
                    $text = '=?UTF-8?B?'.base64_encode($text).'?=';
408
                }
409
            } else {
410
                $text = '';
411
            }
412
413
            // add to header comma seperated
414
            if($headers != '') {
415
                $headers .= ', ';
416
            }
417
            $headers .= $text.' '.$addr;
418
        }
419
420
        $headers = trim($headers);
421
        if(empty($headers)) return false;
422
423
        return $headers;
424
    }
425
426
427
    /**
428
     * Prepare the mime multiparts for all attachments
429
     *
430
     * Replaces placeholders in the HTML with the correct CIDs
431
     *
432
     * @return string mime multiparts
433
     */
434
    protected function prepareAttachments() {
435
        $mime = '';
436
        $part = 1;
437
        // embedded attachments
438
        foreach($this->attach as $media) {
439
            $media['name'] = str_replace(':', '_', cleanID($media['name'], true));
440
441
            // create content id
442
            $cid = 'part'.$part.'.'.$this->partid;
443
444
            // replace wildcards
445
            if($media['embed']) {
446
                $this->html = str_replace('%%'.$media['embed'].'%%', 'cid:'.$cid, $this->html);
447
            }
448
449
            $mime .= '--'.$this->boundary.MAILHEADER_EOL;
450
            $mime .= $this->wrappedHeaderLine('Content-Type', $media['mime'].'; id="'.$cid.'"');
451
            $mime .= $this->wrappedHeaderLine('Content-Transfer-Encoding', 'base64');
452
            $mime .= $this->wrappedHeaderLine('Content-ID',"<$cid>");
453
            if($media['embed']) {
454
                $mime .= $this->wrappedHeaderLine('Content-Disposition', 'inline; filename='.$media['name']);
455
            } else {
456
                $mime .= $this->wrappedHeaderLine('Content-Disposition', 'attachment; filename='.$media['name']);
457
            }
458
            $mime .= MAILHEADER_EOL; //end of headers
459
            $mime .= chunk_split(base64_encode($media['data']), 74, MAILHEADER_EOL);
460
461
            $part++;
462
        }
463
        return $mime;
464
    }
465
466
    /**
467
     * Build the body and handles multi part mails
468
     *
469
     * Needs to be called before prepareHeaders!
470
     *
471
     * @return string the prepared mail body, false on errors
472
     */
473
    protected function prepareBody() {
474
475
        // no HTML mails allowed? remove HTML body
476
        if(!$this->allowhtml) {
477
            $this->html = '';
478
        }
479
480
        // check for body
481
        if(!$this->text && !$this->html) {
482
            return false;
483
        }
484
485
        // add general headers
486
        $this->headers['MIME-Version'] = '1.0';
487
488
        $body = '';
489
490
        if(!$this->html && !count($this->attach)) { // we can send a simple single part message
491
            $this->headers['Content-Type']              = 'text/plain; charset=UTF-8';
492
            $this->headers['Content-Transfer-Encoding'] = 'base64';
493
            $body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL);
494
        } else { // multi part it is
495
            $body .= "This is a multi-part message in MIME format.".MAILHEADER_EOL;
496
497
            // prepare the attachments
498
            $attachments = $this->prepareAttachments();
499
500
            // do we have alternative text content?
501
            if($this->text && $this->html) {
502
                $this->headers['Content-Type'] = 'multipart/alternative;'.MAILHEADER_EOL.
503
                    '  boundary="'.$this->boundary.'XX"';
504
                $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
505
                $body .= 'Content-Type: text/plain; charset=UTF-8'.MAILHEADER_EOL;
506
                $body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
507
                $body .= MAILHEADER_EOL;
508
                $body .= chunk_split(base64_encode($this->text), 72, MAILHEADER_EOL);
509
                $body .= '--'.$this->boundary.'XX'.MAILHEADER_EOL;
510
                $body .= 'Content-Type: multipart/related;'.MAILHEADER_EOL.
511
                    '  boundary="'.$this->boundary.'";'.MAILHEADER_EOL.
512
                    '  type="text/html"'.MAILHEADER_EOL;
513
                $body .= MAILHEADER_EOL;
514
            }
515
516
            $body .= '--'.$this->boundary.MAILHEADER_EOL;
517
            $body .= 'Content-Type: text/html; charset=UTF-8'.MAILHEADER_EOL;
518
            $body .= 'Content-Transfer-Encoding: base64'.MAILHEADER_EOL;
519
            $body .= MAILHEADER_EOL;
520
            $body .= chunk_split(base64_encode($this->html), 72, MAILHEADER_EOL);
521
            $body .= MAILHEADER_EOL;
522
            $body .= $attachments;
523
            $body .= '--'.$this->boundary.'--'.MAILHEADER_EOL;
524
525
            // close open multipart/alternative boundary
526
            if($this->text && $this->html) {
527
                $body .= '--'.$this->boundary.'XX--'.MAILHEADER_EOL;
528
            }
529
        }
530
531
        return $body;
532
    }
533
534
    /**
535
     * Cleanup and encode the headers array
536
     */
537
    protected function cleanHeaders() {
538
        global $conf;
539
540
        // clean up addresses
541
        if(empty($this->headers['From'])) $this->from($conf['mailfrom']);
542
        $addrs = array('To', 'From', 'Cc', 'Bcc', 'Reply-To', 'Sender');
543
        foreach($addrs as $addr) {
544
            if(isset($this->headers[$addr])) {
545
                $this->headers[$addr] = $this->cleanAddress($this->headers[$addr]);
546
            }
547
        }
548
549
        if(isset($this->headers['Subject'])) {
550
            // add prefix to subject
551
            if(empty($conf['mailprefix'])) {
552
                if(utf8_strlen($conf['title']) < 20) {
553
                    $prefix = '['.$conf['title'].']';
554
                } else {
555
                    $prefix = '['.utf8_substr($conf['title'], 0, 20).'...]';
556
                }
557
            } else {
558
                $prefix = '['.$conf['mailprefix'].']';
559
            }
560
            $len = strlen($prefix);
561
            if(substr($this->headers['Subject'], 0, $len) != $prefix) {
562
                $this->headers['Subject'] = $prefix.' '.$this->headers['Subject'];
563
            }
564
565
            // encode subject
566
            if(defined('MAILHEADER_ASCIIONLY')) {
567
                $this->headers['Subject'] = utf8_deaccent($this->headers['Subject']);
568
                $this->headers['Subject'] = utf8_strip($this->headers['Subject']);
569
            }
570
            if(!utf8_isASCII($this->headers['Subject'])) {
571
                $this->headers['Subject'] = '=?UTF-8?B?'.base64_encode($this->headers['Subject']).'?=';
572
            }
573
        }
574
575
    }
576
577
    /**
578
     * Returns a complete, EOL terminated header line, wraps it if necessary
579
     *
580
     * @param string $key
581
     * @param string $val
582
     * @return string line
583
     */
584
    protected function wrappedHeaderLine($key, $val){
585
        return wordwrap("$key: $val", 78, MAILHEADER_EOL.'  ').MAILHEADER_EOL;
586
    }
587
588
    /**
589
     * Create a string from the headers array
590
     *
591
     * @returns string the headers
592
     */
593
    protected function prepareHeaders() {
594
        $headers = '';
595
        foreach($this->headers as $key => $val) {
596
            if ($val === '' || is_null($val)) continue;
597
            $headers .= $this->wrappedHeaderLine($key, $val);
598
        }
599
        return $headers;
600
    }
601
602
    /**
603
     * return a full email with all headers
604
     *
605
     * This is mainly intended for debugging and testing but could also be
606
     * used for MHT exports
607
     *
608
     * @return string the mail, false on errors
609
     */
610
    public function dump() {
611
        $this->cleanHeaders();
612
        $body = $this->prepareBody();
613
        if($body === false) return false;
614
        $headers = $this->prepareHeaders();
615
616
        return $headers.MAILHEADER_EOL.$body;
617
    }
618
619
    /**
620
     * Prepare default token replacement strings
621
     *
622
     * Populates the '$replacements' property.
623
     * Should be called by the class constructor
624
     */
625
    protected function prepareTokenReplacements() {
626
        global $INFO;
627
        global $conf;
628
        /* @var Input $INPUT */
629
        global $INPUT;
630
        global $lang;
631
632
        $ip   = clientIP();
633
        $cip  = gethostsbyaddrs($ip);
634
635
        $this->replacements['text'] = array(
636
            'DATE' => dformat(),
637
            'BROWSER' => $INPUT->server->str('HTTP_USER_AGENT'),
638
            'IPADDRESS' => $ip,
639
            'HOSTNAME' => $cip,
640
            'TITLE' => $conf['title'],
641
            'DOKUWIKIURL' => DOKU_URL,
642
            'USER' => $INPUT->server->str('REMOTE_USER'),
643
            'NAME' => $INFO['userinfo']['name'],
644
            'MAIL' => $INFO['userinfo']['mail']
645
        );
646
        $signature = str_replace('@DOKUWIKIURL@', $this->replacements['text']['DOKUWIKIURL'], $lang['email_signature_text']);
647
        $this->replacements['text']['EMAILSIGNATURE'] = "\n-- \n" . $signature . "\n";
648
649
        $this->replacements['html'] = array(
650
            'DATE' => '<i>' . hsc(dformat()) . '</i>',
651
            'BROWSER' => hsc($INPUT->server->str('HTTP_USER_AGENT')),
652
            'IPADDRESS' => '<code>' . hsc($ip) . '</code>',
653
            'HOSTNAME' => '<code>' . hsc($cip) . '</code>',
654
            'TITLE' => hsc($conf['title']),
655
            'DOKUWIKIURL' => '<a href="' . DOKU_URL . '">' . DOKU_URL . '</a>',
656
            'USER' => hsc($INPUT->server->str('REMOTE_USER')),
657
            'NAME' => hsc($INFO['userinfo']['name']),
658
            'MAIL' => '<a href="mailto:"' . hsc($INFO['userinfo']['mail']) . '">' .
659
                hsc($INFO['userinfo']['mail']) . '</a>'
660
        );
661
        $signature = $lang['email_signature_text'];
662
        if(!empty($lang['email_signature_html'])) {
663
            $signature = $lang['email_signature_html'];
664
        }
665
        $signature = str_replace(
666
            array(
667
                '@DOKUWIKIURL@',
668
                "\n"
669
            ),
670
            array(
671
                $this->replacements['html']['DOKUWIKIURL'],
672
                '<br />'
673
            ),
674
            $signature
675
        );
676
        $this->replacements['html']['EMAILSIGNATURE'] = $signature;
677
    }
678
679
    /**
680
     * Send the mail
681
     *
682
     * Call this after all data was set
683
     *
684
     * @triggers MAIL_MESSAGE_SEND
685
     * @return bool true if the mail was successfully passed to the MTA
686
     */
687
    public function send() {
688
        $success = false;
689
690
        // prepare hook data
691
        $data = array(
692
            // pass the whole mail class to plugin
693
            'mail'    => $this,
694
            // pass references for backward compatibility
695
            'to'      => &$this->headers['To'],
696
            'cc'      => &$this->headers['Cc'],
697
            'bcc'     => &$this->headers['Bcc'],
698
            'from'    => &$this->headers['From'],
699
            'subject' => &$this->headers['Subject'],
700
            'body'    => &$this->text,
701
            'params'  => &$this->sendparam,
702
            'headers' => '', // plugins shouldn't use this
703
            // signal if we mailed successfully to AFTER event
704
            'success' => &$success,
705
        );
706
707
        // do our thing if BEFORE hook approves
708
        $evt = new Doku_Event('MAIL_MESSAGE_SEND', $data);
709
        if($evt->advise_before(true)) {
710
            // clean up before using the headers
711
            $this->cleanHeaders();
712
713
            // any recipients?
714
            if(trim($this->headers['To']) === '' &&
715
                trim($this->headers['Cc']) === '' &&
716
                trim($this->headers['Bcc']) === ''
717
            ) return false;
718
719
            // The To: header is special
720
            if(array_key_exists('To', $this->headers)) {
721
                $to = (string)$this->headers['To'];
722
                unset($this->headers['To']);
723
            } else {
724
                $to = '';
725
            }
726
727
            // so is the subject
728
            if(array_key_exists('Subject', $this->headers)) {
729
                $subject = (string)$this->headers['Subject'];
730
                unset($this->headers['Subject']);
731
            } else {
732
                $subject = '';
733
            }
734
735
            // make the body
736
            $body = $this->prepareBody();
737
            if($body === false) return false;
738
739
            // cook the headers
740
            $headers = $this->prepareHeaders();
741
            // add any headers set by legacy plugins
742
            if(trim($data['headers'])) {
743
                $headers .= MAILHEADER_EOL.trim($data['headers']);
744
            }
745
746
            // send the thing
747
            if(is_null($this->sendparam)) {
748
                $success = @mail($to, $subject, $body, $headers);
749
            } else {
750
                $success = @mail($to, $subject, $body, $headers, $this->sendparam);
751
            }
752
        }
753
        // any AFTER actions?
754
        $evt->advise_after();
755
        return $success;
756
    }
757
}
758