Index: trunk/docs/examples/contact_form/contact.php
===================================================================
--- trunk/docs/examples/contact_form/contact.php	(revision 548)
+++ trunk/docs/examples/contact_form/contact.php	(revision 550)
@@ -4,5 +4,5 @@
  * For details visit the project site: <http://trac.strangecode.com/codebase/>
  * Copyright 2001-2012 Strangecode, LLC
- * 
+ *
  * This file is part of The Strangecode Codebase.
  *
@@ -11,10 +11,10 @@
  * Free Software Foundation, either version 3 of the License, or (at your option)
  * any later version.
- * 
+ *
  * The Strangecode Codebase is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  * details.
- * 
+ *
  * You should have received a copy of the GNU General Public License along with
  * The Strangecode Codebase. If not, see <http://www.gnu.org/licenses/>.
@@ -59,5 +59,5 @@
         $email = new Email(array(
             'to' => $email_to,
-            'from' => sprintf('%s <%s>', getFormData('name'), getFormData('email')),
+            'from' => sprintf('"%s" <%s>', addcslashes(getFormData('name'), '"'), getFormData('email')),
             'subject' => 'Contact form email',
         ));
@@ -110,9 +110,9 @@
     $fv->notEmpty('name', sprintf(_("%s cannot be blank."), _("Name")));
     $fv->stringLength('name', 0, 255, sprintf(_("%s must be %d-to-%d characters in length."), _("Name"), 0, 255));
-    
+
     $fv->notEmpty('email', sprintf(_("%s cannot be blank."), _("Email")));
     $fv->stringLength('email', 0, 255, sprintf(_("%s must be %d-to-%d characters in length."), _("Email"), 0, 255));
     $fv->validateEmail('email');
-    
+
     $fv->notEmpty('message', sprintf(_("%s cannot be blank."), _("Message")));
     $fv->stringLength('message', 0, 32000, sprintf(_("%s must be %d-to-%d characters in length."), _("Message"), 0, 32000));
Index: trunk/docs/version.txt
===================================================================
--- trunk/docs/version.txt	(revision 548)
+++ trunk/docs/version.txt	(revision 550)
@@ -1,1 +1,1 @@
-2.2.0-3
+2.2.0-4
Index: trunk/lib/App.inc.php
===================================================================
--- trunk/lib/App.inc.php	(revision 548)
+++ trunk/lib/App.inc.php	(revision 550)
@@ -42,5 +42,4 @@
 class App
 {
-
     // Minimum version of PHP required for this version of the Codebase.
     const CODEBASE_MIN_PHP_VERSION = '5.3.0';
@@ -753,5 +752,5 @@
 
         // Serialize multi-line messages.
-        $message = preg_replace('/\s+/m', ' ', $message);
+        $message = preg_replace('/\s+/m', ' ', trim($message));
 
         // Store this event under a unique key, counting each time it occurs so that it only gets reported a limited number of times.
@@ -807,5 +806,5 @@
             'file:line' => "$file : $line",
             'url'       => mb_substr(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '', 0, 1024),
-            'message'   => $message
+            'message'   => mb_substr($message, 0, 1024),
         );
         // Here's a shortened version of event data.
@@ -819,5 +818,5 @@
         if (false !== $this->getParam('log_file_priority') && $priority <= $this->getParam('log_file_priority')) {
             $event_str = '[' . join('] [', $event_short) . ']';
-            error_log(mb_substr($event_str, 0, 1024) . "\n", 3, $this->getParam('log_directory') . '/' . $this->getParam('log_filename'));
+            error_log("$event_str\n", 3, $this->getParam('log_directory') . '/' . $this->getParam('log_filename'));
         }
 
@@ -826,9 +825,11 @@
             $hostname = (isset($_SERVER['HTTP_HOST']) && '' != $_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : php_uname('n');
             $subject = sprintf('[%s %s] %s', $hostname, $event['type'], mb_substr($event['message'], 0, 64));
-            $email_msg = sprintf("A %s log event occurred on %s\n\n", $event['type'], $hostname);
+            $email_msg = sprintf("A log event of type '%s' occurred on %s\n\n", $event['type'], $hostname);
             $headers = 'From: ' . $this->getParam('site_email');
             foreach ($event as $k=>$v) {
-                $email_msg .= sprintf("%-11s%s\n", $k, $v);
-            }
+                $email_msg .= sprintf("%-16s %s\n", $k, $v);
+            }
+            $email_msg .= sprintf("%-16s %s\n", 'codebase version', $this->getParam('codebase_version'));
+            $email_msg .= sprintf("%-16s %s\n", 'site version', $this->getParam('site_version'));
             mb_send_mail($this->getParam('log_to_email_address'), $subject, $email_msg, $headers);
         }
