Completed
Push — svgpagetools ( bbaba7...d7814e )
by Andreas
10:59 queued 07:19
created

Mailer::cleanAddress()   C

Complexity

Conditions 15
Paths 216

Size

Total Lines 65
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 36
nc 216
nop 1
dl 0
loc 65
rs 5.1047
c 0
b 0
f 0

How to fix   Long Method    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
    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
        $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
        $this->prepareTokenReplacements();
66
    }
67
68
    /**
69
     * Attach a file
70
     *
71
     * @param string $path  Path to the file to attach
72
     * @param string $mime  Mimetype of the attached file
73
     * @param string $name The filename to use
74
     * @param string $embed Unique key to reference this file from the HTML part
75
     */
76
    public function attachFile($path, $mime, $name = '', $embed = '') {
77
        if(!$name) {
78
            $name = utf8_basename($path);
79
        }
80
81
        $this->attach[] = array(
82
            'data'  => file_get_contents($path),
83
            'mime'  => $mime,
84
            'name'  => $name,
85
            'embed' => $embed
86
        );
87
    }
88
89
    /**
90
     * Attach a file
91
     *
92
     * @param string $data  The file contents to attach
93
     * @param string $mime  Mimetype of the attached file
94
     * @param string $name  The filename to use
95
     * @param string $embed Unique key to reference this file from the HTML part
96
     */
97
    public function attachContent($data, $mime, $name = '', $embed = '') {
98
        if(!$name) {
99
            list(, $ext) = explode('/', $mime);
100
            $name = count($this->attach).".$ext";
101
        }
102
103
        $this->attach[] = array(
104
            'data'  => $data,
105
            'mime'  => $mime,
106
            'name'  => $name,
107
            'embed' => $embed
108
        );
109
    }
110
111
    /**
112
     * Callback function to automatically embed images referenced in HTML templates
113
     *
114
     * @param array $matches
115
     * @return string placeholder
116
     */
117
    protected function autoembed_cb($matches) {
118
        static $embeds = 0;
119
        $embeds++;
120
121
        // get file and mime type
122
        $media = cleanID($matches[1]);
123
        list(, $mime) = mimetype($media);
124
        $file = mediaFN($media);
125
        if(!file_exists($file)) return $matches[0]; //bad reference, keep as is
126
127
        // attach it and set placeholder
128
        $this->attachFile($file, $mime, '', 'autoembed'.$embeds);
129
        return '%%autoembed'.$embeds.'%%';
130
    }
131
132
    /**
133
     * Add an arbitrary header to the mail
134
     *
135
     * If an empy value is passed, the header is removed
136
     *
137
     * @param string $header the header name (no trailing colon!)
138
     * @param string|string[] $value  the value of the header
139
     * @param bool   $clean  remove all non-ASCII chars and line feeds?
140
     */
141
    public function setHeader($header, $value, $clean = true) {
142
        $header = str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $header)))); // streamline casing
143
        if($clean) {
144
            $header = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@]+/', '', $header);
145
            $value  = preg_replace('/[^a-zA-Z0-9_ \-\.\+\@<>]+/', '', $value);
146
        }
147
148
        // empty value deletes
149
        if(is_array($value)){
150
            $value = array_map('trim', $value);
151
            $value = array_filter($value);
152
            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...
153
        }else{
154
            $value = trim($value);
155
        }
156
        if($value === '') {
157
            if(isset($this->headers[$header])) unset($this->headers[$header]);
158
        } else {
159
            $this->headers[$header] = $value;
160
        }
161
    }
162
163
    /**
164
     * Set additional parameters to be passed to sendmail
165
     *
166
     * Whatever is set here is directly passed to PHP's mail() command as last
167
     * parameter. Depending on the PHP setup this might break mailing alltogether
168
     *
169
     * @param string $param
170
     */
171
    public function setParameters($param) {
172
        $this->sendparam = $param;
173
    }
174
175
    /**
176
     * Set the text and HTML body and apply replacements
177
     *
178
     * This function applies a whole bunch of default replacements in addition
179
     * to the ones specified as parameters
180
     *
181
     * If you pass the HTML part or HTML replacements yourself you have to make
182
     * sure you encode all HTML special chars correctly
183
     *
184
     * @param string $text     plain text body
185
     * @param array  $textrep  replacements to apply on the text part
186
     * @param array  $htmlrep  replacements to apply on the HTML part, null to use $textrep (with urls wrapped in <a> tags)
187
     * @param string $html     the HTML body, leave null to create it from $text
188
     * @param bool   $wrap     wrap the HTML in the default header/Footer
189
     */
