Creating repository for dokuwiki modifications for sudaraka.org
[sudaraka-org:dokuwiki-mods.git] / inc / mail.php
1 <?php
2 /**
3  * Mail functions
4  *
5  * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6  * @author     Andreas Gohr <andi@splitbrain.org>
7  */
8
9 if(!defined('DOKU_INC')) die('meh.');
10
11 // end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?)
12 // think different
13 if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL',"\n");
14 #define('MAILHEADER_ASCIIONLY',1);
15
16 /**
17  * Patterns for use in email detection and validation
18  *
19  * NOTE: there is an unquoted '/' in RFC2822_ATEXT, it must remain unquoted to be used in the parser
20  * the pattern uses non-capturing groups as captured groups aren't allowed in the parser
21  * select pattern delimiters with care!
22  *
23  * May not be completly RFC conform!
24  * @link http://www.faqs.org/rfcs/rfc2822.html (paras 3.4.1 & 3.2.4)
25  *
26  * @author Chris Smith <chris@jalakai.co.uk>
27  * Check if a given mail address is valid
28  */
29 if (!defined('RFC2822_ATEXT')) define('RFC2822_ATEXT',"0-9a-zA-Z!#$%&'*+/=?^_`{|}~-");
30 if (!defined('PREG_PATTERN_VALID_EMAIL')) define('PREG_PATTERN_VALID_EMAIL', '['.RFC2822_ATEXT.']+(?:\.['.RFC2822_ATEXT.']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,4}|museum|travel)');
31
32 /**
33  * Prepare mailfrom replacement patterns
34  *
35  * @author Andreas Gohr <andi@splitbrain.org>
36  */
37 function mail_setup(){
38     global $conf;
39     global $USERINFO;
40
41     $replace = array();
42
43     if(!empty($USERINFO['mail'])){
44         $replace['@MAIL@'] = $USERINFO['mail'];
45     }else{
46         $host = @parse_url(DOKU_URL,PHP_URL_HOST);
47         if(!$host) $host = 'example.com';
48         $replace['@MAIL@'] = 'noreply@'.$host;
49     }
50
51     if(!empty($_SERVER['REMOTE_USER'])){
52         $replace['@USER@'] = $_SERVER['REMOTE_USER'];
53     }else{
54         $replace['@USER@'] = 'noreply';
55     }
56
57     if(!empty($USERINFO['name'])){
58         $replace['@NAME@'] = $USERINFO['name'];
59     }else{
60         $replace['@NAME@'] = '';
61     }
62
63     $conf['mailfrom'] = str_replace(array_keys($replace),
64                                     array_values($replace),
65                                     $conf['mailfrom']);
66 }
67
68 /**
69  * UTF-8 autoencoding replacement for PHPs mail function
70  *
71  * Email address fields (To, From, Cc, Bcc can contain a textpart and an address
72  * like this: 'Andreas Gohr <andi@splitbrain.org>' - the text part is encoded
73  * automatically. You can seperate receivers by commas.
74  *
75  * @param string $to      Receiver of the mail (multiple seperated by commas)
76  * @param string $subject Mailsubject
77  * @param string $body    Messagebody
78  * @param string $from    Sender address
79  * @param string $cc      CarbonCopy receiver (multiple seperated by commas)
80  * @param string $bcc     BlindCarbonCopy receiver (multiple seperated by commas)
81  * @param string $headers Additional Headers (seperated by MAILHEADER_EOL
82  * @param string $params  Additonal Sendmail params (passed to mail())
83  *
84  * @author Andreas Gohr <andi@splitbrain.org>
85  * @see    mail()
86  */
87 function mail_send($to, $subject, $body, $from='', $cc='', $bcc='', $headers=null, $params=null){
88
89     $message = compact('to','subject','body','from','cc','bcc','headers','params');
90     return trigger_event('MAIL_MESSAGE_SEND',$message,'_mail_send_action');
91 }
92
93 function _mail_send_action($data) {
94
95     // retrieve parameters from event data, $to, $subject, $body, $from, $cc, $bcc, $headers, $params
96     $to = $data['to'];
97     $subject = $data['subject'];
98     $body = $data['body'];
99
100     // add robustness in case plugin removes any of these optional values
101     $from = isset($data['from']) ? $data['from'] : '';
102     $cc = isset($data['cc']) ? $data['cc'] : '';
103     $bcc = isset($data['bcc']) ? $data['bcc'] : '';
104     $headers = isset($data['headers']) ? $data['headers'] : null;
105     $params = isset($data['params']) ? $data['params'] : null;
106
107     // discard mail request if no recipients are available
108     if(trim($to) === '' && trim($cc) === '' && trim($bcc) === '') return false;
109     
110     // end additional code to support event ... original mail_send() code from here
111
112     if(defined('MAILHEADER_ASCIIONLY')){
113         $subject = utf8_deaccent($subject);
114         $subject = utf8_strip($subject);
115     }
116
117     if(!utf8_isASCII($subject)) {
118         $enc_subj = '=?UTF-8?Q?'.mail_quotedprintable_encode($subject,0).'?=';
119         // Spaces must be encoded according to rfc2047. Use the "_" shorthand
120         $enc_subj = preg_replace('/ /', '_', $enc_subj);
121
122         // quoted printable has length restriction, use base64 if needed
123         if(strlen($subject) > 74){
124             $enc_subj = '=?UTF-8?B?'.base64_encode($subject).'?=';
125         }
126
127         $subject = $enc_subj;
128     }
129
130     $header  = '';
131
132     // No named recipients for To: in Windows (see FS#652)
133     $usenames = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? false : true;
134
135     $to = mail_encode_address($to,'',$usenames);
136     $header .= mail_encode_address($from,'From');
137     $header .= mail_encode_address($cc,'Cc');
138     $header .= mail_encode_address($bcc,'Bcc');
139     $header .= 'MIME-Version: 1.0'.MAILHEADER_EOL;
140     $header .= 'Content-Type: text/plain; charset=UTF-8'.MAILHEADER_EOL;
141     $header .= 'Content-Transfer-Encoding: quoted-printable'.MAILHEADER_EOL;
142     $header .= $headers;
143     $header  = trim($header);
144
145     $body = mail_quotedprintable_encode($body);
146
147     if($params == null){
148         return @mail($to,$subject,$body,$header);
149     }else{
150         return @mail($to,$subject,$body,$header,$params);
151     }
152 }
153
154 /**
155  * Encodes an email address header
156  *
157  * Unicode characters will be deaccented and encoded
158  * quoted_printable for headers.
159  * Addresses may not contain Non-ASCII data!
160  *
161  * Example:
162  *   mail_encode_address("föö <foo@bar.com>, me@somewhere.com","TBcc");
163  *
164  * @param string  $string Multiple adresses separated by commas
165  * @param string  $header Name of the header (To,Bcc,Cc,...)
166  * @param boolean $names  Allow named Recipients?
167  */
168 function mail_encode_address($string,$header='',$names=true){
169     $headers = '';
170     $parts = explode(',',$string);
171     foreach ($parts as $part){
172         $part = trim($part);
173
174         // parse address
175         if(preg_match('#(.*?)<(.*?)>#',$part,$matches)){
176             $text = trim($matches[1]);
177             $addr = $matches[2];
178         }else{
179             $addr = $part;
180         }
181
182         // skip empty ones
183         if(empty($addr)){
184             continue;
185         }
186
187         // FIXME: is there a way to encode the localpart of a emailaddress?
188         if(!utf8_isASCII($addr)){
189             msg(htmlspecialchars("E-Mail address <$addr> is not ASCII"),-1);
190             continue;
191         }
192
193         if(!mail_isvalid($addr)){
194             msg(htmlspecialchars("E-Mail address <$addr> is not valid"),-1);
195             continue;
196         }
197
198         // text was given
199         if(!empty($text) && $names){
200             // add address quotes
201             $addr = "<$addr>";
202
203             if(defined('MAILHEADER_ASCIIONLY')){
204                 $text = utf8_deaccent($text);
205                 $text = utf8_strip($text);
206             }
207
208             if(!utf8_isASCII($text)){
209                 // put the quotes outside as in =?UTF-8?Q?"Elan Ruusam=C3=A4e"?= vs "=?UTF-8?Q?Elan Ruusam=C3=A4e?="
210                 if (preg_match('/^"(.+)"$/', $text, $matches)) {
211                   $text = '"=?UTF-8?Q?'.mail_quotedprintable_encode($matches[1], 0).'?="';
212                 } else {
213                   $text = '=?UTF-8?Q?'.mail_quotedprintable_encode($text, 0).'?=';
214                 }
215                 // additionally the space character should be encoded as =20 (or each
216                 // word QP encoded separately).
217                 // however this is needed only in mail headers, not globally in mail_quotedprintable_encode().
218                 $text = str_replace(" ", "=20", $text);
219             }
220         }else{
221             $text = '';
222         }
223
224         // add to header comma seperated
225         if($headers != ''){
226             $headers .= ',';
227             if($header) $headers .= MAILHEADER_EOL.' '; // avoid overlong mail headers
228         }
229         $headers .= $text.' '.$addr;
230     }
231
232     if(empty($headers)) return null;
233
234     //if headername was given add it and close correctly
235     if($header) $headers = $header.': '.$headers.MAILHEADER_EOL;
236
237     return $headers;
238 }
239
240 /**
241  * Check if a given mail address is valid
242  *
243  * @param   string $email the address to check
244  * @return  bool          true if address is valid
245  */
246 function mail_isvalid($email){
247     $validator = new EmailAddressValidator;
248     $validator->allowLocalAddresses = true;
249     return $validator->check_email_address($email);
250 }
251
252 /**
253  * Quoted printable encoding
254  *
255  * @author umu <umuAThrz.tu-chemnitz.de>
256  * @link   http://www.php.net/manual/en/function.imap-8bit.php#61216
257  */
258 function mail_quotedprintable_encode($sText,$maxlen=74,$bEmulate_imap_8bit=true) {
259     // split text into lines
260     $aLines= preg_split("/(?:\r\n|\r|\n)/", $sText);
261     $cnt = count($aLines);
262
263     for ($i=0;$i<$cnt;$i++) {
264         $sLine =& $aLines[$i];
265         if (strlen($sLine)===0) continue; // do nothing, if empty
266
267         $sRegExp = '/[^\x09\x20\x21-\x3C\x3E-\x7E]/e';
268
269         // imap_8bit encodes x09 everywhere, not only at lineends,
270         // for EBCDIC safeness encode !"#$@[\]^`{|}~,
271         // for complete safeness encode every character :)
272         if ($bEmulate_imap_8bit)
273             $sRegExp = '/[^\x20\x21-\x3C\x3E-\x7E]/e';
274
275         $sReplmt = 'sprintf( "=%02X", ord ( "$0" ) ) ;';
276         $sLine = preg_replace( $sRegExp, $sReplmt, $sLine );
277
278         // encode x09,x20 at lineends
279         {
280             $iLength = strlen($sLine);
281             $iLastChar = ord($sLine{$iLength-1});
282
283             //              !!!!!!!!
284             // imap_8_bit does not encode x20 at the very end of a text,
285             // here is, where I don't agree with imap_8_bit,
286             // please correct me, if I'm wrong,
287             // or comment next line for RFC2045 conformance, if you like
288             if (!($bEmulate_imap_8bit && ($i==count($aLines)-1))){
289                 if (($iLastChar==0x09)||($iLastChar==0x20)) {
290                     $sLine{$iLength-1}='=';
291                     $sLine .= ($iLastChar==0x09)?'09':'20';
292                 }
293             }
294         }    // imap_8bit encodes x20 before chr(13), too
295         // although IMHO not requested by RFC2045, why not do it safer :)
296         // and why not encode any x20 around chr(10) or chr(13)
297         if ($bEmulate_imap_8bit) {
298             $sLine=str_replace(' =0D','=20=0D',$sLine);
299             //$sLine=str_replace(' =0A','=20=0A',$sLine);
300             //$sLine=str_replace('=0D ','=0D=20',$sLine);
301             //$sLine=str_replace('=0A ','=0A=20',$sLine);
302         }
303
304         // finally split into softlines no longer than $maxlen chars,
305         // for even more safeness one could encode x09,x20
306         // at the very first character of the line
307         // and after soft linebreaks, as well,
308         // but this wouldn't be caught by such an easy RegExp
309         if($maxlen){
310             preg_match_all( '/.{1,'.($maxlen - 2).'}([^=]{0,2})?/', $sLine, $aMatch );
311             $sLine = implode( '=' . MAILHEADER_EOL, $aMatch[0] ); // add soft crlf's
312         }
313     }
314
315     // join lines into text
316     return implode(MAILHEADER_EOL,$aLines);
317 }
318