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; } } ?>