Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F727103
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Mon, Jan 6, 6:50 AM
Size
16 KB
Mime Type
text/x-diff
Expires
Wed, Jan 8, 6:50 AM (2 d, 3 h ago)
Engine
blob
Format
Raw Data
Handle
537165
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.2.x/core/kernel/utility/validator.php
===================================================================
--- branches/5.2.x/core/kernel/utility/validator.php (revision 16755)
+++ branches/5.2.x/core/kernel/utility/validator.php (revision 16756)
@@ -1,542 +1,604 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kValidator extends kBase {
/**
* Form name, used for validation
*
* @var string
*/
protected $lastFormName = false;
/**
* Prefix & special of last data source
*
* @var string
*/
protected $lastPrefixSpecial = '';
/**
* Object, that holds a data to be validated
*
* @var kDBItem
*/
protected $dataSource = null;
/**
* Holds field errors
*
* @var Array
* @access protected
*/
protected $FieldErrors = Array ();
/**
* Phrases for each error pseudo
*
* @var Array
* @access protected
*/
protected $ErrorMsgs = Array (
'required' => '!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 . ': <strong>' . $this->dataSource->GetID() . '</strong>',
);
if ( $title_info && reset($title_info) ) {
$item_info[] = key($title_info) . ': <strong>' . current($title_info) . '</strong>';
}
$item_info[] = 'Temp Mode: <strong>' . ($this->dataSource->IsTempTable() ? 'Yes' : 'No') . '</strong>';
$raw_errors = array_filter($this->FieldErrors, function ($error_params) {
return isset($error_params['pseudo']);
});
$error_msg = <<<HTML
Validation failed for <strong>%s</strong> item (%s).<br/>
<strong>Validation errors:</strong><br/>
<pre>%s</pre>
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 <strong>' . $params['type'] . '</strong> (in ValidateType method), please use <strong>float</strong> 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) );
- $current_table_only = getArrayValue($params, 'current_table_only'); // check unique record only in current table
- $res_live = $current_table_only ? 0 : $this->Conn->GetOne( str_replace('%s', $this->Application->GetLiveName($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 <strong>' . $error_pseudo . '</strong>', 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();
}
}
Event Timeline
Log In to Comment