%PDF- %PDF-
| Direktori : /home/bitrix/www/bitrix/modules/sale/lib/delivery/ |
| Current File : //home/bitrix/www/bitrix/modules/sale/lib/delivery/externallocationmap.php |
<?
namespace Bitrix\Sale\Delivery;
use Bitrix\Main\Error;
use Bitrix\Sale\Location\Comparator;
use Bitrix\Sale\Result;
use Bitrix\Main\Text\Encoding;
use Bitrix\Main\SystemException;
use Bitrix\Main\ArgumentNullException;
use Bitrix\Sale\Location\ExternalTable;
use Bitrix\Sale\Location\LocationTable;
use Bitrix\Sale\Location\ExternalServiceTable;
class ExternalLocationMap
{
//Dlivery idtifyer, stored in \Bitrix\Sale\Location\ExternalServiceTable : CODE
const EXTERNAL_SERVICE_CODE = '';
//Path to file (if exist) were we can get prepared locations map
const CSV_FILE_PATH = '';
const CITY_NAME_IDX = 0;
const REGION_NAME_IDX = 1;
const CITY_XML_ID_IDX = 2;
/**
* Abstract.
* Must return in Result->data all locations from external delivery service.
* @return Result.
* @throws SystemException
*/
protected static function getAllLocations()
{
throw new SystemException('Must be impemented!');
}
/**
* Returns internal location id
* @param string $externalCode
* @return int
* @throws \Bitrix\Main\ArgumentException
*/
public static function getInternalId($externalCode)
{
if(strlen($externalCode) <= 0)
return 0;
$srvId = static::getExternalServiceId();
if($srvId <= 0)
return 0;
$res = ExternalTable::getList(array(
'filter' => array(
'=XML_ID' => $externalCode,
'=SERVICE_ID' => $srvId
)
));
if($loc = $res->fetch())
return $loc['ID'];
return 0;
}
/**
* Returns external location id
* @param int $locationId
* @return int|string
* @throws \Bitrix\Main\ArgumentException
*/
public static function getExternalId($locationId)
{
if(strlen($locationId) <= 0)
return '';
$srvId = static::getExternalServiceId();
if($srvId <= 0)
return 0;
$res = LocationTable::getList(array(
'filter' => array(
array(
'LOGIC' => 'OR',
'=CODE' => $locationId,
'=ID' => $locationId
),
'=EXTERNAL.SERVICE_ID' => $srvId
),
'select' => array(
'ID', 'CODE',
'XML_ID' => 'EXTERNAL.XML_ID'
)
));
$result = '';
if($loc = $res->fetch())
$result = $loc['XML_ID'];
if(strlen($result) <= 0)
$result = self::getUpperCityExternalId($locationId, $srvId);
return $result;
}
protected static function getUpperCityExternalId($locationId, $srvId)
{
$result = '';
$res = LocationTable::getList(array(
'filter' => array(
array(
'LOGIC' => 'OR',
'=CODE' => $locationId,
'=ID' => $locationId
),
),
'select' => array(
'ID', 'CODE', 'LEFT_MARGIN', 'RIGHT_MARGIN',
'TYPE_CODE' => 'TYPE.CODE'
)
));
if(!$loc = $res->fetch())
return '';
if($loc['TYPE_CODE'] == 'CITY')
return '';
$res = LocationTable::getList(array(
'filter' => array(
'<LEFT_MARGIN' => $loc['LEFT_MARGIN'],
'>RIGHT_MARGIN' => $loc['RIGHT_MARGIN'],
'TYPE.CODE' => 'CITY',
'=EXTERNAL.SERVICE_ID' => $srvId
),
'select' => array(
'ID', 'CODE', 'LEFT_MARGIN', 'RIGHT_MARGIN',
'XML_ID' => 'EXTERNAL.XML_ID'
)
));
if($locParent = $res->fetch())
return $locParent['XML_ID'];
return $result;
}
/**
* Returns external location city id
* @param int $locationId
* @return int|string
* @throws \Bitrix\Main\ArgumentException
*/
public static function getCityId($locationId)
{
if(strlen($locationId) <= 0)
return 0;
$res = LocationTable::getList(array(
'filter' => array(
array(
'LOGIC' => 'OR',
'=CODE' => $locationId,
'=ID' => $locationId,
),
array(
'=TYPE.CODE' => 'CITY',
'=PARENTS.TYPE.CODE' => 'CITY'
),
),
'select' => array(
'ID', 'CODE',
'TYPE_CODE' => 'TYPE.CODE',
'PID' => 'PARENTS.ID',
)
));
if($loc = $res->fetch())
{
return $loc['PID'];
}
return 0;
}
/**
* Install locations map.
* @return Result
*/
public static function install()
{
$result = new Result();
if(static::isInstalled())
return $result;
$imported = static::importFromCsv($_SERVER['DOCUMENT_ROOT'].static::CSV_FILE_PATH);
if(intval($imported) <= 0)
$result = static::refresh();
return $result;
}
/**
* Uninstall locations map.
* @return Result
* @throws \Exception
*/
public static function unInstall()
{
$result = new Result();
if(!static::isInstalled())
return $result;
$con = \Bitrix\Main\Application::getConnection();
$sqlHelper = $con->getSqlHelper();
$srvId = $sqlHelper->forSql(static::getExternalServiceId());
$con->queryExecute("DELETE FROM b_sale_loc_ext WHERE SERVICE_ID=".$srvId);
ExternalServiceTable::delete($srvId);
return $result;
}
/**
* Check locations map was sat.
* @return bool
* @throws \Bitrix\Main\ArgumentException
*/
public static function isInstalled()
{
static $result = null;
if($result === null)
{
$result = false;
$res = ExternalServiceTable::getList(array(
'filter' => array(
'=CODE' => static::EXTERNAL_SERVICE_CODE,
'!=EXTERNAL.ID' => false
)
));
if($res->fetch())
$result = true;
}
return $result;
}
/**
* Refresh locations map.
* @return Result
* @throws ArgumentNullException
*/
public static function refresh()
{
set_time_limit(0);
$result = new Result();
$res = static::getAllLocations();
if($res->isSuccess())
{
$locations = $res->getData();
if(is_array($locations) && !empty($locations))
{
$res = static::setMap($locations);
if(!$res->isSuccess())
$result->addErrors($res->getErrors());
}
}
else
{
$result->addErrors($res->getErrors());
}
return new Result();
}
/**
* Import locations map from csv file to database.
* @param string $path
* @return bool|int Quantity of mapped locations.
* @throws \Bitrix\Main\ArgumentException
* @throws \Exception
*/
public static function importFromCsv($path)
{
set_time_limit(0);
if(strlen($path) <= 0)
return 0;
if(!\Bitrix\Main\IO\File::isFileExists($path))
return 0;
$content = \Bitrix\Main\IO\File::getFileContents($path);
if($content === false)
return 0;
$srvId = self::getExternalServiceId();
if(intval($srvId) < 0)
return 0;
$lines = explode("\n", $content);
if(!is_array($lines))
return array();
$result = 0;
foreach($lines as $line)
{
$columns = explode(';', $line);
if(!is_array($columns) || count($columns) != 2)
continue;
$res = LocationTable::getList(array(
'filter' => array(
'=CODE' => $columns[0],
),
'select' => array('ID')
));
if($loc = $res->fetch())
if(self::setExternalLocation($srvId, $loc['ID'], $columns[1]))
$result++;
}
return $result;
}
/**
* Export locations map from database to file, csv format.
* @param string $path
* @return bool|int
* @throws \Bitrix\Main\ArgumentException
*/
public static function exportToCsv($path)
{
set_time_limit(0);
$srvId = static::getExternalServiceId();
if($srvId <= 0)
return false;
$res = LocationTable::getList(array(
'filter' => array(
'=EXTERNAL.SERVICE_ID' => $srvId
),
'select' => array(
'CODE',
'XML_ID' => 'EXTERNAL.XML_ID'
)
));
$content = '';
while($row = $res->fetch())
if(strlen($row['CODE']) > 0)
$content .= $row['CODE'].";".$row['XML_ID']."\n";
return \Bitrix\Main\IO\File::putFileContents($path, $content);
}
/**
* If exist returns id, if not exist create it
* @return int External service Id
* @throws \Bitrix\Main\ArgumentException
* @throws \Exception
*/
public static function getExternalServiceId()
{
if(strlen(static::EXTERNAL_SERVICE_CODE) <=0)
throw new SystemException('EXTERNAL_SERVICE_CODE must be defined!');
static $result = null;
if($result !== null)
return $result;
$res = ExternalServiceTable::getList(array(
'filter' => array('=CODE' => static::EXTERNAL_SERVICE_CODE)
));
if($srv = $res->fetch())
{
$result = $srv['ID'];
return $result;
}
$res = ExternalServiceTable::add(array('CODE' => static::EXTERNAL_SERVICE_CODE));
if(!$res->isSuccess())
{
$result = 0;
return $result;
}
$result = $res->getId();
return $result;
}
/**
* Decodes data from utf8 if we need
* @param $str
* @return bool|string
*/
protected static function utfDecode($str)
{
if(strtolower(SITE_CHARSET) != 'utf-8')
$str = Encoding::convertEncoding($str, 'UTF-8', SITE_CHARSET);
return $str;
}
/**
* Convert find location by city and region names and add mapping to base
* @param array $cities
* @return Result
* @throws ArgumentNullException
* @throws \Bitrix\Main\ArgumentException
* @throws \Exception
*/
protected static function setMap(array $cities)
{
$result = new Result();
if(empty($cities))
throw new ArgumentNullException('cities');
$xmlIdExist = array();
$locationIdExist = array();
$xmlIds = array_keys($cities);
$srvId = static::getExternalServiceId();
$res = ExternalTable::getList(array(
'filter' => array(
'=SERVICE_ID' => $srvId
)
));
while($map = $res->fetch())
{
$xmlIdExist[] = $map['XML_ID'];
$locationIdExist[] = $map['LOCATION_ID'];
//we already have this location
if(in_array($map['XML_ID'], $xmlIds))
unset($cities[$map['XML_ID']]);
}
//nothing to import
if(empty($cities))
return $result;
foreach($cities as $city)
{
$xmlId = $city[self::CITY_XML_ID_IDX];
$locId = static::getLocationIdByNames($city[static::CITY_NAME_IDX], '', '', $city[static::REGION_NAME_IDX]);
if(intval($locId) > 0 && !in_array($xmlId, $xmlIdExist) && !in_array($locId, $locationIdExist))
{
ExternalTable::add(array(
'SERVICE_ID' => $srvId,
'LOCATION_ID' => $locId,
'XML_ID' => $xmlId
));
$xmlIdExist[] = $xmlId;
$locationIdExist[] = $locId;
}
unset($cities[$xmlId]);
}
return $result;
}
/**
* @param int $srvId
* @param int $locationId
* @param string $xmlId
* @param bool $updateExist
* @return \Bitrix\Main\Entity\AddResult|\Bitrix\Main\Entity\Result|\Bitrix\Main\Entity\UpdateResult
* @throws ArgumentNullException
* @throws \Bitrix\Main\ArgumentException
* @throws \Exception
*/
public static function setExternalLocation2($srvId, $locationId, $xmlId, $updateExist = false)
{
if(strlen($xmlId) <= 0)
throw new ArgumentNullException('code');
if(strlen($srvId) <= 0)
throw new ArgumentNullException('srvId');
if(intval($locationId) <= 0)
throw new ArgumentNullException('locationId');
static $locCache = array();
if(!isset($locCache[$srvId]))
{
$locCache[$srvId] = array();
$eRes = ExternalTable::getList(array(
'filter' => array(
'=SERVICE_ID' => $srvId,
),
'select' => array('ID', 'SERVICE_ID', 'LOCATION_ID', 'XML_ID')
));
while($loc = $eRes->fetch())
$locCache[$srvId][$loc['LOCATION_ID'].'##'.$loc['XML_ID']] = $loc['ID'];
}
if(!empty($locCache[$srvId][$locationId.'##'.$xmlId]))
{
if($updateExist)
{
$res = ExternalTable::update(
$locCache[$srvId][$locationId.'##'.$xmlId],
array(
'SERVICE_ID' => $srvId,
'XML_ID' => $xmlId,
'LOCATION_ID' => $locationId
));
return $res;
}
else
{
$result = new \Bitrix\Main\Entity\UpdateResult();
$result->addError(new Error('External location already exists', 'EXTERNAL_LOCATION_EXISTS'));
return $result;
}
}
else
{
$res = ExternalTable::add(array(
'SERVICE_ID' => $srvId,
'XML_ID' => $xmlId,
'LOCATION_ID' => $locationId
));
$locCache[$srvId][$locationId.'##'.$xmlId] = $res->getId();
return $res;
}
}
/**
* @param int $srvId
* @param int $locationId
* @param string $xmlId
* @param bool $updateExist
* @return bool
* @throws ArgumentNullException
* @throws \Bitrix\Main\ArgumentException
* @throws \Exception
*/
public static function setExternalLocation($srvId, $locationId, $xmlId, $updateExist = false)
{
$result = self::setExternalLocation2($srvId, $locationId, $xmlId, $updateExist);
return $result->isSuccess();
}
protected static function isNormalizedTableFilled()
{
$count = 0;
$con = \Bitrix\Main\Application::getConnection();
$res = $con->query("SELECT COUNT(1) AS COUNT FROM b_sale_hdaln");
if($row = $res->fetch())
$count = intval($row['COUNT']);
return $count > 0;
}
/**
* Fill table b_sale_hdaln with locations with normalized names
*/
public static function fillNormalizedTable($startId = false, $timeout = 0)
{
set_time_limit(0);
$startTime = mktime(true);
$lastProcessedId = 0;
$con = \Bitrix\Main\Application::getConnection();
$sqlHelper = $con->getSqlHelper();
if(intval($startId) <= 0)
$con->queryExecute("DELETE FROM b_sale_hdaln");
$query = "SELECT
L.ID,
L.LEFT_MARGIN,
L.RIGHT_MARGIN,
N.NAME_UPPER
FROM
b_sale_location AS L
INNER JOIN b_sale_loc_name AS N ON L.ID = N.LOCATION_ID
INNER JOIN b_sale_loc_type AS T ON L.TYPE_ID = T.ID
WHERE
N.LANGUAGE_ID = 'ru'
AND (T.CODE = 'VILLAGE' OR T.CODE = 'CITY')";
if($startId !== false)
$query .= " AND L.ID > ".strval(intval($startId));
$query .= " ORDER BY ID ASC";
$res = $con->query($query);
while($loc = $res->fetch())
{
$con->queryExecute("
INSERT INTO
b_sale_hdaln (LOCATION_ID, LEFT_MARGIN, RIGHT_MARGIN, NAME)
VALUES(
".intval($loc['ID']).",
".intval($loc['LEFT_MARGIN']).",
".intval($loc['RIGHT_MARGIN']).",
'".$sqlHelper->forSql(
preg_replace(
'/\s*(\(.*\))/i'.BX_UTF_PCRE_MODIFIER,
'',
\Bitrix\Sale\Location\Comparator::flatten($loc['NAME_UPPER']))
)."'
)
");
$lastProcessedId = $loc['ID'];
if($timeout > 0 && (mktime(true)-$startTime) >= $timeout)
break;
}
return $lastProcessedId;
}
public static function getLocationIdByNames($name, $city, $subregion, $region, $country = '', $exactOnly = false)
{
$nameNorm = Comparator::normalizeEntity($name, 'LOCALITY');
$subregionNorm = null;
$regionNorm = null;
$cityNorm = null;
$searchNames = array($name);
if(!$exactOnly)
$searchNames = array_merge($searchNames, \Bitrix\Sale\Location\Comparator::getLocalityNamesArray($nameNorm['NAME'], $nameNorm['TYPE']));
$searchNames = array_map(array('\Bitrix\Sale\Location\Comparator', 'flatten'), $searchNames);
$searchNames = array_map(function($name){return "'".$name."'";}, $searchNames);
if(empty($searchNames))
return 0;
$con = \Bitrix\Main\Application::getConnection();
$sqlHelper = $con->getSqlHelper();
$margins = array();
$res = $con->query("
SELECT
N.LOCATION_ID AS LOCATION_ID,
N.LEFT_MARGIN AS LEFT_MARGIN,
N.RIGHT_MARGIN AS RIGHT_MARGIN,
N.NAME AS NAME
FROM
b_sale_hdaln AS N
LEFT JOIN b_sale_loc_ext AS E
ON N.LOCATION_ID = E.LOCATION_ID AND E.SERVICE_ID = ".$sqlHelper->forSql(self::getExternalServiceId())."
WHERE
E.LOCATION_ID IS NULL
AND NAME IN (".implode(', ', $searchNames).")");
$results = array();
$exact = array();
while($loc = $res->fetch())
{
if(Comparator::isEntityEqual($loc['NAME'], $nameNorm, 'LOCALITY'))
{
$margins[] = array($loc['LOCATION_ID'], $loc['LEFT_MARGIN'], $loc['RIGHT_MARGIN'], $loc['NAME']);
$results[$loc['LOCATION_ID']] = array('NAME' => true);
if($loc['NAME'] == $nameNorm["NAME"])
$exact[] = $loc['LOCATION_ID'];
}
}
if(empty($margins))
return 0;
$marginFilter = array('LOGIC' => 'OR');
foreach($margins as $v)
$marginFilter[] = array('<LEFT_MARGIN' => $v[1], '>RIGHT_MARGIN' => $v[2]);
$res = LocationTable::getList(array(
'filter' => array(
'=NAME.LANGUAGE_ID' => LANGUAGE_ID,
'=TYPE.CODE' => array('SUBREGION', 'REGION', 'CITY'),
$marginFilter
),
'select' => array(
'ID',
'PARENTS_NAME_UPPER' => 'NAME.NAME_UPPER',
'PARENTS_TYPE_CODE' => 'TYPE.CODE',
'LEFT_MARGIN', 'RIGHT_MARGIN'
)
));
while($loc = $res->fetch())
{
$ids = self::getIdByMargin($loc['LEFT_MARGIN'], $loc['RIGHT_MARGIN'], $margins);
foreach($ids as $id)
{
if(in_array(false, $results[$id], true))
continue;
$found = null;
if($loc['PARENTS_TYPE_CODE'] == 'REGION' && strlen($region) > 0)
{
if(!is_array($regionNorm))
$regionNorm = Comparator::normalizeEntity($region, 'REGION');
$found = Comparator::isEntityEqual($loc['PARENTS_NAME_UPPER'], $regionNorm, 'REGION');
}
elseif(strlen($subregion) > 0 && $loc['PARENTS_TYPE_CODE'] == 'SUBREGION')
{
if(!is_array($subregionNorm))
$subregionNorm = Comparator::normalizeEntity($subregion, 'SUBREGION');
$found = Comparator::isEntityEqual($loc['PARENTS_NAME_UPPER'], $subregionNorm, 'SUBREGION');
}
elseif(strlen($city) > 0 && $loc['PARENTS_TYPE_CODE'] == 'CITY')
{
if(!is_array($cityNorm))
$subregionNorm = Comparator::normalizeEntity($city, 'LOCALITY');
$found = Comparator::isEntityEqual($loc['PARENTS_NAME_UPPER'], $cityNorm, 'LOCALITY');
}
if($found !== null)
{
$isInExact = in_array($id, $exact);
$results[$id][$loc['PARENTS_TYPE_CODE']] = $found;
if($results[$id]['REGION'] === true && $results[$id]['SUBREGION'] === true && $isInExact)
return $id;
if($found === false && $isInExact)
{
$key = array_search($id, $exact);
if($key !== false)
unset($exact[$key]);
}
}
}
}
if(!empty($exact))
foreach($exact as $e)
if(!in_array(false, $results[$e], true))
return $e;
$resCandidates = array();
foreach($results as $id => $result)
{
if(!in_array(false, $result, true))
{
$resCandidates[$id] = count($result);
}
}
if(empty($resCandidates))
return 0;
if(count($resCandidates) > 1)
{
arsort($resCandidates);
reset($resCandidates);
}
return key($resCandidates);
}
protected static function getIdByMargin($parentLeft, $parentRight, $lMargins)
{
$result = array();
foreach($lMargins as $m)
{
if($m[1] > $parentLeft && $m[2] < $parentRight)
$result[] = $m[0];
}
return $result;
}
protected static function getNameByMargin($parentLeft, $parentRight, $lMargins)
{
foreach($lMargins as $m)
{
if($m[1] > $parentLeft && $m[2] < $parentRight)
return $m[3];
}
return 0;
}
}