Property changes on: branches/5.2.x/LICENSE ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/LICENSE:r16226 Property changes on: branches/5.2.x/robots.txt ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/robots.txt:r16226 Index: branches/5.2.x/core/kernel/db/dblist.php =================================================================== --- branches/5.2.x/core/kernel/db/dblist.php (revision 16423) +++ branches/5.2.x/core/kernel/db/dblist.php (revision 16424) @@ -1,1778 +1,1767 @@ OrderFields = Array(); - - $filters = $this->getFilterStructure(); - - foreach ($filters as $filter_params) { - $filter =& $this->$filter_params['type']; - $filter[ $filter_params['class'] ] = $this->Application->makeClass('kMultipleFilter', Array ($filter_params['join_using'])); + foreach ( $this->getFilterStructure() as $filter_params ) { + $property_name = $filter_params['type']; + $filter_group =& $this->$property_name; + + $filter_group[$filter_params['class']] = $this->Application->makeClass( + 'kMultipleFilter', + array($filter_params['join_using']) + ); } $this->PerPage = -1; } function setGridName($grid_name) { $this->gridName = $grid_name; } /** * Returns information about all possible filter types * * @return Array * @access protected */ protected function getFilterStructure() { $filters = Array ( Array ('type' => 'WhereFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'WhereFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'WhereFilter', 'class' => self::FLT_SEARCH, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'WhereFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'WhereFilter', 'class' => self::FLT_CUSTOM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'HavingFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'HavingFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'HavingFilter', 'class' => self::FLT_SEARCH, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'HavingFilter', 'class' => self::FLT_VIEW, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'HavingFilter', 'class' => self::FLT_CUSTOM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'AggregateFilter', 'class' => self::FLT_SYSTEM, 'join_using' => self::FLT_TYPE_AND), Array ('type' => 'AggregateFilter', 'class' => self::FLT_NORMAL, 'join_using' => self::FLT_TYPE_OR), Array ('type' => 'AggregateFilter', 'class' => self::FLT_VIEW, 'join_using' => self::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 */ public function addFilter($name, $clause, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) { - $filter_source = Array ( - self::WHERE_FILTER => 'WhereFilter', - self::HAVING_FILTER => 'HavingFilter', - self::AGGREGATE_FILTER => 'AggregateFilter' - ); - - $filter_name = $filter_source[$filter_type]; - - $filter =& $this->$filter_name; - $filter =& $filter[$filter_scope]; - /* @var $filter kMultipleFilter */ - - $filter->addFilter($name, $clause); + $this->getFilterCollection($filter_type, $filter_scope)->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 * @return string * @access public */ public function getFilter($name, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) { - $filter_source = Array ( - self::WHERE_FILTER => 'WhereFilter', - self::HAVING_FILTER => 'HavingFilter', - self::AGGREGATE_FILTER => 'AggregateFilter' - ); - - $filter_name = $filter_source[$filter_type]; - - $filter =& $this->$filter_name; - $filter =& $filter[$filter_scope]; - /* @var $filter kMultipleFilter */ - - return $filter->getFilter($name); + return $this->getFilterCollection($filter_type, $filter_scope)->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 */ public function removeFilter($name, $filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) { - $filter_source = Array ( + $this->getFilterCollection($filter_type, $filter_scope)->removeFilter($name); + } + + /** + * Returns filter collection. + * + * @param integer $filter_type Is filter having filter or where filter. + * @param integer $filter_scope Filter subtype: FLT_NORMAL,FLT_SYSTEM,FLT_SEARCH,FLT_VIEW,FLT_CUSTOM. + * + * @return kMultipleFilter + */ + protected function getFilterCollection($filter_type = self::WHERE_FILTER, $filter_scope = self::FLT_SYSTEM) + { + $filter_source = array( self::WHERE_FILTER => 'WhereFilter', self::HAVING_FILTER => 'HavingFilter', self::AGGREGATE_FILTER => 'AggregateFilter' ); - $filter_name = $filter_source[$filter_type]; - - $filter =& $this->$filter_name; - $filter =& $filter[$filter_scope]; - /* @var $filter kMultipleFilter */ + /** @var kMultipleFilter[] $filters */ + $property_name = $filter_source[$filter_type]; + $filters =& $this->$property_name; - $filter->removeFilter($name); + return $filters[$filter_scope]; } /** * Clear all filters * * @access public */ public function clearFilters() { - $filters = $this->getFilterStructure(); + foreach ( $this->getFilterStructure() as $filter_params ) { + $property_name = $filter_params['type']; + $filter_group =& $this->$property_name; - foreach ($filters as $filter_params) { - $filter =& $this->$filter_params['type']; - $filter[ $filter_params['class'] ]->clearFilters(); + $filter_group[$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 protected */ protected 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); } } /** * Returns record count in list with/without user filters applied * * @param bool $with_filters * @return int * @access public */ public function GetRecordsCount($with_filters = true) { if (!$this->Counted) { $this->CountRecs(); } return $with_filters ? $this->RecordsCount : $this->NoFilterCount; } /** * Returns record count, that were actually selected * * @return int * @access public */ public function GetSelectedCount() { return $this->SelectedCount; } /** * Transforms given query into count query (DISTINCT is also processed) * * @param string $sql * @return string * @access public */ public function getCountSQL($sql) { if ( preg_match("/^\s*SELECT\s+DISTINCT(.*?\s)FROM(?!_)/is",$sql,$regs ) ) { return preg_replace("/^\s*SELECT\s+DISTINCT(.*?\s)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $sql); } else { return preg_replace("/^\s*SELECT(.*?\s)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(). * * @param bool $force force re-query, when already queried * @return bool * @access public */ public 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)) { if ( $this->Application->isAdmin ) { // 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); } else if ( $this->Application->HttpQuery->refererIsOurSite() ) { // no records & page > 1, try to reset to last page $this->SetPage($this->GetTotalPages()); $this->Query($force); } else { // no records & page > 1, show 404 page trigger_error('Unknown page ' . $this->Page . ' in ' . $this->getPrefixSpecial() . ' list, leading to "404 Not Found"', E_USER_NOTICE); $this->Application->UrlManager->show404(); } } $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; $this->Application->HandleEvent(new kEvent($this->getPrefixSpecial() . ':OnAfterListQuery')); return true; } /** * Adds one more record to list virtually and updates all counters * * @param Array $record * @access public */ public function addRecord($record) { $this->Records[] = $record; $this->SelectedCount++; $this->RecordsCount++; } /** * Calculates totals based on config * * @access protected */ protected function CalculateTotals() { $fields = Array(); $this->Totals = Array(); if ($this->gridName) { $grids = $this->Application->getUnitOption($this->Prefix, 'Grids'); $grid_fields = $grids[$this->gridName]['Fields']; } else { $grid_fields = $this->Fields; } foreach ($grid_fields as $field_name => $field_options) { if ($this->gridName && array_key_exists('totals', $field_options) && $field_options['totals']) { $totals = $field_options['totals']; } elseif (array_key_exists('totals', $this->Fields[$field_name]) && $this->Fields[$field_name]['totals']) { $totals = $this->Fields[$field_name]['totals']; } else { continue; } $calculated_field = array_key_exists($field_name, $this->CalculatedFields) && array_key_exists($field_name, $this->VirtualFields); $db_field = !array_key_exists($field_name, $this->VirtualFields); 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 ; } $sql = $this->GetSelectSQL(true, false); $fields = str_replace('%1$s', $this->TableName, implode(', ', $fields)); if ( preg_match("/DISTINCT(.*?\s)FROM(?!_)/is",$sql,$regs ) ) { $sql = preg_replace("/^\s*SELECT DISTINCT(.*?\s)FROM(?!_)/is", 'SELECT '.$fields.' FROM', $sql); } else { $sql = preg_replace("/^\s*SELECT(.*?\s)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; } /** * Returns previously calculated total (not formatted) * * @param string $field * @param string $total_function * @return float * @access public */ public function getTotal($field, $total_function) { if (!$this->TotalsCalculated) { $this->CalculateTotals(); } return $this->Totals[$field . '_' . $total_function]; } function setTotal($field, $total_function, $value) { $this->Totals[$field . '_' . $total_function] = $value; } function getTotalFunction($field) { if ($this->gridName) { $grids = $this->Application->getUnitOption($this->Prefix, 'Grids'); $field_options = $grids[$this->gridName]['Fields'][$field]; } else { $field_options = $this->Fields[$field]; } if ($this->gridName && array_key_exists('totals', $field_options) && $field_options['totals']) { return $field_options['totals']; } elseif (array_key_exists('totals', $this->Fields[$field]) && $this->Fields[$field]['totals']) { return $this->Fields[$field]['totals']; } return false; } /** * Returns previously calculated total (formatted) * * @param string $field * @param string $total_function * @return float * @access public */ function GetFormattedTotal($field, $total_function) { $res = $this->getTotal($field, $total_function); $formatter_class = $this->GetFieldOption($field, 'formatter'); if ( $formatter_class ) { $formatter = $this->Application->recallObject($formatter_class); /* @var $formatter kFormatter */ $res = $formatter->Format($res, $field, $this); } return $res; } /** * Builds full select query except for LIMIT clause * * @param bool $for_counting * @param bool $system_filters_only * @param string $keep_clause * @return string * @access public */ public function GetSelectSQL($for_counting = false, $system_filters_only = false, $keep_clause = '') { $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 ( $for_counting ) { $usage_string = $where . '|' . $having . '|' . $order . '|' . $group . '|' . $keep_clause; $optimizer = new LeftJoinOptimizer($q, $this->replaceModePrefix( str_replace('%1$s', $this->TableName, $usage_string) )); $q = $optimizer->simplify(); } 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) ); } /** * Replaces all calculated field occurrences with their associated expressions * * @param string $clause where clause to extract calculated fields from * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only * @param bool $replace_table * @return string * @access public */ public function extractCalculatedFields($clause, $aggregated = 1, $replace_table = false) { $fields = $this->getCalculatedFields($aggregated); if ( is_array($fields) && count($fields) > 0 ) { $fields = str_replace('%2$s', $this->Application->GetVar('m_lang'), $fields); 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 $replace_table ? str_replace('%1$s', $this->TableName, $clause) : $clause; } /** * Returns WHERE clause of the query * * @param bool $for_counting merge where filters with having filters + replace field names for having fields with their values * @param bool $system_filters_only * @return string * @access private */ private function GetWhereClause($for_counting=false,$system_filters_only=false) { $where = $this->Application->makeClass('kMultipleFilter'); /* @var $where kMultipleFilter */ $where->addFilter( 'system_where', $this->extractCalculatedFields($this->WhereFilter[self::FLT_SYSTEM]->getSQL()) ); if (!$system_filters_only) { $where->addFilter('view_where', $this->WhereFilter[self::FLT_VIEW] ); $search_w = $this->WhereFilter[self::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[self::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[self::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[self::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[kDBList::FLT_SYSTEM]->getSQL()) ); if (!$system_filters_only) $where->addFilter('view_having', $this->extractCalculatedFields( $this->HavingFilter[kDBList::FLT_VIEW]->getSQL() ) ); } return $where->getSQL(); } /** * 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 private */ private function GetHavingClause($for_counting=false, $system_filters_only=false, $aggregated = 0) { if ($for_counting) { $aggregate_filter = $this->Application->makeClass('kMultipleFilter'); /* @var $aggregate_filter kMultipleFilter */ $aggregate_filter->addFilter('aggregate_system', $this->AggregateFilter[kDBList::FLT_SYSTEM]); if (!$system_filters_only) { $aggregate_filter->addFilter('aggregate_view', $this->AggregateFilter[kDBList::FLT_VIEW]); } return $this->extractCalculatedFields($aggregate_filter->getSQL(), 2); } $having = $this->Application->makeClass('kMultipleFilter'); /* @var $having kMultipleFilter */ $having->addFilter('system_having', $this->HavingFilter[kDBList::FLT_SYSTEM] ); if ($aggregated == 0) { if (!$system_filters_only) { $having->addFilter('view_aggregated', $this->AggregateFilter[kDBList::FLT_VIEW] ); } $having->addFilter('system_aggregated', $this->AggregateFilter[kDBList::FLT_SYSTEM]); } if (!$system_filters_only) { $having->addFilter('view_having', $this->HavingFilter[kDBList::FLT_VIEW] ); $having->addFilter('custom_having', $this->HavingFilter[kDBList::FLT_CUSTOM] ); $search_w = $this->WhereFilter[kDBList::FLT_SEARCH]->getSQL(); if (!$search_w) { $having->addFilter('search_having', $this->HavingFilter[kDBList::FLT_SEARCH] ); } } return $having->getSQL(); } /** * Returns GROUP BY clause of the query * * @return string * @access protected */ protected function GetGroupClause() { return $this->GroupByFields ? implode(',', $this->GroupByFields) : ''; } /** * Adds new group by field * * @param string $field * @access public */ public function AddGroupByField($field) { $this->GroupByFields[$field] = $field; } /** * Removes group by field added before * * @param string $field * @access public */ public function RemoveGroupByField($field) { unset($this->GroupByFields[$field]); } /** * Adds order field to ORDER BY clause * * @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 int * @access public */ public 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'])) { // for now kMultiLanguage formatter is only supported for real (non-virtual) fields $is_expression = true; $field = $this->getMLSortField($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); return count($this->OrderFields) - 1; } /** * Sets new order fields, replacing existing ones * * @param Array $order_fields * @return void * @access public */ public function setOrderFields($order_fields) { $this->OrderFields = $order_fields; } /** * Changes sorting direction for a given sorting field index * * @param int $field_index * @param string $direction * @return void * @access public */ public function changeOrderDirection($field_index, $direction) { if ( !isset($this->OrderFields[$field_index]) ) { return; } $this->OrderFields[$field_index][1] = $direction; } /** * Returns expression, used to sort given multilingual field * * @param string $field * @return string */ function getMLSortField($field) { $table_name = '`' . $this->TableName . '`'; $lang = $this->Application->GetVar('m_lang'); $primary_lang = $this->Application->GetDefaultLanguageId(); $ret = 'IF(COALESCE(%1$s.l' . $lang . '_' . $field . ', ""), %1$s.l' . $lang . '_' . $field . ', %1$s.l' . $primary_lang . '_' . $field . ')'; return sprintf($ret, $table_name); } /** * Removes all order fields * * @access public */ public 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. * * @return string * @access private */ private 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; } /** * Returns order field name in given position * * @param int $pos * @param bool $no_default * @return string * @access public */ public function GetOrderField($pos = NULL, $no_default = false) { if ( !(isset($this->OrderFields[$pos]) && $this->OrderFields[$pos]) && !$no_default ) { $pos = 0; } if ( isset($this->OrderFields[$pos][0]) ) { $field = $this->OrderFields[$pos][0]; $lang = $this->Application->GetVar('m_lang'); if ( preg_match('/^IF\(COALESCE\(.*?\.(l' . $lang . '_.*?), ""\),/', $field, $regs) ) { // undo result of kDBList::getMLSortField method return $regs[1]; } return $field; } return ''; } /** * Returns list order fields * * @return Array * @access public */ public function getOrderFields() { return $this->OrderFields; } /** * Returns order field direction in given position * * @param int $pos * @param bool $no_default * @return string * @access public */ public 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] : ''; } /** * Returns ID of currently processed record * * @return int * @access public */ 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 * @access public */ public function IsNewItem() { // no such thing as NewItem for lists :) return false; } /** * Return unformatted field value * * @param string $name * @return string * @access public */ 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; } /** * Checks if requested field is present after database query * * @param string $name * @return bool * @access public */ public function HasField($name) { $row =& $this->getCurrentRecord(); return isset($row[$name]); } /** * Returns current record fields * * @return Array * @access public */ public function GetFieldValues() { $record =& $this->getCurrentRecord(); return $record; } /** * Returns current record from list * * @param int $offset Offset relative to current record index * @return Array * @access public */ public function &getCurrentRecord($offset = 0) { $record_index = $this->CurrentIndex + $offset; if ($record_index >=0 && $record_index < $this->SelectedCount) { return $this->Records[$record_index]; } $false = false; return $false; } /** * Goes to record with given index * * @param int $index * @access public */ public function GoIndex($index) { $this->CurrentIndex = $index; } /** * Goes to first record * * @access public */ public function GoFirst() { $this->CurrentIndex = 0; } /** * Goes to next record * * @access public */ public function GoNext() { $this->CurrentIndex++; } /** * Goes to previous record * * @access public */ public function GoPrev() { if ($this->CurrentIndex>0) { $this->CurrentIndex--; } } /** * Checks if there is no end of list * * @return bool * @access public */ public function EOL() { return ($this->CurrentIndex >= $this->SelectedCount); } /** * Returns total page count based on list per-page * * @return int * @access public */ public function GetTotalPages() { if ( !$this->Counted ) { $this->CountRecs(); } if ( $this->PerPage == -1 ) { return 1; } $integer_part = ($this->RecordsCount - ($this->RecordsCount % $this->PerPage)) / $this->PerPage; $reminder = ($this->RecordsCount % $this->PerPage) != 0; // adds 1 if there is a reminder $this->TotalPages = $integer_part + $reminder; return $this->TotalPages; } /** * Sets number of records to query per page * * @param int $per_page Number of records to display per page * @access public */ public function SetPerPage($per_page) { $this->PerPage = $per_page; } /** * Returns records per page count * * @param bool $in_fact * @return int * @access public */ public function GetPerPage($in_fact = false) { if ($in_fact) { return $this->PerPage; } return $this->PerPage == -1 ? $this->RecordsCount : $this->PerPage; } /** * Sets current page in list * * @param int $page * @access public */ public 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(); } /** * Returns current list page * * @return int * @access public */ public function GetPage() { return $this->Page; } /** * Sets list query offset * * @param int $offset * @access public */ public function SetOffset($offset) { $this->Offset = $offset; } /** * Gets list query offset * * @return int * @access public */ public function GetOffset() { return $this->Offset; } /** * Sets current item field value (doesn't apply formatting) * * @param string $name Name of the field * @param mixed $value Value to set the field to * @access public */ public 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 */ 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); /* @var $parent_object kDBItem */ if (!$parent_object->isLoaded()) { $this->addFilter('parent_filter', 'FALSE'); trigger_error('Parent ID not found (prefix: "' . rtrim($parent_prefix.'.'.$special, '.') . '"; sub-prefix: "' . $this->getPrefixSpecial() . '")', E_USER_NOTICE); return ; } // only for list in this case $parent_id = $parent_object->GetDBField($parent_table_key); $this->addFilter('parent_filter', '`' . $this->TableName . '`.`' . $foreign_key_field . '` = ' . $this->Conn->qstr($parent_id)); } } /** * Returns true if list was queried (same name as for kDBItem for easy usage) * * @return bool * @access public */ public function isLoaded() { return $this->Queried && !$this->EOL(); } /** * 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 * @access public */ public 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 overwrite existing pseudo translation. * * @param string $field * @param string $pseudo * @param string $error_label * @param Array $error_params * @return bool * @access public * @see kSearchHelper::processRangeField() * @see kDateFormatter::Parse() */ public 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; } /** * Returns error pseudo * * @param string $field * @return string * @access public * @see kSearchHelper::processRangeField() */ public function GetErrorPseudo($field) { if ( !isset($this->FieldErrors[$field]) ) { return ''; } return isset($this->FieldErrors[$field]['pseudo']) ? $this->FieldErrors[$field]['pseudo'] : ''; } /** * Removes error on field * * @param string $field * @access public */ public function RemoveError($field) { unset( $this->FieldErrors[$field] ); } /** * Group list records by header, saves internal order in group * * @param string $heading_field * @access public */ public function groupRecords($heading_field) { $i = 0; $sorted = Array (); while ($i < $this->SelectedCount) { $sorted[ $this->Records[$i][$heading_field] ][] = $this->Records[$i]; $i++; } $this->Records = Array (); foreach ($sorted as $heading => $heading_records) { $this->Records = array_merge_recursive($this->Records, $heading_records); } } /** * Reset list (use for requering purposes) * * @access public */ public function reset() { $this->Counted = false; $this->clearFilters(); $this->ClearOrderFields(); } /** * Checks if list was counted * * @return bool * @access public */ public function isCounted() { return $this->Counted; } /** * Tells, that given list is main * * @return bool * @access public */ public function isMainList() { return $this->mainList; } /** * Makes given list as main * * @access public */ public function becameMain() { $this->mainList = true; } /** * Moves recordset pointer to first element * * @return void * @access public * @implements Iterator::rewind */ public function rewind() { $this->Query(); $this->GoFirst(); } /** * Returns value at current position * * @return mixed * @access public * @implements Iterator::current */ function current() { return $this->getCurrentRecord(); } /** * Returns key at current position * * @return mixed * @access public * @implements Iterator::key */ function key() { return $this->CurrentIndex; } /** * Moves recordset pointer to next position * * @return void * @access public * @implements Iterator::next */ function next() { $this->GoNext(); } /** * Detects if current position is within recordset bounds * * @return bool * @access public * @implements Iterator::valid */ public function valid() { return !$this->EOL(); } /** * Counts recordset rows * * @return int * @access public * @implements Countable::count */ public function count() { return $this->SelectedCount; } } class LeftJoinOptimizer { /** * Input sql for optimization * * @var string * @access private */ private $sql = ''; /** * All sql parts, where LEFT JOINed table aliases could be used * * @var string * @access private */ private $usageString = ''; /** * List of discovered LEFT JOINs * * @var Array * @access private */ private $joins = Array (); /** * LEFT JOIN relations * * @var Array * @access private */ private $joinRelations = Array (); /** * LEFT JOIN table aliases scheduled for removal * * @var Array * @access private */ private $aliasesToRemove = Array (); /** * Creates new instance of the class * * @param string $sql * @param string $usage_string */ public function __construct($sql, $usage_string) { $this->sql = $sql; $this->usageString = $usage_string; $this->parseJoins(); } /** * Tries to remove unused LEFT JOINs * * @return string * @access public */ public function simplify() { if ( !$this->joins ) { // no LEFT JOIN used, return unchanged sql return $this->sql; } $this->updateRelations(); $this->removeAliases(); return $this->sql; } /** * Discovers LEFT JOINs based on given sql * * @return void * @access private */ private function parseJoins() { if ( !preg_match_all('/LEFT\s+JOIN\s+(.*?|.*?\s+AS\s+.*?|.*?\s+.*?)\s+ON\s+(.*?\n|.*?$)/si', $this->sql, $regs) ) { $this->joins = Array (); } // get all LEFT JOIN clause info from sql (without filters) foreach ($regs[1] as $index => $match) { $match_parts = preg_split('/\s+AS\s+|\s+/i', $match, 2); $table_alias = count($match_parts) == 1 ? $match : $match_parts[1]; $this->joins[$table_alias] = Array ( 'table' => $match_parts[0], 'join_clause' => $regs[0][$index], ); } } /** * Detects relations between LEFT JOINs * * @return void * @access private */ private function updateRelations() { foreach ($this->joins as $table_alias => $left_join_info) { $escaped_alias = preg_quote($table_alias, '/'); foreach ($this->joins as $sub_table_alias => $sub_left_join_info) { if ($table_alias == $sub_table_alias) { continue; } if ( $this->matchAlias($escaped_alias, $sub_left_join_info['join_clause']) ) { $this->joinRelations[] = $sub_table_alias . ':' . $table_alias; } } } } /** * Removes scheduled LEFT JOINs, but only if they are not protected * * @return void * @access private */ private function removeAliases() { $this->prepareAliasesRemoval(); foreach ($this->aliasesToRemove as $to_remove_alias) { if ( !$this->aliasProtected($to_remove_alias) ) { $this->sql = str_replace($this->joins[$to_remove_alias]['join_clause'], '', $this->sql); } } } /** * Schedules unused LEFT JOINs to for removal * * @return void * @access private */ private function prepareAliasesRemoval() { foreach ($this->joins as $table_alias => $left_join_info) { $escaped_alias = preg_quote($table_alias, '/'); if ( !$this->matchAlias($escaped_alias, $this->usageString) ) { $this->aliasesToRemove[] = $table_alias; } } } /** * Checks if someone wants to remove LEFT JOIN, but it's used by some other LEFT JOIN, that stays * * @param string $table_alias * @return bool * @access private */ private function aliasProtected($table_alias) { foreach ($this->joinRelations as $relation) { list ($main_alias, $used_alias) = explode(':', $relation); if ( ($used_alias == $table_alias) && !in_array($main_alias, $this->aliasesToRemove) ) { return true; } } return false; } /** * Matches given escaped alias to a string * * @param string $escaped_alias * @param string $string * @return bool * @access private */ private function matchAlias($escaped_alias, $string) { return preg_match('/(`' . $escaped_alias . '`|' . $escaped_alias . ')\./', $string); } } Index: branches/5.2.x/core/kernel/db/db_connection.php =================================================================== --- branches/5.2.x/core/kernel/db/db_connection.php (revision 16423) +++ branches/5.2.x/core/kernel/db/db_connection.php (revision 16424) @@ -1,1418 +1,1409 @@ '', 'user' => '', 'pass' => '', 'db' => ''); /** * Index of database server * * @var int * @access protected */ protected $serverIndex = 0; /** * Handle of currently processed recordset * * @var mysqli_result * @access protected */ protected $queryID = null; /** * Function to handle sql errors * * @var Array|string * @access public */ public $errorHandler = ''; /** * Error code * * @var int * @access protected */ protected $errorCode = 0; /** * Error message * * @var string * @access protected */ protected $errorMessage = ''; /** * Defines if database connection * operations should generate debug * information * * @var bool * @access public */ public $debugMode = false; /** * Save query execution statistics * * @var bool * @access protected */ protected $_captureStatistics = false; /** * Last query to database * * @var string * @access public */ public $lastQuery = ''; /** * Total processed queries count * * @var int * @access protected */ protected $_queryCount = 0; /** * Total time, used for serving queries * * @var Array * @access protected */ protected $_queryTime = 0; /** * Indicates, that next database query could be cached, when memory caching is enabled * * @var bool * @access public */ public $nextQueryCachable = false; /** * For backwards compatibility with kDBLoadBalancer class * * @var bool * @access public */ public $nextQueryFromMaster = false; /** * Initializes connection class with * db type to used in future * * @param string $db_type * @param string $error_handler * @param int $server_index * @access public */ public function __construct($db_type, $error_handler = '', $server_index = 0) { if ( class_exists('kApplication') ) { // prevents "Fatal Error" on 2nd installation step (when database is empty) parent::__construct(); } $this->serverIndex = $server_index; if ( !$error_handler ) { $this->errorHandler = Array(&$this, 'handleError'); } else { $this->errorHandler = $error_handler; } $this->_captureStatistics = defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !(defined('ADMIN') && ADMIN); } /** * Set's custom error * * @param int $code * @param string $msg * @access protected */ protected function setError($code, $msg) { $this->errorCode = $code; $this->errorMessage = $msg; } /** * Checks if previous query execution * raised an error. * * @return bool * @access public */ public function hasError() { return $this->errorCode != 0; } /** * 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 * @param bool $retry * * @return bool * @access public */ public function Connect($host, $user, $pass, $db, $retry = false) { $this->connectionParams = Array ('host' => $host, 'user' => $user, 'pass' => $pass, 'db' => $db); $this->setError(0, ''); // reset error $this->connectionID = mysqli_connect($host, $user, $pass, $db); $this->errorCode = mysqli_connect_errno(); if ( is_object($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 . '\''); } if ( !$this->hasError() ) { $this->connectionOpened = true; return true; } } $this->errorMessage = mysqli_connect_error(); $error_msg = 'Database connection failed, please check your connection settings.
Error (' . $this->errorCode . '): ' . $this->errorMessage; if ( (defined('IS_INSTALL') && IS_INSTALL) || $retry ) { trigger_error($error_msg, E_USER_WARNING); } else { $this->Application->redirectToMaintenance(); throw new RuntimeException($error_msg); } $this->connectionOpened = false; return false; } /** * Checks if connection to database is opened * * @return bool * @access public */ public function connectionOpened() { return $this->connectionOpened; } /** * Setups the connection according given configuration * * @param Array $config * @return bool * @access public */ public function setup($config) { if ( is_object($this->Application) ) { $this->debugMode = $this->Application->isDebugMode(); } return $this->Connect( $config['Database']['DBHost'], $config['Database']['DBUser'], $config['Database']['DBUserPassword'], $config['Database']['DBName'] ); } /** * Performs 3 reconnect attempts in case if connection to a DB was lost in the middle of script run (e.g. server restart) * * @return bool * @access protected */ protected function ReConnect() { $retry_count = 0; $connected = false; $this->connectionID->close(); while ( $retry_count < 3 ) { sleep(5); // wait 5 seconds before each reconnect attempt $connected = $this->Connect( $this->connectionParams['host'], $this->connectionParams['user'], $this->connectionParams['pass'], $this->connectionParams['db'], true ); if ( $connected ) { break; } $retry_count++; } return $connected; } /** * Shows error message from previous operation * if it failed * * @param string $sql * @param string $key_field * @param bool $no_debug * @return bool * @access protected */ protected function showError($sql = '', $key_field = null, $no_debug = false) { static $retry_count = 0; if ( !is_object($this->connectionID) ) { // no connection while doing mysql_query $this->errorCode = mysqli_connect_errno(); if ( $this->hasError() ) { $this->errorMessage = mysqli_connect_error(); $ret = $this->callErrorHandler($sql); if (!$ret) { exit; } } return false; } // checking if there was an error during last mysql_query $this->errorCode = $this->connectionID->errno; if ( $this->hasError() ) { $this->errorMessage = $this->connectionID->error; $ret = $this->callErrorHandler($sql); if ( ($this->errorCode == 2006 || $this->errorCode == 2013) && ($retry_count < 3) ) { // #2006 - MySQL server has gone away // #2013 - Lost connection to MySQL server during query $retry_count++; if ( $this->ReConnect() ) { return $this->Query($sql, $key_field, $no_debug); } } if (!$ret) { exit; } } else { $retry_count = 0; } return false; } /** * Sends db error to a predefined error handler * * @param $sql * @return bool * @access protected */ protected function callErrorHandler($sql) { - 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); - } - - return $ret; + return call_user_func($this->errorHandler, $this->errorCode, $this->errorMessage, $sql); } /** * Default error handler for sql errors * * @param int $code * @param string $msg * @param string $sql * @return bool * @access public */ public function handleError($code, $msg, $sql) { echo 'Processing SQL: ' . $sql . '
'; echo 'Error (' . $code . '): ' . $msg . '
'; return false; } /** * Returns first field of first line * of recordset if query ok or false * otherwise * * @param string $sql * @param int $offset * @return string * @access public */ 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 string $sql * @param int $offset * @return Array * @access public */ 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 */ 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; } /** * Returns iterator for 1st column of a recordset or false in case of error. * 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 bool|kMySQLQueryCol */ public function GetColIterator($sql, $key_field = null) { return $this->GetIterator($sql, $key_field, false, 'kMySQLQueryCol'); } /** * 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 * @param bool $no_debug * @return Array * @access public */ public function Query($sql, $key_field = null, $no_debug = false) { $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin $start_time = $this->_captureStatistics ? microtime(true) : 0; // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { $ret = Array (); if ( isset($key_field) ) { while ( $row = $this->queryID->fetch_assoc() ) { $ret[$row[$key_field]] = $row; } } else { while ( $row = $this->queryID->fetch_assoc() ) { $ret[] = $row; } } // set 2nd checkpoint: begin if ( $this->_captureStatistics ) { $query_time = microtime(true) - $start_time; if ( $query_time > DBG_MAX_SQL_TIME ) { $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 += microtime(true) - $start_time; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field, $no_debug); } /** * Returns iterator to a recordset, produced from running $sql query Queries db with $sql query supplied and returns kMySQLQuery iterator * or false in case of error. 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 * @param bool $no_debug * @param string $iterator_class * @return kMySQLQuery|bool * @access public */ public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery') { $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin $start_time = $this->_captureStatistics ? microtime(true) : 0; // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { $ret = new $iterator_class($this->queryID, $key_field); /* @var $ret kMySQLQuery */ // set 2nd checkpoint: begin if ( $this->_captureStatistics ) { $query_time = microtime(true) - $start_time; if ( $query_time > DBG_MAX_SQL_TIME ) { $this->Application->logSlowQuery($sql, $query_time); } $this->_queryTime += $query_time; } // set 2nd checkpoint: end return $ret; } else { // set 2nd checkpoint: begin if ( $this->_captureStatistics ) { $this->_queryTime += microtime(true) - $start_time; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field, $no_debug); } /** * Free memory used to hold recordset handle * * @access public */ public function Destroy() { $this->queryID->free(); unset($this->queryID); } /** * Performs sql query, that will change database content * * @param string $sql * @return bool * @access public */ public function ChangeQuery($sql) { $this->Query($sql); return !$this->hasError(); } /** * Returns auto increment field value from * insert like operation if any, zero otherwise * * @return int * @access public */ public function getInsertID() { return $this->connectionID->insert_id; } /** * Returns row count affected by last query * * @return int * @access public */ public function getAffectedRows() { return $this->connectionID->affected_rows; } /** * Returns LIMIT sql clause part for specific db * * @param int $offset * @param int $rows * @return string * @access public */ public function getLimitClause($offset, $rows) { if ( !($rows > 0) ) { return ''; } return 'LIMIT ' . $offset . ',' . $rows; } /** * If it's a string, adds quotes and backslashes (only work since PHP 4.3.0) * Otherwise returns as-is * * @param mixed $string * @return string * @access public */ public function qstr($string) { if ( is_null($string) ) { return 'NULL'; } # 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 "'" . $this->connectionID->real_escape_string($string) . "'"; } /** * Calls "qstr" function for each given array element * * @param Array $array * @param string $function * @return Array */ public function qstrArray($array, $function = 'qstr') { return array_map(Array (&$this, $function), $array); } /** * Escapes strings (only work since PHP 4.3.0) * * @param mixed $string * @return string * @access public */ public function escape($string) { if ( is_null($string) ) { return 'NULL'; } $string = $this->connectionID->real_escape_string($string); // prevent double-escaping of MySQL wildcard symbols ("%" and "_") in case if they were already escaped return str_replace(Array ('\\\\%', '\\\\_'), Array ('\\%', '\\_'), $string); } /** * Returns last error code occurred * * @return int * @access public */ public function getErrorCode() { return $this->errorCode; } /** * Returns last error message * * @return string * @access public */ 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 * @access public */ public 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 .= $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) . ')'; $value_sqls = Array (); // reset before query to prevent repeated call from error handler to insert 2 records instead of 1 $insert_result = $this->ChangeQuery($sql); } return $insert_result; } /** * Update given field values to given record using $key_clause * * @param Array $fields_hash * @param string $table * @param string $key_clause * @return bool * @access public */ public 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.'` = ' . $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 presence in database * * @param string $table_name * @param bool $force * @return bool * @access public */ public function TableFound($table_name, $force = false) { static $table_found = false; if ( $table_found === false ) { $table_found = array_flip($this->GetCol('SHOW TABLES')); } if ( !preg_match('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', $table_name) ) { $table_name = TABLE_PREFIX . $table_name; } if ( $force ) { if ( $this->Query('SHOW TABLES LIKE ' . $this->qstr($table_name)) ) { $table_found[$table_name] = 1; } else { unset($table_found[$table_name]); } } return isset($table_found[$table_name]); } /** * Returns query processing statistics * * @return Array * @access public */ public function getQueryStatistics() { return Array ('time' => $this->_queryTime, 'count' => $this->_queryCount); } /** * Get status information from SHOW STATUS in an associative array * * @param string $which * @return Array * @access public */ public function getStatus($which = '%') { $status = Array (); $records = $this->Query('SHOW STATUS LIKE "' . $which . '"'); foreach ($records as $record) { $status[ $record['Variable_name'] ] = $record['Value']; } return $status; } /** * Get slave replication lag. It will only work if the DB user has the PROCESS privilege. * * @return int * @access public */ public function getSlaveLag() { // don't use kDBConnection::Query method, since it will create an array of all server processes $processes = $this->GetIterator('SHOW PROCESSLIST'); $skip_states = Array ( 'Waiting for master to send event', 'Connecting to master', 'Queueing master event to the relay log', 'Waiting for master update', 'Requesting binlog dump', ); // find slave SQL thread foreach ($processes as $process) { if ( $process['User'] == 'system user' && !in_array($process['State'], $skip_states) ) { // this is it, return the time (except -ve) return $process['Time'] > 0x7fffffff ? false : $process['Time']; } } return false; } } class kDBConnectionDebug extends kDBConnection { protected $_profileSQLs = false; /** * Info about this database connection to show in debugger report * * @var string * @access protected */ protected $serverInfoLine = ''; /** * Initializes connection class with * db type to used in future * * @param string $db_type * @param string $error_handler * @param int $server_index * @access public */ public function __construct($db_type, $error_handler = '', $server_index = 0) { parent::__construct($db_type, $error_handler, $server_index); $this->_profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE; } /** * 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 * @param bool $force_new * @param bool $retry * @return bool * @access public */ public function Connect($host, $user, $pass, $db, $force_new = false, $retry = false) { if ( defined('DBG_SQL_SERVERINFO') && DBG_SQL_SERVERINFO ) { $this->serverInfoLine = $this->serverIndex . ' (' . $host . ')'; } return parent::Connect($host, $user, $pass, $db, $force_new, $retry); } /** * 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 * @param bool $no_debug * @return Array * @access public */ public function Query($sql, $key_field = null, $no_debug = false) { if ( $no_debug ) { return parent::Query($sql, $key_field, $no_debug); } global $debugger; $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin if ( $this->_profileSQLs ) { $queryID = $debugger->generateID(); $debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql)); } // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { $ret = Array (); if ( isset($key_field) ) { while ( $row = $this->queryID->fetch_assoc() ) { $ret[$row[$key_field]] = $row; } } else { while ( $row = $this->queryID->fetch_assoc() ) { $ret[] = $row; } } // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $current_element = current($ret); $first_cell = count($ret) == 1 && count($current_element) == 1 ? current($current_element) : null; if ( strlen($first_cell) > 200 ) { $first_cell = substr($first_cell, 0, 50) . ' ...'; } $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end $this->Destroy(); return $ret; } else { // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field); } /** * Queries db with $sql query supplied and returns kMySQLQuery iterator * or false in case of error. 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 * @param bool $no_debug * @param string $iterator_class * @return kMySQLQuery|bool * @access public */ public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery') { if ( $no_debug ) { return parent::Query($sql, $key_field, $no_debug, $iterator_class); } global $debugger; $this->_queryCount++; $this->lastQuery = $sql; // set 1st checkpoint: begin if ( $this->_profileSQLs ) { $queryID = $debugger->generateID(); $debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql)); } // set 1st checkpoint: end $this->setError(0, ''); // reset error $this->queryID = $this->connectionID->query($sql); if ( is_object($this->queryID) ) { $ret = new $iterator_class($this->queryID, $key_field); /* @var $ret kMySQLQuery */ // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $first_cell = count($ret) == 1 && $ret->fieldCount() == 1 ? current($ret->current()) : null; if ( strlen($first_cell) > 200 ) { $first_cell = substr($first_cell, 0, 50) . ' ...'; } $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end return $ret; } else { // set 2nd checkpoint: begin if ( $this->_profileSQLs ) { $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine); $debugger->profilerAddTotal('sql', 'sql_' . $queryID); $this->nextQueryCachable = false; } // set 2nd checkpoint: end } return $this->showError($sql, $key_field); } } class kMySQLQuery implements Iterator, Countable, SeekableIterator { /** * Current index in recordset * * @var int * @access protected */ protected $position = -1; /** * Query resource * * @var mysqli_result * @access protected */ protected $result; /** * Field to act as key in a resulting array * * @var string * @access protected */ protected $keyField = null; /** * Data in current row of recordset * * @var Array * @access protected */ protected $rowData = Array (); /** * Row count in a result * * @var int * @access protected */ protected $rowCount = 0; /** * Creates new instance of a class * * @param mysqli_result $result * @param null|string $key_field */ public function __construct(mysqli_result $result, $key_field = null) { $this->result = $result; $this->keyField = $key_field; $this->rowCount = $this->result->num_rows; $this->rewind(); } /** * Moves recordset pointer to first element * * @return void * @access public * @implements Iterator::rewind */ public function rewind() { $this->seek(0); } /** * Returns value at current position * * @return mixed * @access public * @implements Iterator::current */ function current() { return $this->rowData; } /** * Returns key at current position * * @return mixed * @access public * @implements Iterator::key */ function key() { return $this->keyField ? $this->rowData[$this->keyField] : $this->position; } /** * Moves recordset pointer to next position * * @return void * @access public * @implements Iterator::next */ function next() { $this->seek($this->position + 1); } /** * Detects if current position is within recordset bounds * * @return bool * @access public * @implements Iterator::valid */ public function valid() { return $this->position < $this->rowCount; } /** * Counts recordset rows * * @return int * @access public * @implements Countable::count */ public function count() { return $this->rowCount; } /** * Counts fields in current row * * @return int * @access public */ public function fieldCount() { return count($this->rowData); } /** * Moves cursor into given position within recordset * * @param int $position * @throws OutOfBoundsException * @access public * @implements SeekableIterator::seek */ public function seek($position) { if ( $this->position == $position ) { return; } $this->position = $position; if ( $this->valid() ) { $this->result->data_seek($this->position); $this->rowData = $this->result->fetch_assoc(); } /*if ( !$this->valid() ) { throw new OutOfBoundsException('Invalid seek position (' . $position . ')'); }*/ } /** * Returns first recordset row * * @return Array * @access public */ public function first() { $this->seek(0); return $this->rowData; } /** * Closes recordset and freese memory * * @return void * @access public */ public function close() { $this->result->free(); unset($this->result); } /** * Frees memory when object is destroyed * * @return void * @access public */ public function __destruct() { $this->close(); } /** * Returns all keys * * @return Array * @access public */ public function keys() { $ret = Array (); foreach ($this as $key => $value) { $ret[] = $key; } return $ret; } /** * Returns all values * * @return Array * @access public */ public function values() { $ret = Array (); foreach ($this as $value) { $ret[] = $value; } return $ret; } /** * Returns whole recordset as array * * @return Array * @access public */ public function toArray() { $ret = Array (); foreach ($this as $key => $value) { $ret[$key] = $value; } return $ret; } } class kMySQLQueryCol extends kMySQLQuery { /** * Returns value at current position * * @return mixed * @access public * @implements Iterator::current */ function current() { return reset($this->rowData); } /** * Returns first column of first recordset row * * @return string * @access public */ public function first() { $this->seek(0); return reset($this->rowData); } } Index: branches/5.2.x/core/units/categories/cache_updater.php =================================================================== --- branches/5.2.x/core/units/categories/cache_updater.php (revision 16423) +++ branches/5.2.x/core/units/categories/cache_updater.php (revision 16424) @@ -1,544 +1,553 @@ Stack = Array(); } function Push($values) { array_push($this->Stack, $values); } function Pop() { if ($this->Count() > 0) { return array_pop($this->Stack); } else { return false; } } function Get() { if ($this->Count() > 0) { // return end($this->Stack); return $this->Stack[count($this->Stack)-1]; } else { return false; } } function Update($values) { $this->Stack[count($this->Stack)-1] = $values; } function Count() { return count($this->Stack); } } class clsCachedPermissions { var $Allow = Array(); var $Deny = Array(); var $CatId; /** * Table name used for inserting permissions * * @var string */ var $table = ''; - function clsCachedPermissions($CatId, $table_name) + /** + * Creates class instance. + * + * @param integer $cat_id Category ID. + * @param string $table_name Table name. + */ + public function __construct($cat_id, $table_name) { - $this->CatId = $CatId; + $this->CatId = $cat_id; $this->table = $table_name; } function SetCatId($CatId) { $this->CatId = $CatId; } function CheckPermArray($Perm) { if (!isset($this->Allow[$Perm])) { $this->Allow[$Perm] = array(); $this->Deny[$Perm] = array(); } } function AddAllow($Perm, $GroupId) { $this->CheckPermArray($Perm); if (!in_array($GroupId, $this->Allow[$Perm])) { array_push($this->Allow[$Perm], $GroupId); $this->RemoveDeny($Perm, $GroupId); } } function AddDeny($Perm, $GroupId) { $this->CheckPermArray($Perm); if (!in_array($GroupId, $this->Deny[$Perm])) { array_push($this->Deny[$Perm], $GroupId); $this->RemoveAllow($Perm, $GroupId); } } function RemoveDeny($Perm, $GroupId) { if (in_array($GroupId, $this->Deny[$Perm])) { array_splice($this->Deny[$Perm], array_search($GroupId, $this->Deny[$Perm]), 1); } } function RemoveAllow($Perm, $GroupId) { if (in_array($GroupId, $this->Allow[$Perm])) { array_splice($this->Allow[$Perm], array_search($GroupId, $this->Allow[$Perm]), 1); } } function GetInsertSQL() { $values = array(); foreach ($this->Allow as $perm => $groups) { if (count($groups) > 0) { $values[] = '(' .$this->CatId. ', ' .$perm. ', "' .join(',', $groups). '")'; } } if (!$values) return ''; $sql = 'INSERT INTO '.$this->table.' (CategoryId, PermId, ACL) VALUES '.join(',', $values); return $sql; } } class kPermCacheUpdater extends kHelper { /** * Holds Stack * * @var clsRecursionStack */ var $Stack; /** * Rebuild process iteration * * @var int */ var $iteration; /** * Categories count to process * * @var unknown_type */ var $totalCats = 0; /** * Processed categories count * * @var int */ var $doneCats = 0; /** * Temporary table name used for storing cache building progress * * @var string */ var $progressTable = ''; /** * Temporary table name used for storing not fully built permissions cache * 1. preserves previous cache while new cache is building * 2. when rebuild process fails allows previous cache (in live table) is used * * @var string */ var $permCacheTable = ''; var $primaryLanguageId = 0; var $languages = Array (); var $root_prefixes = Array(); /** * Update cache only for requested categories and it's parent categories * * @var bool */ var $StrictPath = false; /** * Returns instance of perm cache updater * * @param int $continuing * @param mixed $strict_path */ public function __construct($continuing = null, $strict_path = null) { parent::__construct(); if ( isset($strict_path) ) { if ( $strict_path && !is_array($strict_path) ) { $strict_path = explode('|', trim($strict_path, '|')); } $this->StrictPath = $strict_path; } // cache widely used values to speed up process $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $this->languages = $ml_helper->getLanguages(); $this->primaryLanguageId = $this->Application->GetDefaultLanguageId(); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { $this->root_prefixes[ $module_info['RootCat'] ] = $module_info['Var']; } $this->iteration = 0; $this->progressTable = $this->Application->GetTempName('permCacheUpdate'); $this->permCacheTable = $this->Application->GetTempName(TABLE_PREFIX.'CategoryPermissionsCache'); if (!isset($continuing) || $continuing == 1) { $this->InitUpdater(); } elseif ($continuing == 2) { $this->getData(); } } function InitUpdater() { $this->Stack = new clsRecursionStack(); $this->initData(); } function getDonePercent() { if (!$this->totalCats) { return 0; } return min(100, intval( floor( $this->doneCats / $this->totalCats * 100 ) )); } function getData() { $tmp = $this->Conn->GetOne('SELECT data FROM '.$this->progressTable); if ($tmp) $tmp = unserialize($tmp); $this->totalCats = isset($tmp['totalCats']) ? $tmp['totalCats'] : 0; $this->doneCats = isset($tmp['doneCats']) ? $tmp['doneCats'] : 0; if (isset($tmp['stack'])) { $this->Stack = $tmp['stack']; } else { $this->Stack = new clsRecursionStack(); } } function setData() { $tmp = Array ( 'totalCats' => $this->totalCats, 'doneCats' => $this->doneCats, 'stack' => $this->Stack, ); $this->Conn->Query('DELETE FROM '.$this->progressTable); $fields_hash = Array('data' => serialize($tmp)); $this->Conn->doInsert($fields_hash, $this->progressTable); } function initData() { $this->clearData(); // drop table before starting anyway // 1. create table for rebuilding permissions cache $this->Conn->Query('CREATE TABLE '.$this->permCacheTable.' LIKE '.TABLE_PREFIX.'CategoryPermissionsCache'); if ($this->StrictPath) { // when using strict path leave all other cache intact $sql = 'INSERT INTO '.$this->permCacheTable.' SELECT * FROM '.TABLE_PREFIX.'CategoryPermissionsCache'; $this->Conn->Query($sql); // delete only cache related to categories in path $sql = 'DELETE FROM '.$this->permCacheTable.' WHERE CategoryId IN ('.implode(',', $this->StrictPath).')'; $this->Conn->Query($sql); } $add_charset = defined(SQL_CHARSET)? ' CHARACTER SET '.SQL_CHARSET.' ' : ''; $add_collation = defined(SQL_COLLATION)? ' COLLATE '.SQL_COLLATION.' ' : ''; $this->Conn->Query('CREATE TABLE '.$this->progressTable.'(data LONGTEXT'.$add_charset.$add_collation.') '.$add_charset.$add_collation); $this->totalCats = (int)$this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'Categories'); $this->doneCats = 0; } function clearData() { // some templates use this $sql = 'UPDATE ' . TABLE_PREFIX . 'SystemSettings SET VariableValue = VariableValue + 1 WHERE VariableName = "CategoriesRebuildSerial"'; $this->Conn->Query($sql); // always drop temporary tables $this->Conn->Query('DROP TABLE IF EXISTS '.$this->progressTable); $this->Conn->Query('DROP TABLE IF EXISTS '.$this->permCacheTable); $this->Application->deleteDBCache('ForcePermCacheUpdate'); } function SaveData() { // copy data from temp permission cache table back to live $this->Conn->Query('TRUNCATE '.TABLE_PREFIX.'CategoryPermissionsCache'); $sql = 'INSERT INTO '.TABLE_PREFIX.'CategoryPermissionsCache SELECT * FROM '.$this->permCacheTable; $this->Conn->Query($sql); $this->clearData(); $this->Application->incrementCacheSerial('c'); } function DoTheJob() { $data = $this->Stack->Get(); if ($data === false) { //If Stack is empty $data['current_id'] = 0; $data['titles'] = Array(); $data['parent_path'] = Array(); $data['named_path'] = Array(); $data['file_name'] = ''; $data['template'] = ''; // design $data['item_template'] = ''; $data['children_count'] = 0; $data['left'] = 0; $data['right'] = 2; $data['debug_title'] = 'ROOT'; $this->Stack->Push($data); } if (!isset($data['queried'])) { $this->QueryTitle($data); $this->QueryChildren($data); $data['children_count'] = count($data['children']); $this->QueryPermissions($data); $data['queried'] = 1; $data['right'] = $data['left']+1; $sql = $data['perms']->GetInsertSQL(); if ( $sql ) { $this->Conn->Query($sql); // $this->doneCats++; // moved to the place where it pops out of the stack by Kostja } $this->iteration++; } // start with first child if we haven't started yet if (!isset($data['current_child'])) $data['current_child'] = 0; // if we have more children on CURRENT LEVEL if (isset($data['children'][$data['current_child']])) { if ($this->StrictPath) { while ( isset($data['children'][ $data['current_child'] ]) && !in_array($data['children'][ $data['current_child'] ], $this->StrictPath) ) { $data['current_child']++; continue; } if (!isset($data['children'][ $data['current_child'] ])) return false; //error } $next_data = Array(); $next_data['titles'] = $data['titles']; $next_data['parent_path'] = $data['parent_path']; $next_data['named_path'] = $data['named_path']; $next_data['template'] = $data['template']; $next_data['item_template'] = $data['item_template']; $next_data['current_id'] = $data['children'][ $data['current_child'] ]; //next iteration should process child $next_data['perms'] = clone $data['perms']; // copy permissions to child - inheritance $next_data['perms']->SetCatId($next_data['current_id']); $next_data['left'] = $data['right']; $data['current_child']++; $this->Stack->Update($data); //we need to update ourself for the iteration after the next (or further) return to next child $this->Stack->Push($next_data); //next iteration should process this child return true; } else { $this->Stack->Update($data); $prev_data = $this->Stack->Pop(); //remove ourself from stack if we have finished all the childs (or there are none) $data['right'] = $prev_data['right']; $this->UpdateCachedPath($data); // we are getting here if we finished with current level, so check if it's first level - then bail out. $this->doneCats++; // moved by Kostja from above, seems to fix the prob $has_more = $this->Stack->Count() > 0; if ($has_more) { $next_data = $this->Stack->Get(); $next_data['right'] = $data['right']+1; $next_data['children_count'] += $data['children_count']; $this->Stack->Update($next_data); } return $has_more; } } function UpdateCachedPath(&$data) { if ( $data['current_id'] == 0 ) { // don't update non-existing "Home" category return; } // allow old fashion system templates to work (maybe this is no longer needed, since no such urls after upgrade from 4.3.1) $named_parent_path = strpos($data['file_name'], '/') !== false ? $data['file_name'] : implode('/', $data['named_path']); $fields_hash = Array ( 'ParentPath' => '|' . implode('|', $data['parent_path']) . '|', 'NamedParentPath' => $named_parent_path, // url component for a category page 'NamedParentPathHash' => kUtil::crc32(mb_strtolower(preg_replace('/^Content\//i', '', $named_parent_path))), 'CachedTemplate' => $data['template'], // actual template to use when category is visited 'CachedTemplateHash' => kUtil::crc32(mb_strtolower($data['template'])), 'CachedDescendantCatsQty' => $data['children_count'], 'TreeLeft' => $data['left'], 'TreeRight' => $data['right'], ); foreach ($this->languages as $language_id) { $fields_hash['l' . $language_id . '_CachedNavbar'] = implode('&|&', $data['titles'][$language_id]); } $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Categories', 'CategoryId = ' . $data['current_id']); if ( $this->Conn->getAffectedRows() > 0 ) { $this->Application->incrementCacheSerial('c', $data['current_id']); } } function QueryTitle(&$data) { $category_id = $data['current_id']; $sql = 'SELECT * FROM '.TABLE_PREFIX.'Categories WHERE CategoryId = '.$category_id; $record = $this->Conn->GetRow($sql); if ($record) { foreach ($this->languages as $language_id) { $data['titles'][$language_id][] = $record['l'.$language_id.'_Name'] ? $record['l'.$language_id.'_Name'] : $record['l'.$this->primaryLanguageId.'_Name']; } $data['debug_title'] = $record['l1_Name']; $data['parent_path'][] = $category_id; $data['named_path'][] = preg_replace('/^Content\\//', '', $record['Filename']); $data['file_name'] = $record['Filename']; // it is one of the modules root category /*$root_prefix = isset($this->root_prefixes[$category_id]) ? $this->root_prefixes[$category_id] : false; if ( $root_prefix ) { $fields_hash = Array (); if ( !$record['Template'] ) { $record['Template'] = $this->Application->ConfigValue($root_prefix . '_CategoryTemplate'); $fields_hash['Template'] = $record['Template']; } $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Categories', 'CategoryId = ' . $category_id); }*/ // if explicitly set, then use it; use parent template otherwise if ($record['Template'] && ($record['Template'] != CATEGORY_TEMPLATE_INHERIT)) { $data['template'] = $record['Template']; } } } function QueryChildren(&$data) { $sql = 'SELECT CategoryId FROM '.TABLE_PREFIX.'Categories WHERE ParentId = '.$data['current_id']; $data['children'] = $this->Conn->GetCol($sql); } function QueryPermissions(&$data) { // don't search for section "view" permissions here :) $sql = 'SELECT ipc.PermissionConfigId, ip.GroupId, ip.PermissionValue FROM '.TABLE_PREFIX.'Permissions AS ip LEFT JOIN '.TABLE_PREFIX.'CategoryPermissionsConfig AS ipc ON ipc.PermissionName = ip.Permission WHERE (CatId = '.$data['current_id'].') AND (Permission LIKE "%.VIEW") AND (ip.Type = 0)'; $records = $this->Conn->Query($sql); //create permissions array only if we don't have it yet (set by parent) if (!isset($data['perms'])) { $data['perms'] = new clsCachedPermissions($data['current_id'], $this->permCacheTable); } foreach ($records as $record) { if ($record['PermissionValue'] == 1) { $data['perms']->AddAllow($record['PermissionConfigId'], $record['GroupId']); } else { $data['perms']->AddDeny($record['PermissionConfigId'], $record['GroupId']); } } } /** * Rebuild all cache in one step * * @param string $path * @return void */ function OneStepRun($path = '') { $this->InitUpdater(); $needs_more = true; while ($needs_more) { // until proceeded in this step category count exceeds category per step limit $needs_more = $this->DoTheJob(); } $this->SaveData(); } - } \ No newline at end of file + } Index: branches/5.2.x/core/units/helpers/json_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/json_helper.php (revision 16423) +++ branches/5.2.x/core/units/helpers/json_helper.php (revision 16424) @@ -1,173 +1,172 @@ encode($data).')'; } /** * Converts PHP variable to JSON string * Can convert any PHP variable with any content to correct JSON structure * * @param mixed $data Data to convert * @return string */ function encode($data) { $type = $this->getType($data); switch ( $type ) { case 'object': $data = '{' . $this->processData($data, $type) . '}'; break; case 'array': $data = '[' . $this->processData($data, $type) . ']'; break; default: if ( is_int($data) || is_float($data) ) { $data = (string)$data; } elseif ( is_string($data) ) { $data = '"' . $this->escape($data) . '"'; } elseif ( is_bool($data) ) { $data = $data ? 'true' : 'false'; } else { $data = 'null'; } } - + return $data; } /** * Enter description here... * * @param Array $data * @param string $type * @return string */ function processData($data, $type) { $output = Array (); // If data is an object - it should be converted as a key => value pair if ( $type == 'object' ) { foreach ($data as $key => $value) { $output[] = '"' . $key . '": ' . $this->encode($value); } } else { foreach ($data as $value) { $output[] = $this->encode($value); } } return implode(',', $output); } /** * Function determines type of variable * * @param mixed $data * @return string */ function getType(&$data) { if (is_object($data)) { $type = 'object'; } elseif (is_array($data)) { // If array is assoc it should be processed as an object if($this->is_assoc($data)) { $type = 'object'; } else { $type = 'array'; } } elseif (is_numeric($data)) { $type = 'number'; } elseif(is_string($data)) { $type = 'string'; } elseif(is_bool($data)) { $type = 'boolean'; } elseif(is_null($data)) { $type = 'null'; } else { $type = 'string'; } return $type; } /** * Function determines if array is associative * * @param array $array * @return bool */ function is_assoc($array) { // Arrays are defined as integer-indexed arrays starting at index 0, where // the last index is (count($array) -1); any deviation from that is // considered an associative array, and will be encoded as such (from Zend Framework). return !empty($array) && (array_keys($array) !== range(0, count($array) - 1)); } /** * Escapes special characters * Function escapes ", \, /, \n and \r symbols so that not to cause JavaScript error or * data loss * * @param string $string * @return string */ function escape($string) { $string = preg_replace($this->match, $this->replace, $string); return $string; // Escape certain ASCII characters: // 0x08 => \b // 0x0c => \f // return str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string); } - } \ No newline at end of file + } Index: branches/5.2.x/core/units/helpers/mailbox_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/mailbox_helper.php (revision 16423) +++ branches/5.2.x/core/units/helpers/mailbox_helper.php (revision 16424) @@ -1,495 +1,495 @@ Application->makeClass('POP3Helper', Array ($connection_info)); /* @var $pop3_helper POP3Helper */ $connection_status = $pop3_helper->initMailbox(); if (is_string($connection_status)) { return $connection_status; } if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Reading MAILBOX: ' . $connection_info['username']); } // Figure out if all messages are huge $only_big_messages = true; $max_message_size = $this->maxMegabytes * (1024 * 1024); foreach ($pop3_helper->messageSizes as $message_size) { if (($message_size <= $max_message_size) && ($max_message_size > 0)) { $only_big_messages = false; break; } } $count = $total_size = 0; foreach ($pop3_helper->messageSizes as $message_number => $message_size) { // Too many messages? if (($count++ > $this->maxMessages) && ($this->maxMessages > 0)) { break; } // Message too big? if (!$only_big_messages && ($message_size > $max_message_size) && ($max_message_size > 0)) { $this->_displayLogMessage('message #' . $message_number . ' too big, skipped'); continue; } // Processed enough for today? if (($total_size > $max_message_size) && ($max_message_size > 0)) { break; } $total_size += $message_size; $pop3_helper->getEmail($message_number, $message_source); $processed = $this->normalize($message_source, $verify_callback, $process_callback, $callback_params, $include_attachment_contents); if ($processed) { // delete message from server immediatly after retrieving & processing $pop3_helper->deleteEmail($message_number); $this->_displayLogMessage('message #' . $message_number . ': processed'); } else { $this->_displayLogMessage('message #' . $message_number . ': skipped'); } } $pop3_helper->close(); return 'success'; } /** * Displays log message * * @param string $text */ function _displayLogMessage($text) { if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML($text); } } /** * Takes an RFC822 formatted date, returns a unix timestamp (allowing for zone) * * @param string $rfcdate * @return int */ function rfcToTime($rfcdate) { $date = strtotime($rfcdate); if ($date == -1) { return false; } return $date; } /** * Gets recipients from all possible headers * * @return string */ function getRecipients() { $ret = ''; // headers that could contain recipients $recipient_headers = Array ( 'to', 'cc', 'envelope-to', 'resent-to', 'delivered-to', 'apparently-to', 'envelope-to', 'x-envelope-to', 'received', ); foreach ($recipient_headers as $recipient_header) { if (!array_key_exists($recipient_header, $this->headers)) { continue; } if (!is_array($this->headers["$recipient_header"])) { $ret .= ' ' . $this->headers["$recipient_header"]; } else { $ret .= ' ' . implode(' ', $this->headers["$recipient_header"]); } } return $ret; } /** * "Flattens" the multi-demensinal headers array into a single dimension one * * @param Array $input * @param string $add * @return Array */ function flattenHeadersArray($input, $add = '') { $output = Array (); foreach ($input as $key => $value) { if (!empty($add)) { $newkey = ucfirst( strtolower($add) ); } elseif (is_numeric($key)) { $newkey = ''; } else { $newkey = ucfirst( strtolower($key) ); } if (is_array($value)) { $output = array_merge($output, $this->flattenHeadersArray($value, $newkey)); } else { $output[] = (!empty($newkey) ? $newkey . ': ' : '') . $value; } } return $output; } /** * Processes given message using given callbacks * * @param string $message * @param Array $verify_callback * @param Array $process_callback * @param Array $callback_params * @param bool $include_attachment_contents * @return bool * @access protected */ protected function normalize($message, $verify_callback, $process_callback, $callback_params, $include_attachment_contents = true) { // Decode message $this->decodeMime($message, $include_attachment_contents); // Init vars; $good will hold all the correct infomation from now on $good = Array (); // trim() some stuff now instead of later $this->headers['from'] = trim($this->headers['from']); $this->headers['to'] = trim($this->headers['to']); $this->headers['cc'] = array_key_exists('cc', $this->headers) ? trim($this->headers['cc']) : ''; $this->headers['x-forward-to'] = array_key_exists('x-forward-to', $this->headers) ? $this->headers['x-forward-to'] : ''; $this->headers['subject'] = trim($this->headers['subject']); $this->headers['received'] = is_array($this->headers['received']) ? $this->headers['received'] : Array ($this->headers['received']); if (array_key_exists('return-path', $this->headers) && is_array($this->headers['return-path'])) { $this->headers['return-path'] = implode(' ', $this->flattenHeadersArray($this->headers['return-path'])); } // Create our own message-ID if it's missing $message_id = array_key_exists('message-id', $this->headers) ? trim($this->headers['message-id']) : ''; $good['emailid'] = $message_id ? $message_id : md5($message) . "@in-portal"; // Stops us looping in stupid conversations with other mail software if (isset($this->headers['x-loop-detect']) && $this->headers['x-loop-detect'] > 2) { return false; } $esender = $this->Application->recallObject('EmailSender'); /* @var $esender kEmailSendingHelper */ // Get the return address $return_path = ''; if (array_key_exists('return-path', $this->headers)) { $return_path = $esender->ExtractRecipientEmail($this->headers['return-path']); } if (!$return_path) { if (array_key_exists('reply-to', $this->headers)) { $return_path = $esender->ExtractRecipientEmail( $this->headers['reply-to'] ); } else { $return_path = $esender->ExtractRecipientEmail( $this->headers['from'] ); } } // Get the sender's name & email $good['fromemail'] = $esender->ExtractRecipientEmail($this->headers['from']); $good['fromname'] = $esender->ExtractRecipientName($this->headers['from'], $good['fromemail']); - // Get the list of recipients - if (!$verify_callback[0]->$verify_callback[1]($callback_params)) { - // error: mail is propably spam + // Get the list of recipients. + if ( !call_user_func($verify_callback, $callback_params) ) { + // Error: mail is probably spam. return false; } // Handle the subject $good['subject'] = $this->headers['subject']; // Priorities rock $good['priority'] = array_key_exists('x-priority', $this->headers) ? (int)$this->headers['x-priority'] : 0; switch ($good['priority']) { case 1: case 5: break; default: $good['priority'] = 3; } // If we have attachments it's about time we tell the user about it if (array_key_exists('attachments', $this->parsedMessage) && is_array($this->parsedMessage['attachments'])) { $good['attach'] = count( $this->parsedMessage['attachments'] ); } else { $good['attach'] = 0; } // prepare message text (for replies, etc) if (isset($this->parsedMessage['text'][0]) && trim($this->parsedMessage['text'][0]['body']) != '') { $message_body = trim($this->parsedMessage['text'][0]['body']); $message_type = 'text'; } elseif (isset($this->parsedMessage['html']) && trim($this->parsedMessage['html'][0]['body']) != '') { $message_body = trim($this->parsedMessage['html'][0]['body']); $message_type = 'html'; } else { $message_body = '[no message]'; $message_type = 'text'; } // remove scripts $message_body = preg_replace("/]*>[^<]+<\/script[^>]*>/is", '', $message_body); $message_body = preg_replace("/]*>[^<]*<\/iframe[^>]*>/is", '', $message_body); if ($message_type == 'html') { $message_body = $esender->ConvertToText($message_body); } $mime_decode_helper = $this->Application->recallObject('MimeDecodeHelper'); /* @var $mime_decode_helper MimeDecodeHelper */ // convert to site encoding $message_charset = $this->parsedMessage[$message_type][0]['charset']; if ($message_charset) { $good['message'] = $mime_decode_helper->convertEncoding($message_charset, $message_body); } if (array_key_exists('delivery-date', $this->headers)) { // We found the Delivery-Date header (and it's not too far in the future) $dateline = $this->rfcToTime($this->headers['delivery-date']); if ($dateline > TIMENOW + 86400) { unset($dateline); } } // We found the latest date from the received headers $received_timestamp = $this->headers['received'][0]; $dateline = $this->rfcToTime(trim( substr($received_timestamp, strrpos($received_timestamp, ';') + 1) )); if ($dateline == $this->rfcToTime(0)) { unset($dateline); } if (!isset($dateline)) { $dateline = TIMENOW; } // save collected data to database $fields_hash = Array ( 'DeliveryDate' => $dateline, // date, when SMTP server received the message 'ReceivedDate' => TIMENOW, // date, when message was retrieved from POP3 server 'CreatedOn' => $this->rfcToTime($this->headers['date']), // date, when created on sender's computer 'ReturnPath' => $return_path, 'FromEmail' => $good['fromemail'], 'FromName' => $good['fromname'], 'To' => $this->headers['to'], 'Subject' => $good['subject'], 'Message' => $good['message'], 'MessageType' => $message_type, 'AttachmentCount' => $good['attach'], 'MessageId' => $good['emailid'], 'Source' => $message, 'Priority' => $good['priority'], 'Size' => strlen($message), ); - return $process_callback[0]->$process_callback[1]($callback_params, $fields_hash); + return call_user_func($process_callback, $callback_params, $fields_hash); } /** * Function that decodes the MIME message and creates the $this->headers and $this->parsedMessage data arrays * * @param string $message * @param bool $include_attachments * */ function decodeMime($message, $include_attachments = true) { $message = preg_replace("/\r?\n/", "\r\n", trim($message)); $mime_decode_helper = $this->Application->recallObject('MimeDecodeHelper'); /* @var $mime_decode_helper MimeDecodeHelper */ // 1. separate headers from bodies $mime_decode_helper->InitHelper($message); $decoded_message = $mime_decode_helper->decode(true, true, true); // 2. extract attachments $this->parsedMessage = Array (); // ! reset value $this->parseOutput($decoded_message, $this->parsedMessage, $include_attachments); // 3. add "other" attachments (text part, that is not maked as attachment) if (array_key_exists('text', $this->parsedMessage) && count($this->parsedMessage['text']) > 1) { for ($attach = 1; $attach < count($this->parsedMessage['text']); $attach++) { $this->parsedMessage['attachments'][] = Array ( 'data' => $this->parsedMessage['text']["$attach"]['body'], ); } } $this->headers = $this->parsedMessage['headers']; // ! reset value if (empty($decoded_message->ctype_parameters['boundary'])) { // when no boundary, then assume all message is it's text $this->parsedMessage['text'][0]['body'] = $decoded_message->body; } } /** * Returns content-id's from inline attachments in message * * @return Array */ function getContentIds() { $cids = Array(); if (array_key_exists('attachments', $this->parsedMessage) && is_array($this->parsedMessage['attachments'])) { foreach ($this->parsedMessage['attachments'] as $attachnum => $attachment) { if (!isset($attachment['headers']['content-id'])) { continue; } $cid = $attachment['headers']['content-id']; if (substr($cid, 0, 1) == '<' && substr($cid, -1) == '>') { $cid = substr($cid, 1, -1); } $cids["$attachnum"] = $cid; } } return $cids; } /** * Get more detailed information about attachments * * @param stdClass $decoded parsed headers & body as object * @param Array $parts parsed parts * @param bool $include_attachments */ function parseOutput(&$decoded, &$parts, $include_attachments = true) { $ctype = strtolower($decoded->ctype_primary . '/' . $decoded->ctype_secondary); // don't parse attached messages recursevely if (!empty($decoded->parts) && $ctype != 'message/rfc822') { for ($i = 0; $i < count($decoded->parts); $i++) { $this->parseOutput($decoded->parts["$i"], $parts, $include_attachments); } } else/*if (!empty($decoded->disposition) && $decoded->disposition != 'inline' or 1)*/ { switch ($ctype) { case 'text/plain': case 'text/html': if (!empty($decoded->disposition) && ($decoded->disposition == 'attachment')) { $parts['attachments'][] = Array ( 'data' => $include_attachments ? $decoded->body : '', 'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', // from content-disposition 'filename2' => $decoded->ctype_parameters['name'], // from content-type 'type' => $decoded->ctype_primary, // "text" 'encoding' => $decoded->headers['content-transfer-encoding'] ); } else { $body_type = $decoded->ctype_secondary == 'plain' ? 'text' : 'html'; $parts[$body_type][] = Array ( 'content-type' => $ctype, 'charset' => array_key_exists('charset', $decoded->ctype_parameters) ? $decoded->ctype_parameters['charset'] : 'ISO-8859-1', 'body' => $decoded->body ); } break; case 'message/rfc822': // another e-mail as attachment $parts['attachments'][] = Array ( 'data' => $include_attachments ? $decoded->body : '', 'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', 'filename2' => array_key_exists('name', $decoded->ctype_parameters) ? $decoded->ctype_parameters['name'] : $decoded->parts[0]->headers['subject'], 'type' => $decoded->ctype_primary, // "message" 'headers' => $decoded->headers // individual copy of headers with each attachment ); break; default: if (!stristr($decoded->headers['content-type'], 'signature')) { $parts['attachments'][] = Array ( 'data' => $include_attachments ? $decoded->body : '', 'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', // from content-disposition 'filename2' => $decoded->ctype_parameters['name'], // from content-type 'type' => $decoded->ctype_primary, 'headers' => $decoded->headers // individual copy of headers with each attachment ); } } } $parts['headers'] = $decoded->headers; // headers of next parts overwrite previous part headers } - } \ No newline at end of file + } Index: branches/5.2.x/core/units/images/image_tag_processor.php =================================================================== --- branches/5.2.x/core/units/images/image_tag_processor.php (revision 16423) +++ branches/5.2.x/core/units/images/image_tag_processor.php (revision 16424) @@ -1,505 +1,507 @@ ImageSrc($block_params); if ( !$image_url ) { return ; } $parent_prefix = $this->Application->getUnitOption($object->Prefix, 'ParentPrefix'); $parent_item = $this->Application->recallObject($parent_prefix); /* @var $parent_item kDBItem */ $block_params['img_path'] = $image_url; $image_dimensions = $this->ImageSize($block_params); $block_params['img_size'] = $image_dimensions ? $image_dimensions : ' width="' . $block_params['DefaultWidth'] . '"'; $block_params['alt'] = $object->GetField('AltName') ? $object->GetField('AltName') : $this->getItemTitle($parent_item); $block_params['align'] = array_key_exists('align', $block_params) ? $block_params['align'] : 'left'; // TODO: consider escaping in template instead $block_params['alt'] = kUtil::escape($block_params['alt']); } /** * Returns value of object's title field * * @param kDBItem $object * @return string * @access protected */ protected function getItemTitle(&$object) { $title_field = $this->Application->getUnitOption($object->Prefix, 'TitleField'); return $object->GetField($title_field); } /** * [AGGREGATED TAGS] works as * * @param Array $params * @return string */ function ItemImageTag($params) { $this->LoadItemImage($params); - return $this->$params['original_tag']($params); + $tag_name = $params['original_tag']; + + return $this->$tag_name($params); } function LargeImageExists($params) { $object = $this->getObject($params); if ($object->GetDBField('SameImages') == null || $object->GetDBField('SameImages') == 1) { return false; } else { return true; } } function LoadItemImage($params) { $parent_item = $this->Application->recallObject($params['PrefixSpecial']); /* @var $parent_item kCatDBItem */ $object = $this->Application->recallObject($this->getPrefixSpecial(), null, Array('skip_autoload' => true)); /* @var $object kDBItem */ $object->Clear(); // if we need primary thumbnail which is preloaded with category item's list $is_primary = $this->SelectParam($params, 'primary,Primary'); $image_name = $this->SelectParam($params, 'name,Name'); $image_field = $this->SelectParam($params, 'field,Field'); // ie. virtual names PrimaryImage, Image1, Image2 $image_id = $this->Application->GetVar($this->Prefix.'_id'); if ( // is primary, when primary mark set OR name & field not given ($is_primary || !($image_name || $image_field)) && // primary image is preloaded AND direct id not given $parent_item->isField('ThumbPath') && !$image_id ) { if (is_null($parent_item->GetDBField('SameImages'))) { // JOIN definetly failed, because it's not-null column $object->setLoaded(false); } else { $object->SetDBField('Url', $parent_item->GetDBField('FullUrl')); $object->SetDBFieldsFromHash($parent_item->GetFieldValues(), Array('AltName', 'SameImages', 'LocalThumb', 'ThumbPath', 'ThumbUrl', 'LocalImage', 'LocalPath')); if (!$object->GetDBField('AltName')) { $object->SetDBField('AltName', $this->getItemTitle($parent_item)); } $object->setLoaded(); } } else { // if requested image is not primary thumbnail - load it directly $id_field = $this->Application->getUnitOption($this->Prefix, 'ForeignKey'); $parent_table_key = $this->Application->getUnitOption($this->Prefix, 'ParentTableKey'); $keys[$id_field] = $parent_item->GetDBField($parent_table_key); // which image to load? if ($is_primary) { // by PrimaryImage mark $keys['DefaultImg'] = 1; } elseif ($image_name) { // by ImageName $keys['Name'] = $image_name; } elseif ($image_field) { // by virtual field name in main object $field_options = $parent_item->GetFieldOptions( $image_field ); $keys['Name'] = isset($field_options['original_field']) ? $field_options['original_field'] : $image_field; } elseif ($image_id) { // by ID $keys['ImageId'] = $image_id; } else { // by PrimaryImage if no other criteria given $keys['DefaultImg'] = 1; } $object->Load($keys); if ( $image_field ) { $image_src = $parent_item->GetDBField( $image_field ); // when image is uploaded to virtual field in main item, but not saved to db $object->SetDBField('ThumbPath', $image_src); if (!$object->isLoaded() && $image_src) { // set fields for displaying new image during main item suggestion with errors $fields_hash = Array ( 'Url' => '', 'ThumbUrl' => '', 'LocalPath' => '', 'SameImages' => 1, 'LocalThumb' => 1, 'LocalImage' => 1, ); $object->SetDBFieldsFromHash($fields_hash); $object->setLoaded(); } } } } function getImageDimension($type, $params) { $ret = isset($params['Max'.$type]) ? $params['Max'.$type] : false; if (!$ret) { return $ret; } $parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix'); if ($ret == 'thumbnail') { $ret = $this->Application->ConfigValue($parent_prefix.'_ThumbnailImage'.$type); } if ($ret == 'fullsize') { $ret = $this->Application->ConfigValue($parent_prefix.'_FullImage'.$type); } return $ret; } /** * Appends "/" to beginning of image path (in case when missing) * * @param kDBItem $object * @todo old in-portal doesn't append first slash, but we do => append first slash for him :) */ function makeRelativePaths(&$object) { $thumb_path = $object->GetDBField('ThumbPath'); if ($thumb_path && substr($thumb_path, 0, 1) != DIRECTORY_SEPARATOR) { $object->SetDBField('ThumbPath', DIRECTORY_SEPARATOR . $thumb_path); } $local_path = $object->GetDBField('LocalPath'); if ($local_path && substr($local_path, 0, 1) != DIRECTORY_SEPARATOR) { $object->SetDBField('LocalPath', DIRECTORY_SEPARATOR . $local_path); } } function ImageSrc($params) { $object = $this->getObject($params); /* @var $object kDBItem */ $this->makeRelativePaths($object); // show "noimage.gif" when requested image is missing OR was not uploaded $use_default_image = !(defined('DBG_IMAGE_RECOVERY') && DBG_IMAGE_RECOVERY); $src_image_url = $this->_getImageUrl($params); $src_image = $this->_getImagePath($src_image_url); if (!$object->isLoaded() || ($src_image_url && $src_image)) { // we can auto-resize image, when it is stored locally $max_width = $this->getImageDimension('Width', $params); $max_height = $this->getImageDimension('Height', $params); $format = array_key_exists('format', $params) ? $params['format'] : false; if (!$max_width && $format) { // user watermarks from format param $max_width = $format; } if ($max_width > 0 || $max_height > 0 || $format) { list ($max_width, $max_height) = $this->_transformParams($params, $max_width, $max_height); if ($object->isLoaded() && file_exists($src_image)) { $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ return $image_helper->ResizeImage($src_image, $max_width, $max_height); } elseif ($use_default_image) { return $this->_getDefaultImage($params, $max_width, $max_height); } return $src_image_url; } } if ($src_image_url) { // convert full url to full path! $dst_image = $this->_getImagePath($src_image_url); $image_found = $dst_image ? file_exists($dst_image) : true; if ($image_found) { // image isn't deleted OR is stored on remote location return $src_image_url; } } // return Default Image or false if NOT specified (only for case, when SameImages = 0) return $use_default_image ? $this->_getDefaultImage($params) : $src_image_url; } /** * Get location on disk for images, stored locally and false for remote images * * @param string $src_image * @return string */ function _getImagePath($src_image) { if (!$src_image) { return false; } $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $dst_image = $file_helper->urlToPath($src_image); return $dst_image != $src_image ? $dst_image : false; } function _getImageUrl($params) { $object = $this->getObject($params); /* @var $object kDBItem */ $base_url = rtrim($this->Application->BaseURL(), '/'); // if we need thumbnail, or full image is same as thumbnail $show_thumbnail = $this->SelectParam($params, 'thumbnail,Thumbnail') || // old style (isset($params['MaxWidth']) && $params['MaxWidth'] == 'thumbnail') || // new style (isset($params['MaxHeight']) && $params['MaxHeight'] == 'thumbnail'); if ($show_thumbnail || $object->GetDBField('SameImages')) { // return local image or url $ret = $object->GetDBField('LocalThumb') ? $base_url . $object->GetDBField('ThumbPath') : $object->GetDBField('ThumbUrl'); } else { // if we need full which is not the same as thumb $ret = $object->GetDBField('LocalImage') ? $base_url . $object->GetDBField('LocalPath') : $object->GetDBField('Url'); } return $ret == $base_url ? '' : $ret; } /** * Transforms Image/ImageSrc aggregated tag parameters into ones, that ResizeImage method understands * * @param Array $params * @param int|bool $max_width * @param int|bool $max_height * @return Array */ function _transformParams($params, $max_width = false, $max_height = false) { $resize_format = 'resize:' . $max_width . 'x' . $max_height; $crop = $this->SelectParam($params, 'Crop,crop'); if ($crop) { if (strpos($crop, '|') === false) { $crop = 'c|c'; } $max_width = (is_null($max_height) ? $max_width : $resize_format) . ';crop:' . $crop; $max_height = null; } $fill = $this->SelectParam($params, 'Fill,fill'); if ($fill) { $max_width = (is_null($max_height) ? $max_width : $resize_format) . ';fill:' . $fill; $max_height = null; } $watermark = $this->SelectParam($params, 'Watermark,watermark'); if ($watermark) { $max_width = (is_null($max_height) ? $max_width : $resize_format) . ';wm:' . $watermark; $max_height = null; } return Array ($max_width, $max_height); } /** * Returns default full url to default images * * @param Array $params * @param int|bool $max_width * @param int|bool $max_height * @return string */ function _getDefaultImage($params, $max_width = false, $max_height = false) { $default_image = $this->SelectParam($params, 'default_image,DefaultImage'); if (!$default_image) { return ''; } // show default image, use different base urls for admin and front-end $base_url = rtrim($this->Application->BaseURL(), '/'); $sub_folder = $this->Application->isAdmin ? rtrim(IMAGES_PATH, '/') : THEMES_PATH; if (($max_width !== false) || ($max_height !== false)) { $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ $src_image = FULL_PATH . $sub_folder . '/' . $default_image; return $image_helper->ResizeImage($src_image, $max_width, $max_height); } return $base_url . $sub_folder . '/' . $default_image; } function getFullPath($path) { if (!$path) { return $path; } // absolute url if (preg_match('/^(.*):\/\/(.*)$/U', $path)) { $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ return $file_helper->urlToPath($path); } // TODO: change to urlToPath usage later // relative url (we add sort of does return FULL_PATH . '/' . mb_substr(THEMES_PATH, 1) . '/' . kUtil::unescape($path, kUtil::ESCAPE_URL); } /** * Makes size clause for img tag, such as * ' width="80" height="100"' according to max_width * and max_heght limits. * * @param array $params * @return string */ function ImageSize($params) { $img_path = $this->getFullPath($params['img_path']); $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ $max_width = $this->getImageDimension('Width', $params); $max_height = $this->getImageDimension('Height', $params); $image_dimensions = $image_helper->GetImageDimensions($img_path, $max_width, $max_height, $params); if (!$image_dimensions) { return false; } return ' width="'.$image_dimensions[0].'" height="'.$image_dimensions[1].'"'; } /** * Prepares image parameters & parses block with them (for admin) * * @param Array $params * @return string * @access protected */ protected function Image($params) { $image_url = $this->ImageSrc($params); if ( !$image_url ) { return ''; } $object = $this->getObject($params); /* @var $object kDBItem */ $params['img_path'] = $image_url; $image_dimensions = $this->ImageSize($params); $params['img_size'] = $image_dimensions ? $image_dimensions : ' width="' . $params['DefaultWidth'] . '"'; $params['alt'] = $object->GetField('AltName'); // really used ? $params['name'] = $this->SelectParam($params, 'block,render_as'); $params['align'] = array_key_exists('align', $params) ? $params['align'] : 'left'; $params['no_editing'] = 1; if ( !$object->isLoaded() && !$this->SelectParam($params, 'default_image,DefaultImage') ) { return ''; } // TODO: consider escaping in template instead $params['alt'] = kUtil::escape($params['alt']); $this->Application->Parser->DataExists = true; return $this->Application->ParseBlock($params); } /** * Returns url for image in case when image source is url (for admin) * * @param Array $params * @return string */ function ImageUrl($params) { $object = $this->getObject($params); if ($object->GetDBField('SameImages') ? $object->GetDBField('LocalThumb') : $object->GetDBField('LocalImage') ) { $ret = $this->Application->Phrase(getArrayValue($params,'local_phrase')); } else { $ret = $object->GetDBField('SameImages') ? $object->GetDBField('ThumbUrl') : $object->GetDBField('Url'); } return $ret; } /** * If data was modfied & is in TempTables mode, then parse block with name passed; * remove modification mark if not in TempTables mode * * @param Array $params * @return string * @access public * @author Alexey */ function SaveWarning($params) { if ($this->Prefix == 'c-img') { return $this->Application->ProcessParsedTag('c', 'SaveWarning', $params); } return parent::SaveWarning($params); } } Index: branches/5.2.x/core/install/install_toolkit.php =================================================================== --- branches/5.2.x/core/install/install_toolkit.php (revision 16423) +++ branches/5.2.x/core/install/install_toolkit.php (revision 16424) @@ -1,1185 +1,1188 @@ defaultWritablePath = DIRECTORY_SEPARATOR . 'system'; if ( class_exists('kApplication') ) { // auto-setup in case of separate module install $this->Application =& kApplication::Instance(); $this->Application->Init(); // needed for standalone module install $this->Conn =& $this->Application->GetADODBConnection(); } $this->INIFile = FULL_PATH . $this->defaultWritablePath . DIRECTORY_SEPARATOR . 'config.php'; $this->systemConfig = $this->ParseConfig(true); } /** * Sets installator * * @param kInstallator $instance */ function setInstallator(&$instance) { $this->_installator =& $instance; } /** * Checks prerequisities before module install or upgrade * * @param string $module_path * @param string $versions * @param string $mode upgrade mode = {install, standalone, upgrade} * @return bool */ function CheckPrerequisites($module_path, $versions, $mode) { if ( !$versions ) { return Array (); } $prerequisite_object =& $this->getPrerequisiteObject($module_path); /* @var $prerequisite_object InPortalPrerequisites */ // some errors possible return is_object($prerequisite_object) ? $prerequisite_object->CheckPrerequisites($versions, $mode) : Array (); } /** * Call prerequisites method * * @param string $module_path * @param string $method * @return array */ function CallPrerequisitesMethod($module_path, $method) { $prerequisite_object =& $this->getPrerequisiteObject($module_path); /* @var $prerequisite_object InPortalPrerequisites */ return is_object($prerequisite_object) ? $prerequisite_object->$method() : false; } /** * Returns prerequisite object to be used for checks * * @param string $module_path * @return kHelper * @access protected */ protected function &getPrerequisiteObject($module_path) { static $prerequisite_classes = Array (); $prerequisites_file = sprintf(PREREQUISITE_FILE, $module_path); if ( !file_exists($prerequisites_file) ) { $false = false; return $false; } if ( !isset($prerequisite_classes[$module_path]) ) { // save class name, because 2nd time // (in after call $prerequisite_class variable will not be present) include_once $prerequisites_file; $prerequisite_classes[$module_path] = $prerequisite_class; } $prerequisite_object = new $prerequisite_classes[$module_path](); /* @var $prerequisite_object InPortalPrerequisites */ if ( method_exists($prerequisite_object, 'setToolkit') ) { $prerequisite_object->setToolkit($this); } return $prerequisite_object; } /** * Processes one license, received from server * * @param string $file_data */ function processLicense($file_data) { $modules_helper = $this->Application->recallObject('ModulesHelper'); /* @var $modules_helper kModulesHelper */ $file_data = explode('Code==:', $file_data); $file_data[0] = str_replace('In-Portal License File - do not edit!' . "\n", '', $file_data[0]); $file_data = array_map('trim', $file_data); if ($modules_helper->verifyLicense($file_data[0])) { $this->setSystemConfig('Intechnic', 'License', $file_data[0]); if (array_key_exists(1, $file_data)) { $this->setSystemConfig('Intechnic', 'LicenseCode', $file_data[1]); } else { $this->setSystemConfig('Intechnic', 'LicenseCode'); } $this->SaveConfig(); } else { // invalid license received from licensing server $this->_installator->errorMessage = 'Invalid License File'; } } /** * Saves given configuration values to database * * @param Array $config */ function saveConfigValues($config) { foreach ($config as $config_var => $value) { $sql = 'UPDATE ' . TABLE_PREFIX . 'SystemSettings SET VariableValue = ' . $this->Conn->qstr($value) . ' WHERE VariableName = ' . $this->Conn->qstr($config_var); $this->Conn->Query($sql); } } /** * Sets module version to passed * * @param string $module_name * @param string|bool $module_path * @param string|bool $version */ function SetModuleVersion($module_name, $module_path = false, $version = false) { if ($version === false) { if (!$module_path) { throw new Exception('Module path must be given to "SetModuleVersion" method to auto-detect version'); return ; } $version = $this->GetMaxModuleVersion($module_path); } // get table prefix from config, because application may not be available here $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); if ($module_name == 'kernel') { $module_name = 'in-portal'; } // don't use "adodb_mktime" here, because it's not yet included $sql = 'UPDATE ' . $table_prefix . 'Modules SET Version = "' . $version . '", BuildDate = ' . time() . ' WHERE LOWER(Name) = "' . strtolower($module_name) . '"'; $this->Conn->Query($sql); } /** * Sets module root category to passed * * @param string $module_name * @param int $category_id */ function SetModuleRootCategory($module_name, $category_id = 0) { // get table prefix from config, because application may not be available here $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); if ($module_name == 'kernel') { $module_name = 'in-portal'; } $sql = 'UPDATE ' . $table_prefix . 'Modules SET RootCat = ' . $category_id . ' WHERE LOWER(Name) = "' . strtolower($module_name) . '"'; $this->Conn->Query($sql); } /** * Returns maximal version of given module by scanning it's upgrade scripts * * @param string $module_path * @return string */ function GetMaxModuleVersion($module_path) { $module_path = rtrim(mb_strtolower($module_path), '/'); $upgrades_file = sprintf(UPGRADES_FILE, $module_path . '/', 'sql'); if (!file_exists($upgrades_file)) { // no upgrade file return '5.0.0'; } $sqls = file_get_contents($upgrades_file); $versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs); if (!$versions_found) { // upgrades file doesn't contain version definitions return '5.0.0'; } return end($regs[1]); } /** * Runs SQLs from file * * @param string $filename * @param mixed $replace_from * @param mixed $replace_to */ function RunSQL($filename, $replace_from = null, $replace_to = null) { if (!file_exists(FULL_PATH.$filename)) { return ; } $sqls = file_get_contents(FULL_PATH.$filename); if (!$this->RunSQLText($sqls, $replace_from, $replace_to)) { if (is_object($this->_installator)) { $this->_installator->Done(); } else { if (isset($this->Application)) { $this->Application->Done(); } exit; } } } /** * Runs SQLs from string * * @param string $sqls * @param mixed $replace_from * @param mixed $replace_to * @param int $start_from * @return bool */ function RunSQLText(&$sqls, $replace_from = null, $replace_to = null, $start_from = 0) { $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); // add prefix to all tables if (strlen($table_prefix) > 0) { $replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO '); foreach ($replacements as $replacement) { $sqls = str_replace($replacement, $replacement . $table_prefix, $sqls); } } $sqls = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sqls); $sqls = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sqls); $sqls = str_replace('<%TABLE_PREFIX%>', $table_prefix, $sqls); $primary_language = is_object($this->Application) ? $this->Application->GetDefaultLanguageId() : 1; $sqls = str_replace('<%PRIMARY_LANGUAGE%>', $primary_language, $sqls); if (isset($replace_from) && isset($replace_to)) { // replace something additionally, e.g. module root category $sqls = str_replace($replace_from, $replace_to, $sqls); } $sqls = str_replace("\r\n", "\n", $sqls); // convert to linux line endings $no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", '', $sqls); // remove all comments "#" on new lines if ($no_comment_sqls === null) { // "ini.pcre.backtrack-limit" reached and error happened $sqls = explode(";\n", $sqls . "\n"); // ensures that last sql won't have ";" in it $sqls = array_map('trim', $sqls); // remove all comments "#" on new lines (takes about 2 seconds for 53000 sqls) $sqls = preg_replace("/#\s([^;]*?)/", '', $sqls); } else { $sqls = explode(";\n", $no_comment_sqls . "\n"); // ensures that last sql won't have ";" in it $sqls = array_map('trim', $sqls); } $sql_count = count($sqls); $db_collation = $this->getSystemConfig('Database', 'DBCollation'); for ($i = $start_from; $i < $sql_count; $i++) { $sql = $sqls[$i]; if (!$sql || (substr($sql, 0, 1) == '#')) { continue; // usually last line } if (substr($sql, 0, 13) == 'CREATE TABLE ' && $db_collation) { // it is CREATE TABLE statement -> add collation $sql .= ' COLLATE \'' . $db_collation . '\''; } $this->Conn->Query($sql); if ($this->Conn->getErrorCode() != 0) { if (is_object($this->_installator)) { $this->_installator->errorMessage = 'Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg().'

Last Database Query:
'; $this->_installator->LastQueryNum = $i + 1; } return false; } } return true; } /** * Performs clean language import from given xml file * * @param string $lang_file * @param bool $upgrade * @todo Import for "core/install/english.lang" (322KB) takes 18 seconds to work on Windows */ function ImportLanguage($lang_file, $upgrade = false) { $lang_file = FULL_PATH.$lang_file.'.lang'; if (!file_exists($lang_file)) { return ; } $language_import_helper = $this->Application->recallObject('LanguageImportHelper'); /* @var $language_import_helper LanguageImportHelper */ $language_import_helper->performImport($lang_file, '|0|1|2|', '', $upgrade ? LANG_SKIP_EXISTING : LANG_OVERWRITE_EXISTING); } /** * Converts module version in format X.Y.Z[-BN/-RCM] to signle integer * * @param string $version * @return int */ function ConvertModuleVersion($version) { if (preg_match('/(.*)-(B|RC)([\d]+)/', $version, $regs)) { // -B or RC- $parts = explode('.', $regs[1]); $parts[] = $regs[2] == 'B' ? 1 : 2; // B reliases goes before RC releases $parts[] = $regs[3]; } else { // releases without B/RC marks go after any B/RC releases $parts = explode('.', $version . '.3.100'); } $bin = ''; foreach ($parts as $part_index => $part) { if ($part_index == 3) { // version type only can be 1/2/3 (11 in binary form), so don't use padding at all $pad_count = 2; } else { $pad_count = 8; } $bin .= str_pad(decbin($part), $pad_count, '0', STR_PAD_LEFT); } return bindec($bin); } /** * Returns themes, found in system * * @param bool $rebuild * @return int */ function getThemes($rebuild = false) { if ($rebuild) { $this->rebuildThemes(); } $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'SELECT Name, ' . $id_field . ' FROM ' . $table_name . ' ORDER BY Name ASC'; return $this->Conn->GetCol($sql, $id_field); } function ParseConfig($parse_section = false) { if (!file_exists($this->INIFile)) { return Array (); } if (file_exists($this->INIFile) && !is_readable($this->INIFile)) { die('Could Not Open Ini File'); } $contents = file($this->INIFile); if ($contents && $contents[0] == '<' . '?' . 'php die() ?' . ">\n") { // format of "config.php" file before 5.1.0 version array_shift($contents); return $this->parseIniString(implode('', $contents), $parse_section); } $_CONFIG = Array (); require($this->INIFile); if ($parse_section) { return $_CONFIG; } $ret = Array (); foreach ($_CONFIG as $section => $section_variables) { $ret = array_merge($ret, $section_variables); } return $ret; } /** * Equivalent for "parse_ini_string" function available since PHP 5.3.0 * * @param string $ini * @param bool $process_sections * @param int $scanner_mode * @return Array */ function parseIniString($ini, $process_sections = false, $scanner_mode = null) { # Generate a temporary file. $tempname = tempnam('/tmp', 'ini'); $fp = fopen($tempname, 'w'); fwrite($fp, $ini); $ini = parse_ini_file($tempname, !empty($process_sections)); fclose($fp); @unlink($tempname); return $ini; } function SaveConfig($silent = false) { if (!is_writable($this->INIFile) && !is_writable(dirname($this->INIFile))) { $error_msg = 'Cannot write to "' . $this->INIFile . '" file'; if ($silent) { trigger_error($error_msg, E_USER_WARNING); } else { throw new Exception($error_msg); } return ; } $fp = fopen($this->INIFile, 'w'); fwrite($fp, '<' . '?' . 'php' . "\n\n"); foreach ($this->systemConfig as $section_name => $section_data) { foreach ($section_data as $key => $value) { fwrite($fp, '$_CONFIG[\'' . $section_name . '\'][\'' . $key . '\'] = \'' . addslashes($value) . '\';' . "\n"); } fwrite($fp, "\n"); } fclose($fp); if ( function_exists('opcache_invalidate') ) { opcache_invalidate($this->INIFile); } $this->systemConfigChanged = false; } /** * Sets value to system config (yet SaveConfig must be called to write it to file) * * @param string $section * @param string $key * @param string $value */ function setSystemConfig($section, $key, $value = null) { $this->systemConfigChanged = true; if (isset($value)) { if (!array_key_exists($section, $this->systemConfig)) { // create section, when missing $this->systemConfig[$section] = Array (); } // create key in section $this->systemConfig[$section][$key] = $value; return ; } unset($this->systemConfig[$section][$key]); } /** * Returns information from system config * * @param string $section * @param string $key * @param mixed $default * @return string|bool */ function getSystemConfig($section, $key, $default = false) { if ( !array_key_exists($section, $this->systemConfig) ) { return $default; } if ( !array_key_exists($key, $this->systemConfig[$section]) ) { return $default; } return isset($this->systemConfig[$section][$key]) ? $this->systemConfig[$section][$key] : $default; } /** * Checks if system config is present and is not empty * * @return bool */ function systemConfigFound() { return file_exists($this->INIFile) && $this->systemConfig; } /** * Checks if given section is present in config * * @param string $section * @return bool */ function sectionFound($section) { return array_key_exists($section, $this->systemConfig); } /** * Returns formatted module name based on it's root folder * * @param string $module_folder * @return string */ function getModuleName($module_folder) { return implode('-', array_map('ucfirst', explode('-', $module_folder))); } /** * Returns information about module (based on "install/module_info.xml" file) * * @param string $module_name * @return Array */ function getModuleInfo($module_name) { if ( $module_name == 'core' ) { $info_file = FULL_PATH . '/' . $module_name . '/install/module_info.xml'; } else { $info_file = MODULES_PATH . '/' . $module_name . '/install/module_info.xml'; } if ( !file_exists($info_file) ) { return Array (); } $ret = Array (); $module_info = simplexml_load_file($info_file); if ( $module_info === false ) { // non-valid xml file return Array (); } foreach ($module_info as $node) { /* @var $node SimpleXMLElement */ $ret[strtolower($node->getName())] = trim($node); } return $ret; } /** * Returns nice module string to be used on install/upgrade screens * * @param string $module_name * @param string $version_string * @return string */ function getModuleString($module_name, $version_string) { // image (if exists) ( ) $ret = Array (); $module_info = $this->getModuleInfo($module_name); if (array_key_exists('name', $module_info) && $module_info['name']) { $module_name = $module_info['name']; } else { $module_name = $this->getModuleName($module_name); } if (array_key_exists('image', $module_info) && $module_info['image']) { $image_src = $module_info['image']; if (!preg_match('/^(http|https):\/\//', $image_src)) { // local image -> make absolute url $image_src = $this->Application->BaseURL() . $image_src; } $ret[] = '' . htmlspecialchars($module_name, ENT_QUOTES, 'UTF-8') . ''; } if (array_key_exists('description', $module_info) && $module_info['description']) { $ret[] = $module_info['description']; } else { $ret[] = $module_name; } $ret[] = '(' . $module_name . ' ' . $version_string . ')'; return implode(' ', $ret); } /** * Creates module root category in "Home" category using given data and returns it * * @param string $name * @param string $description * @param string $category_template * @param string $category_icon * @return kDBItem */ function &createModuleCategory($name, $description, $category_template = null, $category_icon = null) { static $fields = null; if ( !isset($fields) ) { $ml_formatter = $this->Application->recallObject('kMultiLanguage'); /* @var $ml_formatter kMultiLanguage */ $fields['name'] = $ml_formatter->LangFieldName('Name'); $fields['description'] = $ml_formatter->LangFieldName('Description'); } $category = $this->Application->recallObject('c', null, Array ('skip_autoload' => true)); /* @var $category kDBItem */ $category_fields = Array ( $fields['name'] => $name, 'Filename' => $name, 'AutomaticFilename' => 1, $fields['description'] => $description, 'Status' => STATUS_ACTIVE, 'Priority' => -9999, // prevents empty link to module category on spearate module install 'NamedParentPath' => 'Content/' . $name, ); $category_fields['ParentId'] = $this->Application->getBaseCategory(); if ( isset($category_template) ) { $category_fields['Template'] = $category_template; $category_fields['CachedTemplate'] = $category_template; } if ( isset($category_icon) ) { $category_fields['UseMenuIconUrl'] = 1; $category_fields['MenuIconUrl'] = $category_icon; } $category->Clear(); $category->SetDBFieldsFromHash($category_fields); $category->Create(); $priority_helper = $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ $event = new kEvent('c:OnListBuild'); // ensure, that newly created category has proper value in Priority field $priority_helper->recalculatePriorities($event, 'ParentId = ' . $category_fields['ParentId']); // update Priority field in object, becase "CategoriesItem::Update" method will be called // from "kInstallToolkit::setModuleItemTemplate" and otherwise will set 0 to Priority field $sql = 'SELECT Priority FROM ' . $category->TableName . ' WHERE ' . $category->IDField . ' = ' . $category->GetID(); $category->SetDBField('Priority', $this->Conn->GetOne($sql)); return $category; } /** * Sets category item template into custom field for given prefix * * @param kDBItem $category * @param string $prefix * @param string $item_template */ function setModuleItemTemplate(&$category, $prefix, $item_template) { $this->Application->removeObject('c-cdata'); // recreate all fields, because custom fields are added during install script $category->Configure(); $category->SetDBField('cust_' . $prefix .'_ItemTemplate', $item_template); $category->Update(); } /** * Link custom field records with search config records + create custom field columns * * @param string $module_folder * @param string $prefix * @param int $item_type */ function linkCustomFields($module_folder, $prefix, $item_type) { $module_folder = strtolower($module_folder); $module_name = $module_folder; if ( $module_folder == 'kernel' ) { $module_name = 'in-portal'; $module_folder = 'core'; } $db =& $this->Application->GetADODBConnection(); $sql = 'SELECT FieldName, CustomFieldId FROM ' . TABLE_PREFIX . 'CustomFields WHERE Type = ' . $item_type . ' AND IsSystem = 0'; // config is not read here yet :( $this->Application->getUnitOption('p', 'ItemType'); $custom_fields = $db->GetCol($sql, 'CustomFieldId'); foreach ($custom_fields as $cf_id => $cf_name) { $sql = 'UPDATE ' . TABLE_PREFIX . 'SearchConfig SET CustomFieldId = ' . $cf_id . ' WHERE (TableName = "CustomFields") AND (LOWER(ModuleName) = "' . $module_name . '") AND (FieldName = ' . $db->qstr($cf_name) . ')'; $db->Query($sql); } // because of configs was read only from installed before modules (in-portal), then reread configs $this->Application->UnitConfigReader->scanModules(MODULES_PATH . DIRECTORY_SEPARATOR . $module_folder); // create correct columns in CustomData table $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $ml_helper->createFields($prefix . '-cdata', true); } /** * Deletes cache, useful after separate module install and installator last step * * @param bool $refresh_permissions * @return void */ function deleteCache($refresh_permissions = false) { $this->Application->HandleEvent(new kEvent('adm:OnResetMemcache')); // not in DB = 100% invalidate $this->Application->HandleEvent(new kEvent('adm:OnResetConfigsCache')); $this->Application->HandleEvent(new kEvent('adm:OnResetSections')); $this->Application->HandleEvent(new kEvent('c:OnResetCMSMenuCache')); $this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls'); if ( $refresh_permissions ) { $rebuild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode'); if ( $rebuild_mode == CategoryPermissionRebuild::SILENT ) { // refresh permission without progress bar $updater = $this->Application->makeClass('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } elseif ( $rebuild_mode == CategoryPermissionRebuild::AUTOMATIC ) { // refresh permissions with ajax progress bar (when available) $this->Application->setDBCache('ForcePermCacheUpdate', 1); } } } /** * Deletes all temp tables (from active sessions too) * */ function deleteEditTables() { $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); $tables = $this->Conn->GetCol('SHOW TABLES'); $mask_edit_table = '/' . $table_prefix . 'ses_(.*)_edit_(.*)/'; $mask_search_table = '/' . $table_prefix . 'ses_(.*?)_(.*)/'; foreach ($tables as $table) { if ( preg_match($mask_edit_table, $table, $rets) || preg_match($mask_search_table, $table, $rets) ) { $this->Conn->Query('DROP TABLE IF EXISTS ' . $table); } } } /** * Perform redirect after separate module install * * @param string $module_folder * @param bool $refresh_permissions */ function finalizeModuleInstall($module_folder, $refresh_permissions = false) { $this->SetModuleVersion(basename($module_folder), $module_folder); if (!$this->Application->GetVar('redirect')) { return ; } $themes_helper = $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ // use direct query, since module isn't yet in kApplication::ModuleInfo array $sql = 'SELECT Name FROM ' . TABLE_PREFIX . 'Modules WHERE Path = ' . $this->Conn->qstr(rtrim($module_folder, '/') . '/'); $module_name = $this->Conn->GetOne($sql); $themes_helper->synchronizeModule($module_name); $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $ml_helper->massCreateFields(); $this->deleteCache($refresh_permissions); $url_params = Array ( 'pass' => 'm', 'admin' => 1, 'RefreshTree' => 1, 'index_file' => 'index.php', ); $this->Application->Redirect('modules/modules_list', $url_params); } /** * Performs rebuild of themes * */ function rebuildThemes() { $this->Application->HandleEvent(new kEvent('adm:OnRebuildThemes')); } /** * Checks that file is writable by group or others * * @param string $file * @return boolean */ function checkWritePermissions($file) { if (DIRECTORY_SEPARATOR == '\\') { // windows doen't allow to check permissions (always returns null) return null; } $permissions = fileperms($file); return $permissions & 0x0010 || $permissions & 0x0002; } /** * Upgrades primary skin to the latest version * * @param Array $module_info * @return string|bool */ function upgradeSkin($module_info) { $upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'css'); $data = file_get_contents($upgrades_file); // get all versions with their positions in file $versions = Array (); preg_match_all('/(' . VERSION_MARK . ')/s', $data, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); $from_version_int = $this->ConvertModuleVersion($module_info['FromVersion']); foreach ($matches as $index => $match) { $version_int = $this->ConvertModuleVersion($match[2][0]); if ( $version_int < $from_version_int ) { // only process versions, that were released after currently used version continue; } $start_pos = $match[0][1] + strlen($match[0][0]); $end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : mb_strlen($data); $patch_data = str_replace("\r\n", "\n", substr($data, $start_pos, $end_pos - $start_pos)); $versions[] = Array ( 'Version' => $match[2][0], // fixes trimmed leading spaces by modern text editor 'Data' => ltrim( str_replace("\n\n", "\n \n", $patch_data) ), ); } if ( !$versions ) { // not skin changes -> quit return true; } $primary_skin = $this->Application->recallObject('skin.primary', null, Array ('skip_autoload' => true)); /* @var $primary_skin kDBItem */ $primary_skin->Load(1, 'IsPrimary'); if ( !$primary_skin->isLoaded() ) { // we always got primary skin, but just in case return false; } $temp_handler = $this->Application->recallObject('skin_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler kTempTablesHandler */ // clone current skin $cloned_ids = $temp_handler->CloneItems('skin', '', Array ($primary_skin->GetID())); if ( !$cloned_ids ) { // can't clone return false; } $skin = $this->Application->recallObject('skin.tmp', null, Array ('skip_autoload' => true)); /* @var $skin kDBItem */ $skin->Load($cloned_ids[0]); // save css to temp file (for patching) $skin_file = tempnam('/tmp', 'skin_css_'); $fp = fopen($skin_file, 'w'); fwrite($fp, str_replace("\r\n", "\n", $skin->GetDBField('CSS'))); fclose($fp); $output = Array (); $patch_file = tempnam('/tmp', 'skin_patch_'); foreach ($versions as $version_info) { // for each left version get it's patch and apply to temp file $fp = fopen($patch_file, 'w'); fwrite($fp, $version_info['Data']); fclose($fp); $output[ $version_info['Version'] ] = shell_exec('patch ' . $skin_file . ' ' . $patch_file . ' 2>&1') . "\n"; } // place temp file content into cloned skin $skin->SetDBField('Name', 'Upgraded to ' . $module_info['ToVersion']); $skin->SetDBField('CSS', file_get_contents($skin_file)); $skin->Update(); unlink($skin_file); unlink($patch_file); $has_errors = false; foreach ($output as $version => $version_output) { $version_errors = trim(preg_replace("/(^|\n)(patching file .*?|Hunk #.*?\.)(\n|$)/m", '', $version_output)); if ( $version_errors ) { $has_errors = true; $output[$version] = trim(preg_replace("/(^|\n)(patching file .*?)(\n|$)/m", '', $output[$version])); } else { unset($output[$version]); } } if ( !$has_errors ) { // copy patched css back to primary skin $primary_skin->SetDBField('CSS', $skin->GetDBField('CSS')); $primary_skin->Update(); // delete temporary skin record $temp_handler->DeleteItems('skin', '', Array ($skin->GetID())); return true; } // put clean skin from new version $skin->SetDBField('CSS', file_get_contents(FULL_PATH . '/core/admin_templates/incs/style_template.css')); $skin->Update(); // return output in case of errors return $output; } /** * Returns cache handlers, that are working * * @param string $current * @return Array */ public function getWorkingCacheHandlers($current = null) { if ( !isset($current) ) { $current = $this->getSystemConfig('Misc', 'CacheHandler'); } $cache_handler = $this->Application->makeClass('kCache'); $cache_handlers = Array ( 'Fake' => 'None', 'Memcache' => 'Memcached', 'XCache' => 'XCache', 'Apc' => 'Alternative PHP Cache' ); foreach ($cache_handlers AS $class_prefix => $title) { $handler_class = $class_prefix . 'CacheHandler'; if ( !class_exists($handler_class) ) { unset($cache_handlers[$class_prefix]); } else { $handler = new $handler_class($cache_handler, 'localhost:11211'); /* @var $handler FakeCacheHandler */ if ( !$handler->isWorking() ) { if ( $current == $class_prefix ) { $cache_handlers[$class_prefix] .= ' (offline)'; } else { unset($cache_handlers[$class_prefix]); } } } } return $cache_handlers; } /** * Returns compression engines, that are working * * @param string $current * @return Array */ public function getWorkingCompressionEngines($current = null) { if ( !isset($current) ) { $current = $this->getSystemConfig('Misc', 'CompressionEngine'); } $output = shell_exec('java -version 2>&1'); $compression_engines = Array ('' => 'None', 'yui' => 'YUICompressor (Java)', 'php' => 'PHP-based'); if ( stripos($output, 'java version') === false ) { if ( $current == 'yui' ) { $compression_engines['yui'] .= ' (offline)'; } else { unset($compression_engines['yui']); } } return $compression_engines; } } Property changes on: branches/5.2.x/CREDITS ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/CREDITS:r16226 Property changes on: branches/5.2.x/README ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/README:r16226 Property changes on: branches/5.2.x/index.php ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/index.php:r16226 Property changes on: branches/5.2.x/LICENSES ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/LICENSES:r16226 Property changes on: branches/5.2.x/INSTALL ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/INSTALL:r16226 Property changes on: branches/5.2.x/COPYRIGHT ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/COPYRIGHT:r16226 Property changes on: branches/5.2.x/.htaccess ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x/.htaccess:r16226 Property changes on: branches/5.2.x ___________________________________________________________________ Modified: svn:mergeinfo Merged /in-portal/branches/5.3.x:r16226