Index: trunk/core/kernel/db/dblist.php =================================================================== diff -u -N --- trunk/core/kernel/db/dblist.php (revision 8605) +++ trunk/core/kernel/db/dblist.php (revision 0) @@ -1,910 +0,0 @@ - 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("/^.*SELECT DISTINCT(.*?)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $sql); - } - else - { - return preg_replace("/^.*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; - return true; - } - - 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("/^.*SELECT DISTINCT(.*?)FROM(?!_)/is", 'SELECT '.$fields.' FROM', $sql); - } - else - { - $sql = preg_replace("/^.*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 kDBList->SetWhereClause. Use kDBList->addFilter 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('Incorrect sorting defined (field = '.$field.'; direction = '.$direction.') in config for prefix '.$this->Prefix.'', 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 .= '`'.$field[0] . '` ' . $field[1] . ','; - } - } - $ret = rtrim($ret, ','); - return $ret; - } - - function GetOrderField($pos=NULL) - { - if(!(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) ) - { - $pos = 0; - } - return isset($this->OrderFields[$pos][0]) ? $this->OrderFields[$pos][0] : ''; - } - - function GetOrderDirection($pos=NULL) - { - if( !getArrayValue($this->OrderFields, $pos) ) $pos = 0; - return getArrayValue($this->OrderFields, $pos, 1); - } - - /** - * Return unformatted field value - * - * @param string - * @return mixed - * @access public - */ - function GetDBField($name) - { - $row =& $this->getCurrentRecord(); - - return $row[$name]; - } - - /** - * 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 - * - * @return Array - */ - function &getCurrentRecord() - { - return $this->Records[$this->CurrentIndex]; - } - - /** - * 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; - } -} - -?> \ No newline at end of file