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; /** * Identifies this list as main on the page, that allows to react on "page", "per_page" and "sort_by" parameters from url * * @var bool */ var $mainList = 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, true); $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 kDBList->SetWhereClause. Use kDBList->addFilter instead.', defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_NOTICE); } /** * 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_NOTICE); } $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 "' . $name . '" doesn\'t exist in prefix ' . $this->getPrefixSpecial() . '', E_USER_WARNING); return 'NO SUCH FIELD'; } // 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 * @param bool $formatted * @param string $format * @return Array */ function GetCol($field, $formatted = false, $format = null) { $i = 0; $ret = Array (); if ($formatted && array_key_exists('formatter', $this->Fields[$field])) { $formatter =& $this->Application->recallObject($this->Fields[$field]['formatter']); /* @var $formatter kFormatter */ while ($i < $this->SelectedCount) { $ret[] = $formatter->Format($this->Records[$i][$field], $field, $this, $format); $i++; } } else { 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 false; } $this->Application->StoreVar($var_name, $pseudo); return true; } }