%PDF- %PDF-
Direktori : /proc/self/root/home/bitrix/www/bitrix/modules/sale/lib/compatible/ |
Current File : //proc/self/root/home/bitrix/www/bitrix/modules/sale/lib/compatible/compatible.php |
<?php namespace Bitrix\Sale\Compatible; use Bitrix\Main\Application; use Bitrix\Main\DB; use Bitrix\Main\Entity, Bitrix\Main\Entity\Query, Bitrix\Main\Entity\Field, Bitrix\Main\Entity\ReferenceField, Bitrix\Main\Entity\ExpressionField, Bitrix\Main\SystemException; use Bitrix\Sale\Compatible; class AliasedQuery extends Query { private $aliases = array(); public function __construct($source) { parent::__construct($source); $aliases = &$this->aliases; /** @var Field $field */ foreach ($this->getEntity()->getFields() as $field) { if (! $field instanceof ReferenceField) { $name = $field->getName(); $aliases[$name] = $name; } } } public function getAliases() { return $this->aliases; } public function addAliases(array $aliases) { foreach ($aliases as $alias => $field) { $this->addAlias($alias, $field); } return $this; } public function addAlias($alias, $field = null) { if ($this->aliases[$alias]) { throw new SystemException("`$alias` already added", 0, __FILE__, __LINE__); } elseif (! $field) { $this->aliases[$alias] = $alias; } elseif (is_string($field) || (is_array($field) && $field['expression'])) // TODO Field support { $this->aliases[$alias] = $field; } else { throw new SystemException("invalid `$alias` type", 0, __FILE__, __LINE__); } return $this; } public function getAliasName($alias) { if ($field = $this->aliases[$alias]) { if (is_string($field)) { return $field; // name } elseif (is_array($field)) // TODO Field support { $name = '__'.$alias.'_ALIAS__'; if (! $field['registered']) { $field['registered'] = true; $this->registerRuntimeField($name, $field); } return $name; } else { throw new SystemException("invalid alias '$alias' type", 0, __FILE__, __LINE__); } } else { return null; } } public function addAliasSelect($alias) { return ($name = $this->getAliasName($alias)) ? $this->addSelect($name, $alias) : $this; } public function addAliasGroup($alias) { return ($name = $this->getAliasName($alias)) ? $this->addGroup($name) : $this; } public function addAliasOrder($alias, $order) { return ($name = $this->getAliasName($alias)) ? $this->addOrder($name, $order) : $this; } public function addAliasFilter($key, $value) { preg_match('/^([!%@<=>]{0,3})(.*)$/', $key, $matches); return ($name = $this->getAliasName($matches[2])) ? $this->addFilter($matches[1].$name, $value) : $this; // TODO recursive filters maybe? // if (is_null($key) && is_array($value)) // { // return ($filter = self::getAliasFilterRecursive($value)) // ? $this->addFilter(null, $filter) // : $this; // } // else // { // preg_match('/^([!%@<=>]{0,3})(.*)$/', $key, $matches); // // $alias = $matches[2]; // // if (! ($name = $this->getAliasName($alias))) // { // if ($this->getEntity()->hasField($alias)) // $name = $alias; // else // return $this; // } // // $key = $matches[1].$name; // return parent::addFilter($key, $value); // } } // private function getAliasFilterRecursive(array $filter) // { // $resolved = array(); // // foreach ($filter as $key => $value) // { // if ($key === 'LOGIC') // { // $resolved['LOGIC'] = $value; // } // elseif (is_array($value)) // { // $resolved []= self::getAliasFilterRecursive($value); // } // else // { // preg_match('/^([!%@<=>]{0,3})(.*)$/', $key, $matches); // // $alias = $matches[2]; // // if (! ($name = $this->getAliasName($alias))) // { // if ($this->getEntity()->hasField($alias)) // $name = $alias; // else // continue; // } // // $key = $matches[1].$name; // $resolved[$key] = $value; // } // } // // return $resolved; // } } final class CDBResult extends \CDBResult { function compatibleNavQuery(Query $query, array $arNavStartParams) //, $bIgnoreErrors = false) { $cnt = $query->exec()->getSelectedRowsCount(); // TODO check groups global $DB; if(isset($arNavStartParams["SubstitutionFunction"])) { $arNavStartParams["SubstitutionFunction"]($this, $query->getLastQuery(), $cnt, $arNavStartParams); return null; } if(isset($arNavStartParams["bDescPageNumbering"])) $bDescPageNumbering = $arNavStartParams["bDescPageNumbering"]; else $bDescPageNumbering = false; $this->InitNavStartVars($arNavStartParams); $this->NavRecordCount = $cnt; if($this->NavShowAll) $this->NavPageSize = $this->NavRecordCount; //calculate total pages depend on rows count. start with 1 $this->NavPageCount = ($this->NavPageSize>0 ? floor($this->NavRecordCount/$this->NavPageSize) : 0); if($bDescPageNumbering) { $makeweight = 0; if($this->NavPageSize > 0) $makeweight = ($this->NavRecordCount % $this->NavPageSize); if($this->NavPageCount == 0 && $makeweight > 0) $this->NavPageCount = 1; //page number to display $this->NavPageNomer = ( $this->PAGEN < 1 || $this->PAGEN > $this->NavPageCount ? ($_SESSION[$this->SESS_PAGEN] < 1 || $_SESSION[$this->SESS_PAGEN] > $this->NavPageCount ? $this->NavPageCount : $_SESSION[$this->SESS_PAGEN] ) : $this->PAGEN ); //rows to skip $NavFirstRecordShow = 0; if($this->NavPageNomer != $this->NavPageCount) $NavFirstRecordShow += $makeweight; $NavFirstRecordShow += ($this->NavPageCount - $this->NavPageNomer) * $this->NavPageSize; $NavLastRecordShow = $makeweight + ($this->NavPageCount - $this->NavPageNomer + 1) * $this->NavPageSize; } else { if($this->NavPageSize > 0 && ($this->NavRecordCount % $this->NavPageSize > 0)) $this->NavPageCount++; //calculate total pages depend on rows count. start with 1 if($this->PAGEN >= 1 && $this->PAGEN <= $this->NavPageCount) $this->NavPageNomer = $this->PAGEN; elseif($_SESSION[$this->SESS_PAGEN] >= 1 && $_SESSION[$this->SESS_PAGEN] <= $this->NavPageCount) $this->NavPageNomer = $_SESSION[$this->SESS_PAGEN]; elseif($arNavStartParams["checkOutOfRange"] !== true) $this->NavPageNomer = 1; else return null; //rows to skip $NavFirstRecordShow = $this->NavPageSize*($this->NavPageNomer-1); $NavLastRecordShow = $this->NavPageSize*$this->NavPageNomer; } $NavAdditionalRecords = 0; if(is_set($arNavStartParams, "iNavAddRecords")) $NavAdditionalRecords = $arNavStartParams["iNavAddRecords"]; if(!$this->NavShowAll) { $query->setOffset($NavFirstRecordShow); $query->setLimit($NavLastRecordShow - $NavFirstRecordShow + $NavAdditionalRecords); } $res_tmp = $query->exec(); //, $bIgnoreErrors); // // Return false on sql errors (if $bIgnoreErrors == true) // if ($bIgnoreErrors && ($res_tmp === false)) // return false; // $this->result = $res_tmp->result; $this->DB = $DB; if($this->SqlTraceIndex) $start_time = microtime(true); $temp_arrray = array(); $temp_arrray_add = array(); $tmp_cnt = 0; while($ar = $res_tmp->fetch()) { $tmp_cnt++; if (intval($NavLastRecordShow - $NavFirstRecordShow) > 0 && $tmp_cnt > ($NavLastRecordShow - $NavFirstRecordShow)) $temp_arrray_add[] = $ar; else $temp_arrray[] = $ar; } if($this->SqlTraceIndex) { /** @noinspection PhpUndefinedVariableInspection */ $exec_time = round(microtime(true) - $start_time, 10); $DB->addDebugTime($this->SqlTraceIndex, $exec_time); $DB->timeQuery += $exec_time; } $this->arResult = (!empty($temp_arrray)? $temp_arrray : false); $this->arResultAdd = (!empty($temp_arrray_add)? $temp_arrray_add : false); $this->nSelectedCount = $cnt; $this->bDescPageNumbering = $bDescPageNumbering; $this->bFromLimited = true; return null; } // FetchAdapter private $fetchAdapters = array(); public function addFetchAdapter(FetchAdapter $adapter) { $this->fetchAdapters[] = $adapter; } function Fetch() { if ($row = parent::Fetch()) foreach ($this->fetchAdapters as $adapter) $row = $adapter->adapt($row); return $row; } } interface FetchAdapter { public function adapt(array $row); } class AggregateAdapter implements FetchAdapter { private $aggregated = array(); function __construct(array $aggregated) { $this->aggregated = $aggregated; } public function adapt(array $row) { foreach ($this->aggregated as $alias => $name) { $row[$name] = $row[$alias]; unset ($row[$alias]); } return $row; } } class OrderQuery extends AliasedQuery { private $counted, $grouped, $allSelected, $aggregated = array(); public function counted() { return $this->counted; } public function grouped() { return $this->grouped; } public function allSelected() { return $this->allSelected; } public function aggregated() { return $this->aggregated ? true : false; } private function addAggregatedSelect($alias, $aggregate, $name = null) { $aggregateAlias = '__'.$aggregate.'_'.$alias.'_ALIAS__'; $this->aggregated[$aggregateAlias] = $alias; return $this->addSelect( $name ? new ExpressionField($aggregateAlias, $aggregate.'(%s)', $name) : new ExpressionField($aggregateAlias, $aggregate) ); } public static function explodeFilterKey($key) { preg_match('/^([!+*]{0,1})([<=>@%~]{0,2})(.*)$/', $key, $matches); return array( 'modifier' => $matches[1], // can be "" 'operator' => $matches[2], // can be "" 'alias' => $matches[3], // can be "" ); } public function compatibleAddFilter($key, $value) { $keyMatch = static::explodeFilterKey($key); $modifier = $keyMatch['modifier']; $operator = $keyMatch['operator']; $alias = $keyMatch['alias' ]; if (! $name = $this->getAliasName($alias)) return $this; switch ($operator) { case '': case '@': $operator = ($modifier === '*' ? '' : '='); break; case '~': $operator = ''; break; // default: with no changes } switch ($modifier) { case '*': case '' : return $this->addFilter($modifier.$operator.$name, $value); case '!': return $operator == '=' && $value ? $this->addFilter(null, array('LOGIC' => 'OR', array('!='.$name => $value), array('='.$name => ''))) : $this->addFilter($modifier.$operator.$name, $value); case '+': return $this->addFilter(null, array('LOGIC' => 'OR', array($operator.$name => $value), array('='.$name => ''))); default : throw new SystemException("invalid modifier '$modifier'", 0, __FILE__, __LINE__); } } protected function mapLocationRuntimeField($field, $asFilter = false) { return $field; } public function prepare(array $order, array $filter, $group, array $select) { // Do not remove!!! // file_put_contents('/var/www/log' // , spl_object_hash($this)."\n" // . 'Order: '.print_r($order,true) // . 'Filter: '.print_r($filter,true) // . 'Group: '.print_r($group,true) // . 'Select: '.print_r($select,true) // ."\n\n\n" // , FILE_APPEND); static $aggregates = array('COUNT'=>1, 'AVG'=>1, 'MIN'=>1, 'MAX'=>1, 'SUM'=>1); foreach ($filter as $key => $value) { $key = $this->mapLocationRuntimeField($key, true); $this->compatibleAddFilter($key, $value); } if (is_array($group)) { if (empty($group)) { $this->counted = true; return; } else { foreach ($group as $key => $alias) { if ($name = $this->getAliasName($alias)) { if (is_string($key) && ($aggregate = ToUpper($key)) && $aggregates[$aggregate]) { $this->addAggregatedSelect($alias, $aggregate, $name); } else { $this->grouped = true; $this->addGroup($name); $this->addSelect($name, $alias); } } } if ($this->grouped) { $this->addAggregatedSelect('CNT', 'COUNT(*)'); // TODO Maybe? "COUNT(%%_DISTINCT_%% ".$arFields[$arFieldsKeys[0]]["FIELD"].") as CNT "; } } } else { if (empty($select) || $select == array('*')) { $this->allSelected = true; foreach ($this->getAliases() as $alias => $field) { $field = $this->mapLocationRuntimeField($field); $this->addAliasSelect($alias); } } else { foreach ($select as $key => $alias) { $alias = $this->mapLocationRuntimeField($alias); if ($name = $this->getAliasName($alias)) { if (is_string($key) && ($aggregate = ToUpper($key)) && $aggregates[$aggregate]) { $this->addAggregatedSelect($alias, $aggregate, $name); } else { $this->addSelect($name, $alias); } } } } } foreach ($order as $alias => $value) { $alias = $this->mapLocationRuntimeField($alias); $this->addAliasOrder($alias, $value); } } public function getSelectNamesAssoc() { $names = array(); foreach ($this->getSelect() as $k => $v) { if (is_numeric($k)) { if ($v instanceof Field) $names[$v->getName()] = true; else throw new SystemException("invalid", 0, __FILE__, __LINE__); } else { $names[$k] = true; } } return $names; } public function compatibleExec(CDBResult $result, $navStart) { if ($this->aggregated) { $result->addFetchAdapter(new AggregateAdapter($this->aggregated)); } if (!empty($navStart) && is_array($navStart)) { if (!empty($navStart['nTopCount'])) { $this->setLimit($navStart['nTopCount']); } else { $result->compatibleNavQuery($this, $navStart); return $result; } } $rows = $this->exec()->fetchAll(); $result->InitFromArray($rows); return $result; } } class OrderQueryLocation extends OrderQuery { protected $locationFieldMap = array(); public function addLocationRuntimeField($fieldName, $ref = false) { if((string) $fieldName == '') return false; $this->registerRuntimeField( 'LOCATION', array( 'data_type' => '\Bitrix\Sale\Location\LocationTable', 'reference' => array( '=this.'.$fieldName => 'ref.CODE' ), 'join_type' => 'left' ) ); $fieldType = "CHAR"; /** @var DB\Connection $connection */ $connection = Application::getConnection(); if ($connection instanceof DB\MssqlConnection) { $fieldType = "VARCHAR"; } elseif($connection instanceof DB\OracleConnection) { $fieldType = "VARCHAR2"; } $this->registerRuntimeField( 'PROXY_'.$fieldName.'_LINK', array( 'data_type' => 'string', 'expression' => array( "CASE WHEN %s = 'LOCATION' THEN CAST(%s AS ".$fieldType."(500)) ELSE %s END", ($ref !== false ? $ref.'.' : '').'TYPE', 'LOCATION.ID', $fieldName ) ) ); $this->addAliases(array( $fieldName.'_ORIG' => $fieldName, 'PROXY_'.$fieldName => 'PROXY_'.$fieldName.'_LINK' )); $this->locationFieldMap[$fieldName] = true; } protected function mapLocationRuntimeField($field, $asFilter = false) { if($asFilter) { $parsed = static::explodeFilterKey($field); if($this->locationFieldMap[$parsed['alias']]) return $parsed['modifier'].$parsed['operator'].'PROXY_'.$parsed['alias']; else return $field; } else { if($this->locationFieldMap[$field]) return 'PROXY_'.$field; else return $field; } } } final class Test { static function assertLastQuery($name, $query) { $lastQuery = Query::getLastQuery(); return $query == $lastQuery ? "ok\n" : "\n$name - Assert Last Query Failed!\nExpected:\n($query)\nGiven:\n($lastQuery)\n\n"; } static function run() { foreach (glob($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/sale/lib/compatible/tests/*.test.php') as $filename) { include $filename; } } }