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