Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/sale/lib/compatible/ |
Upload File : |
<?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] ?? false)) { 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 (!isset($this->aliases[$alias])) { return null; } $field = $this->aliases[$alias]; if (is_string($field)) { return $field; // name } elseif (is_array($field)) // TODO Field support { $name = '__'.$alias.'_ALIAS__'; $field['registered'] ??= false; if (!$field['registered']) { $field['registered'] = true; $this->registerRuntimeField($name, $field); } return $name; } throw new SystemException("invalid alias '$alias' type", 0, __FILE__, __LINE__); } 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->calculatePageNumber($this->NavPageCount); //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 $this->calculatePageNumber(1, true, (bool)($arNavStartParams["checkOutOfRange"] ?? false)); if ($this->NavPageNomer === null) { 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 = mb_strtoupper($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 = mb_strtoupper($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; } protected function prepareCompatibleRows(array $rows) { return $rows; } 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(); $rows = $this->prepareCompatibleRows($rows); $result->InitFromArray($rows); return $result; } } class OrderPropertyValuesQuery extends OrderQuery { /** * @param array $rows * @return array */ protected function prepareCompatibleRows(array $rows) { $locationIds = []; foreach ($rows as $key => $row) { if ($row['TYPE'] === 'LOCATION' && !empty($row['VALUE'])) { if (is_array($row['VALUE'])) { $locationIds = array_merge($locationIds, $row['VALUE']); } else { $locationIds[] = $row['VALUE']; } } } if (!empty($locationIds)) { $locationMap = []; $locationRaw = \Bitrix\Sale\Location\LocationTable::getList([ 'filter' => ['=CODE' => $locationIds], 'select' => ['ID', 'CODE'] ]); while ($location = $locationRaw->fetch()) { $locationMap[$location['CODE']] = $location['ID']; } } foreach ($rows as &$row) { if (isset($row['VALUE'])) { if ($row['TYPE'] === 'LOCATION' && !empty($row['VALUE'])) { if (is_array($row['VALUE'])) { foreach ($row['VALUE'] as &$valueItem) { $valueItem = $locationMap[$valueItem]; } } else { $row['VALUE'] = $locationMap[$row['VALUE']]; } } $row['PROXY_VALUE'] = $row['VALUE']; if (is_array($row['VALUE'])) { $row['PROXY_VALUE'] = serialize($row['VALUE']); } unset($row['VALUE']); } } return $rows; } } 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 (isset($this->locationFieldMap[$parsed['alias']])) { return $parsed['modifier'].$parsed['operator'].'PROXY_'.$parsed['alias']; } else { return $field; } } else { if (isset($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; } } }