3 * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)
4 * Copyright (C) 2002-2009 The Nucleus Group
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 * (see nucleus/documentation/index.html#license for more info)
13 * Class used to represent a collection of e-mail addresses, to which a
14 * message can be sent (e.g. comment or karma vote notification).
16 * @license http://nucleuscms.org/license.txt GNU General Public License
17 * @copyright Copyright (C) 2002-2009 The Nucleus Group
18 * @version $Id: NOTIFICATION.php 1534 2011-06-22 06:13:23Z sakamocchi $
22 static private $charset;
23 static private $scheme = 'B';
26 * NOTIFICATION::address_validation()
27 * Validating the string as address
29 * FIXME: this is just migrated from globalfunctions.php
30 * we should confirm this regular expression refering to RFC 5322
32 * @link http://www.ietf.org/rfc/rfc5322.txt
33 * @see 3.4. Address Specification
36 * @param String $address Address
37 * @return Boolean valid or not
39 static public function address_validation($address)
41 return (boolean) preg_match('#^(?!\\.)(?:\\.?[-a-zA-Z0-9!\\#$%&\'*+/=?^_`{|}~]+)+@(?!\\.)(?:\\.?(?!-)[-a-zA-Z0-9]+(?<!-)){2,}$#', $address);
45 * NOTIFICATION::get_mail_footer()
46 * Return mail footer with Nucleus CMS singnature
50 * @return String Message body with
52 static public function get_mail_footer()
56 $message .= "-----------------------------\n";
57 $message .= " Powered by Nucleus CMS\n";
58 $message .= "(http://www.nucleuscms.org/)\n";
64 * Send mails with headers including 7bit-encoded multibyte string
67 * @param string $to receivers including singlebyte and multibyte strings, based on RFC 5322
68 * @param string $subject subject including singlebyte and multibyte strings
69 * @param string $message message including singlebyte and multibyte strings
70 * @param string $from senders including singlebyte and multibyte strings, based on RFC 5322
71 * @param string(B/Q) $scheme 7bit-encoder scheme based on RFC 2047
72 * @return boolean accepted delivery or not
74 static public function mail($to, $subject, $message, $from, $charset, $scheme='B')
76 self::$charset = $charset;
77 self::$scheme = $scheme;
79 $to = self::mailbox_list_encoder($to);
80 $subject = self::seven_bit_characters_encoder($subject);
81 $from = 'From: ' . self::mailbox_list_encoder($from);
84 * All of 7bit character encoding derives from ISO/IEC 646
85 * So we can decide the body's encoding bit count by this regular expression.
89 if ( preg_match('#\A[\x00-\x7f]*\z#', $message) )
94 $header = 'Content-Type: text/html; charset=' . self::$charset . "; format=flowed; delsp=yes\n";
95 $header .= "Content-Transfer-Encoding: {$bitcount}\n";
96 $header .= "X-Mailer: Nucleus CMS NOTIFICATION class\n";
98 return mail($to, $subject, $message, "{$from}\n{$header}");
102 * NOTIFICATION::mailbox_list_encoder
103 * Encode multi byte strings included in mailbox.
104 * The format of mailbox is based on RFC 5322, which obsoletes RFC 2822
106 * @link http://www.ietf.org/rfc/rfc5322.txt
107 * @see 3.4. Address Specification
110 * @param string $mailbox_list mailbox list
111 * @return string encoded string
114 static private function mailbox_list_encoder ($mailbox_list)
116 $encoded_mailboxes = array();
117 $mailboxes = preg_split('#,#', $mailbox_list);
118 foreach ( $mailboxes as $mailbox )
120 if ( preg_match("#^([^,]+)?<([^,]+)?@([^,]+)?>$#", $mailbox, $match) )
122 $display_name = self::seven_bit_characters_encoder(trim($match[1]));
123 $local_part = trim($match[2]);
124 $domain = trim($match[3]);
125 $encoded_mailboxes[] = "{$display_name} <{$local_part}@{$domain}>";
127 else if ( preg_match("#([^,]+)?@([^,]+)?#", $mailbox) )
129 $encoded_mailboxes[] = $mailbox;
136 if ( $encoded_mailboxes == array() )
140 return implode(',', $encoded_mailboxes);
144 * NOTIFICATION::seven_bit_characters_encoder
145 * Encoder into 7bit ASCII expression for Non-ASCII Text based on RFC 2047.
147 * @link http://www.ietf.org/rfc/rfc2047.txt
148 * @see 2. Syntax of encoded-words
150 * NOTE: RFC 2047 has a ambiguousity for dealing with 'linear-white-space'.
151 * This causes a trouble related to line breaking between single byte and multi-byte strings.
152 * To avoid this, single byte string is encoded as well as multi byte string here.
154 * NOTE: RFC 2231 also defines the way to use non-ASCII characters in MIME header.
155 * http://www.ietf.org/rfc/rfc2231.txt
157 * NOTE: iconv extension give the same functions as this in PHP5
158 * iconv_mime_encode():
159 * http://www.php.net/manual/en/function.iconv-mime-encode.php
162 * @param string $charset Character set encoding
163 * @param string $type type of 7 bit encoding, should be 'B' or 'Q'
164 * @param string $string Target string with header field
165 * @return string encoded string
168 static private function seven_bit_characters_encoder($string)
170 $header = chr(13) . chr(10) . chr(32) . '=?' . self::$charset . '?' . self::$scheme . '?';
172 $restriction = 78 - strlen($header) - strlen($footer) ;
174 $encoded_words = array();
175 for ( $i = 0; $i < self::strlen($string); $i++ )
177 if ( self::$scheme == 'B' )
184 $letter = self::substr($string, $i, 1);
185 $expected_length = strlen($letters) + strlen($letter) * 4 / 3;
187 if ( $expected_length > $restriction )
189 $encoded_text = self::b_encoder($letters);
190 $encoded_words[] = "{$header}{$encoded_text}{$footer}";
196 if ( $i == self::strlen($string) - 1 )
198 $encoded_text = self::b_encoder($letters);
199 $encoded_words[] = "{$header}{$encoded_text}{$footer}";
211 $encoded_letter = self::q_encoder(self::substr($string, $i, 1));
212 $expected_length = strlen($encoded_text) + strlen($encoded_letter);
214 if ( $expected_length > $restriction )
216 $encoded_words[] = "{$header}{$encoded_text}{$footer}";
220 $encoded_text .= $encoded_letter;
222 if ( $i == self::strlen($string) - 1 )
224 $encoded_words[] = "{$header}{$encoded_text}{$footer}";
231 return implode('', $encoded_words);
235 * NOTIFICATION::b_encoder()
237 * B encoder according to RFC 2047.
238 * The "B" encoding is identical to the "BASE64" encoding defined by RFC 4648.
240 * @link http://www.ietf.org/rfc/rfc4648.txt
241 * @see 6.8. Base64 Content-Transfer-Encoding
243 * NOTE: According to RFC 4648
244 * (1) The final quantum of encoding input is an integral multiple of 24 bits;
245 * here, the final unit of encoded output will be an integral multiple
246 * of 4 characters with no "=" padding.
247 * (2) The final quantum of encoding input is exactly 8 bits; here,
248 * the final unit of encoded output will be two characters followed
249 * by two "=" padding characters.
250 * (3) The final quantum of encoding input is exactly 16 bits; here,
251 * the final unit of encoded output will be three characters followed
252 * by one "=" padding character.
255 * @param string $target targetted string
256 * @return string encoded string
258 static private function b_encoder($target)
260 return base64_encode($target);
264 * NOTIFICATION::q_encoder()
266 * Q encoder according to RFC 2047.
267 * The "Q" encoding is similar to "Quoted-Printable" content-transfer-encoding defined in RFC 2045,
268 * but the "Q" encoding and the "Quoted-Printable" are different a bit.
270 * @link http://www.ietf.org/rfc/rfc2047.txt
271 * @see 4.2. The "Q" encoding
273 * NOTE: According to RFC 2047
274 * (1) Any 8-bit value may be represented by a "=" followed by two hexadecimal digits.
275 * For example, if the character set in use were ISO-8859-1,
276 * the "=" character would thus be encoded as "=3D", and a SPACE by "=20".
277 * (Upper case should be used for hexadecimal digits "A" through "F".)
278 * (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be
279 * represented as "_" (underscore, ASCII 95.).
280 * (This character may not pass through some internetwork mail gateways,
281 * but its use will greatly enhance readability of "Q" encoded data
282 * with mail readers that do not support this encoding.)
283 * Note that the "_" always represents hexadecimal 20,
284 * even if the SPACE character occupies a different code position
285 * in the character set in use.
286 * (3) 8-bit values which correspond to printable ASCII characters
287 * other than "=", "?", and "_" (underscore), MAY be represented as those characters.
288 * (But see section 5 for restrictions.)
289 * In particular, SPACE and TAB MUST NOT be represented as themselves within encoded words.
292 * @param string $target targetted string
293 * @return string encoded string
295 static private function q_encoder($target)
299 for ( $i = 0; $i < strlen($target); $i++ )
301 $letter = substr ($target, $i, 1);
302 $order = ord($letter);
304 // Printable ASCII characters without "=", "?", "_"
305 if ((33 <= $order && $order <= 60)
307 || (64 <= $order && $order <= 94)
308 || (96 <= $order && $order <= 126))
310 $string .= strtoupper(dechex($order));
312 // Space shuold be encoded as the same strings as "_"
313 else if ($order == 32)
320 $string .= '=' . strtoupper(dechex($order));
329 * NOTIFICATION::$addresses
333 private $addresses = array();
337 * takes one string as argument, containing multiple e-mail addresses
338 * separated by semicolons
339 * eg: site@demuynck.org;nucleus@demuynck.org;foo@bar.com
343 function __construct($addresses)
345 $this->addresses = i18n::explode(';' , $addresses);
350 * NOTIFICATION::validAddresses()
352 * returns true if all addresses are valid
358 function validAddresses()
360 foreach ( $this->addresses as $address )
362 if ( !self::address_validation(trim($address)) )
372 * NOTIFICATION::notify()
374 * Sends email messages to all the email addresses
377 * @param String $title
378 * @param String $message
379 * @param String $from
382 function notify($title, $message, $from)
385 $addresses = array();
387 foreach ($this->addresses as $address)
389 if ( $member->isLoggedIn() && ($member->getEmail() == $address) )
393 $addresses[] = $address;
396 self::mail(implode(',', $addresses), $title, $message , $from);