Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Wed, Feb 12, 9:33 AM

in-portal

Index: branches/RC/core/kernel/db/dblist.php
===================================================================
--- branches/RC/core/kernel/db/dblist.php (revision 11926)
+++ branches/RC/core/kernel/db/dblist.php (revision 11927)
@@ -1,991 +1,992 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 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.net/license/ for copyright notices and details.
*/
/**
* DBList
*
* Desciption
* @package kernel4
*/
class kDBList extends kDBBase {
/**
* Holds totals for fields specified in config
*
* @var Array
*/
var $Totals = Array();
/**
* Description
*
* @var array
* @access public
*/
var $OrderFields;
/**
* Holds counted total number of records in the query - without pagination (system+user filters)
*
* @var int
* @access public
*/
var $RecordsCount;
/**
* Records count with system filters only applied
*
* @var int
* @access private
*/
var $NoFilterCount = 0;
/**
* Record count selected to be
* showed on current page
*
* @var int
*/
var $SelectedCount=0;
/**
* Array of records selected
*
* @var Array
* @access private
*/
var $Records;
var $CurrentIndex = 0;
/**
* List items per-page
*
* @var int
* @access public
*/
var $PerPage;
/**
* Pages count in list based on PerPage & RecordsCount attributes
*
* @var int
* @access public
*/
var $TotalPages;
/**
* Description
*
* @var int
* @access public
*/
var $Direction;
/**
* Holds current page number - used when forming LIMIT clause of SELECT statement
*
* @var int
* @access public
*/
var $Page;
/**
* Holds offset for LIMIT clause, calculated in {@link kDBList::PerPage()}
*
* @var int
* @access private
*/
var $Offset;
/**
* Count SQL was already issued on query
*
* @var bool
* @access private
*/
var $hasCounted = false;
/**
* Holds list WHERE filter object
*
* @var kMultipleFilter
* @access private
*/
var $WhereFilter = Array(FLT_SYSTEM => null, FLT_NORMAL => null, FLT_SEARCH => null, FLT_VIEW => null);
/**
* Holds list HAVING filter object
*
* @var kMultipleFilter
* @access private
*/
var $HavingFilter = Array(FLT_SYSTEM => null, FLT_NORMAL => null, FLT_SEARCH => null, FLT_VIEW => null);
var $AggregateFilter = Array(FLT_SYSTEM => null, FLT_NORMAL => null);
var $GroupByFields = Array();
var $Queried = false;
var $Counted = false;
var $TotalsCalculated = false;
/**
* Creates kDBList
*
* @return kDBList
*/
function kDBList()
{
parent::kDBBase();
$this->OrderFields = Array();
$filters = $this->getFilterStructure();
foreach ($filters as $filter_params) {
$filter =& $this->$filter_params['type'];
$filter[ $filter_params['class'] ] =& $this->Application->makeClass('kMultipleFilter', $filter_params['join_using']);
}
$this->PerPage = -1;
}
/**
* Returns information about all possible filter types
*
* @return Array
*/
function getFilterStructure()
{
$filters = Array (
Array ('type' => 'WhereFilter', 'class' => FLT_SYSTEM, 'join_using' => FLT_TYPE_AND),
Array ('type' => 'WhereFilter', 'class' => FLT_NORMAL, 'join_using' => FLT_TYPE_OR),
Array ('type' => 'WhereFilter', 'class' => FLT_SEARCH, 'join_using' => FLT_TYPE_OR),
Array ('type' => 'WhereFilter', 'class' => FLT_VIEW, 'join_using' => FLT_TYPE_AND),
Array ('type' => 'WhereFilter', 'class' => FLT_CUSTOM, 'join_using' => FLT_TYPE_AND),
Array ('type' => 'HavingFilter', 'class' => FLT_SYSTEM, 'join_using' => FLT_TYPE_AND),
Array ('type' => 'HavingFilter', 'class' => FLT_NORMAL, 'join_using' => FLT_TYPE_OR),
Array ('type' => 'HavingFilter', 'class' => FLT_SEARCH, 'join_using' => FLT_TYPE_OR),
Array ('type' => 'HavingFilter', 'class' => FLT_VIEW, 'join_using' => FLT_TYPE_AND),
Array ('type' => 'HavingFilter', 'class' => FLT_CUSTOM, 'join_using' => FLT_TYPE_AND),
Array ('type' => 'AggregateFilter', 'class' => FLT_SYSTEM, 'join_using' => FLT_TYPE_AND),
Array ('type' => 'AggregateFilter', 'class' => FLT_NORMAL, 'join_using' => FLT_TYPE_OR),
Array ('type' => 'AggregateFilter', 'class' => FLT_VIEW, 'join_using' => FLT_TYPE_AND),
);
return $filters;
}
/**
* Adds new or replaces old filter with same name
*
* @param string $name filter name (for internal use)
* @param string $clause where/having clause part (no OR/AND allowed)
* @param int $filter_type is filter having filter or where filter
* @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM
* @access public
*/
function addFilter($name, $clause, $filter_type = WHERE_FILTER, $filter_scope = FLT_SYSTEM)
{
$filter_source = Array( WHERE_FILTER => 'WhereFilter',
HAVING_FILTER => 'HavingFilter',
AGGREGATE_FILTER => 'AggregateFilter');
$filter_name = $filter_source[$filter_type];
$filter =& $this->$filter_name;
$filter =& $filter[$filter_scope];
$filter->addFilter($name,$clause);
}
/**
* Reads filter content
*
* @param string $name filter name (for internal use)
* @param int $filter_type is filter having filter or where filter
* @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM
* @access public
*/
function getFilter($name, $filter_type = WHERE_FILTER, $filter_scope = FLT_SYSTEM)
{
$filter_source = Array( WHERE_FILTER => 'WhereFilter',
HAVING_FILTER => 'HavingFilter',
AGGREGATE_FILTER => 'AggregateFilter');
$filter_name = $filter_source[$filter_type];
$filter =& $this->$filter_name;
$filter =& $filter[$filter_scope];
return $filter->getFilter($name);
}
/**
* Removes specified filter from filters list
*
* @param string $name filter name (for internal use)
* @param int $filter_type is filter having filter or where filter
* @param int $filter_scope filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM
* @access public
*/
function removeFilter($name, $filter_type = WHERE_FILTER, $filter_scope = FLT_SYSTEM)
{
$filter_source = Array( WHERE_FILTER => 'WhereFilter',
HAVING_FILTER => 'HavingFilter',
AGGREGATE_FILTER => 'AggregateFilter');
$filter_name = $filter_source[$filter_type];
$filter =& $this->$filter_name;
$filter =& $filter[$filter_scope];
$filter->removeFilter($name);
}
/**
* Clear list filters
*
*/
function clearFilters()
{
$filters = $this->getFilterStructure();
foreach ($filters as $filter_params) {
$filter =& $this->$filter_params['type'];
$filter[ $filter_params['class'] ]->clearFilters();
}
}
/**
* Counts the total number of records base on the query resulted from {@link kDBList::GetSelectSQL()}
*
* The method modifies the query to substitude SELECT part (fields listing) with COUNT(*).
* Special care should be applied when working with lists based on grouped queries, all aggregate function fields
* like SUM(), AVERAGE() etc. should be added to CountedSQL by using {@link kDBList::SetCountedSQL()}
*
* @access public
* @param string
* @return void
*/
function CountRecs()
{
$all_sql = $this->GetSelectSQL(true,false);
$sql = $this->getCountSQL($all_sql);
$this->Counted = true;
if( $this->GetGroupClause() )
{
$this->RecordsCount = count( $this->Conn->GetCol($sql) );
}
else
{
$this->RecordsCount = (int)$this->Conn->GetOne($sql);
}
$system_sql = $this->GetSelectSQL(true,true);
if($system_sql == $all_sql) //no need to query the same again
{
$this->NoFilterCount = $this->RecordsCount;
return;
}
$sql = $this->getCountSQL($system_sql);
if( $this->GetGroupClause() )
{
$this->NoFilterCount = count( $this->Conn->GetCol($sql) );
}
else
{
$this->NoFilterCount = (int)$this->Conn->GetOne($sql);
}
}
function GetNoFilterCount()
{
if (!$this->Counted) {
$this->CountRecs();
}
return $this->NoFilterCount;
}
function GetRecordsCount()
{
if (!$this->Counted) {
$this->CountRecs();
}
return $this->RecordsCount;
}
function getCountSQL($sql)
{
if ( preg_match("/DISTINCT(.*?)FROM(?!_)/is",$sql,$regs ) )
{
return preg_replace("/^\s*SELECT DISTINCT(.*?)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $sql);
}
else
{
return preg_replace("/^\s*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
}
}
/**
* Queries the database with SQL resulted from {@link kDBList::GetSelectSQL()} and stores result in {@link kDBList::SelectRS}
*
* All the sorting, pagination, filtration of the list should be set prior to calling Query().
*
* @access public
* @param string
* @return void
*/
function Query($force=false)
{
if (!$force && $this->Queried) return true;
$q = $this->GetSelectSQL();
//$rs = $this->Conn->SelectLimit($q, $this->PerPage, $this->Offset);
//in case we have not counted records try to select one more item to find out if we have something more than perpage
$limit = $this->Counted ? $this->PerPage : $this->PerPage+1;
$sql = $q.' '.$this->Conn->getLimitClause($this->Offset,$limit);
$this->Records = $this->Conn->Query($sql);
if (!$this->Records && ($this->Page > 1)) {
// no records & page > 1, try to reset to 1st page (works only when list in not counted before)
$this->Application->StoreVar($this->getPrefixSpecial().'_Page', 1);
$this->SetPage(1);
$this->Query($force);
}
$this->SelectedCount = count($this->Records);
if (!$this->Counted) $this->RecordsCount = $this->SelectedCount;
if (!$this->Counted && $this->SelectedCount > $this->PerPage && $this->PerPage != -1) $this->SelectedCount--;
if ($this->Records === false) {
//handle errors here
return false;
}
$this->Queried = true;
$query_event = new kEvent($this->getPrefixSpecial() . ':OnAfterListQuery');
$this->Application->HandleEvent($query_event);
return true;
}
/**
* Adds one more record to list virtually and updates all counters
*
* @param Array $record
*/
function addRecord($record)
{
$this->Records[] = $record;
$this->SelectedCount++;
$this->RecordsCount++;
}
function CalculateTotals()
{
$this->Totals = Array();
$fields = Array();
foreach($this->Fields as $field_name => $field_options)
{
$totals = getArrayValue($field_options, 'totals');
if(!$totals) continue;
$calculated_field = isset($this->CalculatedFields[$field_name]) && isset($this->VirtualFields[$field_name]);
$db_field = !isset($this->VirtualFields[$field_name]);
if($calculated_field || $db_field)
{
$field_expression = $calculated_field ? $this->CalculatedFields[$field_name] : '`'.$this->TableName.'`.`'.$field_name.'`';
$fields[$field_name] = $totals.'('.$field_expression.') AS '.$field_name.'_'.$totals;
}
}
if(!$fields) return false;
$sql = $this->GetSelectSQL(true, false);
$fields = implode(', ', $fields);
if ( preg_match("/DISTINCT(.*?)FROM(?!_)/is",$sql,$regs ) )
{
$sql = preg_replace("/^\s*SELECT DISTINCT(.*?)FROM(?!_)/is", 'SELECT '.$fields.' FROM', $sql);
}
else
{
$sql = preg_replace("/^\s*SELECT(.*?)FROM(?!_)/is", 'SELECT '.$fields.' FROM ', $sql);
}
$totals = $this->Conn->Query($sql);
foreach($totals as $totals_row)
{
foreach($totals_row as $total_field => $field_value)
{
if(!isset($this->Totals[$total_field])) $this->Totals[$total_field] = 0;
$this->Totals[$total_field] += $field_value;
}
}
$this->TotalsCalculated = true;
}
function getTotal($field, $total_function)
{
if (!$this->TotalsCalculated) $this->CalculateTotals();
return $this->Totals[$field.'_'.$total_function];
}
function GetFormattedTotal($field, $total_function)
{
$val = $this->getTotal($field, $total_function);
$options = $this->GetFieldOptions($field);
$res = $val;
if (isset($options['formatter'])) {
$formatter =& $this->Application->recallObject($options['formatter']);
$res = $formatter->Format($val, $field, $this );
}
return $res;
}
/**
* Builds full select query except for LIMIT clause
*
* @access public
* @return string
*/
function GetSelectSQL($for_counting=false,$system_filters_only=false)
{
$q = parent::GetSelectSQL($this->SelectClause);
$q = !$for_counting ? $this->addCalculatedFields($q, 0) : str_replace('%2$s', '', $q);
$where = $this->GetWhereClause($for_counting,$system_filters_only);
$having = $this->GetHavingClause($for_counting,$system_filters_only);
$order = $this->GetOrderClause();
$group = $this->GetGroupClause();
if (!empty($where)) $q .= ' WHERE ' . $where;
if (!empty($group)) $q .= ' GROUP BY ' . $group;
if (!empty($having)) $q .= ' HAVING ' . $having;
if ( !$for_counting && !empty($order) ) $q .= ' ORDER BY ' . $order;
return $this->replaceModePrefix( str_replace('%1$s', $this->TableName, $q) );
}
/**
* Enter description here...
*
* @param string $clause where clause to extract calculated fields from
* @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only
* @return string
*/
function extractCalculatedFields($clause, $aggregated = 1)
{
$fields = $this->getCalculatedFields($aggregated);
if (is_array($fields) && count($fields) > 0) {
foreach ($fields as $field_name => $field_expression) {
$clause = preg_replace('/(\\(+)[(,` ]*'.$field_name.'[` ]{1}/', '\1 ('.$field_expression.') ', $clause);
$clause = preg_replace('/[,` ]{1}'.$field_name.'[` ]{1}/', ' ('.$field_expression.') ', $clause);
}
}
return $clause;
}
/**
* Returns WHERE clause of the query
*
* @access public
* @param bool $for_counting merge where filters with having filters + replace field names for having fields with their values
* @return string
*/
function GetWhereClause($for_counting=false,$system_filters_only=false)
{
$where =& $this->Application->makeClass('kMultipleFilter');
$where->addFilter('system_where', $this->WhereFilter[FLT_SYSTEM] );
if (!$system_filters_only) {
$where->addFilter('view_where', $this->WhereFilter[FLT_VIEW] );
$search_w = $this->WhereFilter[FLT_SEARCH]->getSQL();
if ($search_w || $for_counting) { // move search_having to search_where in case search_where isset or we are counting
$search_h = $this->extractCalculatedFields( $this->HavingFilter[FLT_SEARCH]->getSQL() );
$search_w = ($search_w && $search_h) ? $search_w.' OR '.$search_h : $search_w.$search_h;
$where->addFilter('search_where', $search_w );
}
// CUSTOM
$search_w = $this->WhereFilter[FLT_CUSTOM]->getSQL();
if ($search_w || $for_counting) { // move search_having to search_where in case search_where isset or we are counting
$search_h = $this->extractCalculatedFields( $this->HavingFilter[FLT_CUSTOM]->getSQL() );
$search_w = ($search_w && $search_h) ? $search_w.' AND '.$search_h : $search_w.$search_h;
$where->addFilter('custom_where', $search_w );
}
// CUSTOM
}
if( $for_counting ) // add system_having and view_having to where
{
$where->addFilter('system_having', $this->extractCalculatedFields($this->HavingFilter[FLT_SYSTEM]->getSQL()) );
if (!$system_filters_only) $where->addFilter('view_having', $this->extractCalculatedFields( $this->HavingFilter[FLT_VIEW]->getSQL() ) );
}
return $where->getSQL();
}
/**
* Depricated method
*
* @param string $clause
* @todo REMOVE
*/
function SetWhereClause($clause)
{
if( $this->Application->isDebugMode() )
{
global $debugger;
$debugger->appendTrace();
}
trigger_error('Depricated method <b>kDBList->SetWhereClause</b>. Use <b>kDBList->addFilter</b> instead.', E_USER_ERROR);
}
/**
* Returns HAVING clause of the query
*
* @param bool $for_counting don't return having filter in case if this is counting sql
* @param bool $system_filters_only return only system having filters
* @param int $aggregated 0 - aggregated and having, 1 - having only, 2 - aggregated only
* @return string
* @access public
*/
function GetHavingClause($for_counting=false, $system_filters_only=false, $aggregated = 0)
{
if ($for_counting) {
$aggregate_filter =& $this->Application->makeClass('kMultipleFilter');
$aggregate_filter->addFilter('aggregate_system', $this->AggregateFilter[FLT_SYSTEM]);
if (!$system_filters_only) {
$aggregate_filter->addFilter('aggregate_view', $this->AggregateFilter[FLT_VIEW]);
}
return $this->extractCalculatedFields($aggregate_filter->getSQL(), 2);
}
$having =& $this->Application->makeClass('kMultipleFilter');
$having->addFilter('system_having', $this->HavingFilter[FLT_SYSTEM] );
if ($aggregated == 0) {
if (!$system_filters_only) {
$having->addFilter('view_aggregated', $this->AggregateFilter[FLT_VIEW] );
}
$having->addFilter('system_aggregated', $this->AggregateFilter[FLT_SYSTEM]);
}
if (!$system_filters_only) {
$having->addFilter('view_having', $this->HavingFilter[FLT_VIEW] );
$having->addFilter('custom_having', $this->HavingFilter[FLT_CUSTOM] );
$search_w = $this->WhereFilter[FLT_SEARCH]->getSQL();
if (!$search_w) {
$having->addFilter('search_having', $this->HavingFilter[FLT_SEARCH] );
}
}
return $having->getSQL();
}
/**
* Returns GROUP BY clause of the query
*
* @access public
* @return string
*/
function GetGroupClause()
{
return $this->GroupByFields ? implode(',', $this->GroupByFields) : '';
}
function AddGroupByField($field)
{
$this->GroupByFields[$field] = $field;
}
function RemoveGroupByField($field)
{
unset($this->GroupByFields[$field]);
}
/**
* Adds order field to ORDER BY clause
*
* @access public
* @param string $field Field name
* @param string $direction Direction of ordering (asc|desc)
* @param bool $is_expression this is expression, that should not be escapted by "`" symbols
* @return void
*/
function AddOrderField($field, $direction = 'asc', $is_expression = false)
{
// original multilanguage field - convert to current lang field
$formatter = isset($this->Fields[$field]['formatter']) ? $this->Fields[$field]['formatter'] : false;
if ($formatter == 'kMultiLanguage' && !isset($this->Fields[$field]['master_field'])) {
$lang = $this->Application->GetVar('m_lang');
$field = 'l'.$lang.'_'.$field;
}
if (!isset($this->Fields[$field]) && $field != 'RAND()' && !$is_expression) {
trigger_error('<span class="debug_error">Incorrect sorting</span> defined (field = <b>'.$field.'</b>; direction = <b>'.$direction.'</b>) in config for prefix <b>'.$this->Prefix.'</b>', E_USER_WARNING);
}
$this->OrderFields[] = Array($field, $direction, $is_expression);
}
/**
* Removes all order fields
*
* @access public
* @return void
*/
function ClearOrderFields()
{
$this->OrderFields = Array();
}
/**
* Returns ORDER BY Clause of the query
*
* The method builds order by clause by iterating {@link kDBList::OrderFields} array and concatenating it.
*
* @access public
* @return string
*/
function GetOrderClause()
{
$ret = '';
foreach ($this->OrderFields as $field) {
$name = $field[0];
$ret .= isset($this->Fields[$name]) && !isset($this->VirtualFields[$name]) ? '`'.$this->TableName.'`.' : '';
if ($field[0] == 'RAND()' || $field[2]) {
$ret .= $field[0].' '.$field[1].',';
}
else {
$ret .= (strpos($field[0], '.') === false ? '`'.$field[0] . '`' : $field[0]) . ' ' . $field[1] . ',';
}
}
$ret = rtrim($ret, ',');
return $ret;
}
function GetOrderField($pos = NULL, $no_default = false)
{
if ( !(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) && !$no_default ) {
$pos = 0;
}
return isset($this->OrderFields[$pos][0]) ? $this->OrderFields[$pos][0] : '';
}
function GetOrderDirection($pos = NULL, $no_default = false)
{
if ( !(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) && !$no_default ) {
$pos = 0;
}
return isset($this->OrderFields[$pos][1]) ? $this->OrderFields[$pos][1] : '';
}
/**
* Return unformatted field value
*
* @param string $name
* @return string
* @access public
*/
function GetDBField($name)
{
$row =& $this->getCurrentRecord();
if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Queried && !array_key_exists($name, $row)) {
if ($this->Application->isDebugMode()) {
$this->Application->Debugger->appendTrace();
}
trigger_error('Field "<strong>' . $name . '</strong>" doesn\'t exist in prefix <strong>' . $this->getPrefixSpecial() . '</strong>', E_USER_WARNING);
return 'NO SUCH FIELD';
}
- return $row[$name];
+ // return "null" for missing fields, because formatter require such behaviour !
+ return array_key_exists($name, $row) ? $row[$name] : null;
}
/**
* Returns ID of currently processed record
*
* @return int
* @access public
*/
function GetID()
{
return $this->Queried ? $this->GetDBField($this->IDField) : null;
}
/**
* Allows kDBTagProcessor.SectionTitle to detect if it's editing or new item creation
*
* @return bool
*/
function IsNewItem()
{
// no such thing as NewItem for lists :)
return false;
}
function HasField($name)
{
$row =& $this->getCurrentRecord();
return isset($row[$name]);
}
function GetFieldValues()
{
return $this->getCurrentRecord();
}
/**
* Returns current record from list
*
* @param int $offset Offset relative to current record index
* @return Array
*/
function &getCurrentRecord($offset = 0)
{
$record_index = $this->CurrentIndex + $offset;
if ($record_index >=0 && $record_index < $this->SelectedCount) {
return $this->Records[$record_index];
}
return false;
}
/**
* Goes to record with given index
*
* @param int $index
*/
function GoIndex($index)
{
$this->CurrentIndex = $index;
}
/**
* Description
*
* @access public
* @param string
* @return void
*/
function GoFirst()
{
$this->CurrentIndex = 0;
}
/**
* Description
*
* @access public
* @return void
*/
function GoNext()
{
$this->CurrentIndex++;
}
/**
* Description
*
* @access public
* @return void
*/
function GoPrev()
{
if ($this->CurrentIndex>0)
$this->CurrentIndex--;
}
/**
* Description
*
* @access public
* @return bool
*/
function EOL()
{
return ($this->CurrentIndex >= $this->SelectedCount);
}
/**
* Description
*
* @access public
* @param string
* @return void
*/
function GetTotalPages()
{
if (!$this->Counted) $this->CountRecs();
if ($this->PerPage == -1) return 1;
$this->TotalPages = (($this->RecordsCount - ($this->RecordsCount % $this->PerPage)) / $this->PerPage) // integer part of division
+ (($this->RecordsCount % $this->PerPage) != 0); // adds 1 if there is a reminder
return $this->TotalPages;
}
/**
* Sets number of records to query per page
*
* @access public
* @param int $per_page Number of records to display per page
* @return void
*/
function SetPerPage($per_page)
{
$this->PerPage = $per_page;
}
function GetPerPage()
{
return $this->PerPage == -1 ? $this->RecordsCount : $this->PerPage;
}
/**
* Description
*
* @access public
* @param int $page
* @return void
*/
function SetPage($page)
{
if ($this->PerPage == -1) {
$this->Page = 1;
return;
}
if ($page < 1) $page = 1;
$this->Offset = ($page-1)*$this->PerPage;
if ($this->Counted && $this->Offset > $this->RecordsCount) {
$this->SetPage(1);
}
else {
$this->Page = $page;
}
//$this->GoFirst();
}
/**
* 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->Records[$this->CurrentIndex][$name] = $value;
}
/**
* Apply where clause, that links this object to it's parent item
*
* @param string $special
* @access public
*/
function linkToParent($special)
{
$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
if($parent_prefix)
{
$parent_table_key = $this->Application->getUnitOption($this->Prefix, 'ParentTableKey');
if (is_array($parent_table_key)) $parent_table_key = getArrayValue($parent_table_key, $parent_prefix);
$foreign_key_field = $this->Application->getUnitOption($this->Prefix, 'ForeignKey');
if (is_array($foreign_key_field)) $foreign_key_field = getArrayValue($foreign_key_field, $parent_prefix);
if (!$parent_table_key || !$foreign_key_field) return ;
$parent_object =& $this->Application->recallObject($parent_prefix.'.'.$special);
$parent_id = $parent_object->GetDBField($parent_table_key);
if (!$parent_id) return ;
$this->addFilter('parent_filter', '`'.$this->TableName.'`.`'.$foreign_key_field.'` = '.$parent_id); // only for list in this case
}
}
/**
* Returns true if list was queried (same name as for kDBItem for easy usage)
*
* @return bool
*/
function isLoaded()
{
return $this->Queried;
}
/**
* Returns specified field value from all selected rows.
* Don't affect current record index
*
* @param string $field
* @return Array
*/
function GetCol($field)
{
$i = 0;
$ret = Array ();
while ($i < $this->SelectedCount) {
$ret[] = $this->Records[$i][$field];
$i++;
}
return $ret;
}
/**
* 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
*/
function SetError($field, $pseudo, $error_label = null, $error_params = null)
{
$error_field = isset($this->Fields[$field]['error_field']) ? $this->Fields[$field]['error_field'] : $field;
$this->FieldErrors[$error_field]['pseudo'] = $pseudo;
$var_name = $this->getPrefixSpecial().'_'.$field.'_error';
$previous_pseudo = $this->Application->RecallVar($var_name);
if ($previous_pseudo) {
// don't set more then one error on field
return ;
}
$this->Application->StoreVar($var_name, $pseudo);
}
}
?>
\ No newline at end of file
Index: branches/RC/core/kernel/db/db_connection.php
===================================================================
--- branches/RC/core/kernel/db/db_connection.php (revision 11926)
+++ branches/RC/core/kernel/db/db_connection.php (revision 11927)
@@ -1,722 +1,702 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 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.net/license/ for copyright notices and details.
*/
/**
* Multi database connection class
*
*/
class kDBConnection {
/**
* Holds reference to global KernelApplication instance
* @access public
* @var kApplication
*/
var $Application;
/**
* Current database type
*
* @var string
* @access private
*/
var $dbType = 'mysql';
/**
* Created connection handle
*
* @var resource
* @access private
*/
var $connectionID = null;
/**
* Handle of currenty processed recordset
*
* @var resource
* @access private
*/
var $queryID = null;
/**
* DB type specific function mappings
*
* @var Array
* @access private
*/
var $metaFunctions = Array();
/**
* Function to handle sql errors
*
* @var string
* @access private
*/
var $errorHandler = '';
/**
* Error code
*
* @var int
* @access private
*/
var $errorCode = 0;
/**
* Error message
*
* @var string
* @access private
*/
var $errorMessage = '';
/**
* Defines if database connection
* operations should generate debug
* information
*
* @var bool
*/
var $debugMode = false;
/**
* Save query execution statistics
*
* @var bool
*/
var $_captureStatistics = false;
/**
* Last query to database
*
* @var string
*/
var $lastQuery = '';
/**
* Total processed queries count
*
* @var int
*/
var $_queryCount = 0;
/**
* Total time, used for serving queries
*
* @var Array
*/
var $_queryTime = 0;
/**
* Initializes connection class with
* db type to used in future
*
* @param string $dbType
* @return DBConnection
* @access public
*/
function kDBConnection($dbType, $errorHandler = '')
{
$this->dbType = $dbType;
// $this->initMetaFunctions();
if (!$errorHandler) {
$this->errorHandler = Array(&$this, 'handleError');
}
else {
$this->errorHandler = $errorHandler;
}
$this->_captureStatistics = defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !(defined('ADMIN') && ADMIN);
if (class_exists('kApplication')) {
// prevents "Fatal Error" on 2nd installation step (when database is empty)
$this->Application =& kApplication::Instance();
}
}
/**
* Set's custom error
*
* @param int $code
* @param string $msg
* @access public
*/
function setError($code, $msg)
{
$this->errorCode = $code;
$this->errorMessage = $msg;
}
/**
* Checks if previous query execution
* raised an error.
*
* @return bool
* @access public
*/
function hasError()
{
return !($this->errorCode == 0);
}
/**
* Caches function specific to requested
* db type
*
* @access private
*/
function initMetaFunctions()
{
$ret = Array();
switch ($this->dbType)
{
case 'mysql':
$ret = Array(); // only define functions, that name differs from "dbType_<meta_name>"
break;
}
$this->metaFunctions = $ret;
}
/**
* Get's function for specific db type
* based on it's meta name
*
* @param string $name
* @return string
* @access private
*/
function getMetaFunction($name)
{
/*if (!isset($this->metaFunctions[$name])) {
$this->metaFunctions[$name] = $name;
}*/
return $this->dbType.'_'.$name;
}
/**
* Try to connect to database server
* using specified parameters and set
* database to $db if connection made
*
* @param string $host
* @param string $user
* @param string $pass
* @param string $db
* @access public
*/
function Connect($host, $user, $pass, $db, $force_new = false)
{
$func = $this->getMetaFunction('connect');
$this->connectionID = $func($host, $user, $pass, $force_new) or trigger_error("Database connection failed, please check your connection settings", defined('IS_INSTALL') && IS_INSTALL ? E_USER_WARNING : E_USER_ERROR);
if ($this->connectionID) {
if (defined('DBG_SQL_MODE')) {
$this->Query('SET sql_mode = \''.DBG_SQL_MODE.'\'');
}
if (defined('SQL_COLLATION') && defined('SQL_CHARSET')) {
$this->Query('SET NAMES \''.SQL_CHARSET.'\' COLLATE \''.SQL_COLLATION.'\'');
}
$this->setDB($db);
$this->showError();
}
else {
// simulate error, becase php mysql client doesn't provide such information
$this->errorCode = 2003;
$this->errorMessage = "Can't connect to MySQL server on '$host' (113)";
}
}
function ReConnect($host, $user, $pass, $db, $force_new = false)
{
$func = $this->getMetaFunction('close');
$func($this->connectionID);
$this->Connect($host, $user, $pass, $db, $force_new);
}
/**
* Shows error message from previous operation
* if it failed
*
* @access private
*/
function showError($sql = '')
{
$this->setError(0, ''); // reset error
if ($this->connectionID) {
$func = $this->getMetaFunction('errno'); $this->errorCode = $func($this->connectionID);
if ($this->hasError()) {
$func = $this->getMetaFunction('error'); $this->errorMessage = $func($this->connectionID);
if (is_array($this->errorHandler)) {
$func = $this->errorHandler[1];
$ret = $this->errorHandler[0]->$func($this->errorCode, $this->errorMessage, $sql);
}
else {
$func = $this->errorHandler;
$ret = $func($this->errorCode,$this->errorMessage,$sql);
}
if (!$ret) exit;
}
}
}
/**
* Default error handler for sql errors
*
* @param int $code
* @param string $msg
* @param string $sql
* @return bool
* @access private
*/
function handleError($code, $msg, $sql)
{
echo '<b>Processing SQL</b>: '.$sql.'<br>';
echo '<b>Error ('.$code.'):</b> '.$msg.'<br>';
return false;
}
/**
* Set's database name for connection
* to $new_name
*
* @param string $new_name
* @return bool
* @access public
*/
function setDB($new_name)
{
if (!$this->connectionID) return false;
$func = $this->getMetaFunction('select_db');
return $func($new_name, $this->connectionID);
}
/**
* Returns first field of first line
* of recordset if query ok or false
* otherwise
*
* @param string $sql
* @param int $offset
* @return string
* @access public
*/
function GetOne($sql, $offset = 0)
{
$row = $this->GetRow($sql, $offset);
if(!$row) return false;
return array_shift($row);
}
/**
* Returns first row of recordset
* if query ok, false otherwise
*
* @param stirng $sql
* @param int $offset
* @return Array
* @access public
*/
function GetRow($sql, $offset = 0)
{
$sql .= ' '.$this->getLimitClause($offset, 1);
$ret = $this->Query($sql);
if(!$ret) return false;
return array_shift($ret);
}
/**
* Returns 1st column of recordset as
* one-dimensional array or false otherwise
* Optional parameter $key_field can be used
* to set field name to be used as resulting
* array key
*
* @param string $sql
* @param string $key_field
* @return Array
* @access public
*/
function GetCol($sql, $key_field = null)
{
$rows = $this->Query($sql);
if (!$rows) return $rows;
$i = 0; $row_count = count($rows);
$ret = Array();
if (isset($key_field)) {
while ($i < $row_count) {
$ret[$rows[$i][$key_field]] = array_shift($rows[$i]);
$i++;
}
}
else {
while ($i < $row_count) {
$ret[] = array_shift($rows[$i]);
$i++;
}
}
return $ret;
}
/**
* Queries db with $sql query supplied
* and returns rows selected if any, false
* otherwise. Optional parameter $key_field
* allows to set one of the query fields
* value as key in string array.
*
* @param string $sql
* @param string $key_field
* @return Array
*/
function Query($sql, $key_field = null, $no_debug = false)
{
$this->lastQuery = $sql;
if (!$no_debug) {
$this->_queryCount++;
}
if ($this->debugMode && !$no_debug) {
return $this->debugQuery($sql,$key_field);
}
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
if ($this->_captureStatistics) {
$start_time = getmicrotime();
}
// set 1st checkpoint: end
$this->queryID = $query_func($sql,$this->connectionID);
if (is_resource($this->queryID)) {
$ret = Array();
$fetch_func = $this->getMetaFunction('fetch_assoc');
if (isset($key_field)) {
while (($row = $fetch_func($this->queryID))) {
$ret[$row[$key_field]] = $row;
}
}
else {
while (($row = $fetch_func($this->queryID))) {
$ret[] = $row;
}
}
// set 2nd checkpoint: begin
if ($this->_captureStatistics) {
$query_time = getmicrotime() - $start_time;
if ($query_time > DBG_MAX_SQL_TIME && !$no_debug) {
$this->Application->logSlowQuery($sql, $query_time);
}
$this->_queryTime += $query_time;
}
// set 2nd checkpoint: end
$this->Destroy();
return $ret;
}
else {
// set 2nd checkpoint: begin
if ($this->_captureStatistics) {
$this->_queryTime += getmicrotime() - $start_time;
}
// set 2nd checkpoint: end
}
$this->showError($sql);
return false;
}
function ChangeQuery($sql)
{
$this->Query($sql);
return $this->errorCode == 0 ? true : false;
}
function debugQuery($sql, $key_field = null)
{
global $debugger;
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
$profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE;
if ($profileSQLs) {
$queryID = $debugger->generateID();
$debugger->profileStart('sql_'.$queryID, $debugger->formatSQL($sql));
}
// set 1st checkpoint: end
$this->queryID = $query_func($sql, $this->connectionID);
if( is_resource($this->queryID) )
{
$ret = Array();
$fetch_func = $this->getMetaFunction('fetch_assoc');
if( isset($key_field) )
{
while( ($row = $fetch_func($this->queryID)) )
{
$ret[$row[$key_field]] = $row;
}
}
else
{
while( ($row = $fetch_func($this->queryID)) )
{
$ret[] = $row;
}
}
// set 2nd checkpoint: begin
$first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : null;
if ($profileSQLs) {
$debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount);
$debugger->profilerAddTotal('sql', 'sql_'.$queryID);
}
// set 2nd checkpoint: end
$this->Destroy();
return $ret;
}
else {
// set 2nd checkpoint: begin
if ($profileSQLs) {
$debugger->profileFinish('sql_'.$queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount);
$debugger->profilerAddTotal('sql', 'sql_'.$queryID);
}
// set 2nd checkpoint: end
}
$this->showError($sql);
return false;
}
/**
* Free memory used to hold recordset handle
*
* @access private
*/
function Destroy()
{
if($this->queryID)
{
$free_func = $this->getMetaFunction('free_result');
$free_func($this->queryID);
$this->queryID = null;
}
}
/**
* Returns auto increment field value from
* insert like operation if any, zero otherwise
*
* @return int
* @access public
*/
function getInsertID()
{
$func = $this->getMetaFunction('insert_id');
return $func($this->connectionID);
}
/**
* Returns row count affected by last query
*
* @return int
* @access public
*/
function getAffectedRows()
{
$func = $this->getMetaFunction('affected_rows');
return $func($this->connectionID);
}
/**
* Returns LIMIT sql clause part for specific db
*
* @param int $offset
* @param int $rows
* @return string
* @access private
*/
function getLimitClause($offset, $rows)
{
if(!($rows > 0)) return '';
switch ($this->dbType) {
default:
return 'LIMIT '.$offset.','.$rows;
break;
}
}
/**
- * Correctly quotes a string so that all strings are escaped. We prefix and append
- * to the string single-quotes.
- * An example is $db->qstr("Don't bother",magic_quotes_runtime());
+ * If it's a string, adds quotes and backslashes (only work since PHP 4.3.0)
+ * Otherwise returns as-is
*
- * @param s the string to quote
- * @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc().
- * This undoes the stupidity of magic quotes for GPC.
- *
- * @return quoted string to be sent back to database
+ * @param mixed $string
*/
- function qstr($s,$magic_quotes=false)
+ function qstr($string)
{
- $replaceQuote = "\\'";
- if (!$magic_quotes)
- {
- if ($replaceQuote[0] == '\\')
- {
- // only since php 4.0.5
- $s = str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s);
- //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s));
- }
- return "'".str_replace("'",$replaceQuote,$s)."'";
+ if ( is_null($string) ) {
+ return 'NULL';
}
- // undo magic quotes for "
- $s = str_replace('\\"','"',$s);
-
- if($replaceQuote == "\\'") // ' already quoted, no need to change anything
- {
- return "'$s'";
- }
- else // change \' to '' for sybase/mssql
- {
- $s = str_replace('\\\\','\\',$s);
- return "'".str_replace("\\'",$replaceQuote,$s)."'";
- }
+ # This will also quote numeric values. This should be harmless,
+ # and protects against weird problems that occur when they really
+ # _are_ strings such as article titles and string->number->string
+ # conversion is not 1:1.
+ return "'" . mysql_real_escape_string($string, $this->connectionID) . "'";
}
/**
* Returns last error code occured
*
* @return int
*/
function getErrorCode()
{
return $this->errorCode;
}
/**
* Returns last error message
*
* @return string
* @access public
*/
function getErrorMsg()
{
return $this->errorMessage;
}
/**
* Performs insert of given data (useful with small number of queries)
* or stores it to perform multiple insert later (useful with large number of queries)
*
* @param Array $fields_hash
* @param string $table
* @param string $type
* @param bool $insert_now
* @return bool
*/
function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true)
{
static $value_sqls = Array ();
if ($insert_now) {
$fields_sql = '`' . implode('`,`', array_keys($fields_hash)) . '`';
}
$values_sql = '';
foreach ($fields_hash as $field_name => $field_value) {
- $values_sql .= ( is_null($field_value) ? 'NULL' : $this->qstr($field_value) ) . ',';
+ $values_sql .= $this->qstr($field_value) . ',';
}
// don't use preg here, as it may fail when string is too long
$value_sqls[] = rtrim($values_sql, ',');
$insert_result = true;
if ($insert_now) {
$insert_count = count($value_sqls);
if (($insert_count > 1) && ($value_sqls[$insert_count - 1] == $value_sqls[$insert_count - 2])) {
// last two records are the same
array_pop($value_sqls);
}
$sql = strtoupper($type) . ' INTO `' . $table . '` (' . $fields_sql . ') VALUES (' . implode('),(', $value_sqls) . ')';
$insert_result = $this->ChangeQuery($sql);
$value_sqls = Array ();
}
return $insert_result;
}
function doUpdate($fields_hash, $table, $key_clause)
{
if (!$fields_hash) return true;
$fields_sql = '';
foreach ($fields_hash as $field_name => $field_value) {
- $fields_sql .= '`'.$field_name.'` = ' . ( is_null($field_value) ? 'NULL' : $this->qstr($field_value) ) . ',';
+ $fields_sql .= '`'.$field_name.'` = ' . $this->qstr($field_value) . ',';
}
// don't use preg here, as it may fail when string is too long
$fields_sql = rtrim($fields_sql, ',');
$sql = 'UPDATE `'.$table.'` SET '.$fields_sql.' WHERE '.$key_clause;
return $this->ChangeQuery($sql);
}
/**
* Allows to detect table's presense in database
*
* @param string $table_name
* @return bool
*/
function TableFound($table_name)
{
static $table_found = Array();
if (!preg_match('/^'.preg_quote(TABLE_PREFIX, '/').'(.*)/', $table_name)) {
$table_name = TABLE_PREFIX.$table_name;
}
if (!isset($table_found[$table_name])) {
$table_found[$table_name] = $this->Query('SHOW TABLES LIKE "'.$table_name.'"');
}
return $table_found[$table_name];
}
/**
* Returns query processing statistics
*
* @return Array
*/
function getQueryStatistics()
{
return Array ('time' => $this->_queryTime, 'count' => $this->_queryCount);
}
}
?>
\ No newline at end of file
Index: branches/RC/core/kernel/db/dbitem.php
===================================================================
--- branches/RC/core/kernel/db/dbitem.php (revision 11926)
+++ branches/RC/core/kernel/db/dbitem.php (revision 11927)
@@ -1,1223 +1,1291 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 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.net/license/ for copyright notices and details.
*/
/**
* DBItem
*
* Desciption
* @package kernel4
*/
class kDBItem extends kDBBase {
/**
* Description
*
* @var array Associative array of current item' field values
* @access public
*/
var $FieldValues;
/**
* Unformatted field values, before parse
*
* @var Array
* @access private
*/
var $DirtyFieldValues = Array();
/**
* Holds item values after loading (not affected by submit)
*
* @var Array
* @access private
*/
var $OriginalFieldValues = Array ();
var $FieldErrors;
var $ErrorMsgs = Array();
/**
* If set to true, Update will skip Validation before running
*
* @var array Associative array of current item' field values
* @access public
*/
var $IgnoreValidation = false;
var $Loaded = false;
/**
* Holds item' primary key value
*
* @var int Value of primary key field for current item
* @access public
*/
var $ID;
function kDBItem()
{
parent::kDBBase();
$this->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
*/
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 ;
}
$this->FieldErrors[$error_field]['pseudo'] = $pseudo;
if (isset($error_params)) {
// 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 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)
{
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)
+ function SetFieldsFromHash($hash, $set_fields = null)
{
// used in formatter which work with multiple fields together
- foreach($hash as $field_name => $field_value)
- {
- if( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue;
- if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue;
+ 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( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue;
- if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue;
+ 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)
+ function SetDBFieldsFromHash($hash, $set_fields = null)
{
- foreach ($hash as $field_name => $field_value)
- {
- if( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue;
- if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue;
+ 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 preg_replace('/(.*) AND $/', '\\1', $ret);
}
/**
* 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
* @return bool True if item has been loaded, false otherwise
*/
function Load($id, $id_field_name = null)
{
- if ( isset($id_field_name) ) $this->SetIDField( $id_field_name );
+ if ( isset($id_field_name) ) {
+ $this->SetIDField($id_field_name); // set new IDField
+ }
+
$keys_sql = '';
- if( is_array($id) )
- {
+ if (is_array($id)) {
$keys_sql = $this->GetKeyClause('load', $id);
}
- else
- {
+ else {
$this->setID($id);
$keys_sql = $this->GetKeyClause('load');
}
- if ( isset($id_field_name) ) $this->setIDField( $this->Application->getUnitOption($this->Prefix, 'IDField') );
-
- if( ($id === false) || !$keys_sql ) return $this->Clear();
+ if ( isset($id_field_name) ) {
+ // restore original IDField from unit config
+ $this->setIDField( $this->Application->getUnitOption($this->Prefix, 'IDField') );
+ }
- if( !$this->raiseEvent('OnBeforeItemLoad', $id) ) return false;
+ if (($id === false) || !$keys_sql) {
+ return $this->Clear();
+ }
- $q = $this->GetSelectSQL().' WHERE '.$keys_sql;
+ if (!$this->raiseEvent('OnBeforeItemLoad', $id)) {
+ return false;
+ }
+ $q = $this->GetSelectSQL() . ' WHERE ' . $keys_sql;
$field_values = $this->Conn->GetRow($q);
- if($field_values)
- {
+
+ 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] );
+ 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->raiseEvent('OnAfterItemLoad', $this->GetID());
$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);
}
}
}
- function SkipField($field_name, $force_id=false)
+ /**
+ * 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;
- $skip = $skip || ( isset($this->VirtualFields[$field_name]) ); //skipping 'virtual' field
- $skip = $skip || ( !getArrayValue($this->FieldValues, $field_name) && getArrayValue($this->Fields[$field_name], 'skip_empty') ); //skipping marked field with 'skip_empty'
-// $skip = $skip || ($field_name == $this->IDField && !$force_id); //skipping Primary Key
-// $table_name = preg_replace("/^(.*)\./", "$1", $field_name);
-// $skip = $skip || ($table_name && ($table_name != $this->TableName)); //skipping field from other tables
+ // 1. skipping 'virtual' field
+ $skip = $skip || array_key_exists($field_name, $this->VirtualFields);
- $skip = $skip || ( !isset($this->Fields[$field_name]) ); //skipping field not in Fields (nor virtual, nor real)
+ // 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;
+ $skip_empty = array_key_exists('skip_empty', $this->Fields[$field_name]) ? $this->Fields[$field_name]['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)
+ function Update($id = null, $system_update = false)
{
- if( isset($id) ) $this->setID($id);
+ if (isset($id)) {
+ $this->setID($id);
+ }
- if( !$this->raiseEvent('OnBeforeItemUpdate') ) return false;
+ if (!$this->raiseEvent('OnBeforeItemUpdate')) {
+ return false;
+ }
- if( !isset($this->ID) ) 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
+ // validate before updating
if (!$this->Validate()) {
return false;
}
- if( !$this->raiseEvent('OnAfterItemValidate') ) return false;
- //Nothing to update
- if(!$this->FieldValues) return true;
+ if (!$this->raiseEvent('OnAfterItemValidate')) {
+ 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;
+ }
- $sql = sprintf('UPDATE %s SET ',$this->TableName);
- foreach ($this->FieldValues as $field_name => $field_value)
- {
- if ($this->SkipField($field_name)) continue;
-
- $real_field_name = eregi_replace("^.*\.", '',$field_name); //removing table names from field names
-
- //Adding part of SET clause for current field, escaping data with ADODB' qstr
- if (is_null( $this->FieldValues[$field_name] )) {
- if (isset($this->Fields[$field_name]['not_null']) && $this->Fields[$field_name]['not_null']) {
- $sql .= '`'.$real_field_name.'` = '.$this->Conn->qstr($this->Fields[$field_name]['default']).', ';
+ 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'];
}
- else {
- $sql .= '`'.$real_field_name.'` = NULL, ';
- }
- }
- else {
- $sql.= sprintf('`%s`=%s, ', $real_field_name, $this->Conn->qstr($this->FieldValues[$field_name], 0));
}
- }
- $sql = ereg_replace(", $", '', $sql); //Removing last comma and space
- $sql.= sprintf(' WHERE %s', $this->GetKeyClause('update')); //Adding WHERE clause with Primary Key
+ $sql .= '`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ', ';
+ }
- if( $this->Conn->ChangeQuery($sql) === false ) return false;
+ $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 = $this->Conn->getAffectedRows();
- if (!$system_update && $affected == 1){
+ if (!$system_update && $affected == 1) {
$this->setModifiedFlag(clUPDATE);
}
$this->saveCustomFields();
$this->raiseEvent('OnAfterItemUpdate');
$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;
}
$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 <strong>'.$this->Prefix.'</strong>,
FieldErrors follow (look at items with <strong>"pseudo"</strong> key set)<br />
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);
}
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 <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->FieldValues[$field]);
}
if (!$res) {
$this->SetError($field, 'bad_type', null, $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 = '-&infin;';
if ( !isset($max_val) ) $max_val = '&infin;';
$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'));
$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 <b>'.$err.'</b><br>', 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)
+ function Create($force_id = false, $system_create = false)
{
- if( !$this->raiseEvent('OnBeforeItemCreate') ) return false;
+ if (!$this->raiseEvent('OnBeforeItemCreate')) {
+ return false;
+ }
// Validating fields before attempting to create record
if (!$this->Validate()) {
return false;
}
- if( !$this->raiseEvent('OnAfterItemValidate') ) return false;
+
+ if (!$this->raiseEvent('OnAfterItemValidate')) {
+ 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 ($this->_skipField($field_name, $force_id)) {
+ continue;
+ }
- //Adding field' value to Values block of Insert statement, escaping it with qstr
- if (is_null( $this->FieldValues[$field_name] )) {
- if (isset($this->Fields[$field_name]['not_null']) && $this->Fields[$field_name]['not_null']) {
- $values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default'], 0);
+ 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 .= 'NULL';
+ $values_sql .= $this->Conn->qstr($field_value);
}
}
else {
- if ($field_name == $this->IDField && $this->FieldValues[$field_name] == 0) {
+ if (($field_name == $this->IDField) && ($field_value == 0)) {
+ // don't skip IDField in INSERT statement, just use DEFAULT keyword as it's value
$values_sql .= 'DEFAULT';
}
else {
- $values_sql .= $this->Conn->qstr($this->FieldValues[$field_name], 0);
+ $values_sql .= $this->Conn->qstr($field_value);
}
}
- $fields_sql .= '`'.$field_name.'`, '; //Adding field name to fields block of Insert statement
+
+ $fields_sql .= '`' . $field_name . '`, '; //Adding field name to fields block of Insert statement
$values_sql .= ', ';
}
- //Cutting last commas and spaces
- $fields_sql = ereg_replace(", $", '', $fields_sql);
- $values_sql = ereg_replace(", $", '', $values_sql);
- $sql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $this->TableName, $fields_sql, $values_sql); //Formatting query
+
+ $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;
+ 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);
if (!$system_create){
$this->setModifiedFlag(clCREATE);
}
$this->saveCustomFields();
if ($this->mode != 't') {
$this->Application->resetCounters($this->TableName);
}
$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 (isset($id)) {
+ $this->setID($id);
+ }
- if( !$this->raiseEvent('OnBeforeItemDelete') ) return false;
+ if (!$this->raiseEvent('OnBeforeItemDelete')) {
+ return false;
+ }
- $q = 'DELETE FROM '.$this->TableName.' WHERE '.$this->GetKeyClause('Delete');
+ $sql = 'DELETE FROM ' . $this->TableName . '
+ WHERE ' . $this->GetKeyClause('Delete');
- $ret = $this->Conn->ChangeQuery($q);
+ $ret = $this->Conn->ChangeQuery($sql);
$affected_rows = $this->Conn->getAffectedRows();
- $this->setModifiedFlag(clDELETE);
+ $this->setModifiedFlag(clDELETE); // will change affected rows, so get it before this line
if ($affected_rows > 0) {
// something was actually deleted
$this->raiseEvent('OnAfterItemDelete');
}
if ($this->mode != 't') {
$this->Application->resetCounters($this->TableName);
}
+
return $ret;
}
function PopulateMultiLangFields()
{
$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
/* @var $ml_helper kMultiLanguageHelper */
$lang_count = $ml_helper->getLanguageCount();
foreach ($this->Fields as $field => $options)
{
// master field is set only for CURRENT language
if (isset($options['formatter']) && $options['formatter'] == 'kMultiLanguage' && isset($options['master_field'])) {
if (preg_match('/^l([0-9]+)_(.*)/', $field, $regs)) {
$l = $regs[1];
$name = $regs[2];
// 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
$this->Fields['l' . $l . '_' . $name]['error_field'] = 'l' . $l . '_' . $name;
for ($i = 1; $i <= $lang_count; $i++) {
if ($i == $l || !$ml_helper->LanguageFound($i)) continue;
$f_options = $options;
$f_options['error_field'] = 'l' . $i . '_' . $name; // set error field back to itself - see comment above
if ($i != $this->Application->GetDefaultLanguageId()) {
unset($f_options['required']); // all non-primary language field set to non-required
}
$this->Fields['l' . $i . '_' . $name] = $f_options;
}
}
}
}
}
/**
* 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()) {
// 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()) {
$changes[$key]['ItemId'] = $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');
if ($this->ShouldLogChanges()) {
$this->LogChanges($main_prefix, $mode);
if (!$this->IsTempTable()) {
$handler =& $this->Application->recallObject($this->Prefix.'_EventHandler');
$ses_var_name = $main_prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix);
$handler->SaveLoggedChanges($ses_var_name);
}
}
}
/**
* Determines, that changes made to this item should be written to change log
*
* @return bool
*/
function ShouldLogChanges()
{
$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();
$general = array(
'Prefix' => $this->Prefix,
'ItemId' => $this->GetID(),
'OccuredOn' => adodb_mktime(),
'MasterPrefix' => $main_prefix,
'MasterId' => $this->Prefix == $main_prefix ? $this->GetID() : $this->Application->GetVar($main_prefix.'_id'), // is that correct (Kostja)??
'Action' => $mode,
);
switch ($mode) {
case clUPDATE:
$changes[] = array_merge($general, Array(
'Changes' => serialize(array_merge($this->GetTitleField(), $this->GetChangedFields())),
));
break;
case clCREATE:
$changes[] = array_merge($general, Array(
'Changes' => serialize($this->GetTitleField()),
));
break;
case clDELETE:
$changes[] = array_merge($general, Array(
'Changes' => serialize(array_merge($this->GetTitleField(), $this->GetRealFields())),
));
}
$this->Application->StoreVar($ses_var_name, serialize($changes));
}
function GetTitleField()
{
$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
if ($title_field && $this->GetField($title_field)) {
return Array($title_field => $this->GetField($title_field));
}
}
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;
}
function GetChangedFields()
{
$changes = array();
$diff = array_diff_assoc($this->GetRealFields(), $this->OriginalFieldValues);
foreach ($diff as $field => $new_value) {
$changes[$field] = array('old' => $this->GetOriginalField($field, true), 'new' => $this->GetField($field));
}
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->setID($new_id);
$this->Loaded = false;
$this->FieldValues = Array();
$this->OriginalFieldValues = Array ();
$this->SetDefaultValues();
$this->FieldErrors = Array();
return $this->Loaded;
}
function Query($force = false)
{
if( $this->Application->isDebugMode() )
{
$this->Application->Debugger->appendTrace();
}
trigger_error('<b>Query</b> method is called in class <b>'.get_class($this).'</b> for prefix <b>'.$this->getPrefixSpecial().'</b>', E_USER_ERROR);
}
function saveCustomFields()
{
if (!$this->customFields) {
return true;
}
$cdata_key = rtrim($this->Prefix.'-cdata.'.$this->Special, '.');
$cdata =& $this->Application->recallObject($cdata_key, null, Array('skip_autoload' => true, 'populate_ml_fields' => true));
$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 */
foreach ($this->customFields as $custom_id => $custom_name) {
$force_primary = isset($cdata->Fields['cust_'.$custom_id]['force_primary']) && $cdata->Fields['cust_'.$custom_id]['force_primary'];
$cdata->SetDBField($ml_formatter->LangFieldName('cust_'.$custom_id, $force_primary), $this->GetDBField('cust_'.$custom_name));
}
if ($cdata->isLoaded()) {
$ret = $cdata->Update();
}
else {
$ret = $cdata->Create();
if ($cdata->mode == 't') $cdata->setTempID();
}
return $ret;
}
/**
* Returns specified field value from all selected rows.
* Don't affect current record index
*
* @param string $field
* @return Array
*/
function GetCol($field)
{
return Array (0 => $this->GetDBField($field));
}
}
?>
\ No newline at end of file

Event Timeline