1
0
Fork 0
mirror of synced 2025-04-18 16:41:02 +00:00

New ICML generator

This commit is contained in:
Сергей Чазов 2021-05-31 16:33:02 +03:00 committed by DmitreyZl
parent 16a1a5e9d8
commit 47e6de9d94
40 changed files with 3010 additions and 117 deletions

View file

@ -1,3 +1,10 @@
## 2021-05-31 v.5.7.0
* Переработан генератор ICML каталога:
- генератор использует потоковую запись в файл через `XMLWriter`;
- по умолчанию опция выгрузки каталога в момент установки модуля отключена;
- код генератора каталога теперь использует автолоадер Битрикса.
* Скидка на позицию больше не учитывается в заказе при установке произвольной цены для позиции.
## 2021-01-14 v.5.6.2
* Исправлено формирование картинок в ICML при включеном CDN
* Убрана некорректная запись внешнего идентификатора платежа для новых платежей по истории

View file

@ -1,3 +1,2 @@
- Исправлено формирование картинок в ICML при включеном CDN
- Убрана некорректная запись внешнего идентификатора платежа для новых платежей по истории
- Добавлена проверка на длину email при отправке в систему
- Переработан генератор ICML каталога;
- Скидка на позицию больше не учитывается в заказе при установке произвольной цены для позиции.

View file

@ -1,59 +1,65 @@
<?php
if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/export_run.php")){
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/export_run.php");
use Bitrix\Highloadblock\HighloadBlockTable;
use Intaro\RetailCrm\Icml\IcmlDirector;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupProps;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories;
use Intaro\RetailCrm\Repository\CatalogRepository;
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/bitrix/php_interface/retailcrm/export_run.php')) {
require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/php_interface/retailcrm/export_run.php');
} else {
ignore_user_abort(true);
set_time_limit(0);
global $APPLICATION;
if (!CModule::IncludeModule("iblock")){
return;
}
if (!CModule::IncludeModule("catalog")){
return;
}
if (!CModule::IncludeModule("intaro.retailcrm")){
return;
}
$rsSites = CSite::GetList($by, $sort, array('ACTIVE' => 'Y'));
while ($ar = $rsSites->Fetch()) {
if ($ar['DEF'] == 'Y') {
$SERVER_NAME = $ar['SERVER_NAME'];
}
if (
!CModule::IncludeModule('iblock')
|| !CModule::IncludeModule('catalog')
|| !CModule::IncludeModule('intaro.retailcrm')
) {
return;
}
$hlblockModule = false;
if (CModule::IncludeModule('highloadblock')) {
$hlblockModule = true;
$hlblockList = array();
$hlblockListDb = \Bitrix\Highloadblock\HighloadBlockTable::getList();
$hlblockList = [];
$hlblockListDb = HighloadBlockTable::getList();
while ($hlblockArr = $hlblockListDb->Fetch()) {
$hlblockList[$hlblockArr["TABLE_NAME"]] = $hlblockArr;
}
}
$iblockProperties = array(
"article" => "article",
"manufacturer" => "manufacturer",
"color" =>"color",
"weight" => "weight",
"size" => "size",
"length" => "length",
"width" => "width",
"height" => "height",
);
$IBLOCK_PROPERTY_SKU = array();
$IBLOCK_PROPERTY_SKU_HIGHLOADBLOCK = array();
$IBLOCK_PROPERTY_UNIT_SKU = array();
$iblockProperties = [
'article' => 'article',
'manufacturer' => 'manufacturer',
'color' => 'color',
'weight' => 'weight',
'size' => 'size',
'length' => 'length',
'width' => 'width',
'height' => 'height',
];
$iblockPropertySku = [];
$iblockPropertySkuHl = [];
$iblockPropertyUnitSku = [];
$iblockPropertyProduct = [];
$iblockPropertyProductHl = [];
$iblockPropertyUnitProduct = [];
foreach ($iblockProperties as $prop) {
$skuUnitProps = ('IBLOCK_PROPERTY_UNIT_SKU' . "_" . $prop);
$skuUnitProps = $$skuUnitProps;
if (is_array($skuUnitProps)) {
foreach ($skuUnitProps as $iblock => $val) {
$IBLOCK_PROPERTY_UNIT_SKU[$iblock][$prop] = $val;
$iblockPropertyUnitSku[$iblock][$prop] = $val;
}
}
@ -61,7 +67,7 @@ if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/expor
$skuProps = $$skuProps;
if (is_array($skuProps)) {
foreach ($skuProps as $iblock => $val) {
$IBLOCK_PROPERTY_SKU[$iblock][$prop] = $val;
$iblockPropertySku[$iblock][$prop] = $val;
}
}
@ -72,22 +78,17 @@ if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/expor
if (is_array($hbProps)) {
foreach ($hbProps as $iblock => $val) {
$IBLOCK_PROPERTY_SKU_HIGHLOADBLOCK[$hlblockTable][$iblock][$prop] = $val;
$iblockPropertySkuHl[$hlblockTable][$iblock][$prop] = $val;
}
}
}
}
}
$IBLOCK_PROPERTY_PRODUCT = array();
$IBLOCK_PROPERTY_PRODUCT_HIGHLOADBLOCK = array();
$IBLOCK_PROPERTY_UNIT_PRODUCT = array();
foreach ($iblockProperties as $prop) {
$productUnitProps = "IBLOCK_PROPERTY_UNIT_PRODUCT" . "_" . $prop;
$productUnitProps = $$productUnitProps;
if (is_array($productUnitProps)) {
foreach ($productUnitProps as $iblock => $val) {
$IBLOCK_PROPERTY_UNIT_PRODUCT[$iblock][$prop] = $val;
$iblockPropertyUnitProduct[$iblock][$prop] = $val;
}
}
@ -95,7 +96,7 @@ if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/expor
$productProps = $$productProps;
if (is_array($productProps)) {
foreach ($productProps as $iblock => $val) {
$IBLOCK_PROPERTY_PRODUCT[$iblock][$prop] = $val;
$iblockPropertyProduct[$iblock][$prop] = $val;
}
}
@ -106,51 +107,52 @@ if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/expor
if (is_array($hbProps)) {
foreach ($hbProps as $iblock => $val) {
$IBLOCK_PROPERTY_PRODUCT_HIGHLOADBLOCK[$hlblockTable][$iblock][$prop] = $val;
$iblockPropertyProductHl[$hlblockTable][$iblock][$prop] = $val;
}
}
}
}
}
$productPictures = array();
$productPictures = [];
if (is_array($IBLOCK_PROPERTY_PRODUCT_picture)) {
foreach ($IBLOCK_PROPERTY_PRODUCT_picture as $key => $value) {
$productPictures[$key]['picture'] = $value;
$productPictures[$key] = $value;
}
}
$skuPictures = array();
$skuPictures = [];
if (is_array($IBLOCK_PROPERTY_SKU_picture)) {
foreach ($IBLOCK_PROPERTY_SKU_picture as $key => $value) {
$skuPictures[$key]['picture'] = $value;
$skuPictures[$key] = $value;
}
}
$loader = new RetailCrmICML();
$loader->profileID = $profile_id;
$loader->iblocks = $IBLOCK_EXPORT;
$loader->propertiesSKU = $IBLOCK_PROPERTY_SKU;
$loader->propertiesUnitSKU = $IBLOCK_PROPERTY_UNIT_SKU;
$loader->propertiesProduct = $IBLOCK_PROPERTY_PRODUCT;
$loader->propertiesUnitProduct = $IBLOCK_PROPERTY_UNIT_PRODUCT;
$loader->productPictures = $productPictures;
$loader->skuPictures = $skuPictures;
$xmlProps = new XmlSetupPropsCategories(
new XmlSetupProps($iblockPropertyProduct, $iblockPropertyUnitProduct, $productPictures),
new XmlSetupProps($iblockPropertySku, $iblockPropertyUnitSku, $skuPictures)
);
if ($hlblockModule === true) {
$loader->highloadblockSkuProperties = $IBLOCK_PROPERTY_SKU_HIGHLOADBLOCK;
$loader->highloadblockProductProperties = $IBLOCK_PROPERTY_PRODUCT_HIGHLOADBLOCK;
$xmlProps->highloadblockSku = $iblockPropertySkuHl;
$xmlProps->highloadblockProduct = $iblockPropertyProductHl;
}
if ($MAX_OFFERS_VALUE) {
$loader->offerPageSize = $MAX_OFFERS_VALUE;
}
$fileSetup = new XmlSetup($xmlProps);
$fileSetup->profileId = $profile_id;
$fileSetup->iblocksForExport = $IBLOCK_EXPORT;
$fileSetup->maxOffersValue = $MAX_OFFERS_VALUE ?? null;
$fileSetup->filePath = $SETUP_FILE_NAME;
$fileSetup->loadPurchasePrice = $LOAD_PURCHASE_PRICE === 'Y';
$fileSetup->basePriceId = CatalogRepository::getBasePriceId($fileSetup->profileId);
$logger = Logger::getInstance('/bitrix/catalog_export/');
$loader->filename = $SETUP_FILE_NAME;
$loader->defaultServerName = $SERVER_NAME;
$loader->application = $APPLICATION;
$loader->loadPurchasePrice = $LOAD_PURCHASE_PRICE == 'Y';
$loader->Load();
}
if (!is_array($fileSetup->iblocksForExport) || count($fileSetup->iblocksForExport) === 0) {
$logger->write(GetMessage("IBLOCK_NOT_SELECTED"), 'i_crm_load_log');
} else {
$loader = new IcmlDirector($fileSetup, $logger);
$loader->generateXml();
}
}

View file