190
    public function setBody($text, $textrep = null, $htmlrep = null, $html = null, $wrap = true) {
191
192
        $htmlrep = (array)$htmlrep;
193
        $textrep = (array)$textrep;
194
195
        // create HTML from text if not given
196
        if(is_null($html)) {
197
            $html = $text;
198
            $html = hsc($html);
199
            $html = preg_replace('/^----+$/m', '<hr >', $html);
200
            $html = nl2br($html);
201
        }
202
        if($wrap) {
203
            $wrap = rawLocale('mailwrap', 'html');
204
            $html = preg_replace('/\n-- <br \/>.*$/s', '', $html); //strip signature
205
            $html = str_replace('@EMAILSIGNATURE@', '', $html); //strip @EMAILSIGNATURE@
206
            $html = str_replace('@HTMLBODY@', $html, $wrap);
207
        }
208
209
        if(strpos($text, '@EMAILSIGNATURE@') === false) {
210
            $text .= '@EMAILSIGNATURE@';
211
        }
212
213
        // copy over all replacements missing for HTML (autolink URLs)
214
        foreach($textrep as $key => $value) {
215
            if(isset($htmlrep[$key])) continue;
216
            if(media_isexternal($value)) {
217
                $htmlrep[$key] = '<a href="'.hsc($value).'">'.hsc($value).'</a>';
218
            } else {
219
                $htmlrep[$key] = hsc($value);
220
            }
221
        }
222
223
        // embed media from templates
224
        $html = preg_replace_callback(
225
            '/@MEDIA\(([^\)]+)\)@/',
226
            array($this, 'autoembed_cb'), $html
227
        );
228
229
        // add default token replacements
230
        $trep = array_merge($this->replacements['text'], (array)$textrep);
231
        $hrep = array_merge($this->replacements['html'], (array)$htmlrep);
232
233
        // Apply replacements
234
        foreach($trep as $key => $substitution) {
235
            $text = str_replace('@'.strtoupper($key).'@', $substitution, $text);
236
        }
237
        foreach($hrep as $key => $substitution) {
238
            $html = str_replace('@'.strtoupper($key).'@', $substitution, $html);
239
        }
240
241
        $this->setHTML($html);
242
        $this->setText($text);
243
    }
244
245
    /**
246
     * Set the HTML part of the mail
247
     *
248
     * Placeholders can be used to reference embedded attachments
249
     *
250
     * You probably want to use setBody() instead
251
     *
252
     * @param string $html
253
     */
254
    public function setHTML($html) {
255
        $this->html = $html;
256
    }
257
258
    /**
259
     * Set the plain text part of the mail
260
     *
261
     * You probably want to use setBody() instead
262
     *
263
     * @param string $text
264
     */
265
    public function setText($text) {
266
        $this->text = $text;
267
    }
268
269
    /**
270
     * Add the To: recipients
271
     *
272
     * @see cleanAddress
273
     * @param string|string[]  $address Multiple adresses separated by commas or as array
274
     */
275
    public function to($address) {
276
        $this->setHeader('To', $address, false);
277
    }
278
279
    /**
280
     * Add the Cc: recipients
281
     *
282
     * @see cleanAddress
283
     * @param string|string[]  $address Multiple adresses separated by commas or as array
284
     */
285
    public function cc($address) {
286
        $this->setHeader('Cc', $address, false);
287
    }
288
289
    /**
290
     * Add the Bcc: recipients
291
     *
292
     * @see cleanAddress
293
     * @param string|string[]  $address Multiple adresses separated by commas or as array
294
     */
295
    public function bcc($address) {
296
        $this->setHeader('Bcc', $address, false);
297
    }
298
299
    /**
300
     * Add the From: address
301
     *
302
     * This is set to $conf['mailfrom'] when not specified so you shouldn't need
303
     * to call this function
304
     *
305
     * @see cleanAddress
306
     * @param string  $address from address
307
     */
308
    public function from($address) {
309
        $this->setHeader('From', $address, false);
310
    }
311
312
    /**
313
     * Add the mail's Subject: header
314
     *
315
     * @param string $subject the mail subject
316
     */
317
    public function subject($subject) {
318
        $this->headers['Subject'] = $subject;
319
    }
320
321
    /**
322
     * Sets an email address header with correct encoding
323
     *
324
     * Unicode characters will be deaccented and encoded base64
325
     * for headers. Addresses may not contain Non-ASCII data!
326
     *
327
     * Example:
328
     *   cc("föö <[email protected]>, [email protected]","TBcc");
329
     *
330
     * @param string|string[]  $addresses Multiple adresses separated by commas or as array
331
     * @return false|string  the prepared header (can contain multiple lines)
332
     */
