/** * Set the format to be applied to all field names used in fValidationExceptions * * The format should contain exactly one `%s` * [http://php.net/sprintf sprintf()] conversion specification, which will * be replaced with the field name. Any literal `%` characters should be * written as `%%`. * * The default format is just `%s: `, which simply inserts a `:` and space * after the field name. * * @param string $format A string to format the field name with - `%s` will be replaced with the field name * @return void */ public static function setFieldFormat($format) { if (substr_count(str_replace('%%', '', $format), '%') != 1 || strpos($format, '%s') === FALSE) { throw new fProgrammerException('The format, %s, has more or less than exactly one %%s sprintf() conversion specification', $format); } self::$field_format = $format; }
/** * Validates uploaded files to ensure they match all of the criteria defined * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param array &$validation_messages The existing validation messages * @return void */ public static function validate($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages) { $class = get_class($object); foreach (self::$file_upload_columns[$class] as $column => $directory) { $column_name = fORM::getColumnName($class, $column); if (isset($validation_messages[$column])) { $search_message = self::compose('%sPlease enter a value', fValidationException::formatField($column_name)); $replace_message = self::compose('%sPlease upload a file', fValidationException::formatField($column_name)); $validation_messages[$column] = str_replace($search_message, $replace_message, $validation_messages[$column]); } // Grab the error that occured try { if (fUpload::check($column)) { $uploader = self::setUpFUpload($class, $column); $uploader->validate($column); } } catch (fValidationException $e) { if ($e->getMessage() != self::compose('Please upload a file')) { $validation_messages[$column] = fValidationException::formatField($column_name) . $e->getMessage(); } } } }
/** * Validates the URL fields, requiring that any URL fields that have a value are valid URLs * * @param array &$messages The messages to display to the user * @return void */ private function checkURLFields(&$messages) { foreach ($this->url_fields as $url_field) { $value = trim(fRequest::get($url_field)); if (self::stringlike($value) && !preg_match('#^https?://[^ ]+$#iD', $value)) { $messages[] = self::compose('%sPlease enter a URL in the form http://www.example.com/page', fValidationException::formatField(fGrammar::humanize($url_field))); } } }
/** * Validates all money columns * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param array &$validation_messages An array of ordered validation messages * @return void */ public static function validateMoneyColumns($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages) { $class = get_class($object); if (empty(self::$money_columns[$class])) { return; } foreach (self::$money_columns[$class] as $column => $currency_column) { if ($values[$column] instanceof fMoney || $values[$column] === NULL) { continue; } // Remove any previous validation warnings unset($validation_messages[$column]); $column_name = fValidationException::formatField(fORM::getColumnName($class, $currency_column)); if ($currency_column && !in_array($values[$currency_column], fMoney::getCurrencies())) { $validation_messages[$column] = self::compose('%sThe currency specified is invalid', $column_name); } else { $validation_messages[$column] = self::compose('%sPlease enter a monetary value', $column_name); } } }
/** * Validates the values of the record against the database and any additional validation rules * * @throws fValidationException When the record, or one of the associated records, violates one of the validation rules for the class or can not be properly stored in the database * * @param boolean $return_messages If an array of validation messages should be returned instead of an exception being thrown * @param boolean $remove_column_names If column names should be removed from the returned messages, leaving just the message itself * @return void|array If $return_messages is TRUE, an array of validation messages will be returned */ public function validate($return_messages = FALSE, $remove_column_names = FALSE) { $class = get_class($this); if (fORM::getActiveRecordMethod($class, 'validate')) { return $this->__call('validate', array($return_messages)); } $validation_messages = array(); fORM::callHookCallbacks($this, 'pre::validate()', $this->values, $this->old_values, $this->related_records, $this->cache, $validation_messages); // Validate the local values $local_validation_messages = fORMValidation::validate($this, $this->values, $this->old_values); // Validate related records $related_validation_messages = fORMValidation::validateRelated($this, $this->values, $this->related_records); $validation_messages = array_merge($validation_messages, $local_validation_messages, $related_validation_messages); fORM::callHookCallbacks($this, 'post::validate()', $this->values, $this->old_values, $this->related_records, $this->cache, $validation_messages); $validation_messages = fORMValidation::replaceMessages($class, $validation_messages); $validation_messages = fORMValidation::reorderMessages($class, $validation_messages); if ($return_messages) { if ($remove_column_names) { $validation_messages = fValidationException::removeFieldNames($validation_messages); } return $validation_messages; } if (!empty($validation_messages)) { throw new fValidationException('The following problems were found:', $validation_messages); } }
/** * Validates many-to-many related records * * @param string $class The class to validate the related records for * @param string $related_class The name of the class for this record set * @param string $route The route between the table and related table * @param array $related_info The related info to validate * @return array An array of validation messages */ private static function validateManyToMany($class, $related_class, $route, $related_info) { $related_record_name = self::getRelatedRecordName($class, $related_class, $route); $record_number = 1; $messages = array(); $related_records = $related_info['record_set'] ? $related_info['record_set'] : $related_info['primary_keys']; foreach ($related_records as $record) { if (is_object($record) && !$record->exists() || !$record) { $messages[fORM::tablize($related_class)] = self::compose('%1$sPlease select a %2$s', fValidationException::formatField(self::compose('%1$s #%2$s', $related_record_name, $record_number)), $related_record_name); } $record_number++; } return $messages; }
/** * Checks for required fields, email field formatting and email header injection using values previously set * * @throws fValidationException When one of the options set for the object is violated * * @param boolean $return_messages If an array of validation messages should be returned instead of an exception being thrown * @param boolean $remove_field_names If field names should be removed from the returned messages, leaving just the message itself * @return void|array If $return_messages is TRUE, an array of validation messages will be returned */ public function validate($return_messages = FALSE, $remove_field_names = FALSE) { if (!$this->callback_rules && !$this->conditional_rules && !$this->date_fields && !$this->file_upload_rules && !$this->one_or_more_rules && !$this->only_one_rules && !$this->regex_rules && !$this->required_fields && !$this->valid_values_rules) { throw new fProgrammerException('No fields or rules have been added for validation'); } $messages = array(); $this->checkRequiredFields($messages); $this->checkFileUploadRules($messages); $this->checkConditionalRules($messages); $this->checkOneOrMoreRules($messages); $this->checkOnlyOneRules($messages); $this->checkValidValuesRules($messages); $this->checkDateFields($messages); $this->checkRegexRules($messages); $this->checkCallbackRules($messages); if ($this->regex_replacements) { $messages = preg_replace(array_keys($this->regex_replacements), array_values($this->regex_replacements), $messages); } if ($this->string_replacements) { $messages = str_replace(array_keys($this->string_replacements), array_values($this->string_replacements), $messages); } $messages = $this->reorderMessages($messages); if ($return_messages) { if ($remove_field_names) { $messages = fValidationException::removeFieldNames($messages); } return $messages; } if ($messages) { throw new fValidationException('The following problems were found:', $messages); } }
/** * Validates all timestamp/timezone columns * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param array &$validation_messages An array of ordered validation messages * @return void */ public static function validateTimezoneColumns($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages) { $class = get_class($object); if (empty(self::$timezone_columns[$class])) { return; } foreach (self::$timezone_columns[$class] as $timezone_column => $timestamp_column) { if ($values[$timestamp_column] instanceof fTimestamp || $values[$timestamp_column] === NULL) { continue; } if (!fTimestamp::isValidTimezone($values[$timezone_column])) { $validation_messages[$timezone_column] = self::compose('%sThe timezone specified is invalid', fValidationException::formatField(fORM::getColumnName($class, $timezone_column))); } else { $validation_messages[$timestamp_column] = self::compose('%sPlease enter a date/time', fValidationException::formatField(fORM::getColumnName($class, $timestamp_column))); } } }
/** * Validates against a valid values rule * * @param string $class The class this rule applies to * @param array &$values An associative array of all values for the record * @param string $column The column the rule applies to * @param array $valid_values An array of valid values to check the column against * @return string The error message for the rule specified */ private static function checkValidValuesRule($class, &$values, $column, $valid_values) { if ($values[$column] === NULL) { return; } if (!in_array($values[$column], $valid_values)) { return self::compose('%1$sPlease choose from one of the following: %2$s', fValidationException::formatField(fORM::getColumnName($class, $column)), join(', ', $valid_values)); } }
/** * Makes sure the ordering value is sane, removes error messages about missing values * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param array &$validation_messages An array of ordered validation messages * @return void */ public static function validate($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages) { $class = get_class($object); $table = fORM::tablize($class); $db = fORMDatabase::retrieve($class, 'read'); $schema = fORMSchema::retrieve($class); foreach (self::$ordering_columns[$class] as $column => $other_columns) { $current_value = $values[$column]; $old_value = fActiveRecord::retrieveOld($old_values, $column); $params = array("SELECT MAX(%r) FROM %r", $column, $table); if ($other_columns) { $params[0] .= " WHERE "; $params = self::addOtherFieldsWhereParams($schema, $params, $table, $other_columns, $values); } $current_max_value = (int) call_user_func_array($db->translatedQuery, $params)->fetchScalar(); $new_max_value = $current_max_value; if ($new_set = self::isInNewSet($column, $other_columns, $values, $old_values)) { $new_max_value = $current_max_value + 1; $new_set_new_value = fActiveRecord::changed($values, $old_values, $column); } $column_name = fORM::getColumnName($class, $column); // Remove any previous validation warnings $filtered_messages = array(); foreach ($validation_messages as $validation_column => $validation_message) { if (!preg_match('#(^|,)' . preg_quote($column, '#') . '(,|$)#D', $validation_column)) { $filtered_messages[$validation_column] = $validation_message; } } $validation_messages = $filtered_messages; // If we have a completely empty value, we don't need to validate since a valid value will be generated if ($current_value === '' || $current_value === NULL) { continue; } if (!is_numeric($current_value) || strlen((int) $current_value) != strlen($current_value)) { $validation_messages[$column] = self::compose('%sPlease enter an integer', fValidationException::formatField($column_name)); } elseif ($current_value < 1) { $validation_messages[$column] = self::compose('%sThe value can not be less than 1', fValidationException::formatField($column_name)); } } }
/** * Makes sure the ordering value is sane, removes error messages about missing values * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param array &$validation_messages An array of ordered validation messages * @return void */ public static function validate($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages) { $class = get_class($object); $table = fORM::tablize($class); $column = self::$ordering_columns[$class]['column']; $other_columns = self::$ordering_columns[$class]['other_columns']; $current_value = $values[$column]; $old_value = fActiveRecord::retrieveOld($old_values, $column); $sql = "SELECT max(" . $column . ") FROM " . $table; if ($other_columns) { $sql .= " WHERE " . self::createOtherFieldsWhereClause($table, $other_columns, $values); } $current_max_value = (int) fORMDatabase::retrieve()->translatedQuery($sql)->fetchScalar(); $new_max_value = $current_max_value; if ($new_set = self::isInNewSet($column, $other_columns, $values, $old_values)) { $new_max_value = $current_max_value + 1; $new_set_new_value = fActiveRecord::changed($values, $old_values, $column); } $column_name = fORM::getColumnName($class, $column); // Remove any previous validation warnings $filtered_messages = array(); foreach ($validation_messages as $validation_message) { if (!preg_match('#^' . str_replace('___', '(.*?)', preg_quote(fValidationException::formatField('___' . $column_name . '___'), '#')) . '#', $validation_message)) { $filtered_messages[] = $validation_message; } } $validation_messages = $filtered_messages; // If we have a completely empty value, we don't need to validate since a valid value will be generated if ($current_value === '' || $current_value === NULL) { return; } if (!is_numeric($current_value) || strlen((int) $current_value) != strlen($current_value)) { $validation_messages[] = self::compose('%sPlease enter an integer', fValidationException::formatField($column_name)); } elseif ($current_value < 1) { $validation_messages[] = self::compose('%sThe value can not be less than 1', fValidationException::formatField($column_name)); } }
/** * Validates all link columns * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param array &$validation_messages An array of ordered validation messages * @return void */ public static function validateLinkColumns($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages) { $class = get_class($object); if (empty(self::$link_columns[$class])) { return; } foreach (self::$link_columns[$class] as $column => $enabled) { if (!is_string($values[$column])) { continue; } $ip_regex = '(?:(?:[01]?\\d?\\d|2[0-4]\\d|25[0-5])\\.){3}(?:[01]?\\d?\\d|2[0-4]\\d|25[0-5])'; $hostname_regex = '[a-z]+(?:[a-z0-9\\-]*[a-z0-9]\\.?|\\.)*'; $domain_regex = '([a-z]+([a-z0-9\\-]*[a-z0-9])?\\.)+[a-z]{2,}'; if (!preg_match('#^(https?://(' . $ip_regex . '|' . $hostname_regex . ')(?=/|$)|' . $domain_regex . '(?=/|$)|/)#i', $values[$column])) { $validation_messages[$column] = self::compose('%sPlease enter a link in the form http://www.example.com', fValidationException::formatField(fORM::getColumnName($class, $column))); } } }
/** * Validates all link columns * * @internal * * @param fActiveRecord $object The fActiveRecord instance * @param array &$values The current values * @param array &$old_values The old values * @param array &$related_records Any records related to this record * @param array &$cache The cache array for the record * @param array &$validation_messages An array of ordered validation messages * @return void */ public static function validateLinkColumns($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages) { $class = get_class($object); if (empty(self::$link_columns[$class])) { return; } foreach (self::$link_columns[$class] as $column => $enabled) { if (!strlen($values[$column])) { continue; } if (!preg_match('#^(http(s)?://|/|([a-z0-9\\-]+\\.)+[a-z]{2,})#i', $values[$column])) { $validation_messages[] = self::compose('%sPlease enter a link in the form http://www.example.com', fValidationException::formatField(fORM::getColumnName($class, $column))); } } }