%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/sale/lib/location/search/ |
Current File : //home/bitrix/www/bitrix/modules/sale/lib/location/search/finder.php |
<?php /** * Bitrix Framework * @package Bitrix\Sale\Location * @subpackage sale * @copyright 2001-2014 Bitrix */ namespace Bitrix\Sale\Location\Search; use Bitrix\Main; use Bitrix\Main\DB; use Bitrix\Main\Localization\Loc; use Bitrix\Main\Config\Option; use Bitrix\Sale\Location; use Bitrix\Sale\Location\Util\Assert; Loc::loadMessages(__FILE__); class Finder { const SALE_LOCATION_INDEXED_TYPES_OPT = 'sale.location.indexed_types'; const SALE_LOCATION_INDEXED_LANGUAGES_OPT = 'sale.location.indexed_langs'; const SALE_LOCATION_INDEX_VALID_OPT = 'sale.location.index_valid'; protected static $allowedOperations = array( '=' => true ); public static function checkIndexValid() { return Option::get('sale', self::SALE_LOCATION_INDEX_VALID_OPT, '', '') == 'Y'; } public static function setIndexValid() { Option::set('sale', self::SALE_LOCATION_INDEX_VALID_OPT, 'Y', ''); } public static function setIndexInvalid() { Option::set('sale', self::SALE_LOCATION_INDEX_VALID_OPT, 'N', ''); } public static function getIndexedTypes() { $types = Option::get('sale', self::SALE_LOCATION_INDEXED_TYPES_OPT, '', ''); $typesFromDb = static::getTypesFromDb(); if(!strlen($types)) // means "all" return array_keys($typesFromDb); $types = explode(':', $types); $result = array(); if(is_array($types)) { foreach($types as $type) { $type = intval($type); if(isset($typesFromDb[$type])) $result[] = $type; } } return array_unique($result); } public static function setIndexedTypes($types = array()) { $result = array(); if(is_array($types) && !empty($types)) { $typesFromDb = static::getTypesFromDb(); foreach($types as $type) { $type = intval($type); if(isset($typesFromDb[$type])) $result[] = $type; } $result = array_unique($result); } Option::set('sale', self::SALE_LOCATION_INDEXED_TYPES_OPT, implode(':', $result), ''); } public static function getIndexedLanguages() { $langs = Option::get('sale', self::SALE_LOCATION_INDEXED_LANGUAGES_OPT, '', ''); $langsFromDb = static::getLangsFromDb(); if(!strlen($langs)) return array_keys($langsFromDb); $result = array(); $langs = explode(':', $langs); if(is_array($langs)) { foreach($langs as $lang) { if(isset($langsFromDb[$lang])) $result[] = $lang; } } return array_unique($result); } public static function setIndexedLanguages($langs = array()) { if(is_array($langs) && !empty($langs)) $langs = array_unique($langs); else $langs = array(); $result = array(); if(is_array($langs) && !empty($langs)) { $langsFromDb = static::getLangsFromDb(); foreach($langs as $lang) { if(isset($langsFromDb[$lang])) $result[] = $lang; } $result = array_unique($result); } Option::set('sale', self::SALE_LOCATION_INDEXED_LANGUAGES_OPT, implode(':', $result), ''); } protected static function getLangsFromDb() { $langsFromDb = array(); $res = \Bitrix\Main\Localization\LanguageTable::getList(array('select' => array('ID'))); while($item = $res->fetch()) $langsFromDb[$item['ID']] = true; return $langsFromDb; } protected static function getTypesFromDb() { $typesFromDb = array(); $res = Location\TypeTable::getList(array('select' => array('ID'))); while($item = $res->fetch()) $typesFromDb[intval($item['ID'])] = true; return $typesFromDb; } /** * * $parameters is an ORM`s getList compatible array of parameters * * */ public static function find($parameters, $behaviour = array('FALLBACK_TO_NOINDEX_ON_NOTFOUND' => true, 'USE_INDEX' => true, 'USE_ORM' => true)) { ///////////////////////////////// // parameter check and process Assert::expectArray($parameters, '$parameters'); if(!is_array($behaviour)) $behaviour = array(); if(!isset($behaviour['FALLBACK_TO_NOINDEX_ON_NOTFOUND'])) $behaviour['FALLBACK_TO_NOINDEX_ON_NOTFOUND'] = true; if(!isset($behaviour['USE_INDEX'])) $behaviour['USE_INDEX'] = true; if(!isset($behaviour['USE_ORM'])) $behaviour['USE_ORM'] = true; if(!isset($parameters['select'])) $parameters['select'] = array('ID'); Assert::expectArray($parameters['select'], '$parameters[select]'); if(isset($parameters['filter'])) { Assert::expectArray($parameters['filter'], '$parameters[filter]'); // spikes, refactor later if(isset($parameters['filter']['PHRASE']) || isset($parameters['filter']['=PHRASE'])) { $key = isset($parameters['filter']['PHRASE']) ? 'PHRASE' : '=PHRASE'; $parameters['filter'][$key] = Assert::expectStringNotNull($parameters['filter'][$key], '$parameters[filter]['.$key.']'); $parameters['filter'][$key] = str_replace('%', '', $parameters['filter'][$key]); // cannot pass '%' to like } if(isset($parameters['filter']['SITE_ID']) || isset($parameters['filter']['=SITE_ID'])) { $key = isset($parameters['filter']['SITE_ID']) ? 'SITE_ID' : '=SITE_ID'; $parameters['filter'][$key] = Assert::expectStringNotNull($parameters['filter'][$key], '$parameters[filter]['.$key.']'); // stronger here if(!Location\SiteLocationTable::checkLinkUsageAny($parameters['filter'][$key])) unset($parameters['filter'][$key]); } } if(isset($parameters['limit'])) $parameters['limit'] = Assert::expectIntegerNonNegative($parameters['limit'], '$parameters[limit]'); if(isset($parameters['offset'])) $parameters['offset'] = Assert::expectIntegerNonNegative($parameters['offset'], '$parameters[offset]'); ///////////////////////////////// if( (isset($parameters['filter']['PHRASE']) || isset($parameters['filter']['SITE_ID']) || isset($parameters['filter']['=PHRASE']) || isset($parameters['filter']['=SITE_ID'])) || $behaviour['USE_ORM'] === false ) { if(static::checkIndexValid() && $behaviour['USE_INDEX']) { $result = static::findUsingIndex($parameters); if(!$behaviour['FALLBACK_TO_NOINDEX_ON_NOTFOUND']) { return $result; } else { $temporalBuffer = array(); while($item = $result->fetch()) { $temporalBuffer[] = $item; } if(empty($temporalBuffer)) { return static::findNoIndex($parameters); } else { return new DB\ArrayResult($temporalBuffer); } } } else { return static::findNoIndex($parameters); } } else { return Location\LocationTable::getList($parameters); } } protected static function parseFilter($filter) { $parsed = array(); if(is_array($filter)) { foreach($filter as $field => $value) { $found = array(); preg_match("#^(=?)(.+)#", $field, $found); if(strlen($found[1])) $op = $found[1]; else $op = '='; if(!isset(static::$allowedOperations[$op])) throw new Main\ArgumentException('Unknown modifier in the filter'); $fieldParsed = $found[2]; $parsed[$fieldParsed] = array( 'OP' => strlen($op) ? $op : '=', 'VALUE' => $value ); } } return $parsed; } protected static function findUsingIndex($parameters) { $query = array(); $dbConnection = Main\HttpApplication::getConnection(); $dbHelper = Main\HttpApplication::getConnection()->getSqlHelper(); $filter = static::parseFilter($parameters['filter']); $filterByPhrase = isset($filter['PHRASE']) && strlen($filter['PHRASE']['VALUE']); if($filterByPhrase) // filter by phrase { $bounds = WordTable::getBoundsForPhrase($filter['PHRASE']['VALUE']); $firstBound = array_shift($bounds); $k = 0; foreach($bounds as $bound) { $query['JOIN'][] = " inner join ".ChainTable::getTableName()." A".$k." on A.LOCATION_ID = A".$k.".LOCATION_ID and ( ".($bound['INF'] == $bound['SUP'] ? " A".$k.".POSITION = '".$bound['INF']."'" : " A".$k.".POSITION >= '".$bound['INF']."' and A".$k.".POSITION <= '".$bound['SUP']."'" )." )"; $k++; } $query['WHERE'][] = ( $firstBound['INF'] == $firstBound['SUP'] ? " A.POSITION = '".$firstBound['INF']."'" : " A.POSITION >= '".$firstBound['INF']."' and A.POSITION <= '".$firstBound['SUP']."'" ); $mainTableJoinCondition = 'A.LOCATION_ID'; } else { $mainTableJoinCondition = 'L.ID'; } // site link search if(strlen($filter['SITE_ID']['VALUE']) && SiteLinkTable::checkTableExists()) { $query['JOIN'][] = "inner join ".SiteLinkTable::getTableName()." SL on SL.LOCATION_ID = ".$mainTableJoinCondition." and SL.SITE_ID = '".$dbHelper->forSql($filter['SITE_ID']['VALUE'])."'"; } // process filter and select statements // at least, we support here basic field selection and filtration + NAME.NAME and NAME.LANGUAGE_ID $map = Location\LocationTable::getMap(); $nameRequired = false; $locationRequred = false; if(is_array($parameters['select'])) { foreach($parameters['select'] as $alias => $field) { if($field == 'NAME.NAME' || $field == 'NAME.LANGUAGE_ID') { $nameRequired = true; continue; } if( !isset($map[$field]) || !in_array($map[$field]['data_type'], array('integer', 'string', 'float', 'boolean')) || isset($map[$field]['expression']) ) { unset($parameters['select'][$alias]); } $locationRequred = true; } } foreach($filter as $field => $params) { if($field == 'NAME.NAME' || $field == 'NAME.LANGUAGE_ID') { $nameRequired = true; continue; } if( !isset($map[$field]) || !in_array($map[$field]['data_type'], array('integer', 'string', 'float', 'boolean')) || isset($map[$field]['expression']) ) { unset($filter[$field]); } $locationRequred = true; } // data join, only if extended select specified if($locationRequred && $filterByPhrase) $query['JOIN'][] = "inner join ".Location\LocationTable::getTableName()." L on A.LOCATION_ID = L.ID"; if($nameRequired) $query['JOIN'][] = "inner join ".Location\Name\LocationTable::getTableName()." NAME on NAME.LOCATION_ID = ".$mainTableJoinCondition; // and N.LANGUAGE_ID = 'ru' // making select if(is_array($parameters['select'])) { $select = array(); foreach($parameters['select'] as $alias => $field) { if($field != 'NAME.NAME' && $field != 'NAME.LANGUAGE_ID') $field = 'L.'.$dbHelper->forSql($field); if((string) $alias === (string) intval($alias)) $select[] = $field; else $select[] = $field.' as '.$dbHelper->forSql($alias); } $sqlSelect = implode(', ', $select); } else $sqlSelect = $mainTableJoinCondition.' as ID'; // making filter foreach($filter as $field => $params) { if($field != 'NAME.NAME' && $field != 'NAME.LANGUAGE_ID') $field = 'L.'.$dbHelper->forSql($field); $values = $params['VALUE']; if(!is_array($values)) $values = array($values); foreach($values as $value) $query['WHERE'][] = $field.' '.$params['OP']." '".$dbHelper->forSql($value)."'"; } if($filterByPhrase) { $sql = " select ".($dbConnection->getType() != 'mysql' ? '' : 'distinct')/*fix this in more clever way later*/." ".$sqlSelect.(\Bitrix\Sale\Location\DB\Helper::needSelectFieldsInOrderByWhenDistinct() ? ', A.RELEVANCY' : '')." from ".ChainTable::getTableName()." A ".implode(' ', $query['JOIN'])." ".(count($query['WHERE']) ? 'where ' : '').implode(' and ', $query['WHERE'])." order by A.RELEVANCY asc "; } else { $sql = " select ".$sqlSelect." from ".Location\LocationTable::getTableName()." L ".implode(' ', $query['JOIN'])." ".(count($query['WHERE']) ? 'where ' : '').implode(' and ', $query['WHERE'])." "; } $offset = intval($parameters['offset']); $limit = intval($parameters['limit']); if($limit) $sql = $dbHelper->getTopSql($sql, $limit, $offset); $res = $dbConnection->query($sql); return $res; } /** * * * @param * * @return */ protected static function findNoIndex($parameters) { $dbConnection = Main\HttpApplication::getConnection(); $dbHelper = $dbConnection->getSqlHelper(); // tables $locationTable = Location\LocationTable::getTableName(); $locationNameTable = Location\Name\LocationTable::getTableName(); $locationGroupTable = Location\GroupLocationTable::getTableName(); $locationSiteTable = Location\SiteLocationTable::getTableName(); $locationTypeTable = Location\TypeTable::getTableName(); ////////////////////////////////// // sql parameters prepare ////////////////////////////////// $filter = static::parseFilter($parameters['filter']); if(strlen($filter['SITE_ID']['VALUE'])) { $filterSite = $dbHelper->forSql(substr($filter['SITE_ID']['VALUE'], 0, 2)); $hasLocLinks = Location\SiteLocationTable::checkLinkUsage($filterSite, Location\SiteLocationTable::DB_LOCATION_FLAG); $hasGrpLinks = Location\SiteLocationTable::checkLinkUsage($filterSite, Location\SiteLocationTable::DB_GROUP_FLAG); $doFilterBySite = true; } if(strlen($filter['PHRASE']['VALUE'])) { $doFilterByName = true; $filterName = ToUpper($dbHelper->forSql($filter['PHRASE']['VALUE'])); } if(intval($filter['ID']['VALUE'])) { $doFilterById = true; $filterId = intval($filter['ID']['VALUE']); } if(intval($filter['CODE']['VALUE'])) { $doFilterByCode = true; $filterCode = $dbHelper->forSql($filter['CODE']['VALUE']); } $doFilterByLang = true; if(strlen($filter['NAME.LANGUAGE_ID']['VALUE'])) { $filterLang = $dbHelper->forSql(substr($filter['NAME.LANGUAGE_ID']['VALUE'], 0, 2)); } else $filterLang = LANGUAGE_ID; if(isset($filter['PARENT_ID']) && intval($filter['PARENT_ID']['VALUE']) >= 0) { $doFilterByParent = true; $filterParentId = intval($filter['PARENT_ID']['VALUE']); } if(intval($filter['TYPE_ID']['VALUE'])) { $doFilterByType = true; $filterTypeId = intval($filter['TYPE_ID']['VALUE']); } // filter select fields if(!is_array($parameters['select'])) $parameters['select'] = array(); $map = Location\LocationTable::getMap(); $nameAlias = false; foreach($parameters['select'] as $alias => $field) { if($field == 'CHILD_CNT') $doCountChildren = true; if($field == 'NAME.NAME') $nameAlias = $alias; if(/*in_array($field, array('ID', 'CODE', 'SORT', 'LEFT_MARGIN', 'RIGHT_MARGIN')) || */ !isset($map[$field]) || !in_array($map[$field]['data_type'], array('integer', 'string', 'float', 'boolean')) || isset($map[$field]['expression']) ) { unset($parameters['select'][$alias]); } } ////////////////////////////////// // sql query build ////////////////////////////////// // mandatory fields to be selected anyway // alias => field $fields = array( 'L.ID' => 'L.ID', 'L.CODE' => 'L.CODE', 'L.SORT' => 'L.SORT', 'LT_SORT' => 'LT.DISPLAY_SORT' ); if($nameAlias === false || !preg_match('#^[a-zA-Z0-9]+$#', $nameAlias)) { $fields['NAME'] = 'LN.NAME'; } else { $fields[$nameAlias] = 'LN.NAME'; } $fields = array_merge($fields, array( 'L.LEFT_MARGIN' => 'L.LEFT_MARGIN', 'L.RIGHT_MARGIN' => 'L.RIGHT_MARGIN' )); $groupFields = $fields; // additional fields to select foreach($parameters['select'] as $alias => $fld) { $lFld = 'L.'.$fld; // check if field is already selected if((string) $alias === (string) intval($alias)) { // already selected if(in_array($lFld, $fields)) continue; $fields[$lFld] = $lFld; //$groupFields[$lFld] = $lFld; } else // alias is not a number { if(isset($fields[$alias])) continue; $fields[$alias] = $lFld; //$groupFields[$alias] = $lFld; } $groupFields[$lFld] = $lFld; } if($doCountChildren) $fields['CHILD_CNT'] = 'COUNT(LC.ID)'; // make select sql $selectSql = array(); foreach($fields as $alias => $fld) { if($fld == $alias) $selectSql[] = $fld; else $selectSql[] = $fld.' as '.$alias; } $selectSql = implode(', ', $selectSql); //$groupSql = implode(', ', array_keys($groupFields)); $groupSql = implode(', ', $groupFields); $mainSql = "select {$selectSql} from {$locationTable} L inner join {$locationNameTable} LN on L.ID = LN.LOCATION_ID inner join {$locationTypeTable} LT on L.TYPE_ID = LT.ID ". ($doCountChildren ? " left join {$locationTable} LC on L.ID = LC.PARENT_ID " : "")." %SITE_FILTER_CONDITION% where %MAIN_FILTER_CONDITION% %GROUP_BY% "; $where = array(); if($doFilterByLang) $where[] = "LN.LANGUAGE_ID = '".$filterLang."'"; if($doFilterByParent) $where[] = "L.PARENT_ID = '".$filterParentId."'"; if($doFilterById) $where[] = "L.ID = '".$filterId."'"; if($doFilterByCode) $where[] = "L.CODE = '".$filterCode."'"; if($doFilterByType) $where[] = "L.TYPE_ID = '".$filterTypeId."'"; if($doFilterByName) $where[] = "LN.NAME_UPPER like '".$filterName."%'"; $mainSql = str_replace('%MAIN_FILTER_CONDITION%', implode(' and ', $where), $mainSql); $needDistinct = false; $unionized = false; $artificialNav = false; if(!$doFilterBySite) { $sql = str_replace('%SITE_FILTER_CONDITION%', '', $mainSql); } else { $sql = array(); if($hasLocLinks) { $sql[] = str_replace('%SITE_FILTER_CONDITION%', " inner join {$locationTable} L2 on L2.LEFT_MARGIN <= L.LEFT_MARGIN and L2.RIGHT_MARGIN >= L.RIGHT_MARGIN inner join {$locationSiteTable} LS2 on L2.ID = LS2.LOCATION_ID and LS2.LOCATION_TYPE = 'L' and LS2.SITE_ID = '{$filterSite}' ", $mainSql); } if($hasGrpLinks) { $sql[] = str_replace('%SITE_FILTER_CONDITION%', " inner join {$locationTable} L2 on L2.LEFT_MARGIN <= L.LEFT_MARGIN and L2.RIGHT_MARGIN >= L.RIGHT_MARGIN inner join {$locationGroupTable} LG on LG.LOCATION_ID = L2.ID inner join {$locationSiteTable} LS2 on LG.LOCATION_GROUP_ID = LS2.LOCATION_ID and LS2.LOCATION_TYPE = 'G' and LS2.SITE_ID = '{$filterSite}' ", $mainSql); $useDistinct = true; } $cnt = count($sql); if($cnt == 1) { $needDistinct = true; } else { // UNION removes duplicates, so distinct is required only when no union here $unionized = true; } $sql = ($cnt > 1 ? '(' : '').implode(') union (', $sql).($cnt > 1 ? ')' : ''); } // set groupping if needed $sql = str_replace('%GROUP_BY%', $needDistinct || $doCountChildren ? "group by {$groupSql}" : '', $sql); if(!is_array($parameters['order'])) { $sql .= " order by 3, 4 asc, 5"; } else { // currenly spike if(isset($parameters['order']['NAME.NAME'])) $sql .= " order by 5 ".($parameters['order']['NAME.NAME'] == 'asc' ? 'asc' : 'desc'); } $offset = intval($parameters['offset']); $limit = intval($parameters['limit']); if($limit) { if($dbConnection->getType() == 'mssql') { // due to huge amount of limitations of windowed functions in transact, using artificial nav here // (does not support UNION and integer indices in ORDER BY) $artificialNav = true; } else { $sql = $dbHelper->getTopSql($sql, $limit, $offset); } } $res = $dbConnection->query($sql); if($artificialNav) { $result = array(); $i = -1; while($item = $res->fetch()) { $i++; if($i < $offset) continue; if($i >= $offset + $limit) break; $result[] = $item; } return new DB\ArrayResult($result); } else { return $res; } } }