Completed
Push — emailsignature ( acb389...774514 )
by Gerrit
04:26
created

Mailer   D

Complexity

Total Complexity 90

Size/Duplication

Total Lines 717
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 717
rs 4.4444
c 2
b 0
f 0
wmc 90
lcom 1
cbo 3

23 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 27 2
A attachFile() 0 12 2
A attachContent() 0 13 2
A autoembed_cb() 0 14 2
B setHeader() 0 21 6
A setParameters() 0 3 1
C setBody() 0 54 9
A setHTML() 0 3 1
A setText() 0 3 1
A to() 0 3 1
A cc() 0 3 1
A bcc() 0 3 1
A from() 0 3 1
A subject() 0 3 1
D cleanAddress() 0 69 16
B prepareAttachments() 0 31 4
C prepareBody() 0 60 10
D cleanHeaders() 0 39 10
A wrappedHeaderLine() 0 3 1
A prepareHeaders() 0 8 4
A dump() 0 8 2
A prepareTokenReplacements() 0 53 2
C send() 0 70 10

How to fix   Complexity   

Complex Class

Complex classes like Mailer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mailer, and based on these observations, apply Extract Interface, too.

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