Index: trunk/lib/App.inc.php
===================================================================
--- trunk/lib/App.inc.php	(revision 22)
+++ trunk/lib/App.inc.php	(revision 23)
@@ -479,6 +479,6 @@
         $event['type'] = $this->logPriorityToString($priority);
         $event['file:line'] = "$file : $line";
-        $event['message'] = strip_tags(preg_replace('/\s{2,}/', ' ', $message));
-    
+        preg_match_all('/(<[^>\s]{7,})[^>]*>/', $message, $strip_tags_allow); // <...> with lots of chars maybe we don't want stripped.
+        $event['message'] = strip_tags(preg_replace('/\s{2,}/', ' ', $message), (!empty($strip_tags_allow[1]) ? join('> ', $strip_tags_allow[1]) . '>' : null));
         $event_str = '[' . join('] [', $event) . ']';
         
Index: trunk/lib/Email.inc.php
===================================================================
--- trunk/lib/Email.inc.php	(revision 23)
+++ trunk/lib/Email.inc.php	(revision 23)
@@ -0,0 +1,275 @@
+<?php
+/**
+ * Email.inc.php
+ * code by strangecode :: www.strangecode.com :: this document contains copyrighted information
+ *
+ * Easy email template usage.
+ *
+ * @author  Quinn Comendant <quinn@strangecode.com>
+ * @version 1.0
+ 
+-------------------------------------------------------------------------------------
+// Example.
+$email = new Email(array(
+    'to' => array($frm['email'], 'q@lovemachine.local'),
+    'from' => sprintf('%s <%s>', App::getParam('site_name'), App::getParam('site_email')),
+    'subject' => 'Your Golbon account has been activated',
+));
+$email->setTemplate('email_registration_confirm.ihtml');
+$email->replace(array(
+    'site_name' => App::getParam('site_name'),
+    'site_url' => App::getParam('site_url'),
+    'username' => $frm['username'],
+    'password' => $frm['password1'],
+    'inviting_member_business_name' => $frm['inviting_member_business_name'],
+));
+if ($email->send()) {
+    App::raiseMsg(sprintf(_("A confirmation email has been sent to %s."), $frm['email']), MSG_SUCCESS, __FILE__, __LINE__);
+} else {
+    App::logMsg(sprintf('Error sending confirmation email to address %s', $frm['email']), LOG_DEBUG, __FILE__, __LINE__);
+    App::raiseMsg(sprintf('We tried sending you a confirmation email to address %s but there was an error. After logging in, please visit the "My Account" page to confirm that this address is correct.', $frm['email']), MSG_NOTICE, __FILE__, __LINE__);
+}
+-------------------------------------------------------------------------------------
+ */
+class Email {
+
+    var $_params = array(
+        'to' => null,
+        'from' => null,
+        'subject' => null,
+        'regex' => null
+    );
+    var $_template;
+    var $_template_replaced;
+    var $regex;
+
+    /**
+     * Constructor.
+     *
+     * @access  public
+     * @param   array   $params     Array of object parameters.
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   28 Nov 2005 12:59:41
+     */
+    function Email($params=null)
+    {
+        $this->setParam(array('regex' => '/^(?:[^,@]*\s+|[^,@]*(<)|)'   // Display name
+        . '((?:[^.<>\s@\",\[\]]+[^<>\s@\",\[\]])*[^.<>\s@\",\[\]]+)'    // Local-part 
+        . '@'                                                           // @
+        . '((?:(\[)|[A-Z0-9]?)'                                         // Domain, first char
+        . '(?(4)'                                                       // Domain conditional for if first domain char is [
+        . '(?:[0-9]{1,3}\.){3}[0-9]{1,3}\]'                             // TRUE, matches IP address
+        . '|'
+        . '[.-]?(?:[A-Z0-9]+[-.])*(?:[A-Z0-9]+\.)+[A-Z]{2,6}))'         // FALSE, matches domain name
+        . '(?(1)'                                                       // Comment conditional for if initial < exists
+        . '(?:>\s*|>\s+\([^,@]+\)\s*)'                                  // TRUE, ensure ending >
+        . '|'
+        . '(?:|\s*|\s+\([^,@]+\)\s*))$/i'));                            // FALSE ensure there is no ending >
+
+        $this->setParam($params);
+    }
+
+    /**
+     * Set (or overwrite existing) parameters by passing an array of new parameters.
+     *
+     * @access public
+     * @param  array    $params     Array of parameters (key => val pairs).
+     */
+    function setParam($params)
+    {
+        if (isset($params) && is_array($params)) {
+
+            // Enforce valid email addresses.
+            if (isset($params['to']) && !$this->validEmail($params['to'])) {
+                $params['to'] = null;
+            }
+            if (isset($params['from']) && !$this->validEmail($params['from'])) {
+                $params['from'] = null;
+            }
+
+            // Merge new parameters with old overriding only those passed.
+            $this->_params = array_merge($this->_params, $params);
+        } else {
+            App::logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
+        }
+    }
+
+    /**
+     * Return the value of a parameter, if it exists.
+     *
+     * @access public
+     * @param string $param        Which parameter to return.
+     * @return mixed               Configured parameter value.
+     */
+    function getParam($param)
+    {
+        if (isset($this->_params[$param])) {
+            return $this->_params[$param];
+        } else {
+            App::logMsg(sprintf('Parameter is not set: %s', $param), LOG_NOTICE, __FILE__, __LINE__);
+            return null;
+        }
+    }
+
+    /**
+     * Loads template from file to generate email body.
+     *
+     * @access  public
+     * @param   string  $template   Filename of email template.
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   28 Nov 2005 12:56:23
+     */
+    function setTemplate($template)
+    {
+        // Load file, using include_path.
+        if (!$this->_template = file_get_contents($template, true)) {
+            App::logMsg(sprintf('Email template file does not exist: %s', $template), LOG_ERR, __FILE__, __LINE__);
+            $this->_template = null;
+            $this->_template_replaced = null;
+            return false;
+        }
+        $this->_template_replaced = null;
+        return true;
+    }
+
+    /**
+     * Loads template from string to generate email body.
+     *
+     * @access  public
+     * @param   string  $template   Filename of email template.
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   28 Nov 2005 12:56:23
+     */
+    function setString($string)
+    {
+        // Load file, using include_path.
+        if ('' == $string) {
+            App::logMsg(sprintf('Empty string provided.', null), LOG_ERR, __FILE__, __LINE__);
+            $this->_template_replaced = null;
+            return false;
+        } else {
+            $_template = $string;
+            $this->_template_replaced = null;
+            return true;
+        }
+    }
+    
+    /**
+     * Replace variables in template with argument data.
+     *
+     * @access  public
+     * @param   array   $replacements   Array keys are the values to search for, array vales are the replacement values.
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   28 Nov 2005 13:08:51
+     */
+    function replace($replacements)
+    {
+        if (!isset($this->_template)) {
+            App::logMsg(sprintf('Cannot replace variables, no template defined.', null), LOG_ERR, __FILE__, __LINE__);
+        	return false;
+        }
+
+        if (!is_array($replacements)) {
+            App::logMsg(sprintf('Cannot replace variables, invalid replacements.', null), LOG_ERR, __FILE__, __LINE__);
+        	return false;
+        }
+        
+        // Apply regex pattern to search elements.
+        $search = array_keys($replacements);
+        array_walk($search, create_function('&$v', '$v = "/{" . preg_quote($v) . "}/i";'));
+
+        // Replace values.
+        $replace = array_values($replacements);
+
+        $this->_template_replaced = preg_replace($search, $replace, $this->_template);
+    }
+
+    /**
+     * Send email using PHP's mail() function.
+     *
+     * @access  public
+     * @param   string  $to
+     * @param   string  $from
+     * @param   string  $subject
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   28 Nov 2005 12:56:09
+     */
+    function send($to=null, $from=null, $subject=null)
+    {
+        if (isset($to)) {
+        	 $this->setParam(array('to' => $to));
+        }
+        if (isset($from)) {
+        	 $this->setParam(array('from' => $from));
+        }
+        if (isset($subject)) {
+        	 $this->setParam(array('subject' => $subject));
+        }
+
+        if (!isset($this->_template)) {
+        	App::logMsg(sprintf('Cannot send email. Template not set.', null), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        } else if (!isset($this->_params['to'])) {
+        	App::logMsg(sprintf('Cannot send email. TO not defined.', null), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        } else if (!isset($this->_params['from'])) {
+        	App::logMsg(sprintf('Cannot send email. FROM not defined.', null), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        } else if (!isset($this->_params['subject'])) {
+        	App::logMsg(sprintf('Cannot send email. SUBJECT not defined.', null), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        }
+
+        $final_body = wordwrap(isset($this->_template_replaced) ? $this->_template_replaced : $this->_template);
+        // Test that all placeholders have been replaced.
+        if (preg_match('/({[^}]+})/', $final_body, $unreplaced_match)) {
+        	App::logMsg(sprintf('Cannot send email. Variables left unreplaced in template: %s', (isset($unreplaced_match) ? $unreplaced_match[1] : '')), LOG_ERR, __FILE__, __LINE__);
+            return false;
+        }
+        
+        $final_to = is_array($this->_params['to']) ? join(', ', $this->_params['to']) : $this->_params['to'];
+        $final_from = sprintf("From: %s\r\n", $this->_params['from']);
+        $envelope_sender_header = sprintf('-f %s', preg_replace('/^.*<?([^\s@\[\]<>()]+\@[A-Za-z0-9.-]{1,}\.[A-Za-z]{2,5})>?$/iU', '$1', $this->_params['from']));
+        if (!mail($final_to, $this->_params['subject'], $final_body, $final_from, $envelope_sender_header)) {
+            App::logMsg(sprintf('Email failure with parameters: %s, %s, %s, %s', $this->_params['to'], $this->_params['subject'], str_replace("\r\n", '', $final_from), $envelope_sender_header), LOG_NOTICE, __FILE__, __LINE__);
+            return false;
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Validates an email address based on the recommendations in RFC 3696.
+     * Is more loose than restrictive, to allow the many valid variants of 
+     * email addresses while catching the most common mistakes. Checks an array too.
+     * http://www.faqs.org/rfcs/rfc822.html
+     * http://www.faqs.org/rfcs/rfc2822.html
+     * http://www.faqs.org/rfcs/rfc3696.html
+     * http://www.faqs.org/rfcs/rfc1035.html
+     *
+     * @access  public
+     * @param   mixed  $email  Address to check, string or array.
+     * @return  bool    Validity of address.
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     * @since   30 Nov 2005 22:00:50
+     */
+    function validEmail($email)
+    {
+        if (is_array($email)) {
+            foreach ($email as $e) {
+                if (!$this->validEmail($e)) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            if (!preg_match($this->getParam('regex'), $email)) {
+                App::logMsg(sprintf('Invalid email: %s', $email), LOG_DEBUG, __FILE__, __LINE__);
+                return false;
+            }
+            return true;
+        }
+    }
+}
+
+?>
Index: trunk/lib/FormValidator.inc.php
===================================================================
--- trunk/lib/FormValidator.inc.php	(revision 22)
+++ trunk/lib/FormValidator.inc.php	(revision 23)
@@ -374,12 +374,18 @@
 
     /**
-     * Validates email address length, domain name existance, format.
-     *
-     * @param  string  $form_name       The name of the incoming form variable
-     * @param  boolean $allow_fullname  Allow the use of rfc822 expanded email address with comment: Quinn Commie <quinn@strangecode.com>
-     *
-     * @return bool    true if no errors found, false otherwise
-     */
-    function validateEmail($form_name, $allow_fullname=false)
+     * Validates an email address based on the recommendations in RFC 3696.
+     * Is more loose than restrictive, to allow the many valid variants of 
+     * email addresses while catching the most common mistakes.
+     * http://www.faqs.org/rfcs/rfc822.html
+     * http://www.faqs.org/rfcs/rfc2822.html
+     * http://www.faqs.org/rfcs/rfc3696.html
+     * http://www.faqs.org/rfcs/rfc1035.html
+     *
+     * @access  public
+     * @param   string  $form_name  The name of the incoming form variable.
+     * @return  bool    Validity of address.
+     * @author  Quinn Comendant <quinn@strangecode.com>
+     */
+    function validateEmail($form_name)
     {
         $email = getFormData($form_name);
@@ -387,36 +393,74 @@
             return false;
         }
-        
+
+        require_once 'codebase/lib/Email.inc.php';
+        $e = new Email();
+
         // Test email address format.
-        if ($allow_fullname) {
-            if (!$this->checkRegex($form_name, '/^[\w\s]*<?[^\s@\[\]<>]{1,}\@[A-Za-z0-9.-]{1,}\.[A-Za-z]{2,5}>?$/i', true, sprintf(_("<strong>%s</strong> is not a valid email address."), $email))) {
-                App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
-                return false;
-            }
-        } else {
-            if (!$this->checkRegex($form_name, '/^[^\s@\[\]<>]{1,}\@[A-Za-z0-9.-]{1,}\.[A-Za-z]{2,5}$/i', true, sprintf(_("<strong>%s</strong> is not a valid email address."), $email))) {
-                App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
-                return false;
-            }
-        }
-        
+        if (!preg_match($e->getParam('regex'), $email, $e_parts)) {
+            $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email address."), oTxt($email)));
+            App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
+            return false;
+        }
+        
+        // We have a match! Here are the captured subpatterns, on which further tests are run.
+        $local = $e_parts[2];
+        // If domain is an IP [XXX.XXX.XXX.XXX] strip off the brackets.
+        $domain = $e_parts[3]{0} == '[' ? substr($e_parts[3], 1, -1) : $e_parts[3];
+
         // Test length.
-        if (!$this->stringLength($form_name, 0, 255, sprintf(_("<strong>Email address</strong> must contain less than 256 characters."), $email))) {
+        if (strlen($local) > 64 || strlen($domain) > 191) {
+            $this->addError($form_name, sprintf(_("<strong>Email address</strong> must contain less than 256 characters."), oTxt($email)));
             App::logMsg(sprintf('The email address %s must contain less than 256 characters.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
             return false;
         }
         
-        // Check domain exists and has valid MX record.
-        preg_match('/^[\w\s]*<?[^\s@\[\]<>]{1,}\@([A-Za-z0-9.-]{1,}\.[A-Za-z]{2,5})>?$/i', $email, $matches);
-        if (!empty($matches[1])) {
-            if (!checkdnsrr($matches[1] . '.', 'MX') && gethostbyname($matches[1]) == $matches[1]) {
-                $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email domain name"), $matches[1]));
-                App::logMsg(sprintf('The email address %s contains an invalid email domain name (%s).', getFormData($form_name), $matches[1]), LOG_DEBUG, __FILE__, __LINE__);
-                return false;
-            }
+        // Check domain exists: It's a domain if ip2long fails; Checkdnsrr ensures a MX record exists; Gethostbyname() ensures the domain exists.
+        if (ip2long($domain) == '-1' && function_exists('checkdnsrr') && !checkdnsrr($domain . '.', 'MX') && gethostbyname($domain) == $domain) {
+            $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email domain name"), oTxt($domain)));
+            App::logMsg(sprintf('The email address %s contains an invalid email domain name (%s).', getFormData($form_name), $domain), LOG_DEBUG, __FILE__, __LINE__);
+            return false;
         }
         
         return true;
     }
+//     function validateEmail($form_name, $allow_fullname=false)
+//     {
+//         $email = getFormData($form_name);
+//         if ('' == trim($email)) {
+//             return false;
+//         }
+//         
+//         // Test email address format.
+//         if ($allow_fullname) {
+//             if (!$this->checkRegex($form_name, '/^[^<>@]*<?[^\s@\[\]<>()]+\@[A-Za-z0-9.-]+\.[A-Za-z]{2,5}>?$/i', true, sprintf(_("<strong>%s</strong> is not a valid email address."), $email))) {
+//                 App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
+//                 return false;
+//             }
+//         } else {
+//             if (!$this->checkRegex($form_name, '/^[^\s@\[\]<>()]+\@[A-Za-z0-9.-]+\.[A-Za-z]{2,5}$/i', true, sprintf(_("<strong>%s</strong> is not a valid email address."), $email))) {
+//                 App::logMsg(sprintf('The email address %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
+//                 return false;
+//             }
+//         }
+//         
+//         // Test length.
+//         if (!$this->stringLength($form_name, 0, 255, sprintf(_("<strong>Email address</strong> must contain less than 256 characters."), $email))) {
+//             App::logMsg(sprintf('The email address %s must contain less than 256 characters.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
+//             return false;
+//         }
+//         
+//         // Check domain exists and has valid MX record.
+//         preg_match('/^[^<>@]*<?[^\s@\[\]<>()]+\@([A-Za-z0-9.-]+\.[A-Za-z]{2,5})>?$/i', $email, $matches);
+//         if (!empty($matches[1])) {
+//             if (!checkdnsrr($matches[1] . '.', 'MX') && gethostbyname($matches[1]) == $matches[1]) {
+//                 $this->addError($form_name, sprintf(_("<strong>%s</strong> is not a valid email domain name"), $matches[1]));
+//                 App::logMsg(sprintf('The email address %s contains an invalid email domain name (%s).', getFormData($form_name), $matches[1]), LOG_DEBUG, __FILE__, __LINE__);
+//                 return false;
+//             }
+//         }
+//         
+//         return true;
+//     }
 
     /**
