Completed
Push — bccfix ( fc6c14 )
by Andreas
09:03
created

Mailer::cleanHeaders()   F

Complexity

Conditions 12
Paths 300

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 43
rs 3.7957
cc 12
eloc 25
nc 300
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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