ErrorMsgs['required'] = '!la_err_required!'; //'Field is required'; $this->ErrorMsgs['unique'] = '!la_err_unique!'; //'Field value must be unique'; $this->ErrorMsgs['value_out_of_range'] = '!la_err_value_out_of_range!'; //'Field is out of range, possible values from %s to %s'; $this->ErrorMsgs['length_out_of_range'] = '!la_err_length_out_of_range!'; //'Field is out of range'; $this->ErrorMsgs['bad_type'] = '!la_err_bad_type!'; //'Incorrect data format, please use %s'; $this->ErrorMsgs['invalid_format'] = '!la_err_invalid_format!'; //'Incorrect data format, please use %s'; $this->ErrorMsgs['bad_date_format'] = '!la_err_bad_date_format!'; //'Incorrect date format, please use (%s) ex. (%s)'; $this->ErrorMsgs['primary_lang_required'] = '!la_err_primary_lang_required!'; } function SetDirtyField($field_name, $field_value) { $this->DirtyFieldValues[$field_name] = $field_value; } function GetDirtyField($field_name) { return $this->DirtyFieldValues[$field_name]; } function GetOriginalField($field_name, $formatted = false, $format=null) { if (array_key_exists($field_name, $this->OriginalFieldValues)) { // item was loaded before $value = $this->OriginalFieldValues[$field_name]; } else { // no original fields -> use default field value $value = $this->Fields[$field_name]['default']; } if (!$formatted) { return $value; } $options = $this->GetFieldOptions($field_name); $res = $value; if (array_key_exists('formatter', $options)) { $formatter =& $this->Application->recallObject($options['formatter']); /* @var $formatter kFormatter */ $res = $formatter->Format($value, $field_name, $this, $format); } return $res; } /** * Sets original field value (useful for custom virtual fields) * * @param string $field_name */ function SetOriginalField($field_name, $field_value) { $this->OriginalFieldValues[$field_name] = $field_value; } /** * Set's default values for all fields * * @param bool $populate_ml_fields create all ml fields from db in config or not * * @access public */ function SetDefaultValues($populate_ml_fields = false) { parent::SetDefaultValues($populate_ml_fields); if ($populate_ml_fields) { $this->PopulateMultiLangFields(); } foreach ($this->Fields as $field => $params) { if ( isset($params['default']) ) { $this->SetDBField($field, $params['default']); } else { $this->SetDBField($field, NULL); } } } /** * Sets current item field value * (applies formatting) * * @access public * @param string $name Name of the field * @param mixed $value Value to set the field to * @return void */ function SetField($name,$value) { $options = $this->GetFieldOptions($name); $parsed = $value; if ($value == '') { $parsed = NULL; } // kFormatter is always used, to make sure, that numeric value is converted to normal representation // according to regional format, even when formatter is not set (try seting format to 1.234,56 to understand why) $formatter =& $this->Application->recallObject(isset($options['formatter']) ? $options['formatter'] : 'kFormatter'); $parsed = $formatter->Parse($value, $name, $this); $this->SetDBField($name,$parsed); } /** * Sets current item field value * (doesn't apply formatting) * * @access public * @param string $name Name of the field * @param mixed $value Value to set the field to * @return void */ function SetDBField($name,$value) { $this->FieldValues[$name] = $value; /*if (isset($this->Fields[$name]['formatter'])) { $formatter =& $this->Application->recallObject($this->Fields[$name]['formatter']); $formatter->UpdateSubFields($name, $value, $this->Fields[$name], $this); }*/ } /** * Set's field error, if pseudo passed not found then create it with message text supplied. * Don't owerrite existing pseudo translation. * * @param string $field * @param string $pseudo * @param string $error_label * * @return bool */ function SetError($field, $pseudo, $error_label = null, $error_params = null) { $error_field = isset($this->Fields[$field]['error_field']) ? $this->Fields[$field]['error_field'] : $field; if (isset($this->FieldErrors[$error_field]['pseudo'])) { // 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 current item' field value by field name * (doesn't apply formatter) * * @access public * @param string $name field name to return * @return mixed */ function GetDBField($name) { /*if (!array_key_exists($name, $this->FieldValues) && defined('DEBUG_MODE') && DEBUG_MODE) { $this->Application->Debugger->appendTrace(); }*/ return $this->FieldValues[$name]; } function HasField($name) { return isset($this->FieldValues[$name]); } function GetFieldValues() { return $this->FieldValues; } /** * Sets item' fields corresponding to elements in passed $hash values. * * The function sets current item fields to values passed in $hash, by matching $hash keys with field names * of current item. If current item' fields are unknown {@link kDBItem::PrepareFields()} is called before acutally setting the fields * * @access public * @param Array $hash * @param Array $set_fields Optional param, field names in target object to set, other fields will be skipped * @return void */ function SetFieldsFromHash($hash, $set_fields = null) { // used in formatter which work with multiple fields together foreach($hash as $field_name => $field_value) { if (is_numeric($field_name) || !array_key_exists($field_name, $this->Fields)) { continue; } if (is_array($set_fields) && !in_array($field_name, $set_fields)) { continue; } $this->SetDirtyField($field_name, $field_value); } // formats all fields using associated formatters foreach ($hash as $field_name => $field_value) { if (is_numeric($field_name) || !array_key_exists($field_name, $this->Fields)) { continue; } if (is_array($set_fields) && !in_array($field_name, $set_fields)) { continue; } $this->SetField($field_name,$field_value); } } function SetDBFieldsFromHash($hash, $set_fields = null) { foreach ($hash as $field_name => $field_value) { if (is_numeric($field_name) || !array_key_exists($field_name, $this->Fields)) { continue; } if (is_array($set_fields) && !in_array($field_name, $set_fields)) { continue; } $this->SetDBField($field_name, $field_value); } } /** * Returns part of SQL WHERE clause identifing the record, ex. id = 25 * * @access public * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method * @param Array $keys_hash alternative, then item id, keys hash to load item by * @return void * @see kDBItem::Load() * @see kDBItem::Update() * @see kDBItem::Delete() */ function GetKeyClause($method=null, $keys_hash = null) { if (!isset($keys_hash)) { $keys_hash = Array ($this->IDField => $this->ID); } $ret = ''; foreach ($keys_hash as $field => $value) { if (!preg_match('/\./', $field)) { $ret .= '(`' . $this->TableName . '`.' . $field . ' = ' . $this->Conn->qstr($value) . ') AND '; } else { $ret .= '(' . $field . ' = ' . $this->Conn->qstr($value) . ') AND '; } } return substr($ret, 0, -5); } /** * Loads item from the database by given id * * @access public * @param mixed $id item id of keys->values hash to load item by * @param string $id_field_name Optional parameter to load item by given Id field * @param bool $cachable cache this query result based on it's prefix serial * @return bool True if item has been loaded, false otherwise */ function Load($id, $id_field_name = null, $cachable = false) { if ( isset($id_field_name) ) { $this->SetIDField($id_field_name); // set new IDField } $keys_sql = ''; if (is_array($id)) { $keys_sql = $this->GetKeyClause('load', $id); } else { $this->setID($id); $keys_sql = $this->GetKeyClause('load'); } if ( isset($id_field_name) ) { // restore original IDField from unit config $this->setIDField( $this->Application->getUnitOption($this->Prefix, 'IDField') ); } if (($id === false) || !$keys_sql) { return $this->Clear(); } if (!$this->raiseEvent('OnBeforeItemLoad', $id)) { return false; } $q = $this->GetSelectSQL() . ' WHERE ' . $keys_sql; if ($cachable && $this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $serial_name = $this->Application->incrementCacheSerial($this->Prefix == 'st' ? 'c' : $this->Prefix, isset($id_field_name) ? null : $id, false); $cache_key = 'kDBItem::Load_' . crc32(serialize($id) . '-' . $this->IDField) . '[%' . $serial_name . '%]'; $field_values = $this->Application->getCache($cache_key, false); if ($field_values === false) { $field_values = $this->Conn->GetRow($q); if ($field_values !== false) { // only cache, when data was retrieved $this->Application->setCache($cache_key, $field_values); } } } else { $field_values = $this->Conn->GetRow($q); } if ($field_values) { $this->FieldValues = array_merge_recursive2($this->FieldValues, $field_values); $this->OriginalFieldValues = $this->FieldValues; } else { return $this->Clear(); } if (is_array($id) || isset($id_field_name)) { $this->setID($this->FieldValues[$this->IDField]); } $this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example) $this->raiseEvent('OnAfterItemLoad', $this->GetID()); $this->Loaded = true; return true; } /** * Loads object from hash (not db) * * @param Array $fields_hash * @param string $id_field */ function LoadFromHash($fields_hash, $id_field = null) { if (!isset($id_field)) { $id_field = $this->IDField; } $this->Clear(); if (!$fields_hash || !array_key_exists($id_field, $fields_hash)) { // no data OR id field missing return false; } $id = $fields_hash[$id_field]; if ( !$this->raiseEvent('OnBeforeItemLoad', $id) ) { return false; } $this->FieldValues = array_merge_recursive2($this->FieldValues, $fields_hash); $this->OriginalFieldValues = $this->FieldValues; $this->setID($id); $this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example) $this->raiseEvent('OnAfterItemLoad', $id); $this->Loaded = true; return true; } /** * Builds select sql, SELECT ... FROM parts only * * @access public * @return string */ function GetSelectSQL() { $sql = $this->addCalculatedFields($this->SelectClause); return parent::GetSelectSQL($sql); } function UpdateFormattersMasterFields() { foreach ($this->Fields as $field => $options) { if (isset($options['formatter'])) { $formatter =& $this->Application->recallObject($options['formatter']); $formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this); } } } /** * Allows to skip certain fields from getting into sql queries * * @param string $field_name * @param mixed $force_id * @return bool */ function skipField($field_name, $force_id = false) { $skip = false; // 1. skipping 'virtual' field $skip = $skip || array_key_exists($field_name, $this->VirtualFields); // 2. don't write empty field value to db, when "skip_empty" option is set $field_value = array_key_exists($field_name, $this->FieldValues) ? $this->FieldValues[$field_name] : false; if (array_key_exists($field_name, $this->Fields)) { $skip_empty = array_key_exists('skip_empty', $this->Fields[$field_name]) ? $this->Fields[$field_name]['skip_empty'] : false; } else { // field found in database, but not declared in unit config $skip_empty = false; } $skip = $skip || (!$field_value && $skip_empty); // 3. skipping field not in Fields (nor virtual, nor real) $skip = $skip || !array_key_exists($field_name, $this->Fields); return $skip; } /** * Updates previously loaded record with current item' values * * @access public * @param int Primery Key Id to update * @return bool */ function Update($id = null, $system_update = false) { if (isset($id)) { $this->setID($id); } if (!$this->raiseEvent('OnBeforeItemUpdate')) { return false; } if (!isset($this->ID)) { // ID could be set inside OnBeforeItemUpdate event, so don't combine this check with previous one return false; } // validate before updating if (!$this->Validate()) { return false; } if (!$this->FieldValues) { // nothing to update return true; } $sql = ''; foreach ($this->FieldValues as $field_name => $field_value) { if ($this->skipField($field_name)) { continue; } if ( is_null($field_value) ) { if (array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null']) { // "kFormatter::Parse" methods converts empty values to NULL and for // not-null fields they are replaced with default value here $field_value = $this->Fields[$field_name]['default']; } } $sql .= '`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ', '; } $sql = 'UPDATE ' . $this->TableName . ' SET ' . substr($sql, 0, -2) . ' WHERE ' . $this->GetKeyClause('update'); if ($this->Conn->ChangeQuery($sql) === false) { // there was and sql error return false; } $affected_rows = $this->Conn->getAffectedRows(); if (!$system_update && ($affected_rows > 0)) { $this->setModifiedFlag(clUPDATE); } $this->saveCustomFields(); $this->raiseEvent('OnAfterItemUpdate'); $this->OriginalFieldValues = $this->FieldValues; $this->Loaded = true; if ($this->mode != 't') { $this->Application->resetCounters($this->TableName); } return true; } function ValidateField($field) { $options = $this->Fields[$field]; /*if (isset($options['formatter'])) { $formatter =& $this->Application->recallObject($options['formatter']); $formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this); }*/ $error_field = isset($options['error_field']) ? $options['error_field'] : $field; $res = !isset($this->FieldErrors[$error_field]['pseudo']) || !$this->FieldErrors[$error_field]['pseudo']; $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; } /** * Validate all item fields based on * constraints set in each field options * in config * * @return bool * @access private */ function Validate() { $this->UpdateFormattersMasterFields(); //order is critical - should be called BEFORE checking errors if ($this->IgnoreValidation) { return true; } // will apply any custom validation to the item $this->raiseEvent('OnBeforeItemValidate'); $global_res = true; foreach ($this->Fields as $field => $params) { $res = $this->ValidateField($field); $global_res = $global_res && $res; } if (!$global_res && $this->Application->isDebugMode()) { $error_msg = ' Validation failed in prefix '.$this->Prefix.', FieldErrors follow (look at items with "pseudo" key set)
You may ignore this notice if submitted data really has a validation error'; trigger_error(trim($error_msg), E_USER_NOTICE); $this->Application->Debugger->dumpVars($this->FieldErrors); } if ($global_res) { // no validation errors $this->raiseEvent('OnAfterItemValidate'); } return $global_res; } /** * Check field value by user-defined alghoritm * * @param string $field field name * @param Array $params field options from config * @return bool */ function CustomValidation($field, $params) { return true; } /** * Check if item has errors * * @param Array $skip_fields fields to skip during error checking * @return bool */ function HasErrors($skip_fields) { $global_res = false; foreach ($this->Fields as $field => $field_params) { // If Formatter has set some error messages during values parsing if ( !( in_array($field, $skip_fields) ) && isset($this->FieldErrors[$field]['pseudo']) && $this->FieldErrors[$field] != '') { $global_res = true; } } return $global_res; } /** * 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 */ function ValidateType($field, $params) { $res = true; $val = $this->FieldValues[$field]; if ( $val != '' && isset($params['type']) && preg_match("#int|integer|double|float|real|numeric|string#", $params['type']) ) { 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->FieldValues[$field]); } if (!$res) { $this->SetError($field, 'bad_type', null, Array ($params['type'])); } } 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 private */ function ValidateRequired($field, $params) { $res = true; if (isset($params['required']) && $params['required']) { $check_value = $this->FieldValues[$field]; if ($this->Application->ConfigValue('TrimRequiredFields')) { $check_value = trim($check_value); } $res = ((string)$check_value != ''); } if (!$res) { $this->SetError($field, 'required'); } return $res; } /** * 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 */ function ValidateUnique($field, $params) { $res = true; $unique_fields = getArrayValue($params,'unique'); if($unique_fields !== false) { $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 if ((string)$this->GetDBField($unique_field) != '' || (isset($this->Fields[$unique_field]['required']) && $this->Fields[$unique_field]['required'])) { $where[] = '`'.$unique_field.'` = '.$this->Conn->qstr( $this->GetDBField($unique_field) ); } 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->IDField.' <> '.(int)$this->ID.')'; $res_temp = $this->Conn->GetOne( str_replace('%s', $this->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->TableName), $sql) ); $res = ($res_temp == 0) && ($res_live == 0); if (!$res) { $this->SetError($field, 'unique'); } } return $res; } /** * 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 */ function ValidateRange($field, $params) { $res = true; $val = $this->FieldValues[$field]; if ( isset($params['type']) && preg_match("#int|integer|double|float|real#", $params['type']) && strlen($val) > 0 ) { 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_val, $max_val)); return $res; } 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 (getArrayValue($params, 'min_len'), getArrayValue($params, 'max_len'), mb_strlen($val)); $this->SetError($field, 'length_out_of_range', null, $error_params); return $res; } return $res; } /** * Return error message for field * * @param string $field * @return string * @access public */ function GetErrorMsg($field, $force_escape = null) { if( !isset($this->FieldErrors[$field]) ) return ''; $err = getArrayValue($this->FieldErrors[$field], 'pseudo'); if (!$err) return ''; // if special error msg defined in config if( isset($this->Fields[$field]['error_msgs'][$err]) ) { $msg = $this->Fields[$field]['error_msgs'][$err]; } else //fall back to defaults { if( !isset($this->ErrorMsgs[$err]) ) { trigger_error('No user message is defined for pseudo error '.$err.'
', E_USER_WARNING); return $err; //return the pseudo itself } $msg = $this->ErrorMsgs[$err]; } $msg = $this->Application->ReplaceLanguageTags($msg, $force_escape); if ( isset($this->FieldErrors[$field]['params']) ) { return vsprintf($msg, $this->FieldErrors[$field]['params']); } return $msg; } /** * Creates a record in the database table with current item' values * * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE! * @access public * @return bool */ function Create($force_id = false, $system_create = false) { if (!$this->raiseEvent('OnBeforeItemCreate')) { return false; } // Validating fields before attempting to create record if (!$this->Validate()) { return false; } if (is_int($force_id)) { $this->FieldValues[$this->IDField] = $force_id; } elseif (!$force_id || !is_bool($force_id)) { $this->FieldValues[$this->IDField] = $this->generateID(); } $fields_sql = ''; $values_sql = ''; foreach ($this->FieldValues as $field_name => $field_value) { if ($this->skipField($field_name, $force_id)) { continue; } if (is_null($field_value)) { if (array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null']) { // "kFormatter::Parse" methods converts empty values to NULL and for // not-null fields they are replaced with default value here $values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default']); } else { $values_sql .= $this->Conn->qstr($field_value); } } else { if (($field_name == $this->IDField) && ($field_value == 0) && !is_int($force_id)) { // don't skip IDField in INSERT statement, just use DEFAULT keyword as it's value $values_sql .= 'DEFAULT'; } else { $values_sql .= $this->Conn->qstr($field_value); } } $fields_sql .= '`' . $field_name . '`, '; //Adding field name to fields block of Insert statement $values_sql .= ', '; } $sql = 'INSERT INTO ' . $this->TableName . ' (' . substr($fields_sql, 0, -2) . ') VALUES (' . substr($values_sql, 0, -2) . ')'; //Executing the query and checking the result if ($this->Conn->ChangeQuery($sql) === false) { return false; } $insert_id = $this->Conn->getInsertID(); if ($insert_id == 0) { // insert into temp table (id is not auto-increment field) $insert_id = $this->FieldValues[$this->IDField]; } $this->setID($insert_id); $this->OriginalFieldValues = $this->FieldValues; if (!$system_create){ $this->setModifiedFlag(clCREATE); } $this->saveCustomFields(); if ($this->mode != 't') { $this->Application->resetCounters($this->TableName); } if ($this->IsTempTable() && ($this->Application->GetTopmostPrefix($this->Prefix) != $this->Prefix) && !is_int($force_id)) { // temp table + subitem = set negative id $this->setTempID(); } $this->raiseEvent('OnAfterItemCreate'); $this->Loaded = true; return true; } /** * Deletes the record from databse * * @access public * @return bool */ function Delete($id = null) { if (isset($id)) { $this->setID($id); } if (!$this->raiseEvent('OnBeforeItemDelete')) { return false; } $sql = 'DELETE FROM ' . $this->TableName . ' WHERE ' . $this->GetKeyClause('Delete'); $ret = $this->Conn->ChangeQuery($sql); $affected_rows = $this->Conn->getAffectedRows(); if ($affected_rows > 0) { $this->setModifiedFlag(clDELETE); // will change affected rows, so get it before this line // something was actually deleted $this->raiseEvent('OnAfterItemDelete'); } if ($this->mode != 't') { $this->Application->resetCounters($this->TableName); } return $ret; } function PopulateMultiLangFields() { foreach ($this->Fields as $field => $options) { // master field is set only for CURRENT language $formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false; if (($formatter == 'kMultiLanguage') && array_key_exists('master_field', $options) && array_key_exists('error_field', $options)) { // MuliLanguage formatter sets error_field to master_field, but in PopulateMlFields mode, // we display ML fields directly so we set it back to itself, otherwise error will not be displayed unset($this->Fields[$field]['error_field']); } } } /** * Sets new name for item in case if it is beeing copied * in same table * * @param array $master Table data from TempHandler * @param int $foreign_key ForeignKey value to filter name check query by * @param string $title_field FieldName to alter, by default - TitleField of the prefix * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name * @access private */ function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s') { if (!isset($title_field)) { $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return; } $new_name = $this->GetDBField($title_field); $original_checked = false; do { if ( preg_match('/'.sprintf($format, '([0-9]*) *', '(.*)').'/', $new_name, $regs) ) { $new_name = sprintf($format, ($regs[1]+1), $regs[2]); } elseif ($original_checked) { $new_name = sprintf($format, '', $new_name); } // if we are cloning in temp table this will look for names in temp table, // since object' TableName contains correct TableName (for temp also!) // if we are cloning live - look in live $query = 'SELECT '.$title_field.' FROM '.$this->TableName.' WHERE '.$title_field.' = '.$this->Conn->qstr($new_name); $foreign_key_field = getArrayValue($master, 'ForeignKey'); $foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field; if ($foreign_key_field && isset($foreign_key)) { $query .= ' AND '.$foreign_key_field.' = '.$foreign_key; } $res = $this->Conn->GetOne($query); /*// if not found in live table, check in temp table if applicable if ($res === false && $object->Special == 'temp') { $query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).' WHERE '.$name_field.' = '.$this->Conn->qstr($new_name); $res = $this->Conn->GetOne($query); }*/ $original_checked = true; } while ($res !== false); $this->SetDBField($title_field, $new_name); } function raiseEvent($name, $id = null, $additional_params = Array()) { if( !isset($id) ) $id = $this->GetID(); $event = new kEvent( Array('name'=>$name,'prefix'=>$this->Prefix,'special'=>$this->Special) ); $event->setEventParam('id', $id); if ($additional_params) { foreach ($additional_params as $ap_name => $ap_value) { $event->setEventParam($ap_name, $ap_value); } } $this->Application->HandleEvent($event); return $event->status == erSUCCESS ? true : false; } /** * Set's new ID for item * * @param int $new_id * @access public */ function setID($new_id) { $this->ID = $new_id; $this->SetDBField($this->IDField, $new_id); } /** * Generate and set new temporary id * * @access private */ function setTempID() { $new_id = (int)$this->Conn->GetOne('SELECT MIN('.$this->IDField.') FROM '.$this->TableName); if($new_id > 0) $new_id = 0; --$new_id; $this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID()); if ($this->ShouldLogChanges(true)) { // Updating TempId in ChangesLog, if changes are disabled $ses_var_name = $this->Application->GetTopmostPrefix($this->Prefix) . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $changes = $this->Application->RecallVar($ses_var_name); $changes = $changes ? unserialize($changes) : Array (); if ($changes) { foreach ($changes as $key => $rec) { if ($rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID()) { // change log for record, that's ID was just updated -> update in change log record too $changes[$key]['ItemId'] = $new_id; } if ($rec['MasterPrefix'] == $this->Prefix && $rec['MasterId'] == $this->GetID()) { // master item id was changed $changes[$key]['MasterId'] = $new_id; } if (in_array($this->Prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->Prefix] == $this->GetID()) { // change log record of given item's sub item -> update changed id's in dependent fields $changes[$key]['ParentId'][$this->Prefix] = $new_id; if (array_key_exists('DependentFields', $rec)) { // these are fields from table of $rec['Prefix'] table! // when one of dependent fields goes into idfield of it's parent item, that was changed $parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey'); $parent_table_key = is_array($parent_table_key) ? $parent_table_key[$this->Prefix] : $parent_table_key; if ($parent_table_key == $this->IDField) { $foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey'); $foreign_key = is_array($foreign_key) ? $foreign_key[$this->Prefix] : $foreign_key; $changes[$key]['DependentFields'][$foreign_key] = $new_id; } } } } } $this->Application->StoreVar($ses_var_name, serialize($changes)); } $this->SetID($new_id); } /** * Set's modification flag for main prefix of current prefix to true * * @access private * @author Alexey */ function setModifiedFlag($mode = null) { $main_prefix = $this->Application->GetTopmostPrefix($this->Prefix); $this->Application->StoreVar($main_prefix . '_modified', '1', true); // true for optional if ($this->ShouldLogChanges(true)) { $this->LogChanges($main_prefix, $mode); if (!$this->IsTempTable()) { $handler =& $this->Application->recallObject($this->Prefix . '_EventHandler'); /* @var $handler kDBEventHandler */ $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $handler->SaveLoggedChanges($ses_var_name, $this->ShouldLogChanges()); } } } /** * Determines, that changes made to this item should be written to change log * * @param bool $log_changes * @return bool */ function ShouldLogChanges($log_changes = null) { if (!isset($log_changes)) { // specific logging mode no forced -> use global logging settings $log_changes = $this->Application->getUnitOption($this->Prefix, 'LogChanges') || $this->Application->ConfigValue('UseChangeLog'); } return $log_changes && !$this->Application->getUnitOption($this->Prefix, 'ForceDontLogChanges'); } function LogChanges($main_prefix, $mode) { if (!$mode) { return ; } $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $changes = $this->Application->RecallVar($ses_var_name); $changes = $changes ? unserialize($changes) : Array (); $fields_hash = Array ( 'Prefix' => $this->Prefix, 'ItemId' => $this->GetID(), 'OccuredOn' => adodb_mktime(), 'MasterPrefix' => $main_prefix, 'Action' => $mode, ); if ($this->Prefix == $main_prefix) { // main item $fields_hash['MasterId'] = $this->GetID(); $fields_hash['ParentPrefix'] = Array ($main_prefix); $fields_hash['ParentId'] = Array ($main_prefix => $this->GetID()); } else { // sub item // collect foreign key values (for serial reset) $foreign_keys = $this->Application->getUnitOption($this->Prefix, 'ForeignKey'); $dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = Array (); if (is_array($foreign_keys)) { foreach ($foreign_keys as $prefix => $field_name) { $dependent_fields[$field_name] = $this->GetDBField($field_name); $fields_hash['ParentPrefix'][] = $prefix; $fields_hash['ParentId'][$prefix] = $this->getParentId($prefix); } } else { $dependent_fields[$foreign_keys] = $this->GetDBField($foreign_keys); $fields_hash['ParentPrefix'] = Array ( $this->Application->getUnitOption($this->Prefix, 'ParentPrefix') ); $fields_hash['ParentId'][ $fields_hash['ParentPrefix'][0] ] = $this->getParentId('auto'); } $fields_hash['DependentFields'] = $dependent_fields; // works only, when main item is present in url, when subitem is changed $master_id = $this->Application->GetVar($main_prefix . '_id'); if ($master_id === false) { // works in case of we are not editing topmost item, when subitem is created/updated/deleted $master_id = $this->getParentId('auto', true); } $fields_hash['MasterId'] = $master_id; } switch ($mode) { case clUPDATE: $to_save = array_merge($this->GetTitleField(), $this->GetChangedFields()); break; case clCREATE: $to_save = $this->GetTitleField(); break; case clDELETE: $to_save = array_merge($this->GetTitleField(), $this->GetRealFields()); break; } $fields_hash['Changes'] = serialize($to_save); $changes[] = $fields_hash; $this->Application->StoreVar($ses_var_name, serialize($changes)); } /** * Returns current item parent's ID * * @param bool $top_most return topmost parent, when used * @return int */ function getParentId($parent_prefix, $top_most = false) { $current_id = $this->GetID(); $current_prefix = $this->Prefix; if ($parent_prefix == 'auto') { $parent_prefix = $this->Application->getUnitOption($current_prefix, 'ParentPrefix'); } if (!$parent_prefix) { return $current_id; } do { // field in this table $foreign_key = $this->Application->getUnitOption($current_prefix, 'ForeignKey'); $foreign_key = is_array($foreign_key) ? $foreign_key[$parent_prefix] : $foreign_key; // get foreign key value for $current_prefix if ($current_prefix == $this->Prefix) { $foreign_key_value = $this->GetDBField($foreign_key); } else { $id_field = $this->Application->getUnitOption($current_prefix, 'IDField'); $table_name = $this->Application->getUnitOption($current_prefix, 'TableName'); if ($this->IsTempTable()) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $current_prefix); } $sql = 'SELECT ' . $foreign_key . ' FROM ' . $table_name . ' WHERE ' . $id_field . ' = ' . $current_id; $foreign_key_value = $this->Conn->GetOne($sql); } // field in parent table $parent_table_key = $this->Application->getUnitOption($current_prefix, 'ParentTableKey'); $parent_table_key = is_array($parent_table_key) ? $parent_table_key[$parent_prefix] : $parent_table_key; $parent_id_field = $this->Application->getUnitOption($parent_prefix, 'IDField'); $parent_table_name = $this->Application->getUnitOption($parent_prefix, 'TableName'); if ($this->IsTempTable()) { $parent_table_name = $this->Application->GetTempName($parent_table_name, 'prefix:' . $current_prefix); } if ($parent_id_field == $parent_table_key) { // sub-item is related by parent item idfield $current_id = $foreign_key_value; } else { // sub-item is related by other parent item field $sql = 'SELECT ' . $parent_id_field . ' FROM ' . $parent_table_name . ' WHERE ' . $parent_table_key . ' = ' . $foreign_key_value; $current_id = $this->Conn->GetOne($sql); } $current_prefix = $parent_prefix; if (!$top_most) { break; } } while ( $parent_prefix = $this->Application->getUnitOption($current_prefix, 'ParentPrefix') ); return $current_id; } /** * Returns title field (if any) * * @return Array */ function GetTitleField() { $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); if ($title_field) { $value = $this->GetField($title_field); return $value ? Array ($title_field => $value) : Array (); } return Array (); } /** * Returns only fields, that are present in database (no virtual and no calculated fields) * * @return Array */ function GetRealFields() { if (function_exists('array_diff_key')) { $db_fields = array_diff_key($this->FieldValues, $this->VirtualFields, $this->CalculatedFields); } else { $db_fields = Array(); foreach ($this->FieldValues as $key => $value) { if (array_key_exists($key, $this->VirtualFields) || array_key_exists($key, $this->CalculatedFields)) { continue; } $db_fields[$key] = $value; } } return $db_fields; } /** * Returns only changed database field * * @param bool $include_virtual_fields * @return Array */ function GetChangedFields($include_virtual_fields = false) { $changes = Array (); $fields = $include_virtual_fields ? $this->FieldValues : $this->GetRealFields(); $diff = array_diff_assoc($fields, $this->OriginalFieldValues); foreach ($diff as $field => $new_value) { $old_value = $this->GetOriginalField($field, true); $new_value = $this->GetField($field); if ($old_value != $new_value) { // "0.00" and "0.0000" are stored as strings and will differ. Double check to prevent that. $changes[$field] = Array ('old' => $old_value, 'new' => $new_value); } } return $changes; } /** * Returns ID of currently processed record * * @return int * @access public */ function GetID() { return $this->ID; } /** * Generates ID for new items before inserting into database * * @return int * @access private */ function generateID() { return 0; } /** * Returns true if item was loaded successfully by Load method * * @return bool */ function isLoaded() { return $this->Loaded; } /** * Checks if field is required * * @param string $field * @return bool */ function isRequired($field) { return getArrayValue( $this->Fields[$field], 'required' ); } /** * Sets new required flag to field * * @param string $field * @param bool $is_required */ function setRequired($field, $is_required = true) { $this->Fields[$field]['required'] = $is_required; } function Clear($new_id = null) { $this->Loaded = false; $this->FieldValues = Array(); $this->OriginalFieldValues = Array (); $this->SetDefaultValues(); // will wear off kDBItem::setID effect, so set it later $this->FieldErrors = Array(); $this->setID($new_id); return $this->Loaded; } function Query($force = false) { if ($this->Application->isDebugMode()) { $this->Application->Debugger->appendTrace(); } trigger_error('Query method is called in class '.get_class($this).' for prefix '.$this->getPrefixSpecial().'', E_USER_ERROR); } function saveCustomFields() { if (!$this->customFields || $this->inCloning) { return true; } $cdata_key = rtrim($this->Prefix . '-cdata.' . $this->Special, '.'); $cdata =& $this->Application->recallObject($cdata_key, null, Array('skip_autoload' => true)); /* @var $cdata kDBItem */ $resource_id = $this->GetDBField('ResourceId'); $cdata->Load($resource_id, 'ResourceId'); $cdata->SetDBField('ResourceId', $resource_id); $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); /* @var $ml_formatter kMultiLanguage */ $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ foreach ($this->customFields as $custom_id => $custom_name) { $field_options = $cdata->GetFieldOptions('cust_' . $custom_id); $force_primary = isset($field_options['force_primary']) && $field_options['force_primary']; if ($force_primary) { $cdata->SetDBField($ml_formatter->LangFieldName('cust_' . $custom_id, true), $this->GetDBField('cust_' . $custom_name)); } else { for ($language_id = 1; $language_id <= $ml_helper->languageCount; $language_id++) { if (!$ml_helper->LanguageFound($language_id)) { continue; } $cdata->SetDBField('l' . $language_id . '_cust_' . $custom_id, $this->GetDBField('l' . $language_id . '_cust_' . $custom_name)); } } } return $cdata->isLoaded() ? $cdata->Update() : $cdata->Create(); } /** * Returns specified field value from all selected rows. * Don't affect current record index * * @param string $field * @param bool $formatted * @param string $format * @return Array */ function GetCol($field, $formatted = false, $format = null) { if ($formatted) { return Array (0 => $this->GetField($field, $format)); } return Array (0 => $this->GetDBField($field)); } }