'!la_err_required!', // Field is required 'unique' => '!la_err_unique!', // Field value must be unique 'value_out_of_range' => '!la_err_value_out_of_range!', // Field is out of range, possible values from %s to %s 'length_out_of_range' => '!la_err_length_out_of_range!', // Field is out of range 'bad_type' => '!la_err_bad_type!', // Incorrect data format, please use %s 'invalid_format' => '!la_err_invalid_format!', // Incorrect data format, please use %s 'bad_date_format' => '!la_err_bad_date_format!', // Incorrect date format, please use (%s) ex. (%s) 'primary_lang_required' => '!la_err_primary_lang_required!', // Primary Lang. value Required ); /** * Sets data source for validation * * @param kDBItem $object */ public function setDataSource(&$object) { if ( $object->getFormName() === $this->lastFormName && $object->getPrefixSpecial() === $this->lastPrefixSpecial ) { return ; } $this->reset(); $this->dataSource =& $object; $this->lastFormName = $object->getFormName(); $this->lastPrefixSpecial = $object->getPrefixSpecial(); } /** * Validate all item fields based on * constraints set in each field options * in config * * @return bool * @access private */ public function Validate() { // order is critical - should be called BEFORE checking errors $this->dataSource->UpdateFormattersMasterFields(); $global_res = true; $fields = array_keys( $this->dataSource->getFields() ); foreach ($fields as $field) { // call separately, otherwise 2+ validation errors will be ignored $res = $this->ValidateField($field); $global_res = $global_res && $res; } if ( !$global_res && $this->Application->isDebugMode() ) { $title_info = $this->dataSource->GetTitleField(); $item_info = Array ( $this->dataSource->IDField . ': ' . $this->dataSource->GetID() . '', ); if ( $title_info && reset($title_info) ) { $item_info[] = key($title_info) . ': ' . current($title_info) . ''; } $item_info[] = 'Temp Mode: ' . ($this->dataSource->IsTempTable() ? 'Yes' : 'No') . ''; $raw_errors = array_filter($this->FieldErrors, function ($error_params) { return isset($error_params['pseudo']); }); $error_msg = <<%s item (%s).
Validation errors:
%s
You may ignore this notice if submitted data really has a validation error. HTML; $error_msg = sprintf( $error_msg, $this->dataSource->getPrefixSpecial(), implode('; ', $item_info), print_r($raw_errors, true) ); trigger_error(trim($error_msg), E_USER_NOTICE); } return $global_res; } /** * Validates given field * * @param string $field * @return bool * @access public */ public function ValidateField($field) { $options = $this->dataSource->GetFieldOptions($field); $error_field = isset($options['error_field']) ? $options['error_field'] : $field; $res = $this->GetErrorPseudo($error_field) == ''; $res = $res && $this->ValidateRequired($field, $options); $res = $res && $this->ValidateType($field, $options); $res = $res && $this->ValidateRange($field, $options); $res = $res && $this->ValidateUnique($field, $options); $res = $res && $this->CustomValidation($field, $options); return $res; } /** * Check if value is set for required field * * @param string $field field name * @param Array $params field options from config * @return bool * @access public */ public function ValidateRequired($field, $params) { if ( !isset($params['required']) || !$params['required'] ) { return true; } $value = $this->dataSource->GetDBField($field); if ( $this->Application->ConfigValue('TrimRequiredFields') ) { $value = trim($value); } if ( (string)$value == '' ) { $this->SetError($field, 'required'); return false; } return true; } /** * Check if value in field matches field type specified in config * * @param string $field field name * @param Array $params field options from config * @return bool */ protected function ValidateType($field, $params) { $val = $this->dataSource->GetDBField($field); $type_regex = "#int|integer|double|float|real|numeric|string#"; if ( $val == '' || !isset($params['type']) || !preg_match($type_regex, $params['type']) ) { return true; } if ( $params['type'] == 'numeric' ) { trigger_error('Invalid field type ' . $params['type'] . ' (in ValidateType method), please use float instead', E_USER_NOTICE); $params['type'] = 'float'; } $res = is_numeric($val); if ( $params['type'] == 'string' || $res ) { $f = 'is_' . $params['type']; settype($val, $params['type']); $res = $f($val) && ($val == $this->dataSource->GetDBField($field)); } if ( !$res ) { $this->SetError($field, 'bad_type', null, array('type' => $params['type'])); return false; } return true; } /** * Check if field value is in range specified in config * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ protected function ValidateRange($field, $params) { $res = true; $val = $this->dataSource->GetDBField($field); if ( isset($params['type']) && preg_match("#int|integer|double|float|real#", $params['type']) && strlen($val) > 0 ) { // validate number if ( isset($params['max_value_inc'])) { $res = $res && $val <= $params['max_value_inc']; $max_val = $params['max_value_inc'].' (inclusive)'; } if ( isset($params['min_value_inc'])) { $res = $res && $val >= $params['min_value_inc']; $min_val = $params['min_value_inc'].' (inclusive)'; } if ( isset($params['max_value_exc'])) { $res = $res && $val < $params['max_value_exc']; $max_val = $params['max_value_exc'].' (exclusive)'; } if ( isset($params['min_value_exc'])) { $res = $res && $val > $params['min_value_exc']; $min_val = $params['min_value_exc'].' (exclusive)'; } } if ( !$res ) { if ( !isset($min_val) ) $min_val = '-∞'; if ( !isset($max_val) ) $max_val = '∞'; $this->SetError($field, 'value_out_of_range', null, array( 'min_value' => $min_val, 'max_value' => $max_val )); return false; } if ( strlen($val) > 0 ) { // Validate string. if ( isset($params['max_len']) ) { $res = $res && mb_strlen($val) <= $params['max_len']; } if ( isset($params['min_len']) ) { $res = $res && mb_strlen($val) >= $params['min_len']; } } if ( !$res ) { $error_params = array( 'min_length' => (int)getArrayValue($params, 'min_len'), 'max_length' => (int)getArrayValue($params, 'max_len'), 'value' => mb_strlen($val) ); $this->SetError($field, 'length_out_of_range', null, $error_params); return false; } return true; } /** * Validates that current record has unique field combination among other table records * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ protected function ValidateUnique($field, $params) { $unique_fields = getArrayValue($params, 'unique'); if ( $unique_fields === false ) { return true; } $where = Array (); array_push($unique_fields, $field); foreach ($unique_fields as $unique_field) { // if field is not empty or if it is required - we add where condition $field_value = $this->dataSource->GetDBField($unique_field); if ( (string)$field_value != '' || $this->dataSource->isRequired($unique_field) ) { $where[] = '`' . $unique_field . '` = ' . $this->Conn->qstr($field_value); } else { // not good if we check by less fields than indicated return true; } } // This can ONLY happen if all unique fields are empty and not required. // In such case we return true, because if unique field is not required there may be numerous empty values // if (!$where) return true; $sql = 'SELECT COUNT(*) FROM %s WHERE (' . implode(') AND (', $where) . ') AND (' . $this->dataSource->IDField . ' <> ' . (int)$this->dataSource->GetID() . ')'; $res_temp = $this->Conn->GetOne( str_replace('%s', $this->dataSource->TableName, $sql) ); if ( getArrayValue($params, 'current_table_only') || !$this->dataSource->IsTempTable() ) { // Check unique record only in current table. $res_live = 0; } else { $deleted_ids = $this->getTempTableDeletedIDs(); $live_sql = str_replace('%s', $this->Application->GetLiveName($this->dataSource->TableName), $sql); if ( $deleted_ids ) { $live_sql .= ' AND (' . $this->dataSource->IDField . ' NOT IN (' . implode(',', $deleted_ids) . '))'; } $res_live = $this->Conn->GetOne($live_sql); } $res = ($res_temp == 0) && ($res_live == 0); if ( !$res ) { $this->SetError($field, 'unique'); return false; } return true; } /** * Returns IDs deleted in temp table. * * @return array */ protected function getTempTableDeletedIDs() { $parent_prefix = $this->Application->getUnitOption($this->dataSource->Prefix, 'ParentPrefix'); if ( !$parent_prefix ) { return array(); } // 1. Get main IDs, that are edited in temp table. $parent_table_name = $this->Application->GetTempName( $this->Application->getUnitOption($parent_prefix, 'TableName'), 'prefix:' . $parent_prefix ); $sql = 'SELECT ' . $this->Application->getUnitOption($parent_prefix, 'IDField') . ' FROM ' . $parent_table_name; $parent_temp_ids = $this->Conn->GetCol($sql); // Main item not saved to db, but sub-item is validated (only possible via custom code). if ( !$parent_temp_ids ) { return array(); } // 2. From above found IDs find sub-item IDs in LIVE table. $foreign_key = $this->Application->getUnitOption($this->dataSource->Prefix, 'ForeignKey'); $foreign_key = is_array($foreign_key) ? $foreign_key[$parent_prefix] : $foreign_key; $sql = 'SELECT ' . $this->dataSource->IDField . ' FROM ' . $this->Application->GetLiveName($this->dataSource->TableName) . ' WHERE ' . $foreign_key . ' IN (' . implode(',', $parent_temp_ids) . ')'; $live_ids = $this->Conn->GetCol($sql); // Sub-items were never saved to LIVE table. if ( !$live_ids ) { return array(); } // 3. Return IDs of LIVE table, that are no longer present (deleted) in TEMP table. $sql = 'SELECT ' . $this->dataSource->IDField . ' FROM ' . $this->dataSource->TableName; $temp_ids = $this->Conn->GetCol($sql); return array_diff($live_ids, $temp_ids); } /** * Check field value by user-defined alghoritm * * @param string $field field name * @param Array $params field options from config * @return bool */ protected function CustomValidation($field, $params) { return true; } /** * Set's field error, if pseudo passed not found then create it with message text supplied. * Don't overwrite existing pseudo translation. * * @param string $field * @param string $pseudo * @param string $error_label * @param Array $error_params * * @return bool * @access public */ public function SetError($field, $pseudo, $error_label = null, $error_params = null) { $error_field = $this->dataSource->GetFieldOption($field, 'error_field', false, $field); if ( $this->GetErrorPseudo($error_field) ) { // don't set more then one error on field return false; } $this->FieldErrors[$error_field]['pseudo'] = $pseudo; if ( isset($error_params) ) { if ( array_key_exists('value', $error_params) ) { $this->FieldErrors[$error_field]['value'] = $error_params['value']; unset($error_params['value']); } // additional params, that helps to determine error sources $this->FieldErrors[$error_field]['params'] = $error_params; } if ( isset($error_label) && !isset($this->ErrorMsgs[$pseudo]) ) { // label for error (only when not already set) $this->ErrorMsgs[$pseudo] = (substr($error_label, 0, 1) == '+') ? substr($error_label, 1) : '!'.$error_label.'!'; } return true; } /** * Return error message for field * * @param string $field * @param bool $force_escape * @return string * @access public */ public function GetErrorMsg($field, $force_escape = null) { $error_pseudo = $this->GetErrorPseudo($field); if ( !$error_pseudo ) { return ''; } // if special error msg defined in config $error_msgs = $this->dataSource->GetFieldOption($field, 'error_msgs', false, Array ()); if ( isset($error_msgs[$error_pseudo]) ) { $msg = $error_msgs[$error_pseudo]; } else { // fallback to defaults if ( !isset($this->ErrorMsgs[$error_pseudo]) ) { trigger_error('No user message is defined for pseudo error ' . $error_pseudo . '', E_USER_WARNING); return $error_pseudo; //return the pseudo itself } $msg = $this->ErrorMsgs[$error_pseudo]; } $msg = $this->Application->ReplaceLanguageTags($msg, $force_escape); if ( isset($this->FieldErrors[$field]['params']) ) { $params = $this->FieldErrors[$field]['params']; } else { $params = array(); } if ( $params && preg_match('/%[^\s]/', $msg) ) { $msg = vsprintf($msg, array_values($params)); } else { $field_phrase = $this->Application->isAdmin ? 'la_fld_' . $field : 'lu_fld_' . $field; $params['field'] = $this->Application->Phrase($field_phrase); foreach ( $params as $param_name => $param_value ) { $msg = str_replace('{' . $param_name . '}', $param_value, $msg); } } return $msg; } /** * Returns error pseudo * * @param string $field * @return string * @access public * */ public function GetErrorPseudo($field) { if ( !isset($this->FieldErrors[$field]) ) { return ''; } return isset($this->FieldErrors[$field]['pseudo']) ? $this->FieldErrors[$field]['pseudo'] : ''; } /** * Removes error on field * * @param string $field * @access public */ public function RemoveError($field) { unset( $this->FieldErrors[$field] ); } /** * Returns field errors * * @return Array * @access public */ public function GetFieldErrors() { return $this->FieldErrors; } /** * Check if item has errors * * @param Array $skip_fields fields to skip during error checking * @return bool * @access public */ public function HasErrors( $skip_fields = Array () ) { $fields = array_keys( $this->dataSource->getFields() ); $fields = array_diff($fields, $skip_fields); foreach ($fields as $field) { // if Formatter has set some error messages during values parsing if ( $this->GetErrorPseudo($field) ) { return true; } } return false; } /** * Clears all validation errors * * @access public */ public function reset() { $this->FieldErrors = Array(); } }