@@ -916,5 +917,5 @@
         foreach ($query_key as $k) {
             // If not already set, and there is a non-empty value provided in the request...
-            if (!isset($this->_carry_queries[$k]) && false !== getFormData($k, $default)) {
+            if (isset($k) && '' != $k && !isset($this->_carry_queries[$k]) && false !== getFormData($k, $default)) {
                 // Copy the value of the specified query argument into the _carry_queries array.
                 $this->_carry_queries[$k] = getFormData($k, $default);
@@ -990,13 +991,15 @@
         $one_time_carry_queries = array();
         if (!is_null($carry_args)) {
-            if (is_array($carry_args) && !empty($carry_args)) {
-                foreach ($carry_args as $key=>$arg) {
-                    // Get query from appropriate source.
-                    if (false === $arg) {
-                        $do_carry_queries = false;
-                    } else if (false !== getFormData($arg, false)) {
-                        $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
-                    } else if (!is_numeric($key) && '' != $arg) {
-                        $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
+            if (is_array($carry_args)) {
+                if (!empty($carry_args)) {
+                    foreach ($carry_args as $key=>$arg) {
+                        // Get query from appropriate source.
+                        if (false === $arg) {
+                            $do_carry_queries = false;
+                        } else if (false !== getFormData($arg, false)) {
+                            $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
+                        } else if (!is_numeric($key) && '' != $arg) {
+                            $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
+                        }
                     }
                 }
@@ -1110,13 +1113,15 @@
         $one_time_carry_queries = array();
         if (!is_null($carry_args)) {
-            if (is_array($carry_args) && !empty($carry_args)) {
-                foreach ($carry_args as $key=>$arg) {
-                    // Get query from appropriate source.
-                    if (false === $arg) {
-                        $do_carry_queries = false;
-                    } else if (false !== getFormData($arg, false)) {
-                        $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
-                    } else if (!is_numeric($key) && '' != $arg) {
-                        $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
+            if (is_array($carry_args)) {
+                if (!empty($carry_args)) {
+                    foreach ($carry_args as $key=>$arg) {
+                        // Get query from appropriate source.
+                        if (false === $arg) {
+                            $do_carry_queries = false;
+                        } else if (false !== getFormData($arg, false)) {
+                            $one_time_carry_queries[$arg] = getFormData($arg); // Set arg to form data if available.
+                        } else if (!is_numeric($key) && '' != $arg) {
+                            $one_time_carry_queries[$key] = getFormData($key, $arg); // Set to arg to default if specified (overwritten by form data).
+                        }
                     }
                 }
@@ -1136,7 +1141,9 @@
                 if (is_array($val)) {
                     foreach ($val as $subval) {
-                        printf('<input type="hidden" name="%s[]" value="%s" />', $key, $subval);
+                        if ('' != $key && '' != $subval) {
+                            printf('<input type="hidden" name="%s[]" value="%s" />', $key, $subval);
+                        }
                     }
-                } else {
+                } else if ('' != $key && '' != $val) {
                     printf('<input type="hidden" name="%s" value="%s" />', $key, $val);
                 }
Index: trunk/lib/Auth_SQL.inc.php
===================================================================
--- trunk/lib/Auth_SQL.inc.php	(revision 548)
+++ trunk/lib/Auth_SQL.inc.php	(revision 550)
@@ -32,5 +32,4 @@
 class Auth_SQL
 {
-
     // Available hash types for class Auth_SQL.
     const ENCRYPT_PLAINTEXT = 1;
@@ -70,5 +69,5 @@
 
         // The type of hash to use for passwords stored in the db_table. Use one of the Auth_SQL::ENCRYPT_* types specified above.
-        // Hardened password hashes rely on the same key/salt being used to compare hashs.
+        // Hardened password hashes rely on the same key/salt being used to compare hashes.
         // Be aware that when using one of the hardened types the App signing_key or $more_salt below cannot change!
         'hash_type' => self::ENCRYPT_MD5,
@@ -176,5 +175,5 @@
             }
 
-            // The minimal columns for a table compatable with the Auth_SQL class.
+            // The minimal columns for a table compatible with the Auth_SQL class.
             $db->query("CREATE TABLE IF NOT EXISTS " . $db->escapeString($this->getParam('db_table')) . " (
                 " . $this->getParam('db_primary_key') . " MEDIUMINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
@@ -475,5 +474,5 @@
                 switch ($user_data['blocked_reason']) {
                     case 'account abuse' :
-                        $app->raiseMsg(sprintf(_("This account has been blocked due to possible account abuse. Please contact us to reactivate."), null), MSG_WARNING, __FILE__, __LINE__);
+                        $app->raiseMsg(sprintf(_("This account has been blocked due to possible account abuse. Please contact an administrator to reactivate."), null), MSG_WARNING, __FILE__, __LINE__);
                         break;
                     default :
@@ -507,5 +506,5 @@
                 if ($this->get('abuse_warning_level') < $this->_params['login_abuse_warnings']) {
                     // Warn the user with a password reset.
-                    $this->resetPassword(null, _("This is a security precaution. We have detected this account has been accessed from multiple computers simultaneously. It is against policy to share login information with others. If further account abuse is detected this account will be blocked."));
+                    $this->resetPassword(null, _("This is a security precaution. We have detected this account has been accessed from multiple computers simultaneously. It is against policy to share credentials with others. If further account abuse is detected this account will be blocked."));
                     $app->raiseMsg(_("Your password has been reset as a security precaution. Please check your email for more information."), MSG_NOTICE, __FILE__, __LINE__);
                     $app->logMsg(sprintf('Account abuse detected for user_id %s (%s) from IP %s', $this->get('user_id'), $this->get('username'), $this->get('remote_ip')), LOG_WARNING, __FILE__, __LINE__);
@@ -652,5 +651,5 @@
             if (strtotime($_SESSION['_auth_sql'][$this->_ns]['last_access_datetime']) > (time() - 43200)) {
                 // Only raise message if last session is less than 12 hours old.
-                $app->raiseMsg(_("Your session has expired. You need to log-in again."), MSG_NOTICE, __FILE__, __LINE__);
+                $app->raiseMsg(sprintf(_("For your safety we logged you out after being idle for %s. Please log in again."), humanTime($this->_params['idle_timeout'], 'hour', '%01.0f')), MSG_NOTICE, __FILE__, __LINE__);
             }
 
@@ -1044,5 +1043,5 @@
             $email = new Email(array(
                 'to' => $user_data['email'],
-                'from' => sprintf('%s <%s>', $app->getParam('site_name'), $app->getParam('site_email')),
+                'from' => sprintf('"%s" <%s>', addcslashes($app->getParam('site_name'), '"'), $app->getParam('site_email')),
                 'subject' => sprintf('%s password change', $app->getParam('site_name'))
             ));
Index: trunk/lib/Currency.inc.php
===================================================================
--- trunk/lib/Currency.inc.php	(revision 548)
+++ trunk/lib/Currency.inc.php	(revision 550)
@@ -40,5 +40,4 @@
 class Currency
 {
-
     // Configuration parameters for this object.
     protected $_params = array(
@@ -49,4 +48,7 @@
         'api_key' => '', // Used only by xurrency API.
     );
+
+    // Static runtime cache of values.
+    private static $rates = array();
 
     /**
@@ -143,5 +145,15 @@
         $app =& App::getInstance();
 
+        // If we've looked-up this rate during this run, return it from the runtime cache.
+        $rate_key = sprintf('%s-to-%s', $base, $target);
+        if (isset(self::$rates[$rate_key])) {
+            $app->logMsg(sprintf('Found %s in runtime cache: %s', $rate_key, self::$rates[$rate_key]), LOG_DEBUG, __FILE__, __LINE__);
+            return self::$rates[$rate_key];
+        }
+
         $cache_file_path = sprintf('%s/%s-to-%s', $this->getParam('cache_dir'), $base, $target);
+        if (!is_dir(dirname($cache_file_path))) {
+            mkdir(dirname($cache_file_path));
+        }
         $cache_file_mtime = @filemtime($cache_file_path);
         if (!$this->getParam('cache_result') || !$cache_file_mtime || $cache_file_mtime < time() - $this->getParam('cache_age')) {
@@ -172,4 +184,8 @@
         }
         $app->logMsg(sprintf('Found currency exchange rate: %s-to-%s = %s', $base, $target, $value), LOG_DEBUG, __FILE__, __LINE__);
+
+        // Store the value in the runtime cache.
+        self::$rates[$rate_key] = trim($value);
+
         return trim($value);
     }
@@ -239,12 +255,11 @@
                 return (float) $aCurrencies[$parameters['base']][$parameters['target']];
             } else {
-                $app->logMsg(sprintf('API %s does not have base %s or target %s', $this->getParam('api'), $parameters['base'], $parameters['target']), LOG_WARNING, __FILE__, __LINE__);
+                $app->logMsg(sprintf('API %s does not have base %s or target %s', $this->getParam('api'), $parameters['base'], $parameters['target']), LOG_ALERT, __FILE__, __LINE__);
                 return false;
             }
 
         default :
-            trigger_error('Unknown API: ' . $this->getParam('api'), E_USER_ERROR);
-            die;
-            break;
+            $app->logMsg(sprintf('Unknown currency API: %s', $this->getParam('api')), LOG_ERR, __FILE__, __LINE__);
+            return false;
         }
     }
Index: trunk/lib/DB.inc.php
===================================================================
--- trunk/lib/DB.inc.php	(revision 548)
+++ trunk/lib/DB.inc.php	(revision 550)
@@ -374,5 +374,5 @@
      * @param  string $table                The name of the table to search.
      * @param  array  $columns              An array of column names.
-     * @param  bool   $strict               Exact schema match, or are additional fields in the table okay?
+     * @param  bool   $strict               Exact schema match. If TRUE, test if *only* the given columns exist. If FALSE, test if given columns exist (possibly amongst others).
      * @param  bool   $use_cached_results   Get fresh table info (in case DB changed).
      * @return bool                         true if column(s) exist.
Index: trunk/lib/Email.inc.php
===================================================================
--- trunk/lib/Email.inc.php	(revision 548)
+++ trunk/lib/Email.inc.php	(revision 550)
@@ -34,5 +34,5 @@
 $email = new Email(array(
     'to' => array($frm['email'], 'q@lovemachine.local'),
-    'from' => sprintf('%s <%s>', $app->getParam('site_name'), $app->getParam('site_email')),
+    'from' => sprintf('"%s" <%s>', addcslashes($app->getParam('site_name'), '"'), $app->getParam('site_email')),
     'subject' => 'Your account has been activated',
 ));
Index: trunk/lib/FormValidator.inc.php
===================================================================
--- trunk/lib/FormValidator.inc.php	(revision 548)
+++ trunk/lib/FormValidator.inc.php	(revision 550)
@@ -37,14 +37,14 @@
 $fv = new FormValidator();
 
-$fv->empty('field_name', sprintf(_("%s cannot be blank."), _("Field name")));
-$fv->stringLength('field_name', 0, 255, sprintf(_("%s must be %d-to-%d characters in length."), _("Field name"), 0, 255));
-$fv->isInteger('field_name', sprintf(_("%s must be an integer."), _("Field name")));
-$fv->checkRegex('field_name', '/^\d{4}$|^$/', true, sprintf(_("%s must be in MMYY format."), _("Field name")));
-$fv->numericRange('field_name', 0, 65535, sprintf(_("%s must be a number between %d and %d."), _("Field name"), 0, 65535));
-$fv->validatePhone('field_name');
-$fv->validateEmail('field_name');
-$fv->validateStrDate('field_name', sprintf(_("%s must be a valid date in YYYY-MM-DD format."), _("Field name")));
+$fv->empty('field_name', sprintf(_("%s cannot be blank."), _("Field name")), MSG_ERR, __FILE__, __LINE__);
+$fv->stringLength('field_name', 0, 255, sprintf(_("%s must be %d-to-%d characters in length."), _("Field name"), 0, 255), MSG_ERR, __FILE__, __LINE__);
+$fv->isInteger('field_name', sprintf(_("%s must be an integer."), _("Field name")), MSG_ERR, __FILE__, __LINE__);
+$fv->checkRegex('field_name', '/^\d{4}$|^$/', true, sprintf(_("%s must be in MMYY format."), _("Field name")), MSG_ERR, __FILE__, __LINE__);
+$fv->numericRange('field_name', 0, 65535, sprintf(_("%s must be a number between %d and %d."), _("Field name"), 0, 65535), MSG_ERR, __FILE__, __LINE__);
+$fv->validatePhone('field_name', MSG_ERR, __FILE__, __LINE__);
+$fv->validateEmail('field_name', MSG_ERR, __FILE__, __LINE__);
+$fv->validateStrDate('field_name', sprintf(_("%s must be a valid date in YYYY-MM-DD format."), _("Field name")), MSG_ERR, __FILE__, __LINE__);
 if (is_null($var)) {
-    $fv->addError('field_name', sprintf(_("%s is invalid."), _("Field name")));
+    $fv->addError('field_name', sprintf(_("%s is invalid."), _("Field name")), MSG_ERR, __FILE__, __LINE__);
 }
 if ($fv->anyErrors()) {
@@ -73,9 +73,9 @@
 
     /**
-     * FormValidator constructor.
-     *
-     * @access public
-     * @param array $params Configuration parameters for this object.
-     */
+    * FormValidator constructor.
+    *
+    * @access public
+    * @param array $params Configuration parameters for this object.
+    */
     public function __construct($params=array())
     {
@@ -85,9 +85,9 @@
 
     /**
-     * Set (or overwrite existing) parameters by passing an array of new parameters.
-     *
-     * @access public
-     * @param  array    $params     Array of parameters (key => val pairs).
-     */
+    * Set (or overwrite existing) parameters by passing an array of new parameters.
+    *
+    * @access public
+    * @param  array    $params     Array of parameters (key => val pairs).
+    */
     public function setParam($params)
     {
@@ -103,10 +103,13 @@
 
     /**
-     * Return the value of a parameter, if it exists.
-     *
-     * @access public
-     * @param string $param        Which parameter to return.
-     * @return mixed               Configured parameter value.
-     */
+    * Return the value of a parameter, if it exists.
+    *
+    * @access public
+    * @param string $param        Which parameter to return.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return mixed               Configured parameter value.
+    */
     public function getParam($param)
     {
@@ -122,10 +125,13 @@
 
     /**
-     * Return the current list of errors.
-     *
-     * @return array    an array of errors in the following arrangement:
-     *                  keys: the name of the variable with an error
-     *                  vals: the message to display for that error
-     */
+    * Return the current list of errors.
+    *
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return array    an array of errors in the following arrangement:
+    *                  keys: the name of the variable with an error
+    *                  vals: the message to display for that error
+    */
     public function getErrorList()
     {
@@ -134,13 +140,13 @@
 
     /**
-     * Add an error to the errors stack.
-     *
-     * @param   string $form_name   The name of the incoming form variable.
-     * @param   string $msg         The error message for that form.
-     * @param   int    $type        The type of message: MSG_NOTICE,
-     *                              MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
-     * @param   string $file        __FILE__.
-     * @param   string $line        __LINE__.
-     */
+    * Add an error to the errors stack.
+    *
+    * @param   string $form_name   The name of the incoming form variable.
+    * @param   string $msg         The error message for that form.
+    * @param   int    $type        The type of message: MSG_NOTICE,
+    *                              MSG_SUCCESS, MSG_WARNING, or MSG_ERR.
+    * @param   string $file        __FILE__.
+    * @param   string $line        __LINE__.
+    */
     public function addError($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
     {
@@ -155,11 +161,13 @@
 
     /**
-     * Check whether any errors have been triggered.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     *
-     * @return bool   true if any errors were found, or if found for
-     *                a variable of $form_name, false otherwise
-     */
+    * Check whether any errors have been triggered.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if any errors were found, or if found for
+    *                a variable of $form_name, false otherwise
+    */
     public function anyErrors($form_name=null)
     {
@@ -177,6 +185,6 @@
 
     /**
-     * Reset the error list.
-     */
+    * Reset the error list.
+    */
     public function resetErrorList()
     {
@@ -185,19 +193,20 @@
 
     /**
-     * Prints the HTML for displaying error messages.
-     *
-     * @param   string  $above    Additional message to print above error messages (e.g. "Oops!").
-     * @param   string  $below    Additional message to print below error messages (e.g. "Please fix and resubmit").
-     * @param   string  $print_gotohash_js  Print a line of javascript that scrolls the browser window down to view any error messages.
-     * @param   string  $hash     The #hashtag to scroll to.
-     * @access  public
-     * @author  Quinn Comendant <quinn@strangecode.com>
-     * @since   15 Jul 2005 01:39:14
-     */
+    * Prints the HTML for displaying error messages.
+    *
+    * @param   string  $above    Additional message to print above error messages (e.g. "Oops!").
+    * @param   string  $below    Additional message to print below error messages (e.g. "Please fix and resubmit").
+    * @param   string  $print_gotohash_js  Print a line of javascript that scrolls the browser window down to view any error messages.
+    * @param   string  $hash     The #hashtag to scroll to.
+    * @access  public
+    * @author  Quinn Comendant <quinn@strangecode.com>
+    * @since   15 Jul 2005 01:39:14
+    */
     public function printErrorMessages($above='', $below='', $print_gotohash_js=false, $hash='sc-msg-formvalidator')
     {
         $app =& App::getInstance();
         if ($this->anyErrors()) {
-            ?><div class="sc-msg" id="sc-msg-formvalidator"><?php
+            // data-gotohash="âŠ" is to be used by sites that refuse inline JS (via CSP) but needs to be referenced from an external JS.
+            ?><div class="sc-msg" id="sc-msg-formvalidator" data-gotohash="<?php echo oTxt($hash); ?>"><?php
             if ('' != $above) {
                 ?><div class="sc-above"><?php echo oTxt($above); ?></div><?php
@@ -245,10 +254,10 @@
 
     /**
-     * If this form has an error, print an error marker like "<<".
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $marker    A string to print if there is an error. if
-     *                           not provided, use default.
-     */
+    * If this form has an error, print an error marker like "<<".
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $marker    A string to print if there is an error. if
+    *                           not provided, use default.
+    */
     public function err($form_name, $marker=null)
     {
@@ -280,17 +289,19 @@
 
     /**
-     * Ensure the length of string is non-zero.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if form is not empty, false otherwise.
-     */
-    public function notEmpty($form_name, $msg='')
-    {
-        if (!Validator::isEmpty(getFormData($form_name))) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
+    * Ensure the length of string is non-zero.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if form is not empty, false otherwise.
+    */
+    public function notEmpty($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::notEmpty(getFormData($form_name), LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
             return false;
         }
@@ -298,206 +309,230 @@
 
     /*
-    * We were using the isEmpty method *wrong* all these years and should have been using notEmpty.
-    * But the fact is the only use is to ensure a value is not empty, so this function simply becomes
-    * an alias of the one-true notEmpty() function.
+    * We were using the isEmpty method *wrong* for years and should have been using notEmpty becuase it is more grammatically correct.
+    * Because the only use is to ensure a value is not empty, we're simply going to alias this method to notEmpty().
     * @since    03 Jun 2006 22:56:46
     */
-    public function isEmpty($form_name, $msg='')
-    {
-        return $this->notEmpty($form_name, $msg);
-    }
-
-    /**
-     * Check whether input is a string.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if form is a string, false otherwise.
-     */
-    public function isString($form_name, $msg='')
-    {
-        if (Validator::isString(getFormData($form_name))) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * Check whether input is a number. Allows negative numbers.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if no errors found, false otherwise
-     */
-    public function isNumber($form_name, $msg='')
-    {
-        if (Validator::isNumber(getFormData($form_name))) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * addError if input is NOT an integer. Don't just use is_int() because the
-     * data coming from the user is *really* a string.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if value is an integer
-     */
-    public function isInteger($form_name, $msg='', $negative_ok=false)
-    {
-        if (Validator::isInteger(getFormData($form_name), $negative_ok)) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * Check whether input is a float. Don't just use is_float() because the
-     * data coming from the user is *really* a string. Integers will also
-     * pass this test.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if value is a float
-     */
-    public function isFloat($form_name, $msg='', $negative_ok=false)
-    {
-        if (Validator::isFloat(getFormData($form_name), $negative_ok)) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * Check whether input is a Decimal or Fixed type. Check values to be stored in mysql decimal, numeric, num, or fixed types.
-     * Note: some integers and floats will also pass this test.
-     * https://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     * @param  string $val The input data to validate.
-     * @param  bool $negative_ok  If the value can be unsigned.
-     * @param  int  $max    Total max number of digits.
-     * @param  int  $dec    Total max number of digits after the decimal place.
-     * @return bool   true if value is a float
-     */
-    public function isDecimal($form_name, $msg='', $negative_ok=false, $max=10, $dec=2)
-    {
-        if (Validator::isDecimal(getFormData($form_name), $negative_ok, $max, $dec)) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * Check whether input is an array.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if value is a float
-     */
-    public function isArray($form_name, $msg='')
-    {
-        if (Validator::isArray(getFormData($form_name))) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * Check whether input matches the specified perl regular expression
-     * pattern.
-     *
-     * @param  string $form_name        The name of the incoming form variable
-     * @param  int    $regex            Perl regex that the string must match
-     * @param  bool   $valid_on_match   Set to true to be valid if match, or false to be valid if the match fails.
-     * @param  string $msg              The message to display on error
-     *
-     * @return bool   true if value passes regex test
-     */
-    public function checkRegex($form_name, $regex, $valid_on_match=true, $msg='')
-    {
-        if (Validator::checkRegex(getFormData($form_name), $regex, $valid_on_match)) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * Tests if the string length is between specified values. Whitespace excluded for min.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  int    $min       minimum length of string, inclusive
-     * @param  int    $max       maximum length of string, inclusive
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if string length is within given boundaries
-     */
-    public function stringLength($form_name, $min, $max, $msg='')
-    {
-        if (Validator::stringLength(getFormData($form_name), $min, $max)) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-
-    /**
-     * Check whether input is within a valid numeric range.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  int    $min       minimum value of number, inclusive
-     * @param  int    $max       maximum value of number, inclusive
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if no errors found, false otherwise
-     */
-    public function numericRange($form_name, $min, $max, $msg='')
-    {
-        if (Validator::numericRange(getFormData($form_name), $min, $max)) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return 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>
-     */
-    public function validateEmail($form_name, $strict=false)
+    public function isEmpty($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        return $this->notEmpty($form_name, $msg, $type, $file, $line);
+    }
+
+    /**
+    * Check whether input is a string.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if form is a string, false otherwise.
+    */
+    public function isString($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::isString(getFormData($form_name), LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is a number. Allows negative numbers.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if no errors found, false otherwise
+    */
+    public function isNumber($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::isNumber(getFormData($form_name), LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * addError if input is NOT an integer. Don't just use is_int() because the
+    * data coming from the user is *really* a string.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if value is an integer
+    */
+    public function isInteger($form_name, $msg='', $negative_ok=false, $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::isInteger(getFormData($form_name), $negative_ok, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is a float. Don't just use is_float() because the
+    * data coming from the user is *really* a string. Integers will also
+    * pass this test.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if value is a float
+    */
+    public function isFloat($form_name, $msg='', $negative_ok=false, $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::isFloat(getFormData($form_name), $negative_ok, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is a Decimal or Fixed type. Use to check values to be stored in mysql decimal, numeric, num, or fixed types.
+    * The arguments $max and $dec should match M and D of the column definition "DECIMAL(M,D)".
+    * Note: some integers and floats will also pass this test.
+    * https://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html
+    *
+    * @param  string    $form_name      The name of the incoming form variable
+    * @param  int       $max            Total max number of digits.
+    * @param  int       $dec            Total max number of digits after the decimal place.
+    * @param  string    $msg            The message to display on error
+    * @param  bool      $negative_ok    If the value can be unsigned.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool                      True if value is a decimal, false otherwise.
+    */
+    public function isDecimal($form_name, $max=10, $dec=2, $negative_ok=false, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::isDecimal(getFormData($form_name), $max, $dec, $negative_ok, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            // Set the example to a sequence of Ns with $max number of digits and a faction part length of $dec.
+            $msg = str_replace('{EX}', sprintf('%s.%s', str_repeat('N', $max - $dec), str_repeat('N', $dec)), $msg);
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is an array.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if value is an array
+    */
+    public function isArray($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::isArray(getFormData($form_name), LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input matches the specified perl regular expression
+    * pattern.
+    *
+    * @param  string $form_name        The name of the incoming form variable
+    * @param  int    $regex            Perl regex that the string must match
+    * @param  bool   $valid_on_match   Set to true to be valid if match, or false to be valid if the match fails.
+    * @param  string $msg              The message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if value passes regex test (or false if $valid_on_match=false)
+    */
+    public function checkRegex($form_name, $regex, $valid_on_match=true, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::checkRegex(getFormData($form_name), $regex, $valid_on_match, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Tests if the string length is between specified values. Whitespace excluded for min.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  int    $min       minimum length of string, inclusive
+    * @param  int    $max       maximum length of string, inclusive
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if string length is within given boundaries
+    */
+    public function stringLength($form_name, $min, $max, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::stringLength(getFormData($form_name), $min, $max, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is within a valid numeric range.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  int    $min       minimum value of number, inclusive
+    * @param  int    $max       maximum value of number, inclusive
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if no errors found, false otherwise
+    */
+    public function numericRange($form_name, $min, $max, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::numericRange(getFormData($form_name), $min, $max, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return 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.
+    * @param   bool    $strict      Run strict tests (check if the domain exists and has an MX record assigned)
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return  bool                 Validity of address.
+    * @author  Quinn Comendant <quinn@strangecode.com>
+    */
+    public function validateEmail($form_name, $strict=false, $type=MSG_ERR, $file=null, $line=null)
     {
         $app =& App::getInstance();
@@ -511,24 +546,24 @@
 
         // Validator::validateEmail() returns a value that relates to the Validate::EMAIL_* constants (defined in Validator.inc.php).
-        switch (Validator::validateEmail($email, $strict)) {
+        switch (Validator::validateEmail($email, $strict, LOG_NOTICE, $file, $line)) {
         case Validator::EMAIL_REGEX_FAIL:
             // Failed regex match.
-            $this->addError($form_name, sprintf(_("The email address <em>%s</em> is formatted incorrectly."), oTxt($email)));
+            $this->addError($form_name, sprintf(_("The email address <em>%s</em> is formatted incorrectly."), oTxt($email)), $type, $file, $line);
             $app->logMsg(sprintf('The email address %s is not valid.', oTxt($email)), LOG_DEBUG, __FILE__, __LINE__);
             return false;
 
-        case Validator::EMAIL_LENGTH_FAIL :
+        case Validator::EMAIL_LENGTH_FAIL:
             // Failed length requirements.
-            $this->addError($form_name, sprintf(_("The email address <em>%s</em> is too long (email addresses must have fewer than 256 characters)."), oTxt($email)));
+            $this->addError($form_name, sprintf(_("The email address <em>%s</em> is too long (email addresses must have fewer than 256 characters)."), oTxt($email)), $type, $file, $line);
             $app->logMsg(sprintf('The email address %s must contain less than 256 characters.', oTxt($email)), LOG_DEBUG, __FILE__, __LINE__);
             return false;
 
-        case Validator::EMAIL_MX_FAIL :
+        case Validator::EMAIL_MX_FAIL:
             // Failed MX record test.
-            $this->addError($form_name, sprintf(_("The email address <em>%s</em> does not have a valid domain name"), oTxt($email)));
-            $app->logMsg(sprintf('The email address %s does not have a valid domain name.', oTxt($email)), LOG_INFO, __FILE__, __LINE__);
-            return false;
-
-        case Validator::EMAIL_SUCCESS :
+            $this->addError($form_name, sprintf(_("The email address <em>%s</em> does not have a valid domain name"), oTxt($email)), $type, $file, $line);
+            $app->logMsg(sprintf('The email address %s does not have a valid domain name.', oTxt($email)), LOG_NOTICE, __FILE__, __LINE__);
+            return false;
+
+        case Validator::EMAIL_SUCCESS:
         default :
             return true;
@@ -537,13 +572,15 @@
 
     /**
-     * Check whether input is a valid phone number. Notice: it is now set
-     * to allow characters like - or () or + so people can type in a phone
-     * number that looks like: +1 (530) 555-1212
-     *
-     * @param  string  $form_name the name of the incoming form variable
-     *
-     * @return bool    true if no errors found, false otherwise
-     */
-    public function validatePhone($form_name)
+    * Check whether input is a valid phone number. Notice: it is now set
+    * to allow characters like - or () or + so people can type in a phone
+    * number that looks like: +1 (530) 555-1212
+    *
+    * @param  string  $form_name the name of the incoming form variable
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool    true if no errors found, false otherwise
+    */
+    public function validatePhone($form_name, $type=MSG_ERR, $file=null, $line=null)
     {
         $app =& App::getInstance();
@@ -552,18 +589,18 @@
 
         // Validator::validateEmail() returns a value that relates to the Validate::PHONE_* constants (defined in Validator.inc.php).
-        switch (Validator::validatePhone($phone)) {
+        switch (Validator::validatePhone($phone, LOG_NOTICE, $file, $line)) {
         case Validator::PHONE_REGEX_FAIL:
             // Failed regex match.
-            $this->addError($form_name, sprintf(_("The phone number <em>%s</em> is not valid."), oTxt($phone)));
+            $this->addError($form_name, sprintf(_("The phone number <em>%s</em> is not valid."), oTxt($phone)), $type, $file, $line);
             $app->logMsg(sprintf('The phone number %s is not valid.', oTxt($phone)), LOG_DEBUG, __FILE__, __LINE__);
             return false;
 
-        case Validator::PHONE_LENGTH_FAIL :
+        case Validator::PHONE_LENGTH_FAIL:
             // Failed length requirements.
-            $this->addError($form_name, sprintf(_("The phone number <em>%s</em> is too long (phone number must have fewer than 25 characters)."), oTxt($phone)));
+            $this->addError($form_name, sprintf(_("The phone number <em>%s</em> is too long (phone number must have fewer than 25 characters)."), oTxt($phone)), $type, $file, $line);
             $app->logMsg(sprintf('The phone number %s must contain less than 256 characters.', oTxt($phone)), LOG_DEBUG, __FILE__, __LINE__);
             return false;
 
-        case Validator::PHONE_SUCCESS :
+        case Validator::PHONE_SUCCESS:
         default :
             return true;
@@ -572,19 +609,21 @@
 
     /**
-     * Verifies that date can be processed by the strtotime function.
-     *
-     * @param  string  $form_name the name of the incoming form variable
-     * @param  string  $msg       the message to display on error
-     *
-     * @return bool    true if no errors found, false otherwise
-     */
-    public function validateStrDate($form_name, $msg='')
+    * Verifies that date can be processed by the strtotime function.
+    *
+    * @param  string  $form_name the name of the incoming form variable
+    * @param  string  $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool    true if no errors found, false otherwise
+    */
+    public function validateStrDate($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
     {
         $app =& App::getInstance();
 
-        if (Validator::validateStrDate(getFormData($form_name, ''))) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
+        if (Validator::validateStrDate(getFormData($form_name, ''), LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
             $app->logMsg(sprintf('The string date %s is not valid.', getFormData($form_name)), LOG_DEBUG, __FILE__, __LINE__);
             return false;
@@ -594,56 +633,62 @@
 
     /**
-     * Verifies credit card number using the Luhn (mod 10) algorithm.
-     * http://en.wikipedia.org/wiki/Luhn_algorithm
-     *
-     * @param  string  $form_name   The name of the incoming form variable.
-     * @param  string  $cc_type     Optional, card type to do specific checks. One of the Validator::CC_TYPE_* constants.
-     *
-     * @return bool    true if no errors found, false otherwise
-     */
-    public function validateCCNumber($form_name, $cc_type=null)
+    * Verifies credit card number using the Luhn (mod 10) algorithm.
+    * http://en.wikipedia.org/wiki/Luhn_algorithm
+    *
+    * @param  string  $form_name   The name of the incoming form variable.
+    * @param  string  $cc_type     Optional, card type to do specific checks. One of the Validator::CC_TYPE_* constants.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool    true if no errors found, false otherwise
+    */
+    public function validateCCNumber($form_name, $cc_type=null, $type=MSG_ERR, $file=null, $line=null)
     {
         $cc_num = getFormData($form_name);
 
-        if (Validator::validateCCNumber($cc_num, $cc_type)) {
-            return true;
-        } else {
-            $this->addError($form_name, sprintf(_("The credit card number you entered is not valid. Please check the number and try again."), $cc_num));
-            return false;
-        }
-    }
-
-    /**
-     * Check whether a file was selected for uploading. If file is missing, it's an error.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if no errors found, false otherwise
-     */
-    public function fileUploaded($form_name, $msg='')
-    {
-        if (Validator::fileUploaded($form_name)) {
-            return true;
-        } else {
-            $this->addError($form_name, $msg);
-            return false;
-        }
-    }
-    /**
-     * Check whether a file was selected for uploading. If file is missing, it's an error.
-     *
-     * @param  string $form_name the name of the incoming form variable
-     * @param  string $msg       the message to display on error
-     *
-     * @return bool   true if no errors found, false otherwise
-     */
-    public function fileUploadSize($form_name, $msg='')
-    {
-        if (Validator::fileUploadSize($form_name)) {
+        if (Validator::validateCCNumber($cc_num, $cc_type, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, sprintf(_("The credit card number you entered is not valid. Please check the number and try again."), $cc_num), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether a file was selected for uploading. If file is missing, it's an error.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if no errors found, false otherwise
+    */
+    public function fileUploaded($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::fileUploaded($form_name, LOG_NOTICE, $file, $line)) {
+            return true;
+        } else {
+            $this->addError($form_name, $msg, $type, $file, $line);
+            return false;
+        }
+    }
+    /**
+    * Check whether a file was selected for uploading. If file is missing, it's an error.
+    *
+    * @param  string $form_name the name of the incoming form variable
+    * @param  string $msg       the message to display on error
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  const  $file  Filename to log (usually __FILE__)
+    * @param  const  $line  Line number to log (usually __LINE__)
+    * @return bool   true if no errors found, false otherwise
+    */
+    public function fileUploadSize($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
+    {
+        if (Validator::fileUploadSize($form_name, LOG_NOTICE, $file, $line)) {
             return true;
         } else {
             $msg = '' == $msg ? sprintf(_("Maximum filesize exceeded. Got %s, but limit is %s."), humanFileSize($_SERVER['CONTENT_LENGTH']), humanFileSize(phpIniGetBytes('upload_max_filesize'))) : $msg;
-            $this->addError($form_name, $msg);
+            $this->addError($form_name, $msg, $type, $file, $line);
             return false;
         }
Index: trunk/lib/Validator.inc.php
===================================================================
--- trunk/lib/Validator.inc.php	(revision 548)
+++ trunk/lib/Validator.inc.php	(revision 550)
@@ -54,96 +54,155 @@
 
     /**
-     * Check if a value is not empty (just the opposite of isEmpty()).
-     *
-     * @param  string $val The input data to validate.
-     * @return bool   true if form is not empty, false otherwise.
-     */
-    static public function notEmpty($val)
-    {
-        return !self::isEmpty($val);
-    }
-
-    /**
-     * Check if a value is empty.
-     *
-     * @param  string $val The input data to validate.
-     * @return bool   true if form is empty, false otherwise.
-     */
-    static public function isEmpty($val)
-    {
+    * Check if a value is not empty (the opposite of isEmpty()).
+    *
+    * @param  string $val The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if form is not empty, false otherwise.
+    */
+    static public function notEmpty($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         if (is_array($val)) {
-            return empty($val);
-        } else {
-            return '' == trim((string)$val);
-        }
-    }
-
-    /**
-     * Check whether input is a string.
-     *
-     * @param  string $val The input data to validate.
-     * @return bool   true if form is a string, false otherwise.
-     */
-    static public function isString($val)
-    {
-        return '' == trim((string)$val) || is_string($val);
-    }
-
-    /**
-     * Check whether input is a number. Allows negative numbers.
-     *
-     * @param  string $val The input data to validate.
-     * @return bool   True if no errors found, false otherwise.
-     */
-    static public function isNumber($val)
-    {
-        return '' == trim((string)$val) || is_numeric($val);
-    }
-
-    /**
-     * addError if input is NOT an integer. Don't just use is_int() because the
-     * data coming from the user is *really* a string.
-     *
-     * @param  string $val The input data to validate.
-     * @return bool   true if value is an integer
-     */
-    static public function isInteger($val, $negative_ok=false)
-    {
+            if (!empty($val)) {
+                return true;
+            } else {
+                $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+                return false;
+            }
+        } else {
+            if ('' != trim((string)$val)) {
+                return true;
+            } else {
+                $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+                return false;
+            }
+        }
+    }
+
+    /*
+    * We were using the isEmpty method *wrong* for years and should have been using notEmpty becuase it is more grammatically correct.
+    * Because the only use is to ensure a value is not empty, we're simply going to alias this method to notEmpty().
+    *
+    * @param  string $val   The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if form is empty, false otherwise.
+    */
+    static public function isEmpty($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        return !self::notEmpty($val, $type, $file, $line);
+    }
+
+    /**
+    * Check whether input is a string.
+    *
+    * @param  string $val The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if form is a string, false otherwise.
+    */
+    static public function isString($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
+        if ('' == trim((string)$val) || is_string($val)) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is a number. Allows negative numbers.
+    *
+    * @param  string $val The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   True if no errors found, false otherwise.
+    */
+    static public function isNumber($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
+        if ('' == trim((string)$val) || is_numeric($val)) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * addError if input is NOT an integer. Don't just use is_int() because the
+    * data coming from the user is *really* a string.
+    *
+    * @param  string $val The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if value is an integer
+    */
+    static public function isInteger($val, $negative_ok=false, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         $pattern = $negative_ok ? '/^-?[[:digit:]]+$/' : '/^[[:digit:]]+$/';
-        return '' == trim((string)$val) || (is_numeric($val) && preg_match($pattern, $val));
-    }
-
-    /**
-     * Check whether input is a float. Don't just use is_float() because the
-     * data coming from the user is *really* a string. Integers will also
-     * pass this test.
-     *
-     * @param  string $val The input data to validate.
-     * @param  bool $negative_ok  If the value can be unsigned.
-     * @return bool   true if value is a float
-     */
-    static public function isFloat($val, $negative_ok=false)
-    {
+        if ('' == trim((string)$val) || (is_numeric($val) && preg_match($pattern, $val))) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is a float. Don't just use is_float() because the
+    * data coming from the user is *really* a string. Integers will also
+    * pass this test.
+    *
+    * @param  string $val The input data to validate.
+    * @param  bool $negative_ok  If the value can be unsigned.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if value is a float
+    */
+    static public function isFloat($val, $negative_ok=false, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         $pattern = $negative_ok ? '/^-?[[:digit:]]*(?:\.?[[:digit:]]+)$/' : '/^[[:digit:]]*(?:\.?[[:digit:]]+)$/';
-        return '' == trim((string)$val) || (is_numeric($val) && preg_match($pattern, $val));
-    }
-
-    /**
-     * Check whether input is a Decimal or Fixed type. Check values to be stored in mysql decimal, numeric, num, or fixed types.
-     * Note: some integers and floats will also pass this test.
-     * https://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html
-     *
-     * @param  string $val The input data to validate.
-     * @param  bool $negative_ok  If the value can be unsigned.
-     * @param  int  $max    Total max number of digits (for mysql max is 65).
-     * @param  int  $dec    Total max number of digits after the decimal place (for mysql max is 30).
-     * @return bool   true if value is a float
-     */
-    static public function isDecimal($val, $negative_ok=false, $max=10, $dec=2)
-    {
+        if ('' == trim((string)$val) || (is_numeric($val) && preg_match($pattern, $val))) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is a Decimal or Fixed type. Check values to be stored in mysql decimal, numeric, num, or fixed types.
+    * Note: some integers and floats will also pass this test.
+    * https://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html
+    *
+    * @param  string $val The input data to validate.
+    * @param  bool $negative_ok  If the value can be unsigned.
+    * @param  int  $max    Total max number of digits (for mysql max is 65).
+    * @param  int  $dec    Total max number of digits after the decimal place (for mysql max is 30).
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if value is a float
+    */
+    static public function isDecimal($val, $max=10, $dec=2, $negative_ok=false, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         if ('' == trim((string)$val)) {
             return true;
         }
         if (!$negative_ok && is_numeric($val) && $val < 0) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return false;
         }
@@ -152,75 +211,120 @@
         $dec_count = sizeof($num_parts) <= 1 ? 0 : mb_strlen(end($num_parts));
         // Must be numeric, total digits <= $max, dec digits <= $dec.
-        return is_numeric($val) && mb_strlen(str_replace(['-', '.'], '', $val)) <= $max && $dec_count <= $dec;
-    }
-
-    /**
-     * Check whether input is an array.
-     *
-     * @param  string $val The input data to validate.
-     * @return bool   true if value is a float
-     */
-    static public function isArray($val)
-    {
-        return (is_string($val) && '' == trim((string)$val)) || is_array($val);
-    }
-
-    /**
-     * Check whether input matches the specified perl regular expression
-     * pattern.
-     *
-     * @param  string $val The input data to validate.
-     * @param  int    $regex            PREG that the string must match
-     * @param  bool   $valid_on_match   Set to true to be valid if match, or false to be valid if the match fails.
-     * @return bool   true if value passes regex test
-     */
-    static public function checkRegex($val, $regex, $valid_on_match=true)
-    {
-        return $valid_on_match ? preg_match($regex, $val) : !preg_match($regex, $val);
-    }
-
-    /**
-     * Tests if the string length is between specified values. Whitespace excluded for min.
-     *
-     * @param  string $val The input data to validate.
-     * @param  int    $min       minimum length of string, inclusive
-     * @param  int    $max       maximum length of string, inclusive
-     * @return bool   true if string length is within given boundaries
-     */
-    static public function stringLength($val, $min, $max)
-    {
-        return mb_strlen((string)$val) >= $min && mb_strlen((string)$val) <= $max;
-    }
-
-    /**
-     * Check whether input is within a valid numeric range.
-     *
-     * @param  string $val The input data to validate.
-     * @param  int    $min       minimum value of number, inclusive
-     * @param  int    $max       maximum value of number, inclusive
-     * @return bool   True if no errors found, false otherwise.
-     */
-    static public function numericRange($val, $min, $max)
-    {
-        return '' == trim((string)$val) || (is_numeric($val) && $val >= $min && $val <= $max);
-    }
-
-    /**
-     * 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  $val    The input data to validate..
-     * @param   bool    $strict Do we run strict tests?
-     * @return  const           One of the constant values: Validate::EMAIL_SUCCESS|Validate::EMAIL_REGEX_FAIL|Validate::EMAIL_LENGTH_FAIL|Validate::EMAIL_MX_FAIL
-     * @author  Quinn Comendant <quinn@strangecode.com>
-     */
-    static public function validateEmail($val, $strict=false)
-    {
+        if (is_numeric($val) && mb_strlen(str_replace(['-', '.'], '', $val)) <= $max && $dec_count <= $dec) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is an array.
+    *
+    * @param  string $val The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if value is a float
+    */
+    static public function isArray($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
+        if ((is_string($val) && '' == trim((string)$val)) || is_array($val)) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input matches the specified perl regular expression
+    * pattern.
+    *
+    * @param  string $val The input data to validate.
+    * @param  int    $regex            PREG that the string must match
+    * @param  bool   $valid_on_match   Set to true to be valid if match, or false to be valid if the match fails.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if value passes regex test
+    */
+    static public function checkRegex($val, $regex, $valid_on_match=true, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
+        if ($valid_on_match ? preg_match($regex, $val) : !preg_match($regex, $val)) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Tests if the string length is between specified values. Whitespace excluded for min.
+    *
+    * @param  string $val The input data to validate.
+    * @param  int    $min       minimum length of string, inclusive
+    * @param  int    $max       maximum length of string, inclusive
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   true if string length is within given boundaries
+    */
+    static public function stringLength($val, $min, $max, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
+        if (mb_strlen((string)$val) >= $min && mb_strlen((string)$val) <= $max) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether input is within a valid numeric range.
+    *
+    * @param  string $val The input data to validate.
+    * @param  int    $min       minimum value of number, inclusive
+    * @param  int    $max       maximum value of number, inclusive
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   True if no errors found, false otherwise.
+    */
+    static public function numericRange($val, $min, $max, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
+        if ('' == trim((string)$val) || (is_numeric($val) && $val >= $min && $val <= $max)) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return 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   $val    The input data to validate..
+    * @param   bool     $strict Run strict tests (check if the domain exists and has an MX record assigned)
+    * @param   const    $type  A LOG_* constant (see App->logMsg())
+    * @param   string   $file  Filename to log (usually __FILE__)
+    * @param   int      $line  Line number to log (usually __LINE__)
+    * @return  const           One of the constant values: Validate::EMAIL_SUCCESS|Validate::EMAIL_REGEX_FAIL|Validate::EMAIL_LENGTH_FAIL|Validate::EMAIL_MX_FAIL
+    * @author  Quinn Comendant <quinn@strangecode.com>
+    */
+    static public function validateEmail($val, $strict=false, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         require_once 'codebase/lib/Email.inc.php';
         $e = new Email();
@@ -228,4 +332,5 @@
         // Test email address format.
         if (!preg_match($e->getParam('regex'), $val, $e_parts)) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return self::EMAIL_REGEX_FAIL;
         }
@@ -241,12 +346,15 @@
         // Test length.
         if (mb_strlen($local) > 64 || mb_strlen($domain) > 191) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return self::EMAIL_LENGTH_FAIL;
         }
 
-        // Strict tests below.
-
-        // Check domain exists: It's a domain if ip2long fails; checkdnsrr ensures a MX record exists; gethostbyname() ensures the domain exists.
-        if ($strict && ip2long($domain) === false && function_exists('checkdnsrr') && !checkdnsrr($domain . '.', 'MX') && gethostbyname($domain) == $domain) {
-            return self::EMAIL_MX_FAIL;
+        if ($strict) {
+            // Strict tests.
+            if (ip2long($domain) === false && function_exists('checkdnsrr') && !checkdnsrr($domain . '.', 'MX') && gethostbyname($domain) == $domain) {
+                // Check domain exists: It's a domain if ip2long fails; checkdnsrr ensures a MX record exists; gethostbyname() ensures the domain exists.
+                $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+                return self::EMAIL_MX_FAIL;
+            }
         }
 
@@ -255,18 +363,24 @@
 
     /**
-     * Check whether input is a valid phone number. Notice: it is now set
-     * to allow characters like - or () or + so people can type in a phone
-     * number that looks like: +1 (530) 555-1212
-     *
-     * @param  string  $form_name the name of the incoming form variable
-     *
-     * @return bool    true if no errors found, false otherwise
-     */
-    static public function validatePhone($val)
-    {
-        if (!self::checkRegex($val, '/^[0-9 +().-]*$/', true)) {
+    * Check whether input is a valid phone number. Notice: it is now set
+    * to allow characters like - or () or + so people can type in a phone
+    * number that looks like: +1 (530) 555-1212
+    *
+    * @param  string  $form_name the name of the incoming form variable
+    *
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool    true if no errors found, false otherwise
+    */
+    static public function validatePhone($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
+        if (!self::checkRegex($val, '/^[0-9 +().-]*$/', true, $type, $file, $line)) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return self::PHONE_REGEX_FAIL;
         }
-        if (!self::stringLength($val, 0, 25)) {
+        if (!self::stringLength($val, 0, 25, $type, $file, $line)) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return self::PHONE_LENGTH_FAIL;
         }
@@ -275,12 +389,16 @@
 
     /**
-     * Verifies that date can be processed by the strtotime function.
-     * Empty strings are considered valid. Other values are tested on their return value from strtotime(). Null values will fail.
-     *
-     * @param  string  $val The input data to validate.
-     * @return bool    True if no errors found, false otherwise.
-     */
-    static public function validateStrDate($val)
-    {
+    * Verifies that date can be processed by the strtotime function.
+    * Empty strings are considered valid. Other values are tested on their return value from strtotime(). Null values will fail.
+    *
+    * @param  string  $val The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool    True if no errors found, false otherwise.
+    */
+    static public function validateStrDate($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         if (is_string($val) && '' === trim($val)) {
             // Don't be too bothered about empty strings.
@@ -290,4 +408,5 @@
         $timestamp = strtotime($val);
         if (!$timestamp || $timestamp < 1) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return false;
         } else {
@@ -301,4 +420,7 @@
     * @access   public
     * @param    string  $val    String to check.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
     * @return   bool            True if value is an empty date.
     * @author   Quinn Comendant <quinn@strangecode.com>
@@ -306,23 +428,29 @@
     * @since    19 May 2015 09:57:27
     */
-    static public function isEmptyDate($val)
-    {
+    static public function isEmptyDate($val, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         if (empty($val) || '0000-00-00 00:00:00' == $val || '0000-00-00' == $val || '00:00:00' == $val) {
             return true;
         }
+        $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
         return false;
     }
 
     /**
-     * Verifies credit card number using the Luhn (mod 10) algorithm.
-     * http://en.wikipedia.org/wiki/Luhn_algorithm
-     *
-     * @param  string  $val   The input data to validate..
-     * @param  string  $cc_num      Card number to verify.
-     * @param  string  $cc_type     Optional, card type to do specific checks.
-     * @return bool    True if no errors found, false otherwise.
-     */
-    static public function validateCCNumber($val, $cc_type=null)
-    {
+    * Verifies credit card number using the Luhn (mod 10) algorithm.
+    * http://en.wikipedia.org/wiki/Luhn_algorithm
+    *
+    * @param  string  $val   The input data to validate..
+    * @param  string  $cc_num      Card number to verify.
+    * @param  string  $cc_type     Optional, card type to do specific checks.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool    True if no errors found, false otherwise.
+    */
+    static public function validateCCNumber($val, $cc_type=null, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         // Get rid of any non-digits
         $cc_num = preg_replace('/[^\d]/', '', $val);
@@ -355,4 +483,5 @@
         if ('' != $regex && !preg_match($regex, $cc_num)) {
             // Invalid format.
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return false;
         }
@@ -383,16 +512,26 @@
 
         // If the Total is evenly divisible by 10, it's cool!
-        return $luhn_total % 10 == 0;
-    }
-
-    /**
-     * Check whether a file was selected for uploading. If file is missing, it's an error.
-     *
-     * @param  string $form_name The input data to validate.
-     * @return bool   True if no errors found, false otherwise.
-     */
-    static public function fileUploaded($form_name)
-    {
+        if ($luhn_total % 10 == 0) {
+            return true;
+        } else {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
+            return false;
+        }
+    }
+
+    /**
+    * Check whether a file was selected for uploading. If file is missing, it's an error.
+    *
+    * @param  string $form_name The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
+    * @return bool   True if no errors found, false otherwise.
+    */
+    static public function fileUploaded($form_name, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         if (!isset($_FILES[$form_name]['name']) || empty($_FILES[$form_name]['name'])) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, 'no _FILES'), $type, $file, $line);
             return false;
         }
@@ -401,4 +540,5 @@
             foreach($_FILES[$form_name]['name'] as $f) {
                 if ('' == $f) {
+                    $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($_FILES)), $type, $file, $line);
                     return false;
                 }
@@ -406,4 +546,5 @@
         } else {
             if ('' == $_FILES[$form_name]['name']) {
+                $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($_FILES)), $type, $file, $line);
                 return false;
             }
@@ -419,4 +560,7 @@
     * @access   public
     * @param    string $form_name The input data to validate.
+    * @param  const  $type  A LOG_* constant (see App->logMsg())
+    * @param  string $file  Filename to log (usually __FILE__)
+    * @param  int    $line  Line number to log (usually __LINE__)
     * @return   bool   True if no errors found, false otherwise.
     * @author   Quinn Comendant <quinn@strangecode.com>
@@ -424,8 +568,10 @@
     * @since    20 Aug 2014 14:44:23
     */
-    static public function fileUploadSize($form_name)
-    {
+    static public function fileUploadSize($form_name, $type=LOG_NOTICE, $file=null, $line=null)
+    {
+        $app =& App::getInstance();
         $upload_max_filesize = phpIniGetBytes('upload_max_filesize');
         if (isset($_SERVER['CONTENT_LENGTH']) && 0 != $upload_max_filesize && $_SERVER['CONTENT_LENGTH'] > $upload_max_filesize) {
+            $app->logMsg(sprintf('%s (line %s) failed: %s', __METHOD__, __LINE__, getDump($val)), $type, $file, $line);
             return false;
         }
Index: trunk/lib/Version.inc.php
===================================================================
--- trunk/lib/Version.inc.php	(revision 548)
+++ trunk/lib/Version.inc.php	(revision 550)
@@ -56,4 +56,8 @@
         // If true, makes an exact comparison of saved vs. live table schemas. If false, just checks that the saved columns are available.
         'db_schema_strict' => true,
+
+        // Serialization method.
+        // Legacy installations will have been using 'phpserialize' but these should migrate to use 'json' to avoid PHP object injection https://www.owasp.org/index.php/PHP_Object_Injection
+        'serialization_method' => 'phpserialize', // Or 'json'
     );
 
@@ -161,4 +165,9 @@
     public function setParam($params=null)
     {
+        $app =& App::getInstance();
+
+        if (isset($params['serialization_method']) && !in_array($params['serialization_method'], ['phpserialize', 'json'])) {
+            trigger_error(sprintf('Invalid serialization_method: %s', $params['serialization_method']), E_USER_ERROR);
+        }
         if (isset($params) && is_array($params)) {
             // Merge new parameters with old overriding only those passed.
@@ -220,4 +229,15 @@
         // Clean-up old versions.
         $this->deleteOld($record_table, $record_key, $record_val);
+
+        // Serialize the DB record.
+        switch ($this->getParam('serialization_method')) {
+        case 'phpserialize':
+            $data = gzcompress(serialize($record), 9);
+            break;
+
+        case 'json':
+            $data = gzcompress(json_encode($record), 9);
+            break;
+        }
 
         // Save as new version.
@@ -238,5 +258,5 @@
                 '" . $db->escapeString($record_key) . "',
                 '" . $db->escapeString($record_val) . "',
-                '" . $db->escapeString(gzcompress(serialize($record), 9)) . "',
+                '" . $db->escapeString($data) . "',
                 '" . $db->escapeString($title) . "',
                 '" . $db->escapeString($last_version_number + 1) . "',
@@ -266,5 +286,6 @@
         // Get version data.
         $qid = $db->query("
-            SELECT * FROM " . $db->escapeString($this->getParam('db_table')) . "
+            SELECT *
+            FROM " . $db->escapeString($this->getParam('db_table')) . "
             WHERE version_id = '" . $db->escapeString($version_id) . "'
         ");
@@ -274,5 +295,15 @@
             return false;
         }
-        $data = unserialize(gzuncompress($record['version_data']));
+
+        // Unserialize the DB record.
+        switch ($this->getParam('serialization_method')) {
+        case 'phpserialize':
+            $data = unserialize(gzuncompress($record['version_data']));
+            break;
+
+        case 'json':
+            $data = json_decode(gzuncompress($record['version_data']), true);
+            break;
+        }
 
         // Ensure saved db columns match current table schema.
@@ -342,11 +373,13 @@
                 // First query for oldest records, selecting enough to bring total number down to min_qty.
                 $qid = $db->query("
-                    SELECT version_id FROM " . $db->escapeString($this->getParam('db_table')) . "
+                    SELECT version_id
+                    FROM " . $db->escapeString($this->getParam('db_table')) . "
                     WHERE record_table = '" . $db->escapeString($record_table) . "'
                     AND record_key = '" . $db->escapeString($record_key) . "'
                     AND record_val = '" . $db->escapeString($record_val) . "'
                     ORDER BY version_datetime ASC
-                    LIMIT " . ($v_count - $this->getParam('min_qty')) . "
+                    LIMIT " . $db->escapeString($v_count - $this->getParam('min_qty')) . "
                 ");
+                $old_versions = array();
                 while (list($old_id) = mysql_fetch_row($qid)) {
                     $old_versions[] = $old_id;
@@ -359,5 +392,6 @@
                 // Delete versions older than min_days, while still keeping min_qty.
                 $qid = $db->query("
-                    SELECT version_id FROM " . $db->escapeString($this->getParam('db_table')) . "
+                    SELECT version_id
+                    FROM " . $db->escapeString($this->getParam('db_table')) . "
                     WHERE record_table = '" . $db->escapeString($record_table) . "'
                     AND record_key = '" . $db->escapeString($record_key) . "'
@@ -367,4 +401,5 @@
                     LIMIT " . ($v_count - $this->getParam('min_qty')) . "
                 ");
+                $old_versions = array();
                 while (list($old_id) = mysql_fetch_row($qid)) {
                     $old_versions[] = $old_id;
@@ -455,10 +490,18 @@
         // Get version data.
         $qid = $db->query("
-            SELECT * FROM " . $db->escapeString($this->getParam('db_table')) . "
+            SELECT *
+            FROM " . $db->escapeString($this->getParam('db_table')) . "
             WHERE version_id = '" . $db->escapeString($version_id) . "'
         ");
         $record = mysql_fetch_assoc($qid);
         if (isset($record['version_data'])) {
-            return unserialize(gzuncompress($record['version_data']));
+            // Unserialize the DB record.
+            switch ($this->getParam('serialization_method')) {
+            case 'phpserialize':
+                return unserialize(gzuncompress($record['version_data']));
+
+            case 'json':
+                return json_decode(gzuncompress($record['version_data']));
+            }
         } else {
             return false;