333
    public function cleanAddress($addresses) {
334
        // No named recipients for To: in Windows (see FS#652)
335
        $names = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? false : true;
336
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) && $names) {
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 === '' || is_null($val)) 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('@DOKUWIKIURL@', $this->replacements['text']['DOKUWIKIURL'], $lang['email_signature_text']);
620
        $this->replacements['text']['EMAILSIGNATURE'] = "\n-- \n" . $signature . "\n";
621
622
        $this->replacements['html'] = array(
623
            'DATE' => '<i>' . hsc(dformat()) . '</i>',
624
            'BROWSER' => hsc($INPUT->server->str('HTTP_USER_AGENT')),
625
            'IPADDRESS' => '<code>' . hsc($ip) . '</code>',
626
            'HOSTNAME' => '<code>' . hsc($cip) . '</code>',
627
            'TITLE' => hsc($conf['title']),
628
            'DOKUWIKIURL' => '<a href="' . DOKU_URL . '">' . DOKU_URL . '</a>',
629
            'USER' => hsc($INPUT->server->str('REMOTE_USER')),
630
            'NAME' => hsc($INFO['userinfo']['name']),
631
            'MAIL' => '<a href="mailto:"' . hsc($INFO['userinfo']['mail']) . '">' .
632
                hsc($INFO['userinfo']['mail']) . '</a>'
633
        );
634
        $signature = $lang['email_signature_text'];
635
        if(!empty($lang['email_signature_html'])) {
636
            $signature = $lang['email_signature_html'];
637
        }
638
        $signature = str_replace(
639
            array(
640
                '@DOKUWIKIURL@',
641
                "\n"
642
            ),
643
            array(
644
                $this->replacements['html']['DOKUWIKIURL'],
645
                '<br />'
646
            ),
647
            $signature
648
        );
649
        $this->replacements['html']['EMAILSIGNATURE'] = $signature;
650
    }
651
652
    /**
653
     * Send the mail
654
     *
655
     * Call this after all data was set
656
     *
657
     * @triggers MAIL_MESSAGE_SEND
658
     * @return bool true if the mail was successfully passed to the MTA
659
     */
660
    public function send() {
661
        $success = false;
662
663
        // prepare hook data
664
        $data = array(
665
            // pass the whole mail class to plugin
666
            'mail'    => $this,
667
            // pass references for backward compatibility
668
            'to'      => &$this->headers['To'],
669
            'cc'      => &$this->headers['Cc'],
670
            'bcc'     => &$this->headers['Bcc'],
671
            'from'    => &$this->headers['From'],
672
            'subject' => &$this->headers['Subject'],
673
            'body'    => &$this->text,
674
            'params'  => &$this->sendparam,
675
            'headers' => '', // plugins shouldn't use this
676
            // signal if we mailed successfully to AFTER event
677
            'success' => &$success,
678
        );
679
680
        // do our thing if BEFORE hook approves
681
        $evt = new Doku_Event('MAIL_MESSAGE_SEND', $data);
682
        if($evt->advise_before(true)) {
683
            // clean up before using the headers
684
            $this->cleanHeaders();
685
686
            // any recipients?
687
            if(trim($this->headers['To']) === '' &&
688
                trim($this->headers['Cc']) === '' &&
689
                trim($this->headers['Bcc']) === ''
690
            ) return false;
691
692
            // The To: header is special
693
            if(array_key_exists('To', $this->headers)) {
694
                $to = (string)$this->headers['To'];
695
                unset($this->headers['To']);
696
            } else {
697
                $to = '';
698
            }
699
700
            // so is the subject
701
            if(array_key_exists('Subject', $this->headers)) {
702
                $subject = (string)$this->headers['Subject'];
703
                unset($this->headers['Subject']);
704
            } else {
705
                $subject = '';
706
            }
707
708
            // make the body
709
            $body = $this->prepareBody();
710
            if($body === false) return false;
711
712
            // cook the headers
713
            $headers = $this->prepareHeaders();
714
            // add any headers set by legacy plugins
715
            if(trim($data['headers'])) {
716
                $headers .= MAILHEADER_EOL.trim($data['headers']);
717
            }
718
719
            // send the thing
720
            if(is_null($this->sendparam)) {
721
                $success = @mail($to, $subject, $body, $headers);
722
            } else {
723
                $success = @mail($to, $subject, $body, $headers, $this->sendparam);
724
            }
725
        }
726
        // any AFTER actions?
727
        $evt->advise_after();
728
        return $success;
729
    }
730
}
731