@ -1,13 +1,16 @@
<?
use Bitrix\Highloadblock\HighloadBlockTable;
if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/export_setup.php")){
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/export_setup.php");
} else {
if (isset($_POST['ajax']) && $_POST['ajax'] == '1') {
CModule::IncludeModule('highloadblock');
$rsData = \Bitrix\Highloadblock\HighloadBlockTable::getList(array('filter' => array('TABLE_NAME' => $_POST['table'])));
$rsData = HighloadBlockTable::getList(['filter' => ['TABLE_NAME' => $_POST['table']]]);
$hlblockArr = $rsData->Fetch();
$hlblock = \Bitrix\Highloadblock\HighloadBlockTable::getById($hlblockArr["ID"])->fetch();
$entity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hlblock);
$hlblock = HighloadBlockTable::getById($hlblockArr["ID"])->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$hbFields = $entity->getFields();
$hlblockList['table'] = $hlblockArr["TABLE_NAME"];
@ -55,11 +58,11 @@ if (file_exists($_SERVER["DOCUMENT_ROOT"]."/bitrix/php_interface/retailcrm/expor
if (CModule::IncludeModule('highloadblock')) {
$hlblockModule = true;
$hlblockList = array();
$hlblockListDb = \Bitrix\Highloadblock\HighloadBlockTable::getList();
$hlblockListDb = HighloadBlockTable::getList();
while ($hlblockArr = $hlblockListDb->Fetch()) {
$hlblock = \Bitrix\Highloadblock\HighloadBlockTable::getById($hlblockArr["ID"])->fetch();
$entity = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity($hlblock);
$hlblock = HighloadBlockTable::getById($hlblockArr["ID"])->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$hbFields = $entity->getFields();
$hlblockList[$hlblockArr["TABLE_NAME"]]['LABEL'] = $hlblockArr["NAME"];

View file

@ -495,8 +495,9 @@ if (!empty($oldValues)) {
<br>
<br>
<font class="text"><?=GetMessage("LOAD_NOW");?>&nbsp;</font>
<input id="load-now" type="checkbox" name="LOAD_NOW" value="now" checked >
<input id="load-now" onchange="checkLoadStatus(this)" type="checkbox" name="LOAD_NOW" value="now">
<br>
<div id="loadMessage" hidden><?=GetMessage("LOAD_NOW_MSG");?></div>
<br>
<font class="text"><?=GetMessage("BASE_PRICE");?>&nbsp;</font>
<select name="price-types" class="typeselect">
@ -615,6 +616,16 @@ if (!empty($oldValues)) {
getHbFromAjax($(obj).find('option')[obj.selectedIndex], 'product');
}
};
function checkLoadStatus(object)
{
if (object.checked) {
$('#loadMessage').show();
} else {
$('#loadMessage').hide();
}
}
function checkProfile(obj)
{
if (obj.value !== 'none')
@ -677,12 +688,11 @@ if (!empty($oldValues)) {
<input type="hidden" name="continue" value="5">
<div style="padding: 1px 13px 2px; height:28px;">
<div align="right" style="float:right; width:50%; position:relative;">
<input type="submit" name="inst" value="<?php echo GetMessage("MOD_NEXT_STEP"); ?>" class="adm-btn-save">
<input type="submit" name="inst" onclick="BX.showWait()" value="<?= GetMessage("MOD_NEXT_STEP"); ?>"
class="adm-btn-save">
</div>
<div align="left" style="float:right; width:50%; position:relative;">
<input type="submit" name="back" value="<?php echo GetMessage("MOD_PREV_STEP"); ?>" class="adm-btn-save">
<input type="submit" name="back" value="<?= GetMessage("MOD_PREV_STEP"); ?>" class="adm-btn-save">
</div>
</div>
</form>

View file

@ -0,0 +1,3 @@
<?php
$MESS['IBLOCK_NOT_SELECTED'] = 'No infoblock selected for export';

View file

@ -1,5 +1,6 @@
<?php
$MESS['IBLOCK_NOT_SELECTED'] = 'No infoblock selected for export';
$MESS ['RETAIL_MODULE_NAME'] = 'RetailCRM';
$MESS ['MODULE_DESCRIPTION'] = 'Integration module with RetailCRM - specialized CRM for e-commerce';
$MESS ['MODULE_PARTNER_NAME'] = 'Intaro Software';

View file

@ -17,6 +17,8 @@ $MESS ['NOT_LOADING'] = 'No';
$MESS ['CRON_LOADING'] = 'By CRON every 24 hours';
$MESS ['AGENT_LOADING'] = 'By Agent every 24 hours (by default)';
$MESS ['LOAD_NOW'] = 'Export now';
$MESS ['LOAD_NOW_MSG'] = 'The generation will start after you click "Finish installation".
This may take some time';
$MESS ['PROFILE_NAME'] = 'Profile name:';
$MESS ['PROFILE_NAME_EXAMPLE'] = 'RetailCRM catalog export';
$MESS ['ERR_FIELDS_PROFILE'] = 'Invalid profile name field';

View file

@ -0,0 +1,3 @@
<?php
$MESS['IBLOCK_NOT_SELECTED'] = 'Не выбрано ни одного инфоблока для экспорта';

View file

@ -17,6 +17,7 @@ $MESS ['NOT_LOADING'] = 'Нет';
$MESS ['CRON_LOADING'] = 'С помощью CRON каждые 24 часа';
$MESS ['AGENT_LOADING'] = 'Агентом каждые 24 часа (по умолчанию)';
$MESS ['LOAD_NOW'] = 'Выгрузить сейчас';
$MESS ['LOAD_NOW_MSG'] = 'Генерация начнется после нажатия "Завершить установку". Это может занять некоторое время';
$MESS ['PROFILE_NAME'] = 'Имя профиля:';
$MESS ['PROFILE_NAME_EXAMPLE'] = 'Выгрузка каталога RetailCRM';
$MESS ['ERR_FIELDS_PROFILE'] = 'Неверно заполнено поле имени профиля';

View file

@ -0,0 +1,28 @@
<?php
namespace Intaro\RetailCrm\Component;
use CCatalogExport;
use CModule;
use Dotenv\Loader;
/**
* Class Agent
*
* @package Intaro\RetailCrm\Component
*/
class Agent
{
/**
* @param int $profileId
*
* @return string
*/
public function preGenerateExport(int $profileId): string
{
CModule::IncludeModule('catalog');
CCatalogExport::PreGenerateExport($profileId);
return '';
}
}

View file

@ -0,0 +1,334 @@
<?php
namespace Intaro\RetailCrm\Icml;
use COption;
use Intaro\RetailCrm\Icml\Utils\IcmlUtils;
use Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo;
use Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlData;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup;
use Intaro\RetailCrm\Repository\CatalogRepository;
use Logger;
use RetailcrmConfigProvider;
/**
* Class IcmlDirector
* @package Intaro\RetailCrm\Icml
*/
class IcmlDirector
{
public const INFO = 'INFO';
public const OFFERS_PART = 500;
public const FILE_LOG_NAME = 'i_crm_load_log';
public const DEFAULT_PRODUCT_PAGE_SIZE = 1;
/**
* @var IcmlWriter
*/
private $icmlWriter;
/**
* @var \Intaro\RetailCrm\Icml\XmlOfferDirector
*/
private $xmlOfferDirector;
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup
*/
private $setup;
/**
* @var \Intaro\RetailCrm\Repository\CatalogRepository
*/
private $catalogRepository;
/**
* @var string
*/
private $shopName;
/**
* @var \Intaro\RetailCrm\Icml\XmlCategoryDirector
*/
private $xmlCategoryDirector;
/**
* @var \Intaro\RetailCrm\Icml\QueryParamsMolder
*/
private $queryBuilder;
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\XmlData
*/
private $xmlData;
/**
* @var \Logger
*/
private $logger;
/**
* RetailCrmlXml constructor.
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup $setup
* @param \Logger $logger
*/
public function __construct(XmlSetup $setup, Logger $logger)
{
$this->setup = $setup;
$this->shopName = RetailcrmConfigProvider::getSiteName();
$this->catalogRepository = new CatalogRepository();
$this->icmlWriter = new IcmlWriter($this->setup->filePath);
$this->xmlOfferDirector = new XmlOfferDirector($this->setup);
$this->xmlCategoryDirector = new XmlCategoryDirector($this->setup->iblocksForExport);
$this->queryBuilder = new QueryParamsMolder();
$this->xmlData = new XmlData();
$this->logger = $logger;
}
/**
* Основной метод. Генерирует icml файл католога товаров Битрикс
*/
public function generateXml(): void
{
$this->setXmlData();
$this->icmlWriter->writeToXmlTop($this->xmlData);
$this->logger->write(
self::INFO . ': Start writing categories and header',
self::FILE_LOG_NAME
);
$this->icmlWriter->writeToXmlHeaderAndCategories($this->xmlData);
$this->logger->write(
self::INFO . ': End writing categories in XML and Start writing offers',
self::FILE_LOG_NAME
);
$this->writeOffers();
$this->logger->write(
self::INFO . ': End writing offers in XML',
self::FILE_LOG_NAME
);
$this->icmlWriter->writeToXmlBottom();
$this->logger->write(
self::INFO . ': Loading complete (peak memory usage: ' . memory_get_peak_usage() . ')',
self::FILE_LOG_NAME
);
}
/**
* @return void
*/
private function setXmlData(): void
{
$this->xmlData->shopName = $this->shopName;
$this->xmlData->company = $this->shopName;
$this->xmlData->filePath = $this->setup->filePath;
$this->xmlData->categories = $this->xmlCategoryDirector->getXmlCategories();
}
/**
* записывает оферы всех торговых каталогов в xml файл
*/
private function writeOffers(): void
{
$this->icmlWriter->startOffersBlock();
foreach ($this->setup->iblocksForExport as $iblockId) {
$this->writeIblockOffers($iblockId);
}
$this->icmlWriter->endBlock();
}
/**
* записывает оферы конкретного торгового каталога товаров в xml файл
*
* @param int $productIblockId //ID инфоблока товаров в торговом каталоге
*/
private function writeIblockOffers(int $productIblockId): void
{
$catalogIblockInfo = $this->catalogRepository->getCatalogIblockInfo($productIblockId);
//если нет торговых предложений
if ($catalogIblockInfo->skuIblockId === null) {
$selectParams
= $this->queryBuilder->getSelectParams(
$this->setup->properties->products->names[$productIblockId],
$this->setup->basePriceId
);
$selectParams->pageNumber = 1;
$selectParams->nPageSize = self::OFFERS_PART;
$selectParams->parentId = null;
while ($xmlOffers = $this->xmlOfferDirector->getXmlOffersPart($selectParams, $catalogIblockInfo)) {
$this->icmlWriter->writeOffers($xmlOffers);
$selectParams->pageNumber++;
}
return;
}
//если есть торговые предложения
$paramsForProduct
= $this->queryBuilder->getSelectParams(
$this->setup->properties->products->names[$productIblockId],
$this->setup->basePriceId
);
$paramsForOffer
= $this->queryBuilder->getSelectParams(
$this->setup->properties->sku->names[$productIblockId],
$this->setup->basePriceId
);
$this->writeOffersAsOffersInXml($paramsForProduct, $paramsForOffer, $catalogIblockInfo);
}
/**
* Эта стратегия записи используется,
* когда в каталоге есть торговые предложения
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $paramsForProduct
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $paramsForOffer
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
*/
private function writeOffersAsOffersInXml(
SelectParams $paramsForProduct,
SelectParams $paramsForOffer,
CatalogIblockInfo $catalogIblockInfo
): void {
$paramsForProduct->pageNumber = 1;
$paramsForProduct->nPageSize = $this->calculateProductPageSize();
do {
$productsPart = $this->xmlOfferDirector->getXmlOffersPart($paramsForProduct, $catalogIblockInfo);
$paramsForProduct->pageNumber++;
$this->writeProductsOffers($productsPart, $paramsForOffer, $catalogIblockInfo);
} while (!empty($productsPart));
}
/**
* Записывает в файл оферы всех товаров из $products
*
* @param XmlOffer[] $products
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $paramsForOffer
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
*/
private function writeProductsOffers(
array $products,
SelectParams $paramsForOffer,
CatalogIblockInfo $catalogIblockInfo
): void {
$paramsForOffer->nPageSize = $this->calculateOffersPageSize();
foreach ($products as $product) {
$this->writeProductOffers($paramsForOffer, $catalogIblockInfo, $product);
}
}
/**
* Записывает оферы отдельного продукта в xml файл
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $paramsForOffer
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer $product
*/
private function writeProductOffers(
SelectParams $paramsForOffer,
CatalogIblockInfo $catalogIblockInfo,
XmlOffer $product
): void {
$paramsForOffer->pageNumber = 1;
$writingOffersCount = 0;
$paramsForOffer->parentId = $product->id;
do {
$xmlOffersPart
= $this->xmlOfferDirector->getXmlOffersBySingleProduct($paramsForOffer, $catalogIblockInfo, $product);
// если это "простой товар", у которого нет ТП, то просто записываем его
if ($paramsForOffer->pageNumber === 1 && count($xmlOffersPart) === 0) {
$this->icmlWriter->writeOffers([$product]);
break;
}
if (!empty($xmlOffersPart)) {
$xmlOffersPart
= $this->trimOffersList($writingOffersCount, $xmlOffersPart);
$this->icmlWriter->writeOffers($xmlOffersPart);
$writingOffersCount += count($xmlOffersPart);
$paramsForOffer->pageNumber++;
}
} while ($this->shouldContinueWriting($writingOffersCount, $xmlOffersPart));
}
/**
* Проверяет,не достигнул ли лимит по записываемым оферам maxOffersValue
* и обрезает массив до лимита, если он достигнут
*
* @param int $writingOffers
* @param XmlOffer[] $xmlOffers
*
* @return XmlOffer[]
*/
private function trimOffersList(int $writingOffers, array $xmlOffers): array
{
if (!empty($this->setup->maxOffersValue) && ($writingOffers + count($xmlOffers)) > $this->setup->maxOffersValue) {
$sliceIndex
= count($xmlOffers) - ($writingOffers + count($xmlOffers) - $this->setup->maxOffersValue);
return array_slice($xmlOffers, 0, $sliceIndex);
}
return $xmlOffers;
}
/**
* Возвращает размер страницы для запроса товаров
*
* @return int
*/
private function calculateProductPageSize(): int
{
if (empty($this->setup->maxOffersValue)) {
return self::DEFAULT_PRODUCT_PAGE_SIZE;
}
return (int) ceil(self::OFFERS_PART / $this->setup->maxOffersValue);
}
/**
* Возвращает размер страницы для офферов
*
* @return int
*/
private function calculateOffersPageSize(): int
{
if (empty($this->setup->maxOffersValue)) {
return self::OFFERS_PART;
}
return $this->setup->maxOffersValue < self::OFFERS_PART ?
$this->setup->maxOffersValue : self::OFFERS_PART;
}
/**
* Проверяет, нужно ли дальше записывать офферы
*
* @param int $writingOffers
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer[] $xmlOffers
*
* @return bool
*/
private function shouldContinueWriting(int $writingOffers, array $xmlOffers): bool
{
if (empty($this->setup->maxOffersValue)) {
return !empty($xmlOffers);
}
return !empty($xmlOffers) && $writingOffers < $this->setup->maxOffersValue;
}
}

View file

@ -0,0 +1,227 @@
<?php
namespace Intaro\RetailCrm\Icml;
use Intaro\RetailCrm\Model\Bitrix\Xml\OfferParam;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlCategory;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlData;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer;
use XMLWriter;
/**
* Отвечает за запись данных каталога в файл
*
* Class IcmlWriter
* @package Intaro\RetailCrm\Icml
*/
class IcmlWriter
{
public const INFO = 'INFO';
public const CATEGORY_PART = 1000;
/**
* @var \XMLWriter
*/
private $writer;
/**
* IcmlWriter constructor.
* @param $filePath
*/
public function __construct($filePath)
{
$this->writer = new XMLWriter();
$this->writer->openURI($_SERVER['DOCUMENT_ROOT'] . $filePath);
$this->writer->setIndent(true);
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlData $data
*/
public function writeToXmlTop(XmlData $data): void
{
$this->writer->startElement('yml_catalog');
$this->writeSimpleAttribute('date', Date('Y-m-d H:i:s'));
$this->writer->startElement('shop');
$this->writeSimpleElement('name', $data->shopName);
$this->writeSimpleElement('company', $data->company);
}
public function writeToXmlBottom(): void
{
$this->writer->endElement();
$this->writer->endElement();
$this->writer->flush();
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlData $data
*/
public function writeToXmlHeaderAndCategories(XmlData $data): void
{
$this->writer->startElement('categories');
foreach ($data->categories as $key => $category) {
$this->writeCategory($category);
if (
count($data->categories) === $key + 1
|| is_int(count($data->categories) / self::CATEGORY_PART)
) {
$this->writer->flush();
}
}
$this->writer->endElement();
$this->writer->flush();
}
public function startOffersBlock(): void
{
$this->writer->startElement('offers');
}
public function endBlock(): void
{
$this->writer->endElement();
}
/**
* @param XmlOffer[] $offers
*/
public function writeOffers(array $offers): void
{
foreach ($offers as $offer) {
$this->writeOffer($offer);
}
$this->writer->flush();
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer $offer
*/
private function writeOffer(XmlOffer $offer): void
{
$this->writer->startElement('offer');
$this->writeSimpleAttribute('id', $offer->id);
$this->writeSimpleAttribute('productId', $offer->productId);
$this->writeSimpleAttribute('quantity', $offer->quantity);
foreach ($offer->categoryIds as $categoryId) {
$this->writeSimpleElement('categoryId', $categoryId);
}
if (!empty($offer->picture)) {
$this->writeSimpleElement('picture', $offer->picture);
}
if (!empty($offer->unitCode->code)) {
$this->writer->startElement('unit');
$this->writeSimpleAttribute('code', $offer->unitCode->code);
$this->writeSimpleAttribute('name', $offer->unitCode->name);
$this->writeSimpleAttribute('sym', $offer->unitCode->sym);
$this->writer->endElement();
}
foreach ($offer->params as $param) {
$this->writeParam($param);
}
$this->writeSimpleElement('url', $offer->url);
$this->writeSimpleElement('price', $offer->price);
$this->writeSimpleElement('name', $offer->name);
$this->writeSimpleElement('productName', $offer->productName);
$this->writeSimpleElement('xmlId', $offer->xmlId);
$this->writeOptionalSimpleElement('vendor', $offer->vendor);
$this->writeOptionalSimpleElement('barcode', $offer->barcode);
$this->writeOptionalSimpleElement('vatRate', $offer->vatRate);
$this->writeOptionalSimpleElement('weight', $offer->weight);
$this->writeOptionalSimpleElement('dimensions', $offer->dimensions);
$this->writeOptionalSimpleElement('purchasePrice', $offer->purchasePrice);
$this->writer->endElement();
}
/**
* Создает ноду, если значение не пустое
*
* @param string $name
* @param $value
*/
private function writeOptionalSimpleElement(string $name, $value): void
{
if (!empty($value)) {
$this->writeSimpleElement($name, $value);
}
}
/**
* @param string $name
* @param $value
*/
private function writeSimpleElement(string $name, $value): void
{
$this->writer->startElement($name);
$this->writer->text($this->prepareValue($value));
$this->writer->endElement();
}
/**
* @param string $name
* @param $value
*/
private function writeSimpleAttribute(string $name, $value): void
{
$this->writer->startAttribute($name);
$this->writer->text($this->prepareValue($value));
$this->writer->endAttribute();
}
/**
* @param $text
*
* @return string
*/
protected function prepareValue($text): string
{
global $APPLICATION;
return strip_tags($APPLICATION->ConvertCharset($text, 'utf-8', 'utf-8'));
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlCategory $category
*/
private function writeCategory(XmlCategory $category): void
{
$this->writer->startElement('category');
$this->writeSimpleAttribute('id', $category->id);
$this->writeParentId($category->parentId);
$this->writeSimpleElement('name', $category->name);
$this->writeSimpleElement('picture', $category->picture);
$this->writer->endElement();
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\OfferParam $param
*/
private function writeParam(OfferParam $param): void
{
$this->writer->startElement('param');
$this->writeSimpleAttribute('name', $param->name);
$this->writeSimpleAttribute('code', $param->code);
$this->writer->text($this->prepareValue($param->value));
$this->writer->endElement();
}
/**
* @param string $parentId
*/
private function writeParentId(string $parentId)
{
if ($parentId > 0) {
$this->writeSimpleAttribute('parentId', $parentId);
}
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Intaro\RetailCrm\Icml;
use Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo;
use Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams;
/**
* Class QueryParamsMolder
* @package Intaro\RetailCrm\Icml
*/
class QueryParamsMolder
{
/**
* Формирует параметры товаров или офферов для запросов к БД на основе настроек экспорта
*
* @param array|null $userProps
* @param int $basePriceId
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams
*/
public function getSelectParams(?array $userProps, int $basePriceId): SelectParams
{
$catalogFields = ['catalog_length', 'catalog_width', 'catalog_height', 'catalog_weight'];
$params = new SelectParams();
foreach ($userProps as $key => $name) {
if ($name === '') {
unset($userProps[$key]);
continue;
}
if (in_array($name, $catalogFields, true)) {
$userProps[$key] = strtoupper($userProps[$key]);
} else {
$userProps[$key] = 'PROPERTY_' . $userProps[$key];
}
}
$params->configurable = $userProps ?? [];
$params->main = [
'IBLOCK_ID',
'IBLOCK_SECTION_ID',
'NAME',
'DETAIL_PICTURE',
'PREVIEW_PICTURE',
'DETAIL_PAGE_URL',
'CATALOG_QUANTITY',
'CATALOG_PRICE_' . $basePriceId,
'CATALOG_PURCHASING_PRICE',
'EXTERNAL_ID',
'CATALOG_GROUP_' . $basePriceId,
'ID',
'LID',
];
return $params;
}
/**
* @param int|null $parentId
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $info
* @return array
*/
public function getWhereForOfferPart(?int $parentId, CatalogIblockInfo $info): array
{
if ($parentId === null) {
return [
'IBLOCK_ID' => $info->productIblockId,
'ACTIVE' => 'Y',
];
}
return [
'IBLOCK_ID' => $info->skuIblockId,
'ACTIVE' => 'Y',
'PROPERTY_' . $info->skuPropertyId => $parentId,
];
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace Intaro\RetailCrm\Icml;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ORM\Objectify\Collection;
use Bitrix\Main\ORM\Objectify\EntityObject;
use Bitrix\Main\SystemException;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlCategory;
use Intaro\RetailCrm\Repository\CatalogRepository;
use Intaro\RetailCrm\Repository\FileRepository;
use Intaro\RetailCrm\Repository\SiteRepository;
/**
* Class XmlCategoryDirector
* @package Intaro\RetailCrm\Icml
*/
class XmlCategoryDirector
{
private const MILLION = 1000000;
/**
* @var \Intaro\RetailCrm\Repository\CatalogRepository
*/
private $catalogRepository;
/**
* @var array
*/
private $iblocksForExport;
/**
* @var \Intaro\RetailCrm\Icml\XmlCategoryFactory
*/
private $xmlCategoryFactory;
/**
* @var \Intaro\RetailCrm\Repository\FileRepository
*/
private $fileRepository;
public function __construct(array $iblocksForExport)
{
$this->iblocksForExport = $iblocksForExport;
$this->catalogRepository = new CatalogRepository();
$this->xmlCategoryFactory = new XmlCategoryFactory();
$this->fileRepository = new FileRepository(SiteRepository::getDefaultServerName());
}
/**
* @return XmlCategory[]
*/
public function getXmlCategories(): array
{
$xmlCategories = [];
foreach ($this->iblocksForExport as $iblockId) {
$categories = $this->catalogRepository->getCategoriesByIblockId($iblockId);
if ($categories === null) {
$categoryId = self::MILLION + $iblockId;
$xmlCategory = $this->makeXmlCategoryFromIblock($iblockId, $categoryId);
if (!$xmlCategory) {
continue;
}
$xmlCategories[$categoryId] = $xmlCategory;
}
$xmlCategories = array_merge($xmlCategories, $this->getXmlSubCategories($categories));
}
return $xmlCategories;
}
/**
* Возвращает коллекцию подкатегорий категории
*
* @param \Bitrix\Main\ORM\Objectify\Collection $categories
* @return XmlCategory[]
*/
public function getXmlSubCategories(Collection $categories): array
{
$xmlCategories = [];
foreach ($categories as $categoryKey => $category) {
if (!$category instanceof EntityObject) {
continue;
}
try {
$xmlCategory = $this->xmlCategoryFactory->create(
$category,
$this->fileRepository->getImageUrl($category->get('PICTURE'))
);
if (!$xmlCategory) {
continue;
}
$xmlCategories[$categoryKey] = $this->xmlCategoryFactory->create(
$category,
$this->fileRepository->getImageUrl($category->get('PICTURE'))
);
} catch (ArgumentException | SystemException $exception) {
AddMessage2Log($exception->getMessage());
}
}
return $xmlCategories;
}
/**
* Создает XmlCategory из инфоблока
*
* @param int $iblockId
* @param int $categoryId
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\XmlCategory|null
*/
public function makeXmlCategoryFromIblock(int $iblockId, int $categoryId): ?XmlCategory
{
$iblock = $this->catalogRepository->getIblockById($iblockId);
if ($iblock === null) {
return null;
}
try {
return $this->xmlCategoryFactory->create(
$iblock,
$this->fileRepository->getImageUrl($iblock->get('PICTURE')),
$categoryId
);
} catch (ArgumentException | SystemException $exception) {
AddMessage2Log($exception->getMessage());
}
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Intaro\RetailCrm\Icml;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ORM\Objectify\Collection;
use Bitrix\Main\ORM\Objectify\EntityObject;
use Bitrix\Main\SystemException;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlCategory;
use Intaro\RetailCrm\Repository\CatalogRepository;
use Intaro\RetailCrm\Repository\FileRepository;
use Intaro\RetailCrm\Repository\SiteRepository;
/**
* Отвечает за создание XmlCategory
*
* Class XmlCategoryFactory
* @package Intaro\RetailCrm\Icml
*/
class XmlCategoryFactory
{
/**
* @param \Bitrix\Main\ORM\Objectify\EntityObject $category
* @param string $picture
* @param int|null $categoryId
*
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\XmlCategory|null
*/
public function create(EntityObject $category, string $picture, int $categoryId = null): ?XmlCategory
{
try {
$xmlCategory = new XmlCategory();
$xmlCategory->id = $categoryId ?? $category->get('ID');
$xmlCategory->name = $category->get('NAME');
$xmlCategory->parentId = $categoryId ? 0 : $category->get('IBLOCK_SECTION_ID');
$xmlCategory->picture = $picture;
} catch (ArgumentException | SystemException $exception) {
return null;
}
return $xmlCategory;
}
}

View file

@ -0,0 +1,520 @@
<?php
namespace Intaro\RetailCrm\Icml;
use Bitrix\Catalog\ProductTable;
use Bitrix\Main\ArgumentException;
use Intaro\RetailCrm\Icml\Utils\IcmlUtils;
use Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo;
use Intaro\RetailCrm\Model\Bitrix\Xml\OfferParam;
use Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams;
use Intaro\RetailCrm\Model\Bitrix\Xml\Unit;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories;
use Logger;
use RetailcrmConfigProvider;
/**
* Отвечает за создание XMLOffer
*
* Class XmlOfferBuilder
* @package Intaro\RetailCrm\Icml
*/
class XmlOfferBuilder
{
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup
*/
private $setup;
/**
* @var bool|string|null
*/
private $purchasePriceNull;
/**
* @var array
*/
private $measures;
/**
* @var string|null
*/
private $defaultServerName;
/**
* @var array
*/
private $skuHlParams;
/**
* @var array
*/
private $productHlParams;
/**
* @var string
*/
private $productPicture;
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo
*/
private $catalogIblockInfo;
/**
* @var array
*/
private $productProps;
/**
* @var string
*/
private $barcode;
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams
*/
private $selectParams;
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer
*/
private $xmlOffer;
/**
* @var array
*/
private $categories;
/**
* IcmlDataManager constructor.
*
* XmlOfferBuilder constructor.
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup $setup
* @param array $measure
* @param string|null $defaultServerName
*/
public function __construct(XmlSetup $setup, array $measure, ?string $defaultServerName)
{
$this->setup = $setup;
$this->purchasePriceNull = RetailcrmConfigProvider::getCrmPurchasePrice();
$this->measures = $this->prepareMeasures($measure);
$this->defaultServerName = $defaultServerName;
}
/**
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer
*/
public function build(): XmlOffer
{
$this->xmlOffer = new XmlOffer();
$this->xmlOffer->barcode = $this->barcode;
$this->xmlOffer->picture = $this->productPicture;
$this->addDataFromParams();
$this->addDataFromItem($this->productProps, $this->categories);
return $this->xmlOffer;
}
/**
* @param array $categories
*/
public function setCategories(array $categories)
{
$this->categories = $categories;
}
/**
* @param mixed $skuHlParams
*/
public function setSkuHlParams($skuHlParams): void
{
$this->skuHlParams = $skuHlParams;
}
/**
* @param mixed $productHlParams
*/
public function setProductHlParams($productHlParams): void
{
$this->productHlParams = $productHlParams;
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $selectParams
*/
public function setSelectParams(SelectParams $selectParams): void
{
$this->selectParams = $selectParams;
}
/**
* @param array $productProps
*/
public function setOfferProps(array $productProps): void
{
$this->productProps = $productProps;
}
/**
* @param string $barcode
*/
public function setBarcode(string $barcode): void
{
$this->barcode = $barcode;
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
*/
public function setCatalogIblockInfo(CatalogIblockInfo $catalogIblockInfo): void
{
$this->catalogIblockInfo = $catalogIblockInfo;
}
/**
* @param string $getProductPicture
*/
public function setPicturesPath(string $getProductPicture): void
{
$this->productPicture = $getProductPicture;
}
/**
* Добавляет в XmlOffer значения настраиваемых параметров, производителя, вес и габариты
*/
private function addDataFromParams(): void
{
$resultParams = array_merge($this->productHlParams, $this->skuHlParams);
//достаем значения из обычных свойств
$resultParams = array_merge($resultParams, $this->getSimpleParams(
$resultParams,
$this->selectParams->configurable,
$this->productProps
));
[$resultParams, $this->xmlOffer->dimensions]
= $this->extractDimensionsFromParams(
$this->setup->properties,
$resultParams,
$this->catalogIblockInfo->productIblockId
);
[$resultParams, $this->xmlOffer->weight]
= $this->extractWeightFromParams(
$this->setup->properties,
$resultParams,
$this->catalogIblockInfo->productIblockId
);
[$resultParams, $this->xmlOffer->vendor] = $this->extractVendorFromParams($resultParams);
$resultParams = $this->dropEmptyParams($resultParams);
$this->xmlOffer->params = $this->createParamObject($resultParams);
}
/**
* Добавляет в объект XmlOffer информацию из GetList
*
* @param array $item
* @param array $categoryIds
*/
private function addDataFromItem(array $item, array $categoryIds): void
{
$this->xmlOffer->id = $item['ID'];
$this->xmlOffer->productId = $item['ID'];
$this->xmlOffer->quantity = $item['CATALOG_QUANTITY'] ?? '';
$this->xmlOffer->url = $item['DETAIL_PAGE_URL']
? $this->defaultServerName . $item['DETAIL_PAGE_URL']
: '';
$this->xmlOffer->price = $item['CATALOG_PRICE_' . $this->setup->basePriceId];
$this->xmlOffer->purchasePrice = $this->getPurchasePrice(
$item,
$this->setup->loadPurchasePrice,
$this->purchasePriceNull
);
$this->xmlOffer->categoryIds = $categoryIds;
$this->xmlOffer->name = $item['NAME'];
$this->xmlOffer->xmlId = $item['EXTERNAL_ID'] ?? '';
$this->xmlOffer->productName = $item['NAME'];
$this->xmlOffer->vatRate = $item['CATALOG_VAT'] ?? 'none';
$this->xmlOffer->unitCode = $this->getUnitCode($item['CATALOG_MEASURE'], $item['ID']);
}
/**
* Возвращает закупочную цену, если она требуется настройками
*
* @param array $product
* @param bool|null $isLoadPrice
* @param string $purchasePriceNull
* @return int|null
*/
private function getPurchasePrice(array $product, ?bool $isLoadPrice, string $purchasePriceNull): ?int
{
if ($isLoadPrice) {
if ($product['CATALOG_PURCHASING_PRICE']) {
return $product['CATALOG_PURCHASING_PRICE'];
}
if ($purchasePriceNull === 'Y') {
return 0;
}
}
return null;
}
/**
* Возвращает массив обычных свойств
*
* @param array $resultParams
* @param array $configurableParams
* @param array $productProps
* @return array
*/
private function getSimpleParams(array $resultParams, array $configurableParams, array $productProps): array
{
foreach ($configurableParams as $key => $params) {
if (isset($resultParams[$key])) {
continue;
}
$codeWithValue = $params . '_VALUE';
if (isset($productProps[$codeWithValue])) {
$resultParams[$key] = $productProps[$codeWithValue];
} elseif (isset($productProps[$params])) {
$resultParams[$key] = $productProps[$params];
}
}
return $resultParams;
}
/**
* Удаляет параметры с пустыми и нулевыми значениями
*
* @param array $params
* @return array
*/
private function dropEmptyParams(array $params): array
{
return array_diff($params, ['', 0, '0']);
}
/**
* Разделяем вендора и остальные параметры
*
* @param array $resultParams
* @return array
*/
private function extractVendorFromParams(array $resultParams): array
{
$vendor = null;
if (isset($resultParams['manufacturer'])) {
$vendor = $resultParams['manufacturer'];
unset($resultParams['manufacturer']);
}
return [$resultParams, $vendor];
}
/**
* Преобразует вес товара в килограммы для ноды weight
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories $xmlSetupPropsCategories
* @param array $resultParams
* @param int $iblockId
* @return array
*/
private function extractWeightFromParams(
XmlSetupPropsCategories $xmlSetupPropsCategories,
array $resultParams,
int $iblockId
): array {
$factors = [
'mg' => 0.000001,
'g' => 0.001,
'kg' => 1,
];
$unit = '';
if (!empty($xmlSetupPropsCategories->products->names[$iblockId]['weight'])) {
$unit = $xmlSetupPropsCategories->products->units[$iblockId]['weight'];
} elseif (!empty($xmlSetupPropsCategories->sku->names[$iblockId]['weight'])) {
$unit = $xmlSetupPropsCategories->sku->units[$iblockId]['weight'];
}
if (isset($resultParams['weight'], $factors[$unit])) {
$weight = $resultParams['weight'] * $factors[$unit];
} else {
$weight = '';
}
if (isset($resultParams['weight'])) {
unset($resultParams['weight']);
}
return [$resultParams, $weight];
}
/**
* Получение данных для ноды dimensions
*
* Данные должны быть переведены в сантиметры
* и представлены в формате Длина/Ширина/Высота
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories $xmlSetupPropsCategories
* @param array $resultParams
* @param int $iblockId
* @return array
*/
private function extractDimensionsFromParams(
XmlSetupPropsCategories $xmlSetupPropsCategories,
array $resultParams,
int $iblockId
): array {
$dimensionsParams = ['length', 'width', 'height'];
$dimensions = '';
$factors = [
'mm' => 0.1,
'cm' => 1,
'm' => 100,
];
foreach ($dimensionsParams as $key => $param) {
$unit = '';
if (!empty($xmlSetupPropsCategories->products->names[$iblockId][$param])) {
$unit = $xmlSetupPropsCategories->products->units[$iblockId][$param];
} elseif (!empty($xmlSetupPropsCategories->sku->names[$iblockId][$param])) {
$unit = $xmlSetupPropsCategories->sku->units[$iblockId][$param];
}
if (isset($factors[$unit], $resultParams[$param])) {
$dimensions .= $resultParams[$param] * $factors[$unit];
} else {
$dimensions .= '0';
}
if (count($dimensionsParams) > $key + 1) {
$dimensions .= '/';
}
if (isset($resultParams[$param])) {
unset($resultParams[$param]);
}
}
return [$resultParams, $dimensions === '0/0/0' ? '' : $dimensions];
}
/**
* Собираем объект параметре заказа
*
* @param array $params
* @return OfferParam[]
*/
private function createParamObject(array $params): array
{
$offerParams = [];
foreach ($params as $code => $value) {
$paramName = GetMessage('PARAM_NAME_' . $code);
if (empty($paramName)) {
continue;
}
$offerParam = new OfferParam();
$offerParam->name = $paramName;
$offerParam->code = $code;
$offerParam->value = $value;
$offerParams[] = $offerParam;
}
return $offerParams;
}
/**
* Собираем объект единицы измерения для товара
*
* @param int $measureIndex
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\Unit
*/
private function createUnitFromCode(int $measureIndex): Unit
{
$unit = new Unit();
$unit->name = $this->measures[$measureIndex]['MEASURE_TITLE'] ?? '';
$unit->code = $this->measures[$measureIndex]['SYMBOL_INTL'] ?? '';
$unit->sym = $this->measures[$measureIndex]['SYMBOL_RUS'] ?? '';
return $unit;
}
/**
* Удаляет запрещенные в unit сode символы
*
* @link https://docs.retailcrm.ru/Developers/modules/ICML
*
* @param array $measures
*
* @return array
*/
private function prepareMeasures(array $measures): array
{
foreach ($measures as &$measure) {
if (isset($measure['SYMBOL_INTL'])) {
$measure['SYMBOL_INTL'] = preg_replace("/[^a-zA-Z_\-]/",'', $measure['SYMBOL_INTL']);
}
}
return $measures;
}
/**
* @param array $currentMeasure
*
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\Unit
*/
private function createUnitFromProductTable(array $currentMeasure): Unit
{
$clearCurrentMeasure = $this->prepareMeasures(array_shift($currentMeasure));
$unit = new Unit();
$unit->name = $clearCurrentMeasure['MEASURE']['MEASURE_TITLE'] ?? '';
$unit->code = $clearCurrentMeasure['MEASURE']['SYMBOL_INTL'] ?? '';
$unit->sym = $clearCurrentMeasure['MEASURE']['SYMBOL_RUS'] ?? '';
return $unit;
}
/**
* @param int|null $measureId
* @param int $itemId
*
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\Unit|null
*/
private function getUnitCode(?int $measureId, int $itemId): ?Unit
{
if (isset($measureId) && !empty($measureId)) {
return $this->createUnitFromCode($measureId);
} else {
try {
$currentMeasure = ProductTable::getCurrentRatioWithMeasure($itemId);
if (is_array($currentMeasure)) {
return $this->createUnitFromProductTable($currentMeasure);
}
} catch (ArgumentException $exception) {
Logger::getInstance()->write(GetMessage('UNIT_ERROR'), 'i_crm_load_log');
}
}
return null;
}
}

View file

@ -0,0 +1,206 @@
<?php
namespace Intaro\RetailCrm\Icml;
use Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo;
use Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer;
use Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup;
use Intaro\RetailCrm\Repository\CatalogRepository;
use Intaro\RetailCrm\Repository\FileRepository;
use Intaro\RetailCrm\Repository\HlRepository;
use Intaro\RetailCrm\Repository\MeasureRepository;
use Intaro\RetailCrm\Repository\SiteRepository;
/**
* Class XmlOfferDirector
* @package Intaro\RetailCrm\Icml
*/
class XmlOfferDirector
{
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup
*/
private $setup;
/**
* @var \Intaro\RetailCrm\Repository\FileRepository
*/
private $fileRepository;
/**
* @var \Intaro\RetailCrm\Repository\CatalogRepository
*/
private $catalogRepository;
/**
* @var \Intaro\RetailCrm\Icml\XmlOfferBuilder
*/
private $xmlOfferBuilder;
/**
* XmlOfferFactory constructor.
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetup $setup
*/
public function __construct(XmlSetup $setup)
{
$this->setup = $setup;
$this->fileRepository = new FileRepository(SiteRepository::getDefaultServerName());
$this->catalogRepository = new CatalogRepository();
$this->xmlOfferBuilder = new XmlOfferBuilder(
$setup,
MeasureRepository::getMeasures(),
SiteRepository::getDefaultServerName()
);
}
/**
* Возвращает страницу (массив) с товарами или торговыми предложениями (в зависимости от $param)
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $param
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
* @return XmlOffer[]
*/
public function getXmlOffersPart(SelectParams $param, CatalogIblockInfo $catalogIblockInfo): array
{
$ciBlockResult = $this->catalogRepository->getProductPage($param, $catalogIblockInfo);
$barcodes = $this->catalogRepository->getProductBarcodesByIblockId($catalogIblockInfo->productIblockId);
$offers = [];
while ($offer = $ciBlockResult->GetNext()) {
$this->setXmlOfferParams($param, $offer, $catalogIblockInfo, $barcodes);
$this->xmlOfferBuilder->setCategories($this->catalogRepository->getProductCategoriesIds($offer['ID']));
$offers[] = $this->xmlOfferBuilder->build();
}
return $offers;
}
/**
* возвращает массив XmlOffers для конкретного продукта
*
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $paramsForOffer
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer $product
* @return XmlOffer[]
*/
public function getXmlOffersBySingleProduct(
SelectParams $paramsForOffer,
CatalogIblockInfo $catalogIblockInfo,
XmlOffer $product
): array {
$xmlOffers = $this->getXmlOffersPart($paramsForOffer, $catalogIblockInfo);
return $this->addProductInfo($xmlOffers, $product);
}
/**
* Декорирует оферы информацией из товаров
*
* @param XmlOffer[] $xmlOffers
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer $product
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\XmlOffer[]
*/
private function addProductInfo(array $xmlOffers, XmlOffer $product): array
{
foreach ($xmlOffers as $offer) {
$offer->productId = $product->id;
$offer->params = array_merge($offer->params, $product->params);
$offer->unitCode = $offer->unitCode === null ? null : $offer->unitCode->merge($product->unitCode);
$offer->vatRate = $offer->vatRate === 'none' ? $product->vatRate : $offer->vatRate;
$offer->vendor = $offer->mergeValues($product->vendor, $offer->vendor);
$offer->picture = $offer->mergeValues($product->picture, $offer->picture);
$offer->weight = $offer->mergeValues($product->weight, $offer->weight);
$offer->dimensions = $offer->mergeValues($product->dimensions, $offer->dimensions);
$offer->categoryIds = $product->categoryIds;
$offer->productName = $product->productName;
}
return $xmlOffers;
}
/**
* Получение настраиваемых параметров, если они лежат в HL-блоке
*
* @param int $iblockId //ID инфоблока товаров, даже если данные нужны по SKU
* @param array $productProps
* @param array $configurableParams
* @param array $hls
* @return array
*/
private function getHlParams(int $iblockId, array $productProps, array $configurableParams, array $hls): array
{
$params = [];
foreach ($hls as $hlName => $hlBlockProduct) {
if (isset($hlBlockProduct[$iblockId])) {
reset($hlBlockProduct[$iblockId]);
$firstKey = key($hlBlockProduct[$iblockId]);
$hlRepository = new HlRepository($hlName);
if ($hlRepository->getHl() === null) {
continue;
}
$result = $hlRepository->getDataByXmlId($productProps[$configurableParams[$firstKey] . '_VALUE']);
if ($result === null) {
continue;
}
foreach ($hlBlockProduct[$iblockId] as $hlPropCodeKey => $hlPropCode) {
if (isset($result[$hlPropCode])) {
$params[$hlPropCodeKey] = $result[$hlPropCode];
}
}
}
}
return $params;
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $param
* @param array $product
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
* @param array $barcodes
*/
private function setXmlOfferParams(
SelectParams $param,
array $product,
CatalogIblockInfo $catalogIblockInfo,
array $barcodes
): void {
if ($param->parentId === null) {
$pictureProperty = $this->setup->properties->products->pictures[$catalogIblockInfo->productIblockId];
} else {
$pictureProperty = $this->setup->properties->sku->pictures[$catalogIblockInfo->productIblockId];
}
//достаем значения из HL блоков товаров
$this->xmlOfferBuilder->setProductHlParams($this->getHlParams(
$catalogIblockInfo->productIblockId,
$product,
$param->configurable,
$this->setup->properties->highloadblockProduct
));
//достаем значения из HL блоков торговых предложений
$this->xmlOfferBuilder->setSkuHlParams($this->getHlParams(
$catalogIblockInfo->productIblockId,
$product,
$param->configurable,
$this->setup->properties->highloadblockSku
));
$this->xmlOfferBuilder->setSelectParams($param);
$this->xmlOfferBuilder->setOfferProps($product);
$this->xmlOfferBuilder->setBarcode($barcodes[$product['ID']] ?? '');
$this->xmlOfferBuilder->setCatalogIblockInfo($catalogIblockInfo);
$this->xmlOfferBuilder->setPicturesPath(
$this
->fileRepository
->getProductPicture($product, $pictureProperty ?? '')
);
}
}

View file

@ -0,0 +1,13 @@
<?php
$MESS['ROOT_CATEGORY_FOR_CATALOG'] = "Корневой раздел для каталога \"%s\"";
$MESS['PARAM_NAME_article'] = 'Артикул';
$MESS['PARAM_NAME_manufacturer'] = 'Производитель';
$MESS['PARAM_NAME_color'] = 'Цвет';
$MESS['PARAM_NAME_weight'] = 'Вес (габариты)';
$MESS['PARAM_NAME_length'] = 'Длина (габариты)';
$MESS['PARAM_NAME_width'] = 'Ширина (габариты)';
$MESS['PARAM_NAME_height'] = 'Высота (габариты)';
$MESS['PARAM_NAME_picture'] = 'Фото';
$MESS['PARAM_NAME_size'] = 'Размер';
$MESS['UNIT_ERROR'] = 'Ошибка получения unit кода';

View file

@ -0,0 +1,32 @@
<?php
namespace Intaro\RetailCrm\Model\Bitrix\Orm;
/**
* Class CatalogIblockInfo
* @package Intaro\RetailCrm\Model\Bitrix\Orm
*/
class CatalogIblockInfo
{
/**
* ID инфоблока торговых предложений
*
* @var int
*/
public $skuIblockId;
/**
* ID инфоблока товаров
*
* @var int
*/
public $productIblockId;
/**
* ID свойства привязки торговых предложений к товарам
*
* @var int
*/
public $skuPropertyId;
}

View file

@ -0,0 +1,94 @@
<?php
namespace Intaro\RetailCrm\Model\Bitrix\Orm;
use Bitrix\Main,
Bitrix\Main\Localization\Loc;
use Bitrix\Main\Entity\BooleanField;
use Bitrix\Main\Entity\IntegerField;
use Bitrix\Main\Entity\ReferenceField;
Loc::loadMessages(__FILE__);
/**
* Class IblockCatalogTable
*
* Fields:
* <ul>
* <li> IBLOCK_ID int mandatory
* <li> YANDEX_EXPORT bool optional default 'N'
* <li> SUBSCRIPTION bool optional default 'N'
* <li> VAT_ID int optional
* <li> PRODUCT_IBLOCK_ID int mandatory
* <li> SKU_PROPERTY_ID int mandatory
* <li> VAT reference to {@link \Bitrix\Catalog\CatalogVatTable}
* <li> IBLOCK reference to {@link \Bitrix\Iblock\IblockTable}
* <li> PRODUCT_IBLOCK reference to {@link \Bitrix\Iblock\IblockTable}
* <li> SKU_PROPERTY reference to {@link \Bitrix\Iblock\IblockPropertyTable}
* </ul>
*
* @package Bitrix\Catalog
**/
class IblockCatalogTable extends Main\Entity\DataManager
{
/**
* Returns DB table name for entity.
*
* @return string
*/
public static function getTableName()
{
return 'b_catalog_iblock';
}
/**
* Returns entity map definition.
*
* @return array|null
*/
public static function getMap(): ?array
{
try {
return [
new IntegerField('IBLOCK_ID'),
new BooleanField('YANDEX_EXPORT'),
new BooleanField('SUBSCRIPTION'),
new IntegerField('VAT_ID'),
new IntegerField('PRODUCT_IBLOCK_ID'),
new IntegerField('SKU_PROPERTY_ID'),
new ReferenceField(
'VAT',
'Bitrix\Catalog\CatalogVat',
['=this.VAT_ID' => 'ref.ID']
),
new ReferenceField(
'IBLOCK',
'Bitrix\Iblock\Iblock',
['=this.IBLOCK_ID' => 'ref.ID']
),
new ReferenceField(
'PRODUCT_IBLOCK',
'Bitrix\Iblock\Iblock',
['=this.PRODUCT_IBLOCK_ID' => 'ref.ID']
),
new ReferenceField(
'SKU_PROPERTY',
'Bitrix\Iblock\IblockProperty',
['=this.SKU_PROPERTY_ID' => 'ref.ID']
),
new ReferenceField(
'SECTION',
'Bitrix\Iblock\IblockSection',
['=this.IBLOCK_ID' => 'ref.IBLOCK_ID']
),
new ReferenceField(
'OFFERS_IBLOCK',
'Bitrix\Iblock\IblockSection',
['=this.IBLOCK_ID' => 'this.ID']
),
];
} catch (Main\ArgumentException | Main\SystemException $e) {
return null;
}
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class OfferParam
* @package Intaro\RetailCrm\Model\Bitrix\Xml
*/
class OfferParam
{
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $code;
/**
* @var string
*/
public $value;
}

View file

@ -0,0 +1,54 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class SelectParams
* @package Intaro\RetailCrm\Model\Bitrix\Xml
*/
class SelectParams
{
/**
* конфигурируемые свойства
*
* @var array
*/
public $configurable;
/**
* обязательные свойства
*
* @var array
*/
public $main;
/**
* номер запрашиваемой страницы
*
* @var int
*/
public $pageNumber;
/**
* количество товаров на странице
*
* @var int
*/
public $nPageSize;
/**
* id товара у торогового предложения, если запрашивается SKU
*
* @var int
*/
public $parentId;
}

View file

@ -0,0 +1,51 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* единица измерения для товара, элемент не является обязательным в icml
*
* Class Unit
* @package Intaro\RetailCrm\Model\Bitrix\Xml
*/
class Unit
{
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $code;
/**
* единица измерения товара
*
* @var string
*/
public $sym;
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\Unit|null $unitCode
* @return \Intaro\RetailCrm\Model\Bitrix\Xml\Unit
*/
public function merge(?Unit $unitCode): Unit
{
if ($this->code === null && $unitCode !== null) {
return $unitCode;
}
return $this;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class XmlCategory
*/
class XmlCategory
{
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $parentId;
/**
* @var string
*/
public $picture;
/**
* @var int
*/
public $id;
}

View file

@ -0,0 +1,39 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class XmlData
* @package Intaro\RetailCrm\Model\Bitrix
*/
class XmlData
{
/**
* @var string
*/
public $shopName;
/**
* @var string
*/
public $company;
/**
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\XmlCategory[]
*/
public $categories;
/**
* @var string
*/
public $filePath;
}

View file

@ -0,0 +1,131 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class XmlOffer
* @package Intaro\RetailCrm\Model\Bitrix\Xml
*/
class XmlOffer
{
/**
* @var int
*/
public $id;
/**
* @var int
*/
public $productId;
/**
* @var int
*/
public $quantity;
/**
* @var string
*/
public $picture;
/**
* @var string
*/
public $url;
/**
* @var float
*/
public $price;
/**
* Категории, к которым относится товар
*
* @var array
*/
public $categoryIds;
/**
* @var string
*/
public $name;
/**
* @var int
*/
public $xmlId;
/**
* @var string
*/
public $productName;
/**
* @var OfferParam[]
*/
public $params;
/**
* @var string
*/
public $vendor;
/**
* @var Unit
*/
public $unitCode;
/**
* ставка налога (НДС)
*
* @var string
*/
public $vatRate;
/**
* штрих-код
*
* @var string
*/
public $barcode;
/**
* Закупочная цена
*
* @var mixed|null
*/
public $purchasePrice;
/**
* Вес товара
*
* @var int
*/
public $weight;
/**
* Габариты товара
*
* @var string
*/
public $dimensions;
/**
* @param $productValue
* @param $offerValue
* @return mixed
*/
public function mergeValues($productValue, $offerValue)
{
return empty($offerValue) ? $productValue : $offerValue;
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class XmlSetup
* @package Intaro\RetailCrm\Model\Bitrix\Xml
*/
class XmlSetup
{
/**
* XmlSetup constructor.
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories $xmlProps
*/
public function __construct(XmlSetupPropsCategories $xmlProps)
{
$this->properties = $xmlProps;
}
/**
* @var int
*/
public $profileId;
/**
* id инфоблоков, которые подлежат экспорту - IBLOCK_EXPORT
*
* @var array
*/
public $iblocksForExport;
/**
* Путь, по которому сохраняется xml - SETUP_FILE_NAME
*
* @var string
*/
public $filePath;
/**
* синхронизируемые свойства
*
* @var \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupPropsCategories
*/
public $properties;
/**
* максимальное количество торговых предложений у товара - MAX_OFFERS_VALUE
*
* @var null|int
*/
public $maxOffersValue;
/**
* выгружать ли закупочную цену
*
* @var bool
*/
public $loadPurchasePrice;
/**
* @var int|null
*/
public $basePriceId;
}

View file

@ -0,0 +1,55 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class XmlSetupProps
*
* @package Intaro\RetailCrm\Model\Bitrix\Xml
*/
class XmlSetupProps
{
/**
* XmlSetupProps constructor.
* @param array $names
* @param array $units
* @param array|null $pictures
*/
public function __construct(array $names, array $units, ?array $pictures)
{
$this->names = $names;
$this->units = $units;
$this->pictures = $pictures;
}
/**
* названия свойств
*
* @var array
*/
public $names;
/**
* меры измерения
*
* @var array
*/
public $units;
/**
* свойства, из которых нужно брать картинки
*
* @var array
*/
public $pictures;
}

View file

@ -0,0 +1,65 @@
<?php
/**
* PHP version 7.1
*
* @category Integration
* @package Intaro\RetailCrm\Model\Bitrix
* @author RetailCRM <integration@retailcrm.ru>
* @license MIT
* @link http://retailcrm.ru
* @see http://retailcrm.ru/docs
*/
namespace Intaro\RetailCrm\Model\Bitrix\Xml;
/**
* Class XmlSetupProps
* @package Intaro\RetailCrm\Model\Bitrix\Xml
*/
class XmlSetupPropsCategories
{
/**
* XmlSetupPropsCategories constructor.
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupProps $products
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\XmlSetupProps $sku
*/
public function __construct(XmlSetupProps $products, XmlSetupProps $sku)
{
$this->products = $products;
$this->sku = $sku;
}
/**
* Синхронизируемые свойства товаров
*
* @var XmlSetupProps
*/
public $products;
/**
* Синхронизируемые свойства торговых предложений
*
* @var XmlSetupProps
*/
public $sku;
/**
* Синхронизируемые свойства торговых предложений, находящиеся в HL блоках
*
* массив с названиями HL блоков, элементы которого содержат,
* синхронизируемые св-ва
*
* @var array[][]
*/
public $highloadblockSku;
/**
* Синхрозируемые свойства товаров, находящиеся в HL блоках
*
* массив с названиями HL блоков, элементы которого содержат,
* синхронизируемые св-ва
*
* @var array[][]
*/
public $highloadblockProduct;
}

View file

@ -0,0 +1,179 @@
<?php
namespace Intaro\RetailCrm\Repository;
use Bitrix\Iblock\IblockTable;
use Bitrix\Iblock\SectionTable;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\ORM\Objectify\Collection;
use Bitrix\Main\ORM\Objectify\EntityObject;
use Bitrix\Main\SystemException;
use CCatalogGroup;
use CCatalogSku;
use CCatalogStoreBarCode;
use CIBlockElement;
use Intaro\RetailCrm\Icml\QueryParamsMolder;
use Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo;
use Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams;
use RetailcrmConfigProvider;
/**
* Class CatalogRepository
* @package Intaro\RetailCrm\Repository
*/
class CatalogRepository
{
/**
* @var \Intaro\RetailCrm\Icml\QueryParamsMolder
*/
private $builder;
/**
* CatalogRepository constructor.
*/
public function __construct()
{
$this->builder = new QueryParamsMolder();
}
/**
* Получение категорий, к которым относится товар
*
* @param int $offerId
* @return array
*/
public function getProductCategoriesIds(int $offerId): array
{
$query = CIBlockElement::GetElementGroups($offerId, false, ['ID']);
$ids = [];
while ($category = $query->GetNext()) {
$ids[] = $category['ID'];
}
return $ids;
}
/**
* Returns products IDs with barcodes by infoblock id
*
* @param int $iblockId
*
* @return array
*/
public function getProductBarcodesByIblockId(int $iblockId): array
{
$barcodes = [];
$dbBarCode = CCatalogStoreBarCode::getList(
[],
['IBLOCK_ID' => $iblockId],
false,
false,
['PRODUCT_ID', 'BARCODE']
);
while ($arBarCode = $dbBarCode->GetNext()) {
if (!empty($arBarCode)) {
$barcodes[$arBarCode['PRODUCT_ID']] = $arBarCode['BARCODE'];
}
}
return $barcodes;
}
/**
* @param \Intaro\RetailCrm\Model\Bitrix\Xml\SelectParams $param
* @param \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo $catalogIblockInfo
* @return \CIBlockResult|int
*/
public function getProductPage(SelectParams $param, CatalogIblockInfo $catalogIblockInfo)
{
return CIBlockElement::GetList(
[],
$this->builder->getWhereForOfferPart($param->parentId, $catalogIblockInfo),
false,
['nPageSize' => $param->nPageSize, 'iNumPage' => $param->pageNumber, 'checkOutOfRange' => true],
array_merge($param->configurable, $param->main)
);
}
/**
* @param int $iblockId
* @return \Bitrix\Main\ORM\Objectify\Collection|null
*/
public function getCategoriesByIblockId(int $iblockId): ?Collection
{
try {
return SectionTable::query()
->addSelect('*')
->where('IBLOCK_ID', $iblockId)
->fetchCollection();
} catch (ObjectPropertyException | ArgumentException | SystemException $exception) {
return null;
}
}
/**
* @param $iblockId
* @return EntityObject|null
*/
public function getIblockById($iblockId): ?EntityObject
{
try {
return IblockTable::query()
->where('ID', $iblockId)
->fetchObject();
} catch (ObjectPropertyException | ArgumentException | SystemException $exception) {
return null;
}
}
/**
* Возвращает информацию об инфоблоке торговых предложений по ID инфоблока товаров
*
* @param int $productIblockId
* @return \Intaro\RetailCrm\Model\Bitrix\Orm\CatalogIblockInfo
*/
public function getCatalogIblockInfo(int $productIblockId): CatalogIblockInfo
{
$catalogIblockInfo = new CatalogIblockInfo();
$info = CCatalogSKU::GetInfoByProductIBlock($productIblockId);
if ($info === false) {
$catalogIblockInfo->productIblockId = $productIblockId;
return $catalogIblockInfo;
}
$catalogIblockInfo->skuIblockId = $info['IBLOCK_ID'];
$catalogIblockInfo->productIblockId = $info['PRODUCT_IBLOCK_ID'];
$catalogIblockInfo->skuPropertyId = $info['SKU_PROPERTY_ID'];
return $catalogIblockInfo;
}
/**
* @param int|null $profileId
* @return int
*/
public static function getBasePriceId(?int $profileId): int
{
$basePriceId = RetailcrmConfigProvider::getCatalogBasePriceByProfile($profileId);
if (!$basePriceId) {
$dbPriceType = CCatalogGroup::GetList(
[],
['BASE' => 'Y'],
false,
false,
['ID']
);
$result = $dbPriceType->GetNext();
return $result['ID'];
}
return $basePriceId;
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Intaro\RetailCrm\Repository;
use CFile;
/**
* Class FileRepository
* @package Intaro\RetailCrm\Repository
*/
class FileRepository
{
/**
* @var string
*/
private $defaultServerName;
/**
* FileRepository constructor.
* @param string $defaultServerName
*/
public function __construct(string $defaultServerName)
{
$this->defaultServerName = $defaultServerName;
}
/**
* @param int|null $fileId
* @return string
*/
public function getImageUrl(?int $fileId): string
{
if (!$fileId) {
return '';
}
$pathImage = CFile::GetPath($fileId);
$validation = '/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i';
if ((bool) preg_match($validation, $pathImage) === false) {
return $this->defaultServerName . $pathImage;
}
return $pathImage;
}
/**
* @param array $product
* @param string $pictureProp
* @return string
*/
public function getProductPicture(array $product, string $pictureProp = ''): string
{
$picture = '';
$pictureId = $product['PROPERTY_' . $pictureProp . '_VALUE'] ?? null;
if (isset($product['DETAIL_PICTURE'])) {
$picture = $this->getImageUrl($product['DETAIL_PICTURE']);
} elseif (isset($product['PREVIEW_PICTURE'])) {
$picture = $this->getImageUrl($product['PREVIEW_PICTURE']);
} elseif ($pictureId !== null) {
$picture = $this->getImageUrl($pictureId);
}
return $picture ?? '';
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace Intaro\RetailCrm\Repository;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
use Intaro\RetailCrm\Service\Hl;
/**
* Class HlRepository
* @package Intaro\RetailCrm\Repository
*/
class HlRepository
{
/**
* @var \Bitrix\Main\Entity\DataManager|string|null
*/
private $hl;
public function __construct($hlName)
{
$this->hl = Hl::getHlClassByTableName($hlName);
}
/**
* @param string|null $propertyValue
* @return array|null
*/
public function getDataByXmlId(?string $propertyValue): ?array
{
try {
$result = $this->hl::query()
->setSelect(['*'])
->where('UF_XML_ID', '=', $propertyValue)
->fetch();
if ($result === false) {
return null;
}
return $result;
} catch (ObjectPropertyException | ArgumentException | SystemException $exception) {
AddMessage2Log($exception->getMessage());
return null;
}
}
/**
* @return \Bitrix\Main\Entity\DataManager|string|null
*/
public function getHl()
{
return $this->hl;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Intaro\RetailCrm\Repository;
use CCatalogMeasure;
/**
* Class MeasureRepository
* @package Intaro\RetailCrm\Repository
*/
class MeasureRepository
{
/**
* Получает доступные в Битриксе единицы измерения для товаров
*
* @return array
*/
public static function getMeasures(): array
{
$measures = [];
$resMeasure = CCatalogMeasure::getList();
while ($measure = $resMeasure->Fetch()) {
$measures[$measure['ID']] = $measure;
}
return $measures;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Intaro\RetailCrm\Repository;
use CSite;
use RetailcrmConfigProvider;
/**
* Class SiteRepository
* @package Intaro\RetailCrm\Repository
*/
class SiteRepository
{
/**
* @return string
*/
public static function getDefaultServerName(): ?string
{
$rsSites = CSite::GetList($by, $sort, ['ACTIVE' => 'Y']);
while ($ar = $rsSites->Fetch()) {
if ($ar['DEF'] === 'Y') {
return RetailcrmConfigProvider::getProtocol() . $ar['SERVER_NAME'];
}
}
return null;
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Intaro\RetailCrm\Service;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Entity\DataManager;
use Bitrix\Main\Loader;
use Bitrix\Highloadblock as Highloadblock;
use Bitrix\Main\LoaderException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
/**
* Class Hl
*
* Позволяет получить DataManager для HL-блоков
*
* @package Intaro\RetailCrm\Service
*/
class Hl
{
/**
* Получение DataManager класса управления HLBlock
*
* @param int $HlBlockId
* @return \Bitrix\Main\Entity\DataManager|null
* @throws \Bitrix\Main\ArgumentException
* @throws \Bitrix\Main\LoaderException
* @throws \Bitrix\Main\ObjectPropertyException
* @throws \Bitrix\Main\SystemException
*/
public static function getHlClassById(int $HlBlockId): ?DataManager
{
Loader::includeModule('highloadblock');
$hlblock = Highloadblock\HighloadBlockTable::getById($HlBlockId)->fetch();
if (!$hlblock) {
return null;
}
$entity = Highloadblock\HighloadBlockTable::compileEntity($hlblock);
return $entity->getDataClass();
}
/**
* Получение DataManager класса управления HLBlock по названию таблицы
*
* @param string $name
* @return \Bitrix\Main\Entity\DataManager|string|null
*/
public static function getHlClassByTableName(string $name)
{
try {
Loader::includeModule('highloadblock');
$hlblock = Highloadblock\HighloadBlockTable::query()
->addSelect('*')
->where('TABLE_NAME', '=', $name)
->exec()
->fetch();
if (!$hlblock) {
return null;
}
$entity = Highloadblock\HighloadBlockTable::compileEntity($hlblock);
return $entity->getDataClass();
} catch (ObjectPropertyException | ArgumentException | SystemException | LoaderException $exception) {
AddMessage2Log($exception->getMessage());
return null;
}
}
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2004-2021 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Intaro\RetailCrm\Vendor\Symfony\Component\Process;
/**
* Generic executable finder.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ExecutableFinder
{
private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
/**
* Replaces default suffixes of executable.
*/
public function setSuffixes(array $suffixes)
{
$this->suffixes = $suffixes;
}
/**
* Adds new possible suffix to check for executable.
*/
public function addSuffix(string $suffix)
{
$this->suffixes[] = $suffix;
}
/**
* Finds an executable by name.
*
* @param string $name The executable name (without the extension)
* @param string|null $default The default to return if no executable is found
* @param array $extraDirs Additional dirs to check into
*
* @return string|null The executable path or default value
*/
public function find(string $name, string $default = null, array $extraDirs = []): ?string
{
if (ini_get('open_basedir')) {
$searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
$dirs = [];
foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240
if (@is_dir($path)) {
$dirs[] = $path;
} else {
if (basename($path) == $name && @is_executable($path)) {
return $path;
}
}
}
} else {
$dirs = array_merge(
explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs
);
}
$suffixes = [''];
if ('\\' === \DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {
if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file;
}
}
}
return $default;
}
}

View file

@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Intaro\RetailCrm\Vendor\Symfony\Component\Process;
/**
* An executable finder specifically designed for the PHP executable.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PhpExecutableFinder
{
private $executableFinder;
public function __construct()
{
$this->executableFinder = new ExecutableFinder();
}
/**
* Finds The PHP executable.
*
* @return string|false The PHP executable path or false if it cannot be found
*/
public function find(bool $includeArgs = true)
{
if ($php = getenv('PHP_BINARY')) {
if (!is_executable($php)) {
$command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) {
if (!is_executable($php)) {
return false;
}
} else {
return false;
}
}
return $php;
}
$args = $this->findArguments();
$args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
// PHP_BINARY return the current sapi executable
if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) {
return \PHP_BINARY.$args;
}
if ($php = getenv('PHP_PATH')) {
if (!@is_executable($php)) {
return false;
}
return $php;
}
if ($php = getenv('PHP_PEAR_PHP_BIN')) {
if (@is_executable($php)) {
return $php;
}
}
if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
return $php;
}
$dirs = [\PHP_BINDIR];
if ('\\' === \DIRECTORY_SEPARATOR) {
$dirs[] = 'C:\xampp\php\\';
}
return $this->executableFinder->find('php', false, $dirs);
}
/**
* Finds the PHP executable arguments.
*
* @return array The PHP executable arguments
*/
public function findArguments(): array
{
$arguments = [];
if ('phpdbg' === \PHP_SAPI) {
$arguments[] = '-qrr';
}
return $arguments;
}
}

View file

@ -1,37 +0,0 @@
<?php
/**
* Class RetailCrmOrderTest
*/
use \Bitrix\Main\Loader;
class RetailCrmICMLTest extends BitrixTestCase
{
/**
* @inheritdoc
*/
public function setUp()
{
parent::setUp();
COption::SetOptionString('intaro.retailcrm', 'api_version', 'v5');
CModule::IncludeModule('intaro.retailcrm');
}
public function testModuleInstalled()
{
$this->assertTrue(Loader::includeModule("intaro.retailcrm"));
}
public function testGetImageUrl()
{
$test = new RetailCrmICML();
$result = $test->getImageUrl(1);
if (!empty($result)) {
$this->assertIsString($result);
$this->assertEquals("/upload/iblock/c44/test.jpg", $result);
} else {
$this->assertEmpty($result);
}
}
}