Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Fri, Jun 13, 9:07 AM

in-portal

Index: branches/5.2.x/core/kernel/db/dblist.php
===================================================================
--- branches/5.2.x/core/kernel/db/dblist.php (revision 15126)
+++ branches/5.2.x/core/kernel/db/dblist.php (revision 15127)
@@ -1,1688 +1,1688 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* DBList
*
*/
class kDBList extends kDBBase {
// kDBList filter types (then, types are divided into classes)
/**
* Having filter [step1]
*
*/
const HAVING_FILTER = 1;
/**
* Where filter [step1]
*
*/
const WHERE_FILTER = 2;
/**
* Aggregated filter [step1]
*
*/
const AGGREGATE_FILTER = 3;
/**
* System filter [step2, AND]
*
*/
const FLT_SYSTEM = 1;
/**
* User filter [step2, OR]
* @deprecated
*
*/
const FLT_NORMAL = 2;
/**
* User "Search" filter [step2, OR]
*
*/
const FLT_SEARCH = 3;
/**
* User "View Menu" filter [step2, AND]
*
*/
const FLT_VIEW = 4;
/**
* User "Custom" (above grid columns) filter [step2, AND]
*
*/
const FLT_CUSTOM = 5;
/**
* kMultipleFilter AND filter [step3]
*
*/
const FLT_TYPE_AND = 'AND';
/**
* kMultipleFilter OR filter [step3]
*
*/
const FLT_TYPE_OR = 'OR';
/**
* Totals for fields specified in config
*
* @var Array
* @access protected
*/
protected $Totals = Array ();
/**
* Remembers if totals were calculated
*
* @var bool
* @access protected
*/
protected $TotalsCalculated = false;
/**
* List of "ORDER BY" fields
*
* @var Array
* @access protected
*/
protected $OrderFields = Array ();
/**
* Counted total number of records in the query - without pagination (system+user filters)
*
* @var int
* @access protected
*/
protected $RecordsCount = 0;
/**
* Record count with only system filters applied
*
* @var int
* @access protected
*/
protected $NoFilterCount = 0;
/**
* Record count selected to be showed on current page
*
* @var int
* @access protected
*/
protected $SelectedCount = 0;
/**
* Array of selected records
*
* @var Array
* @access public
*/
public $Records;
/**
* Current record index
*
* @var int
* @access protected
*/
protected $CurrentIndex = 0;
/**
* List items per-page
*
* @var int
* @access protected
*/
protected $PerPage;
/**
* Page count in list based on PerPage & RecordsCount attributes
*
* @var int
* @access protected
*/
protected $TotalPages;
/**
* Current page number - used when forming LIMIT clause of SELECT statement
*
* @var int
* @access protected
*/
protected $Page;
/**
* Offset for LIMIT clause, calculated in {@link kDBList::PerPage()}
*
* @var int
* @access protected
*/
protected $Offset;
/**
* WHERE filter objects
*
* @var Array
* @access protected
*/
protected $WhereFilter = Array ();
/**
* HAVING filter objects
*
* @var Array
* @access protected
*/
protected $HavingFilter = Array ();
/**
* AGGREGATED filter objects
*
* @var Array
* @access protected
*/
protected $AggregateFilter = Array ();
/**
* List of "GROUP BY" fields
*
* @var Array
* @access protected
*/
protected $GroupByFields = Array ();
/**
* Remembers if list was queried
*
* @var bool
* @access protected
*/
protected $Queried = false;
/**
* Remembers if list was counted
*
* @var bool
* @access protected
*/
protected $Counted = false;
/**
* Name of the grid, used to display the list
*
* @var string
*/
var $gridName = '';
/**
* Identifies this list as main on the page, that allows to react on "page", "per_page" and "sort_by" parameters from url
*
* @var bool
* @access protected
*/
protected $mainList = false;
/**
* Holds field errors
*
* @var Array
* @access protected
*/
protected $FieldErrors = Array ();
/**
* Creates kDBList
*
* @return kDBList
* @access public
*/
public function __construct()
{
parent::__construct();
$this->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']));
}
$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);
}
/**
* 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);
}
/**
* 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 (
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->removeFilter($name);
}
/**
* Clear all filters
*
* @access public
*/
public function clearFilters()
{
$filters = $this->getFilterStructure();
foreach ($filters as $filter_params) {
$filter =& $this->$filter_params['type'];
$filter[ $filter_params['class'] ]->clearFilters();
}
}
/**
* Counts the total number of records base on the query resulted from {@link kDBList::GetSelectSQL()}
*
* The method modifies the query to substitude SELECT part (fields listing) with COUNT(*).
* Special care should be applied when working with lists based on grouped queries, all aggregate function fields
* like SUM(), AVERAGE() etc. should be added to CountedSQL by using {@link kDBList::SetCountedSQL()}
*
* @access 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(.*?)FROM(?!_)/is",$sql,$regs ) )
+ if ( preg_match("/^\s*SELECT\s+DISTINCT(.*?\s)FROM(?!_)/is",$sql,$regs ) )
{
- return preg_replace("/^\s*SELECT\s+DISTINCT(.*?)FROM(?!_)/is", "SELECT COUNT(DISTINCT ".$regs[1].") AS count FROM", $sql);
+ 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(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
+ 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)) {
// no records & page > 1, try to reset to 1st page (works only when list in not counted before)
$this->Application->StoreVar($this->getPrefixSpecial() . '_Page', 1, true);
$this->SetPage(1);
$this->Query($force);
}
$this->SelectedCount = count($this->Records);
if (!$this->Counted) $this->RecordsCount = $this->SelectedCount;
if (!$this->Counted && $this->SelectedCount > $this->PerPage && $this->PerPage != -1) $this->SelectedCount--;
if ($this->Records === false) {
//handle errors here
return false;
}
$this->Queried = true;
$query_event = new kEvent($this->getPrefixSpecial() . ':OnAfterListQuery');
$this->Application->HandleEvent($query_event);
return true;
}
/**
* Adds one more record to list virtually and updates all counters
*
* @param Array $record
* @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(.*?)FROM(?!_)/is",$sql,$regs ) )
+ if ( preg_match("/DISTINCT(.*?\s)FROM(?!_)/is",$sql,$regs ) )
{
- $sql = preg_replace("/^\s*SELECT DISTINCT(.*?)FROM(?!_)/is", 'SELECT '.$fields.' FROM', $sql);
+ $sql = preg_replace("/^\s*SELECT DISTINCT(.*?\s)FROM(?!_)/is", 'SELECT '.$fields.' FROM', $sql);
}
else
{
- $sql = preg_replace("/^\s*SELECT(.*?)FROM(?!_)/is", 'SELECT '.$fields.' FROM ', $sql);
+ $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 ) {
$optimizer = new LeftJoinOptimizer($q, $where . '|' . $having . '|' . $order . '|' . $group . '|' . $keep_clause);
$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 ) {
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->WhereFilter[self::FLT_SYSTEM] );
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('<span class="debug_error">Incorrect sorting</span> defined (field = <b>'.$field.'</b>; direction = <b>'.$direction.'</b>) in config for prefix <b>'.$this->Prefix.'</b>', E_USER_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 "<strong>' . $name . '</strong>" doesn\'t exist in prefix <strong>' . $this->getPrefixSpecial() . '</strong>', 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: "<strong>' . rtrim($parent_prefix.'.'.$special, '.') . '</strong>"; sub-prefix: "<strong>' . $this->getPrefixSpecial() . '</strong>")', 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;
}
}
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|.*?$)/i', $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);
}
}
\ No newline at end of file
Index: branches/5.2.x/core/units/helpers/cat_dbitem_export_helper.php
===================================================================
--- branches/5.2.x/core/units/helpers/cat_dbitem_export_helper.php (revision 15126)
+++ branches/5.2.x/core/units/helpers/cat_dbitem_export_helper.php (revision 15127)
@@ -1,1569 +1,1569 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
define('EXPORT_STEP', 100); // export by 200 items (e.g. links)
define('IMPORT_STEP', 20); // export by 200 items (e.g. links)
define('IMPORT_CHUNK', 10240); // 10240); //30720); //50120); // 5 KB
define('IMPORT_TEMP', 1);
define('IMPORT_LIVE', 2);
class kCatDBItemExportHelper extends kHelper {
var $false = false;
var $cache = Array();
/**
* Allows to find out what items are new in cache
*
* @var Array
*/
var $cacheStatus = Array();
var $cacheTable = '';
var $exportFields = Array();
/**
* Export options
*
* @var Array
*/
var $exportOptions = Array();
/**
* Item beeing currenly exported
*
* @var kCatDBItem
*/
var $curItem = null;
/**
* Dummy category object
*
* @var CategoriesItem
*/
var $dummyCategory = null;
/**
* Pointer to opened file
*
* @var resource
*/
var $filePointer = null;
/**
* Custom fields definition of current item
*
* @var Array
*/
var $customFields = Array();
public function __construct()
{
parent::__construct();
$this->cacheTable = TABLE_PREFIX.'ImportCache';
}
/**
* Returns value from cache if found or false otherwise
*
* @param string $type
* @param int $key
* @return mixed
*/
function getFromCache($type, $key)
{
return getArrayValue($this->cache, $type, $key);
}
/**
* Adds value to be cached
*
* @param string $type
* @param int $key
* @param mixed $value
* @param bool $is_new
*/
function addToCache($type, $key, $value, $is_new = true)
{
/*if ( !isset($this->cache[$type]) ) {
$this->cache[$type] = Array ();
}*/
$this->cache[$type][$key] = $value;
if ( $is_new ) {
$this->cacheStatus[$type][$key] = true;
}
}
function storeCache($cache_types)
{
$cache_types = explode(',', $cache_types);
$values_sql = '';
foreach ($cache_types as $cache_type) {
$sql_mask = '('.$this->Conn->qstr($cache_type).',%s,%s),';
$cache = getArrayValue($this->cacheStatus, $cache_type);
if (!$cache) $cache = Array();
foreach ($cache as $var_name => $cache_status) {
$var_value = $this->cache[$cache_type][$var_name];
$values_sql .= sprintf($sql_mask, $this->Conn->qstr($var_name), $this->Conn->qstr($var_value) );
}
}
$values_sql = substr($values_sql, 0, -1);
if ($values_sql) {
$sql = 'INSERT INTO '.$this->cacheTable.'(`CacheName`,`VarName`,`VarValue`) VALUES '.$values_sql;
$this->Conn->Query($sql);
}
}
function loadCache()
{
$sql = 'SELECT * FROM '.$this->cacheTable;
$records = $this->Conn->Query($sql);
$this->cache = Array();
foreach ($records as $record) {
$this->addToCache($record['CacheName'], $record['VarName'], $record['VarValue'], false);
}
}
/**
* Fill required fields with dummy values
*
* @param kEvent|bool $event
* @param kCatDBItem|bool $object
* @param bool $set_status
*/
function fillRequiredFields(&$event, &$object, $set_status = false)
{
if ( $object == $this->false ) {
$object =& $event->getObject();
/* @var $object kCatDBItem */
}
$has_empty = false;
$fields = $object->getFields();
if ( $object->isField('CreatedById') ) {
// CSV file was created without required CreatedById column
if ( $object->isRequired('CreatedById') ) {
$object->setRequired('CreatedById', false);
}
if ( !is_numeric( $object->GetDBField('CreatedById') ) ) {
$object->SetDBField('CreatedById', $this->Application->RecallVar('user_id'));
}
}
foreach ($fields as $field_name => $field_options) {
if ( $object->isVirtualField($field_name) || !$object->isRequired($field_name) ) {
continue;
}
if ( $object->GetDBField($field_name) ) {
continue;
}
$formatter_class = getArrayValue($field_options, 'formatter');
if ( $formatter_class ) {
// not tested
$formatter =& $this->Application->recallObject($formatter_class);
/* @var $formatter kFormatter */
$sample_value = $formatter->GetSample($field_name, $field_options, $object);
}
$has_empty = true;
$object->SetField($field_name, isset($sample_value) && $sample_value ? $sample_value : 'no value');
}
$object->UpdateFormattersSubFields();
if ( $set_status && $has_empty ) {
$object->SetDBField('Status', 0);
}
}
/**
* Verifies that all user entered export params are correct
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function verifyOptions(&$event)
{
if ($this->Application->RecallVar($event->getPrefixSpecial().'_ForceNotValid'))
{
$this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 0);
return false;
}
$this->fillRequiredFields($event, $this->false);
$object =& $event->getObject();
/* @var $object kCatDBItem */
$cross_unique_fields = Array('FieldsSeparatedBy', 'FieldsEnclosedBy');
if (($object->GetDBField('CategoryFormat') == 1) || ($event->Special == 'import')) // in one field
{
$object->setRequired('CategorySeparator');
$cross_unique_fields[] = 'CategorySeparator';
}
$ret = $object->Validate();
// check if cross unique fields has no same values
foreach ($cross_unique_fields as $field_index => $field_name)
{
if ($object->GetErrorPseudo($field_name) == 'required') {
continue;
}
$check_fields = $cross_unique_fields;
unset($check_fields[$field_index]);
foreach ($check_fields as $check_field)
{
if ($object->GetDBField($field_name) == $object->GetDBField($check_field))
{
$object->SetError($check_field, 'unique');
}
}
}
if ($event->Special == 'import')
{
$this->exportOptions = $this->loadOptions($event);
$automatic_fields = ($object->GetDBField('FieldTitles') == 1);
$object->setRequired('ExportColumns', !$automatic_fields);
$category_prefix = '__CATEGORY__';
if ( $automatic_fields && ($this->exportOptions['SkipFirstRow']) ) {
$this->openFile($event);
$this->exportOptions['ExportColumns'] = $this->readRecord();
if (!$this->exportOptions['ExportColumns']) {
$this->exportOptions['ExportColumns'] = Array ();
}
$this->closeFile();
// remove additional (non-parseble columns)
foreach ($this->exportOptions['ExportColumns'] as $field_index => $field_name) {
if (!$this->validateField($field_name, $object)) {
unset($this->exportOptions['ExportColumns'][$field_index]);
}
}
$category_prefix = '';
}
// 1. check, that we have column definitions
if (!$this->exportOptions['ExportColumns']) {
$object->setError('ExportColumns', 'required');
$ret = false;
}
else {
// 1.1. check that all required fields are present in imported file
$missing_columns = Array();
$fields = $object->getFields();
foreach ($fields as $field_name => $field_options) {
if ($object->skipField($field_name)) continue;
if ( $object->isRequired($field_name) && !in_array($field_name, $this->exportOptions['ExportColumns']) ) {
$missing_columns[] = $field_name;
$object->setError('ExportColumns', 'required_fields_missing', 'la_error_RequiredColumnsMissing');
$ret = false;
}
}
if (!$ret && $this->Application->isDebugMode()) {
$this->Application->Debugger->appendHTML('Missing required for import/export:');
$this->Application->Debugger->dumpVars($missing_columns);
}
}
// 2. check, that we have only mixed category field or only separated category fields
$category_found['mixed'] = false;
$category_found['separated'] = false;
foreach ($this->exportOptions['ExportColumns'] as $import_field) {
if (preg_match('/^'.$category_prefix.'Category(Path|[0-9]+)/', $import_field, $rets)) {
$category_found[$rets[1] == 'Path' ? 'mixed' : 'separated'] = true;
}
}
if ($category_found['mixed'] && $category_found['separated']) {
$object->SetError('ExportColumns', 'unique_category', 'la_error_unique_category_field');
$ret = false;
}
// 3. check, that duplicates check fields are selected & present in imported fields
if ($this->exportOptions['ReplaceDuplicates']) {
if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
$check_fields = Array($object->IDField);
}
else {
$check_fields = $this->exportOptions['DuplicateCheckFields'] ? explode('|', substr($this->exportOptions['DuplicateCheckFields'], 1, -1)) : Array();
$object =& $event->getObject();
$fields = $object->getFields();
$language_id = $this->Application->GetDefaultLanguageId();
foreach ($check_fields as $index => $check_field) {
foreach ($fields as $field_name => $field_options) {
if ($field_name == 'l'.$language_id.'_'.$check_field) {
$check_fields[$index] = 'l'.$language_id.'_'.$check_field;
break;
}
}
}
}
$this->exportOptions['DuplicateCheckFields'] = $check_fields;
if (!$check_fields) {
$object->setError('CheckDuplicatesMethod', 'required');
$ret = false;
}
else {
foreach ($check_fields as $check_field) {
$check_field = preg_replace('/^cust_(.*)/', 'Custom_\\1', $check_field);
if (!in_array($check_field, $this->exportOptions['ExportColumns'])) {
$object->setError('ExportColumns', 'required');
$ret = false;
break;
}
}
}
}
$this->saveOptions($event);
}
return $ret;
}
/**
* Returns filename to read import data from
*
* @return string
*/
function getImportFilename()
{
if ($this->exportOptions['ImportSource'] == 1)
{
$ret = $this->exportOptions['ImportFilename']; // ['name']; commented by Kostja
}
else {
$ret = $this->exportOptions['ImportLocalFilename'];
}
return EXPORT_PATH.'/'.$ret;
}
/**
* Returns filename to write export data to
*
* @return string
*/
function getExportFilename()
{
$extension = $this->getFileExtension();
$filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $this->exportOptions['ExportFilename']) . '.' . $extension;
return EXPORT_PATH . DIRECTORY_SEPARATOR . $filename;
}
/**
* Opens file required for export/import operations
*
* @param kEvent $event
*/
function openFile(&$event)
{
$file_helper =& $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$file_helper->CheckFolder(EXPORT_PATH);
if ($event->Special == 'export') {
$write_mode = ($this->exportOptions['start_from'] == 0) ? 'w' : 'a';
$this->filePointer = fopen($this->getExportFilename(), $write_mode);
}
else {
$this->filePointer = fopen($this->getImportFilename(), 'r');
}
// skip UTF-8 BOM Modifier
$first_chars = fread($this->filePointer, 3);
if (bin2hex($first_chars) != 'efbbbf') {
fseek($this->filePointer, 0);
}
}
/**
* Closes opened file
*
*/
function closeFile()
{
fclose($this->filePointer);
}
function getCustomSQL()
{
$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
/* @var $ml_formatter kMultiLanguage */
$custom_sql = '';
foreach ($this->customFields as $custom_id => $custom_name) {
$custom_sql .= 'custom_data.' . $ml_formatter->LangFieldName('cust_' . $custom_id) . ' AS cust_' . $custom_name . ', ';
}
return substr($custom_sql, 0, -2);
}
function getPlainExportSQL($count_only = false)
{
if ( $count_only && isset($this->exportOptions['ForceCountSQL']) ) {
$sql = $this->exportOptions['ForceCountSQL'];
}
elseif ( !$count_only && isset($this->exportOptions['ForceSelectSQL']) ) {
$sql = $this->exportOptions['ForceSelectSQL'];
}
else {
$items_list =& $this->Application->recallObject($this->curItem->Prefix . '.export-items-list', $this->curItem->Prefix . '_List');
/* @var $items_list kDBList */
$items_list->SetPerPage(-1);
if ( $this->exportOptions['export_ids'] != '' ) {
$items_list->addFilter('export_ids', $items_list->TableName . '.' . $items_list->IDField . ' IN (' . implode(',', $this->exportOptions['export_ids']) . ')');
}
if ( $count_only ) {
$sql = $items_list->getCountSQL($items_list->GetSelectSQL(true, false));
}
else {
$sql = $items_list->GetSelectSQL();
}
}
if ( !$count_only ) {
$sql .= ' LIMIT ' . $this->exportOptions['start_from'] . ',' . EXPORT_STEP;
}
/*else {
- $sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
+ $sql = preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
}*/
return $sql;
}
function getExportSQL($count_only = false)
{
if ( !$this->Application->getUnitOption($this->curItem->Prefix, 'CatalogItem') ) {
return $this->GetPlainExportSQL($count_only); // in case this is not a CategoryItem
}
if ( $this->exportOptions['export_ids'] === false ) {
// get links from current category & all it's subcategories
$join_clauses = Array ();
$custom_sql = $this->getCustomSQL();
if ( $custom_sql ) {
$custom_table = $this->Application->getUnitOption($this->curItem->Prefix . '-cdata', 'TableName');
$join_clauses[$custom_table . ' custom_data'] = 'custom_data.ResourceId = item_table.ResourceId';
}
$join_clauses[TABLE_PREFIX . 'CategoryItems ci'] = 'ci.ItemResourceId = item_table.ResourceId';
$join_clauses[TABLE_PREFIX . 'Categories c'] = 'c.CategoryId = ci.CategoryId';
$sql = 'SELECT item_table.*, ci.CategoryId' . ($custom_sql ? ', ' . $custom_sql : '') . '
FROM ' . $this->curItem->TableName . ' item_table';
foreach ($join_clauses as $table_name => $join_expression) {
$sql .= ' LEFT JOIN ' . $table_name . ' ON ' . $join_expression;
}
$sql .= ' WHERE ';
if ( $this->exportOptions['export_cats_ids'][0] == 0 ) {
$sql .= '1';
}
else {
foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
$sql .= '(c.ParentPath LIKE "%|' . $category_id . '|%") OR ';
}
$sql = substr($sql, 0, -4);
}
$sql .= ' ORDER BY ci.PrimaryCat DESC'; // NEW
}
else {
// get only selected links
$sql = 'SELECT item_table.*, ' . $this->exportOptions['export_cats_ids'][0] . ' AS CategoryId
FROM ' . $this->curItem->TableName . ' item_table
WHERE ' . $this->curItem->IDField . ' IN (' . implode(',', $this->exportOptions['export_ids']) . ')';
}
if ( !$count_only ) {
$sql .= ' LIMIT ' . $this->exportOptions['start_from'] . ',' . EXPORT_STEP;
}
else {
- $sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
+ $sql = preg_replace("/^\s*SELECT(.*?\s)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql);
}
return $sql;
}
/**
* Enter description here...
*
* @param kEvent $event
*/
function performExport(&$event)
{
$this->exportOptions = $this->loadOptions($event);
$this->exportFields = $this->exportOptions['ExportColumns'];
$this->curItem =& $event->getObject( Array('skip_autoload' => true) );
$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
$this->openFile($event);
if ($this->exportOptions['start_from'] == 0) // first export step
{
if (!getArrayValue($this->exportOptions, 'IsBaseCategory')) {
$this->exportOptions['IsBaseCategory'] = 0;
}
if ($this->exportOptions['IsBaseCategory'] ) {
$sql = 'SELECT ParentPath
FROM '.TABLE_PREFIX.'Categories
WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id');
$parent_path = $this->Conn->GetOne($sql);
$parent_path = explode('|', substr($parent_path, 1, -1));
if ($parent_path && $parent_path[0] == $this->Application->getBaseCategory()) {
array_shift($parent_path);
}
$this->exportOptions['BaseLevel'] = count($parent_path); // level to cut from other categories
}
// 1. export field titles if required
if ($this->exportOptions['IncludeFieldTitles'])
{
$data_array = Array();
foreach ($this->exportFields as $export_field)
{
$data_array = array_merge($data_array, $this->getFieldCaption($export_field));
}
$this->writeRecord($data_array);
}
$this->exportOptions['total_records'] = $this->Conn->GetOne( $this->getExportSQL(true) );
}
// 2. export data
$records = $this->Conn->Query( $this->getExportSQL() );
$records_exported = 0;
foreach ($records as $record_info) {
$this->curItem->LoadFromHash($record_info);
$data_array = Array();
foreach ($this->exportFields as $export_field)
{
$data_array = array_merge($data_array, $this->getFieldValue($export_field) );
}
$this->writeRecord($data_array);
$records_exported++;
}
$this->closeFile();
$this->exportOptions['start_from'] += $records_exported;
$this->saveOptions($event);
return $this->exportOptions;
}
function getItemFields()
{
// just in case dummy user selected automtic mode & moved columns too :(
$src_options = $this->curItem->GetFieldOption('ExportColumns', 'options');
$dst_options = $this->curItem->GetFieldOption('AvailableColumns', 'options');
return array_merge($dst_options, $src_options);
}
/**
* Checks if field really belongs to importable field list
*
* @param string $field_name
* @param kCatDBItem $object
* @return bool
*/
function validateField($field_name, &$object)
{
// 1. convert custom field
$field_name = preg_replace('/^Custom_(.*)/', '__CUSTOM__\\1', $field_name);
// 2. convert category field (mixed version & separated version)
$field_name = preg_replace('/^Category(Path|[0-9]+)/', '__CATEGORY__Category\\1', $field_name);
$valid_fields = $object->getPossibleExportColumns();
return isset($valid_fields[$field_name]) || isset($valid_fields['__VIRTUAL__'.$field_name]);
}
/**
* Enter description here...
*
* @param kEvent $event
*/
function performImport(&$event)
{
if (!$this->exportOptions) {
// load import options in case if not previously loaded in verification function
$this->exportOptions = $this->loadOptions($event);
}
$backup_category_id = $this->Application->GetVar('m_cat_id');
$this->Application->SetVar('m_cat_id', (int)$this->Application->RecallVar('ImportCategory') );
$this->openFile($event);
$bytes_imported = 0;
if ($this->exportOptions['start_from'] == 0) // first export step
{
// 1st time run
if ($this->exportOptions['SkipFirstRow']) {
$this->readRecord();
$this->exportOptions['start_from'] = ftell($this->filePointer);
$bytes_imported = ftell($this->filePointer);
}
$current_category_id = $this->Application->GetVar('m_cat_id');
if ($current_category_id > 0) {
$sql = 'SELECT ParentPath FROM '.TABLE_PREFIX.'Categories WHERE CategoryId = '.$current_category_id;
$this->exportOptions['ImportCategoryPath'] = $this->Conn->GetOne($sql);
}
else {
$this->exportOptions['ImportCategoryPath'] = '';
}
$this->exportOptions['total_records'] = filesize($this->getImportFilename());
}
else {
$this->loadCache();
}
$this->exportFields = $this->exportOptions['ExportColumns'];
$this->addToCache('category_parent_path', $this->Application->GetVar('m_cat_id'), $this->exportOptions['ImportCategoryPath']);
// 2. import data
$this->dummyCategory =& $this->Application->recallObject('c.-tmpitem', 'c', Array('skip_autoload' => true));
fseek($this->filePointer, $this->exportOptions['start_from']);
$items_processed = 0;
while (($bytes_imported < IMPORT_CHUNK && $items_processed < IMPORT_STEP) && !feof($this->filePointer)) {
$data = $this->readRecord();
if ($data) {
if ($this->exportOptions['ReplaceDuplicates']) {
// set fields used as keys for replace duplicates code
$this->resetImportObject($event, IMPORT_TEMP, $data);
}
$this->processCurrentItem($event, $data);
}
$bytes_imported = ftell($this->filePointer) - $this->exportOptions['start_from'];
$items_processed++;
}
$this->closeFile();
$this->Application->SetVar('m_cat_id', $backup_category_id);
$this->exportOptions['start_from'] += $bytes_imported;
$this->storeCache('new_ids');
$this->saveOptions($event);
if ($this->exportOptions['start_from'] == $this->exportOptions['total_records']) {
$this->Conn->Query('TRUNCATE TABLE '.$this->cacheTable);
}
return $this->exportOptions;
}
function setCurrentID()
{
$this->curItem->setID( $this->curItem->GetDBField($this->curItem->IDField) );
}
/**
* Sets value of import/export object
* @param int $field_index
* @param mixed $value
* @return void
* @access protected
*/
protected function setFieldValue($field_index, $value)
{
if ( empty($value) ) {
$value = null;
}
$field_name = getArrayValue($this->exportFields, $field_index);
if ( $field_name == 'ResourceId' ) {
return ;
}
if ( substr($field_name, 0, 7) == 'Custom_' ) {
$field_name = 'cust_' . substr($field_name, 7);
$this->curItem->SetField($field_name, $value);
}
elseif ( $field_name == 'CategoryPath' || $field_name == '__CATEGORY__CategoryPath' ) {
$this->curItem->CategoryPath = $value ? explode($this->exportOptions['CategorySeparator'], $value) : Array ();
}
elseif ( substr($field_name, 0, 8) == 'Category' ) {
$this->curItem->CategoryPath[(int)substr($field_name, 8) - 1] = $value;
}
elseif ( substr($field_name, 0, 20) == '__CATEGORY__Category' ) {
$this->curItem->CategoryPath[(int)substr($field_name, 20) - 1] = $value;
}
elseif ( substr($field_name, 0, 11) == '__VIRTUAL__' ) {
$field_name = substr($field_name, 11);
$this->curItem->SetField($field_name, $value);
}
else {
$this->curItem->SetField($field_name, $value);
}
if ( $this->curItem->GetErrorPseudo($field_name) ) {
$this->curItem->SetDBField($field_name, null);
$this->curItem->RemoveError($field_name);
}
}
/**
* Resets import object
*
* @param kEvent $event
* @param int $object_type
* @param Array $record_data
* @return void
*/
function resetImportObject(&$event, $object_type, $record_data = null)
{
switch ($object_type) {
case IMPORT_TEMP:
$this->curItem =& $event->getObject( Array('skip_autoload' => true) );
break;
case IMPORT_LIVE:
$this->curItem =& $this->Application->recallObject($event->Prefix.'.-tmpitem'.$event->Special, $event->Prefix, Array('skip_autoload' => true));
break;
}
$this->curItem->Clear();
$this->curItem->SetDBField('CategoryId', NULL); // since default value is import root category
$this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
if (isset($record_data)) {
$this->setImportData($record_data);
}
}
function setImportData($record_data)
{
foreach ($record_data as $field_index => $field_value) {
$this->setFieldValue($field_index, $field_value);
}
$this->setCurrentID();
}
function getItemCategory()
{
static $lang_prefix = null;
$backup_category_id = $this->Application->GetVar('m_cat_id');
$category_id = $this->getFromCache('category_names', implode(':', $this->curItem->CategoryPath));
if ($category_id) {
$this->Application->SetVar('m_cat_id', $category_id);
return $category_id;
}
if (is_null($lang_prefix)) {
$lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_';
}
foreach ($this->curItem->CategoryPath as $category_index => $category_name) {
if (!$category_name) continue;
$category_key = crc32( implode(':', array_slice($this->curItem->CategoryPath, 0, $category_index + 1) ) );
$category_id = $this->getFromCache('category_names', $category_key);
if ($category_id === false) {
// get parent category path to search only in it
$current_category_id = $this->Application->GetVar('m_cat_id');
// $parent_path = $this->getParentPath($current_category_id);
// get category id from database by name
$sql = 'SELECT CategoryId
FROM '.TABLE_PREFIX.'Categories
WHERE ('.$lang_prefix.'Name = '.$this->Conn->qstr($category_name).') AND (ParentId = '.(int)$current_category_id.')';
$category_id = $this->Conn->GetOne($sql);
if ( $category_id === false ) {
// category not in db -> create
$category_fields = Array (
$lang_prefix.'Name' => $category_name, $lang_prefix.'Description' => $category_name,
'Status' => STATUS_ACTIVE, 'ParentId' => $current_category_id, 'AutomaticFilename' => 1
);
$this->dummyCategory->Clear();
$this->dummyCategory->SetDBFieldsFromHash($category_fields);
if ( $this->dummyCategory->Create() ) {
$category_id = $this->dummyCategory->GetID();
$this->addToCache('category_parent_path', $category_id, $this->dummyCategory->GetDBField('ParentPath'));
$this->addToCache('category_names', $category_key, $category_id);
}
}
else {
$this->addToCache('category_names', $category_key, $category_id);
}
}
if ($category_id) {
$this->Application->SetVar('m_cat_id', $category_id);
}
}
if (!$this->curItem->CategoryPath) {
$category_id = $backup_category_id;
}
return $category_id;
}
/**
* Enter description here...
*
* @param kEvent $event
* @param Array $record_data
* @return bool
*/
function processCurrentItem(&$event, $record_data)
{
$save_method = 'Create';
$load_keys = Array();
// create/update categories
$backup_category_id = $this->Application->GetVar('m_cat_id');
// perform replace duplicates code
if ($this->exportOptions['ReplaceDuplicates']) {
// get replace keys first, then reset current item to empty one
$category_id = $this->getItemCategory();
if ($this->exportOptions['CheckDuplicatesMethod'] == 1) {
if ($this->curItem->GetID()) {
$load_keys = Array($this->curItem->IDField => $this->curItem->GetID());
}
}
else {
$key_fields = $this->exportOptions['DuplicateCheckFields'];
foreach ($key_fields as $key_field) {
$load_keys[$key_field] = $this->curItem->GetDBField($key_field);
}
}
$this->resetImportObject($event, IMPORT_LIVE);
if (count($load_keys)) {
$where_clause = '';
$language_id = (int)$this->Application->GetVar('m_lang');
if (!$language_id) {
$language_id = 1;
}
foreach ($load_keys as $field_name => $field_value) {
if (preg_match('/^cust_(.*)/', $field_name, $regs)) {
$custom_id = array_search($regs[1], $this->customFields);
$field_name = 'l'.$language_id.'_cust_'.$custom_id;
$where_clause .= '(custom_data.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
}
else {
$where_clause .= '(item_table.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND ';
}
}
$where_clause = substr($where_clause, 0, -5);
$item_id = $this->getFromCache('new_ids', crc32($where_clause));
if (!$item_id) {
if ($this->exportOptions['CheckDuplicatesMethod'] == 2) {
// by other fields
$parent_path = $this->getParentPath($category_id);
$where_clause = '(c.ParentPath LIKE "'.$parent_path.'%") AND '.$where_clause;
}
$cdata_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
$sql = 'SELECT '.$this->curItem->IDField.'
FROM '.$this->curItem->TableName.' item_table
LEFT JOIN '.$cdata_table.' custom_data ON custom_data.ResourceId = item_table.ResourceId
LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId
LEFT JOIN '.TABLE_PREFIX.'Categories c ON c.CategoryId = ci.CategoryId
WHERE '.$where_clause;
$item_id = $this->Conn->GetOne($sql);
}
$save_method = $item_id && $this->curItem->Load($item_id) ? 'Update' : 'Create';
if ($save_method == 'Update') {
// replace id from csv file with found id (only when ID is found in cvs file)
if (in_array($this->curItem->IDField, $this->exportFields)) {
$record_data[ array_search($this->curItem->IDField, $this->exportFields) ] = $item_id;
}
}
}
$this->setImportData($record_data);
}
else {
$this->resetImportObject($event, IMPORT_LIVE, $record_data);
$category_id = $this->getItemCategory();
}
// create main record
if ($save_method == 'Create') {
$this->fillRequiredFields($this->false, $this->curItem, true);
}
// $sql_start = microtime(true);
if (!$this->curItem->$save_method()) {
$this->Application->SetVar('m_cat_id', $backup_category_id);
return false;
}
// $sql_end = microtime(true);
// $this->saveLog('SQL ['.$save_method.'] Time: '.($sql_end - $sql_start).'s');
if ($load_keys && ($save_method == 'Create') && $this->exportOptions['ReplaceDuplicates']) {
// map new id to old id
$this->addToCache('new_ids', crc32($where_clause), $this->curItem->GetID() );
}
// assign item to categories
$this->curItem->assignToCategory($category_id, false);
$this->Application->SetVar('m_cat_id', $backup_category_id);
return true;
}
/*function saveLog($msg)
{
static $first_time = true;
$fp = fopen((defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/sqls.log', $first_time ? 'w' : 'a');
fwrite($fp, $msg."\n");
fclose($fp);
$first_time = false;
}*/
/**
* Returns category parent path, if possible, then from cache
*
* @param int $category_id
* @return string
*/
function getParentPath($category_id)
{
$parent_path = $this->getFromCache('category_parent_path', $category_id);
if ($parent_path === false) {
$sql = 'SELECT ParentPath
FROM '.TABLE_PREFIX.'Categories
WHERE CategoryId = '.$category_id;
$parent_path = $this->Conn->GetOne($sql);
$this->addToCache('category_parent_path', $category_id, $parent_path);
}
return $parent_path;
}
function getFileExtension()
{
return $this->exportOptions['ExportFormat'] == 1 ? 'csv' : 'xml';
}
function getLineSeparator($option = 'LineEndings')
{
return $this->exportOptions[$option] == 1 ? "\r\n" : "\n";
}
/**
* Returns field caption for any exported field
*
* @param string $field
* @return string
*/
function getFieldCaption($field)
{
if (substr($field, 0, 10) == '__CUSTOM__')
{
$ret = 'Custom_'.substr($field, 10, strlen($field) );
}
elseif (substr($field, 0, 12) == '__CATEGORY__')
{
return $this->getCategoryTitle();
}
elseif (substr($field, 0, 11) == '__VIRTUAL__') {
$ret = substr($field, 11);
}
else
{
$ret = $field;
}
return Array($ret);
}
/**
* Returns requested field value (including custom fields and category fields)
*
* @param string $field
* @return string
*/
function getFieldValue($field)
{
if (substr($field, 0, 10) == '__CUSTOM__') {
$field = 'cust_'.substr($field, 10, strlen($field));
$ret = $this->curItem->GetField($field);
}
elseif (substr($field, 0, 12) == '__CATEGORY__') {
return $this->getCategoryPath();
}
elseif (substr($field, 0, 11) == '__VIRTUAL__') {
$field = substr($field, 11);
$ret = $this->curItem->GetField($field);
}
else
{
$ret = $this->curItem->GetField($field);
}
$ret = str_replace("\r\n", $this->getLineSeparator('LineEndingsInside'), $ret);
return Array($ret);
}
/**
* Returns category field(-s) caption based on export mode
*
* @return string
*/
function getCategoryTitle()
{
// category path in separated fields
$category_count = $this->getMaxCategoryLevel();
if ($this->exportOptions['CategoryFormat'] == 1)
{
// category path in one field
return $category_count ? Array('CategoryPath') : Array();
}
else
{
$i = 0;
$ret = Array();
while ($i < $category_count) {
$ret[] = 'Category'.($i + 1);
$i++;
}
return $ret;
}
}
/**
* Returns category path in required format for current link
*
* @return string
*/
function getCategoryPath()
{
$category_id = $this->curItem->GetDBField('CategoryId');
$category_path = $this->getFromCache('category_path', $category_id);
if ( !$category_path ) {
$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
/* @var $ml_formatter kMultiLanguage */
$sql = 'SELECT ' . $ml_formatter->LangFieldName('CachedNavbar') . '
FROM ' . TABLE_PREFIX . 'Categories
WHERE CategoryId = ' . $category_id;
$category_path = $this->Conn->GetOne($sql);
$category_path = $category_path ? explode('&|&', $category_path) : Array ();
if ( $category_path && strtolower($category_path[0]) == 'content' ) {
array_shift($category_path);
}
if ( $this->exportOptions['IsBaseCategory'] ) {
$i = $this->exportOptions['BaseLevel'];
while ( $i > 0 ) {
array_shift($category_path);
$i--;
}
}
$category_count = $this->getMaxCategoryLevel();
if ( $this->exportOptions['CategoryFormat'] == 1 ) {
// category path in single field
$category_path = $category_count ? Array (implode($this->exportOptions['CategorySeparator'], $category_path)) : Array ();
}
else {
// category path in separated fields
$levels_used = count($category_path);
if ( $levels_used < $category_count ) {
$i = 0;
while ( $i < $category_count - $levels_used ) {
$category_path[] = '';
$i++;
}
}
}
$this->addToCache('category_path', $category_id, $category_path);
}
return $category_path;
}
/**
* Get maximal category deep level from links beeing exported
*
* @return int
*/
function getMaxCategoryLevel()
{
static $max_level = -1;
if ($max_level != -1)
{
return $max_level;
}
$sql = 'SELECT IF(c.CategoryId IS NULL, 0, MAX( LENGTH(c.ParentPath) - LENGTH( REPLACE(c.ParentPath, "|", "") ) - 1 ))
FROM '.$this->curItem->TableName.' item_table
LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON item_table.ResourceId = ci.ItemResourceId
LEFT JOIN '.TABLE_PREFIX.'Categories c ON c.CategoryId = ci.CategoryId
WHERE (ci.PrimaryCat = 1) AND ';
$where_clause = '';
if ($this->exportOptions['export_ids'] === false) {
// get links from current category & all it's subcategories
if ($this->exportOptions['export_cats_ids'][0] == 0) {
$where_clause = 1;
}
else {
foreach ($this->exportOptions['export_cats_ids'] as $category_id) {
$where_clause .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR ';
}
$where_clause = substr($where_clause, 0, -4);
}
}
else {
// get only selected links
$where_clause = $this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')';
}
$max_level = $this->Conn->GetOne($sql.'('.$where_clause.')');
if ($this->exportOptions['IsBaseCategory'] ) {
$max_level -= $this->exportOptions['BaseLevel'];
}
return $max_level;
}
/**
* Saves one record to export file
*
* @param Array $fields_hash
*/
function writeRecord($fields_hash)
{
kUtil::fputcsv($this->filePointer, $fields_hash, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy'], $this->getLineSeparator() );
}
function readRecord()
{
return fgetcsv($this->filePointer, 10000, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy']);
}
/**
* Saves import/export options
*
* @param kEvent $event
* @param Array $options
* @return void
*/
function saveOptions(&$event, $options = null)
{
if ( !isset($options) ) {
$options = $this->exportOptions;
}
$this->Application->StoreVar($event->getPrefixSpecial() . '_options', serialize($options));
}
/**
* Loads import/export options
*
* @param kEvent $event
* @return Array
*/
function loadOptions(&$event)
{
return unserialize( $this->Application->RecallVar($event->getPrefixSpecial() . '_options') );
}
/**
* Sets correct available & export fields
*
* @param kEvent $event
*/
function prepareExportColumns(&$event)
{
$object =& $event->getObject( Array('skip_autoload' => true) );
/* @var $object kCatDBItem */
if ( !$object->isField('ExportColumns') ) {
// import/export prefix was used (see kDBEventHandler::prepareObject) but object don't plan to be imported/exported
return ;
}
$available_columns = Array();
if ($this->Application->getUnitOption($event->Prefix, 'CatalogItem')) {
// category field (mixed)
$available_columns['__CATEGORY__CategoryPath'] = 'CategoryPath';
if ($event->Special == 'import') {
// category field (separated fields)
$max_level = $this->Application->ConfigValue('MaxImportCategoryLevels');
$i = 0;
while ($i < $max_level) {
$available_columns['__CATEGORY__Category'.($i + 1)] = 'Category'.($i + 1);
$i++;
}
}
}
// db fields
$fields = $object->getFields();
foreach ($fields as $field_name => $field_options) {
if ( !$object->skipField($field_name) ) {
$available_columns[$field_name] = $field_name.( $object->isRequired($field_name) ? '*' : '');
}
}
$handler =& $this->Application->recallObject($event->Prefix.'_EventHandler');
/* @var $handler kDBEventHandler */
$available_columns = array_merge($available_columns, $handler->getCustomExportColumns($event));
// custom fields
$custom_fields = $object->getCustomFields();
foreach ($custom_fields as $custom_id => $custom_name)
{
$available_columns['__CUSTOM__'.$custom_name] = $custom_name;
}
// columns already in use
$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
if ($items_info)
{
list($item_id, $field_values) = each($items_info);
$export_keys = $field_values['ExportColumns'];
$export_keys = $export_keys ? explode('|', substr($export_keys, 1, -1) ) : Array();
}
else {
$export_keys = Array();
}
$export_columns = Array();
foreach ($export_keys as $field_key)
{
$field_name = $this->getExportField($field_key);
$export_columns[$field_key] = $field_name;
unset($available_columns[$field_key]);
}
$options = $object->GetFieldOptions('ExportColumns');
$options['options'] = $export_columns;
$object->SetFieldOptions('ExportColumns', $options);
$options = $object->GetFieldOptions('AvailableColumns');
$options['options'] = $available_columns;
$object->SetFieldOptions('AvailableColumns', $options);
$this->updateImportFiles($event);
$this->PrepareExportPresets($event);
}
/**
* Prepares export presets
*
* @param kEvent $event
* @return void
*/
function PrepareExportPresets(&$event)
{
$object =& $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$options = $object->GetFieldOptions('ExportPresets');
$export_settings = $this->Application->RecallPersistentVar('export_settings');
if ( !$export_settings ) {
return;
}
$export_settings = unserialize($export_settings);
if ( !isset($export_settings[$event->Prefix]) ) {
return;
}
$export_presets = array ('' => '');
foreach ($export_settings[$event->Prefix] as $key => $val) {
$export_presets[implode('|', $val['ExportColumns'])] = $key;
}
$options['options'] = $export_presets;
$object->SetFieldOptions('ExportPresets', $options);
}
function getExportField($field_key)
{
$prepends = Array('__CUSTOM__', '__CATEGORY__');
foreach ($prepends as $prepend)
{
if (substr($field_key, 0, strlen($prepend) ) == $prepend)
{
$field_key = substr($field_key, strlen($prepend), strlen($field_key) );
break;
}
}
return $field_key;
}
/**
* Updates uploaded files list
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function updateImportFiles(&$event)
{
if ( $event->Special != 'import' ) {
return ;
}
$file_helper =& $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$import_filenames = Array ();
$file_helper->CheckFolder(EXPORT_PATH);
$iterator = new DirectoryIterator(EXPORT_PATH);
/* @var $file_info DirectoryIterator */
foreach ($iterator as $file_info) {
$file = $file_info->getFilename();
if ( $file_info->isDir() || $file == 'dummy' || $file_info->getSize() == 0 ) {
continue;
}
$import_filenames[$file] = $file . ' (' . kUtil::formatSize( $file_info->getSize() ) . ')';
}
$object =& $event->getObject();
/* @var $object kDBItem */
$object->SetFieldOption('ImportLocalFilename', 'options', $import_filenames);
}
/**
* Returns module folder
*
* @param kEvent $event
* @return string
*/
function getModuleName(&$event)
{
$module_path = $this->Application->getUnitOption($event->Prefix, 'ModuleFolder') . '/';
$module_name = $this->Application->findModule('Path', $module_path, 'Name');
return mb_strtolower($module_name);
}
/**
* Export form validation & processing
*
* @param kEvent $event
*/
function OnExportBegin(&$event)
{
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( !$items_info ) {
$items_info = unserialize($this->Application->RecallVar($event->getPrefixSpecial() . '_ItemsInfo'));
$this->Application->SetVar($event->getPrefixSpecial(true), $items_info);
}
list($item_id, $field_values) = each($items_info);
$object =& $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
$object->setID($item_id);
$this->setRequiredFields($event);
// save export/import options
if ( $event->Special == 'export' ) {
$export_ids = $this->Application->RecallVar($event->Prefix . '_export_ids');
$export_cats_ids = $this->Application->RecallVar($event->Prefix . '_export_cats_ids');
// used for multistep export
$field_values['export_ids'] = $export_ids ? explode(',', $export_ids) : false;
$field_values['export_cats_ids'] = $export_cats_ids ? explode(',', $export_cats_ids) : Array ($this->Application->GetVar('m_cat_id'));
}
$field_values['ExportColumns'] = $field_values['ExportColumns'] ? explode('|', substr($field_values['ExportColumns'], 1, -1) ) : Array();
$field_values['start_from'] = 0;
$nevent = new kEvent($event->Prefix . ':OnBeforeExportBegin');
$nevent->setEventParam('options', $field_values);
$this->Application->HandleEvent($nevent);
$field_values = $nevent->getEventParam('options');
$this->saveOptions($event, $field_values);
if ( $this->verifyOptions($event) ) {
if ( $this->_getExportSavePreset($object) ) {
$name = $object->GetDBField('ExportPresetName');
$export_settings = $this->Application->RecallPersistentVar('export_settings');
$export_settings = $export_settings ? unserialize($export_settings) : array ();
$export_settings[$event->Prefix][$name] = $field_values;
$this->Application->StorePersistentVar('export_settings', serialize($export_settings));
}
$progress_t = $this->Application->RecallVar('export_progress_t');
if ( $progress_t ) {
$this->Application->RemoveVar('export_progress_t');
}
else {
$progress_t = $this->getModuleName($event) . '/' . $event->Special . '_progress';
}
$event->redirect = $progress_t;
if ( $event->Special == 'import' ) {
$import_category = (int)$this->Application->RecallVar('ImportCategory');
// in future could use module root category if import category will be unavailable :)
$event->SetRedirectParam('m_cat_id', $import_category); // for template permission checking
$this->Application->StoreVar('m_cat_id', $import_category); // for event permission checking
}
}
else {
// make uploaded file local & change source selection
$filename = getArrayValue($field_values, 'ImportFilename');
if ( $filename ) {
$this->updateImportFiles($event);
$object->SetDBField('ImportSource', 2);
$field_values['ImportSource'] = 2;
$object->SetDBField('ImportLocalFilename', $filename);
$field_values['ImportLocalFilename'] = $filename;
$this->saveOptions($event, $field_values);
}
$event->status = kEvent::erFAIL;
$event->redirect = false;
}
}
/**
* Returns export save preset name, when used at all
*
* @param kDBItem $object
* @return string
*/
function _getExportSavePreset(&$object)
{
if ( !$object->isField('ExportSavePreset') ) {
return '';
}
return $object->GetDBField('ExportSavePreset');
}
/**
* set required fields based on import or export params
*
* @param kEvent $event
*/
function setRequiredFields(&$event)
{
$required_fields['common'] = Array('FieldsSeparatedBy', 'LineEndings', 'CategoryFormat');
$required_fields['export'] = Array('ExportFormat', 'ExportFilename','ExportColumns');
$object =& $event->getObject();
/* @var $object kDBItem */
if ($this->_getExportSavePreset($object)) {
$required_fields['export'][] = 'ExportPresetName';
}
$required_fields['import'] = Array('FieldTitles', 'ImportSource', 'CheckDuplicatesMethod'); // ImportFilename, ImportLocalFilename
if ($event->Special == 'import')
{
$import_source = Array(1 => 'ImportFilename', 2 => 'ImportLocalFilename');
$used_field = $import_source[ $object->GetDBField('ImportSource') ];
$required_fields[$event->Special][] = $used_field;
$object->SetFieldOption($used_field, 'error_field', 'ImportSource');
if ($object->GetDBField('FieldTitles') == 2) $required_fields[$event->Special][] = 'ExportColumns'; // manual field titles
}
$required_fields = array_merge($required_fields['common'], $required_fields[$event->Special]);
$object->setRequired($required_fields);
}
}
\ No newline at end of file

Event Timeline