Completed
Push — psr2 ( 2402e4...749c00 )
by Andreas
03:01
created

Mailer::autoEmbedCallBack()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
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 .= '.localhost';
47
48
        $this->partid   = substr(md5(uniqid(mt_rand(), true)),0, 8).'@'.$server;
49
        $this->boundary = '__________'.md5(uniqid(mt_rand(), true));
50
51
        $listid = implode('.', 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);
0 ignored issues
show
Security Bug introduced by
It seems like $server defined by parse_url(DOKU_URL, PHP_URL_HOST) on line 45 can also be of type false; however, Mailer::setHeader() does only seem to accept string|array<integer,string>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

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 returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
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 autoEmbedCallBack($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 = '';
0 ignored issues
show
Bug Best Practice introduced by
The expression $value of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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