source: branches/2.0singleton/lib/Email.inc.php @ 127

Last change on this file since 127 was 127, checked in by scdev, 18 years ago

Updated App.inc.php thru Hierarchy.inc.php

File size: 13.4 KB
Line 
1<?php
2/**
3 * Email.inc.php
4 * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
5 *
6 * Easy email template usage.
7 *
8 * @author  Quinn Comendant <quinn@strangecode.com>
9 * @version 1.0
10-------------------------------------------------------------------------------------
11// Example.
12$email = new Email(array(
13    'to' => array($frm['email'], 'q@lovemachine.local'),
14    'from' => sprintf('%s <%s>', $app->getParam('site_name'), $app->getParam('site_email')),
15    'subject' => 'Your account has been activated',
16));
17$email->setTemplate('email_registration_confirm.ihtml');
18// $email->setString('Or you can pass your message body as a string, also with {VARIABLES}.');
19$email->replace(array(
20    'site_name' => $app->getParam('site_name'),
21    'site_url' => $app->getParam('site_url'),
22    'username' => $frm['username'],
23    'password' => $frm['password1'],
24));
25if ($email->send()) {
26    $app->raiseMsg(sprintf(_("A confirmation email has been sent to %s."), $frm['email']), MSG_SUCCESS, __FILE__, __LINE__);
27} else {
28    $app->logMsg(sprintf('Error sending confirmation email to address %s', $frm['email']), LOG_NOTICE, __FILE__, __LINE__);
29}
30-------------------------------------------------------------------------------------
31 */
32class Email {
33
34    // Default parameters, to be overwritten by setParam() and read with getParam()
35    var $_params = array(
36        'to' => null,
37        'from' => null,
38        'subject' => null,
39        'headers' => null,
40        'regex' => null
41    );
42
43    // String that contains the email body.
44    var $_template;
45
46    // String that contains the email body after replacements.
47    var $_template_replaced;
48
49    /**
50     * Constructor.
51     *
52     * @access  public
53     * @param   array   $params     Array of object parameters.
54     * @author  Quinn Comendant <quinn@strangecode.com>
55     * @since   28 Nov 2005 12:59:41
56     */
57    function Email($params=null)
58    {
59        // The regex used in validEmail(). Set here instead of in the default _params above so we can use the concatination . dot.
60        // This matches an email address as complex as:
61        //      Bob Smith <bob&smith's/dep=sales!@smith-wick.ca.us> (Sales department)
62        // ...and something as simple as:
63        //      x@x.com
64        $this->setParam(array('regex' => '/^(?:[^,@]*\s+|[^,@]*(<)|)'   // Display name
65        . '((?:[^.<>\s@\",\[\]]+[^<>\s@\",\[\]])*[^.<>\s@\",\[\]]+)'    // Local-part
66        . '@'                                                           // @
67        . '((?:(\[)|[A-Z0-9]?)'                                         // Domain, first char
68        . '(?(4)'                                                       // Domain conditional for if first domain char is [
69        . '(?:[0-9]{1,3}\.){3}[0-9]{1,3}\]'                             // TRUE, matches IP address
70        . '|'
71        . '[.-]?(?:[A-Z0-9]+[-.])*(?:[A-Z0-9]+\.)+[A-Z]{2,6}))'         // FALSE, matches domain name
72        . '(?(1)'                                                       // Comment conditional for if initial < exists
73        . '(?:>\s*|>\s+\([^,@]+\)\s*)'                                  // TRUE, ensure ending >
74        . '|'
75        . '(?:|\s*|\s+\([^,@]+\)\s*))$/i'));                            // FALSE ensure there is no ending >
76
77        if (isset($params)) {
78            $this->setParam($params);
79        }
80    }
81
82    /**
83     * Set (or overwrite existing) parameters by passing an array of new parameters.
84     *
85     * @access public
86     * @param  array    $params     Array of parameters (key => val pairs).
87     */
88    function setParam($params)
89    {
90        $app =& App::getInstance();
91   
92        if (isset($params) && is_array($params)) {
93            // Enforce valid email addresses.
94            if (isset($params['to']) && !$this->validEmail($params['to'])) {
95                $params['to'] = null;
96            }
97            if (isset($params['from']) && !$this->validEmail($params['from'])) {
98                $params['from'] = null;
99            }
100
101            // Merge new parameters with old overriding only those passed.
102            $this->_params = array_merge($this->_params, $params);
103        } else {
104            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
105        }
106    }
107
108    /**
109     * Return the value of a parameter, if it exists.
110     *
111     * @access public
112     * @param string $param        Which parameter to return.
113     * @return mixed               Configured parameter value.
114     */
115    function getParam($param)
116    {
117        $app =& App::getInstance();
118   
119        if (isset($this->_params[$param])) {
120            return $this->_params[$param];
121        } else {
122            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
123            return null;
124        }
125    }
126
127    /**
128     * Loads template from file to generate email body.
129     *
130     * @access  public
131     * @param   string  $template   Filename of email template.
132     * @author  Quinn Comendant <quinn@strangecode.com>
133     * @since   28 Nov 2005 12:56:23
134     */
135    function setTemplate($template)
136    {
137        $app =& App::getInstance();
138   
139        // Load file, using include_path.
140        if (!$this->_template = file_get_contents($template, true)) {
141            $app->logMsg(sprintf('Email template file does not exist: %s', $template), LOG_ERR, __FILE__, __LINE__);
142            $this->_template = null;
143            $this->_template_replaced = null;
144            return false;
145        }
146        // This could be a new template, so reset the _template_replaced.
147        $this->_template_replaced = null;
148        return true;
149    }
150
151    /**
152     * Loads template from string to generate email body.
153     *
154     * @access  public
155     * @param   string  $template   Filename of email template.
156     * @author  Quinn Comendant <quinn@strangecode.com>
157     * @since   28 Nov 2005 12:56:23
158     */
159    function setString($string)
160    {
161        $app =& App::getInstance();
162   
163        // Load file, using include_path.
164        if ('' == trim($string)) {
165            $app->logMsg(sprintf('Empty string provided.', null), LOG_ERR, __FILE__, __LINE__);
166            $this->_template_replaced = null;
167            return false;
168        } else {
169            $this->_template = $string;
170            // This could be a new template, so reset the _template_replaced.
171            $this->_template_replaced = null;
172            return true;
173        }
174    }
175
176    /**
177     * Replace variables in template with argument data.
178     *
179     * @access  public
180     * @param   array   $replacements   Array keys are the values to search for, array vales are the replacement values.
181     * @author  Quinn Comendant <quinn@strangecode.com>
182     * @since   28 Nov 2005 13:08:51
183     */
184    function replace($replacements)
185    {
186        $app =& App::getInstance();
187   
188        // Ensure template exists.
189        if (!isset($this->_template)) {
190            $app->logMsg(sprintf('Cannot replace variables, no template defined.', null), LOG_ERR, __FILE__, __LINE__);
191            return false;
192        }
193
194        // Ensure replacements argument is an array.
195        if (!is_array($replacements)) {
196            $app->logMsg(sprintf('Cannot replace variables, invalid replacements.', null), LOG_ERR, __FILE__, __LINE__);
197            return false;
198        }
199
200        // Apply regex pattern to search elements.
201        $search = array_keys($replacements);
202        array_walk($search, create_function('&$v', '$v = "/{" . preg_quote($v) . "}/i";'));
203
204        // Replacement values.
205        $replace = array_values($replacements);
206
207        // Search and replace all values at once.
208        $this->_template_replaced = preg_replace($search, $replace, $this->_template);
209    }
210
211    /**
212     * Send email using PHP's mail() function.
213     *
214     * @access  public
215     * @param   string  $to
216     * @param   string  $from
217     * @param   string  $subject
218     * @author  Quinn Comendant <quinn@strangecode.com>
219     * @since   28 Nov 2005 12:56:09
220     */
221    function send($to=null, $from=null, $subject=null, $headers=null)
222    {
223        $app =& App::getInstance();
224   
225        // Use arguments if provided.
226        if (isset($to)) {
227             $this->setParam(array('to' => $to));
228        }
229        if (isset($from)) {
230             $this->setParam(array('from' => $from));
231        }
232        if (isset($subject)) {
233             $this->setParam(array('subject' => $subject));
234        }
235        if (isset($headers)) {
236             $this->setParam(array('headers' => $headers));
237        }
238
239        // Ensure required values exist.
240        if (!isset($this->_params['subject'])) {
241            $app->logMsg(sprintf('Cannot send email to %s. SUBJECT not defined.', $this->_params['to']), LOG_ERR, __FILE__, __LINE__);
242            return false;
243        } else if (!isset($this->_template)) {
244            $app->logMsg(sprintf('Cannot send email: "%s". Template not set.', $this->_params['subject']), LOG_ERR, __FILE__, __LINE__);
245            return false;
246        } else if (!isset($this->_params['to'])) {
247            $app->logMsg(sprintf('Cannot send email: "%s". TO not defined.', $this->_params['subject']), LOG_NOTICE, __FILE__, __LINE__);
248            return false;
249        } else if (!isset($this->_params['from'])) {
250            $app->logMsg(sprintf('Cannot send email: "%s". FROM not defined.', $this->_params['subject']), LOG_ERR, __FILE__, __LINE__);
251            return false;
252        }
253
254        // Wrap email text body, using _template_replaced if replacements have been used, or just a fresh _template if not.
255        $final_body = wordwrap(isset($this->_template_replaced) ? $this->_template_replaced : $this->_template);
256
257        // Ensure all placeholders have been replaced. Find anything with {...} characters.
258        if (preg_match('/({[^}]+})/', $final_body, $unreplaced_match)) {
259            $app->logMsg(sprintf('Cannot send email. Variables left unreplaced in template: %s', (isset($unreplaced_match[1]) ? $unreplaced_match[1] : '')), LOG_ERR, __FILE__, __LINE__);
260            return false;
261        }
262
263        // Final "to" header can have multiple addresses if in an array.
264        $final_to = is_array($this->_params['to']) ? join(', ', $this->_params['to']) : $this->_params['to'];
265
266        // From headers are custom headers.
267        $headers = array('From' => $this->_params['from']);
268
269        // Additional headers.
270        if (isset($this->_params['headers']) && is_array($this->_params['headers'])) {
271            $headers = array_merge($this->_params['headers'], $headers);
272        }
273
274        // Process headers.
275        $final_headers = array();
276        foreach ($headers as $key => $val) {
277            $final_headers[] = sprintf('%s: %s', $key, $val);
278        }
279        $final_headers = join("\r\n", $final_headers);
280
281        // This is the address where delivery problems are sent to. We must strip off everything except the local@domain part.
282        $envelope_sender_header = sprintf('-f %s', preg_replace('/^.*<?([^\s@\[\]<>()]+\@[A-Za-z0-9.-]{1,}\.[A-Za-z]{2,5})>?$/iU', '$1', $this->_params['from']));
283
284        // Check for mail header injection attacks.
285        $full_mail_content = join("\n", array($final_to, $this->_params['subject'], $final_body, $final_headers, $envelope_sender_header));
286        if (preg_match("/(Content-Type:|MIME-Version:|Content-Transfer-Encoding:|[\n\r]Bcc:|[\n\r]Cc:)/i", $full_mail_content)) {
287            $app->logMsg(sprintf('Mail header injection attack in content: %s', $full_mail_content), LOG_WARNING, __FILE__, __LINE__);
288            sleep(3);
289            return false;
290        }
291
292        // Ensure message was successfully accepted for delivery.
293        if (mail($final_to, $this->_params['subject'], $final_body, $final_headers, $envelope_sender_header)) {
294            $app->logMsg(sprintf('Email successfully sent to %s', $final_to), LOG_DEBUG, __FILE__, __LINE__);
295            return true;
296        } else {
297            $app->logMsg(sprintf('Email failure with parameters: %s, %s, %s, %s', $final_to, $this->_params['subject'], str_replace("\r\n", '\r\n', $final_headers), $envelope_sender_header), LOG_NOTICE, __FILE__, __LINE__);
298            return false;
299        }
300    }
301
302    /**
303     * Validates an email address based on the recommendations in RFC 3696.
304     * Is more loose than restrictive, to allow the many valid variants of
305     * email addresses while catching the most common mistakes. Checks an array too.
306     * http://www.faqs.org/rfcs/rfc822.html
307     * http://www.faqs.org/rfcs/rfc2822.html
308     * http://www.faqs.org/rfcs/rfc3696.html
309     * http://www.faqs.org/rfcs/rfc1035.html
310     *
311     * @access  public
312     * @param   mixed  $email  Address to check, string or array.
313     * @return  bool    Validity of address.
314     * @author  Quinn Comendant <quinn@strangecode.com>
315     * @since   30 Nov 2005 22:00:50
316     */
317    function validEmail($email)
318    {
319        $app =& App::getInstance();
320   
321        // If an array, check values recursively.
322        if (is_array($email)) {
323            foreach ($email as $e) {
324                if (!$this->validEmail($e)) {
325                    return false;
326                }
327            }
328            return true;
329        } else {
330            // To be valid email address must match regex and fit within the lenth constraints.
331            if (preg_match($this->getParam('regex'), $email, $e_parts) && strlen($e_parts[2]) < 64 && strlen($e_parts[3]) < 255) {
332                return true;
333            } else {
334                $app->logMsg(sprintf('Invalid email: %s', $email), LOG_INFO, __FILE__, __LINE__);
335                return false;
336            }
337        }
338    }
339}
340
341?>
Note: See TracBrowser for help on using the repository browser.