1
0
mirror of synced 2024-11-21 21:06:09 +03:00

New ICML generator

This commit is contained in:
Сергей Чазов 2021-05-31 16:33:02 +03:00 committed by GitHub
parent c37b05c6d2
commit a4c06ed8ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 3425 additions and 1312 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

@ -14,7 +14,8 @@
],
"require": {
"ext-json": "*",
"ext-mbstring": "*"
"ext-mbstring": "*",
"ext-xmlwriter": "*"
},
"require-dev": {
"phpunit/phpunit": "^7",

8
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "032aa518034a20c6776e550379084efc",
"content-hash": "72c54dce7daa3cd6c40577578128091a",
"packages": [],
"packages-dev": [
{
@ -1761,7 +1761,9 @@
"prefer-lowest": false,
"platform": {
"ext-json": "*",
"ext-mbstring": "*"
"ext-mbstring": "*",
"ext-xmlwriter": "*"
},
"platform-dev": []
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Class AddressBuilder
*/

View File

@ -1,4 +1,5 @@
<?php
/**
* Class BuyerProfile
*/

View File

@ -1,4 +1,5 @@
<?php
/**
* Class Customer
*/

View File

@ -1,4 +1,5 @@
<?php
/**
* Class CustomerAddress
*/

View File

@ -1,4 +1,5 @@
<?php
/**
* Class CustomerContragent
*/

View File

@ -224,7 +224,34 @@ class RetailcrmConfigProvider
{
static::setOption(RetailcrmConstants::CRM_ONLINE_CONSULTANT, $value);
}
/**
* @return bool|string|null
*/
public static function getCrmPurchasePrice()
{
return static::getOption(RetailcrmConstants::CRM_PURCHASE_PRICE_NULL);
}
public static function getProtocol()
{
return static::getOption(RetailcrmConstants::PROTOCOL);
}
/**
* @return string
*/
public static function getSiteName(): string
{
$sitename = COption::GetOptionString('main', 'site_name');
if (!$sitename) {
return '';
}
return $sitename;
}
/**
* setOnlineConsultantScript
*
@ -587,6 +614,21 @@ class RetailcrmConfigProvider
return static::$catalogBasePrice;
}
/**
* @param $profileId
*
* @return false|string|null
*/
public static function getCatalogBasePriceByProfile($profileId)
{
return COption::GetOptionString(
RetailcrmConstants::MODULE_ID,
RetailcrmConstants::CRM_CATALOG_BASE_PRICE . '_' . $profileId,
0
);
}
/**
* getOrderDimensions

View File

@ -21,58 +21,59 @@ IncludeModuleLangFile(__FILE__);
*/
class RetailcrmConstants
{
const MODULE_ID = 'intaro.retailcrm';
const CRM_API_HOST_OPTION = 'api_host';
const CRM_API_KEY_OPTION = 'api_key';
const CRM_ORDER_TYPES_ARR = 'order_types_arr';
const CRM_DELIVERY_TYPES_ARR = 'deliv_types_arr';
const CRM_DELIVERY_SERVICES_ARR = 'deliv_services_arr';
const CRM_PAYMENT_TYPES = 'pay_types_arr';
const CRM_INTEGRATION_PAYMENT = 'integration_payment';
const CRM_PAYMENT_STATUSES = 'pay_statuses_arr';
const CRM_PAYMENT = 'payment_arr'; //order payment Y/N
const CRM_ORDER_LAST_ID = 'order_last_id';
const CRM_ORDER_SITES = 'sites_ids';
const CRM_ORDER_DISCHARGE = 'order_discharge';
const CRM_SITES_LIST = 'sites_list';
const CRM_ORDER_PROPS = 'order_props';
const CRM_LEGAL_DETAILS = 'legal_details';
const CRM_CUSTOM_FIELDS = 'custom_fields';
const CRM_CONTRAGENT_TYPE = 'contragent_type';
const CRM_SITES_LIST_CORPORATE = 'shops-corporate';
const CRM_ORDER_NUMBERS = 'order_numbers';
const CRM_CANCEL_ORDER = 'cansel_order';
const CRM_INVENTORIES_UPLOAD = 'inventories_upload';
const CRM_STORES = 'stores';
const CRM_SHOPS = 'shops';
const CRM_IBLOCKS_INVENTORIES = 'iblocks_inventories';
const CRM_PRICES_UPLOAD = 'prices_upload';
const CRM_PRICES = 'prices';
const CRM_PRICE_SHOPS = 'price_shops';
const CRM_IBLOCKS_PRICES = 'iblock_prices';
const CRM_COLLECTOR = 'collector';
const CRM_COLL_KEY = 'coll_key';
const CRM_UA = 'ua';
const CRM_UA_KEYS = 'ua_keys';
const CRM_DISCOUNT_ROUND = 'discount_round';
const CRM_CC = 'cc';
const CRM_CORP_SHOPS = 'shops-corporate';
const CRM_CORP_NAME = 'nickName-corporate';
const CRM_CORP_ADDRESS = 'adres-corporate';
const CRM_API_VERSION = 'api_version';
const CRM_CURRENCY = 'currency';
const CRM_ADDRESS_OPTIONS = 'address_options';
const CRM_DIMENSIONS = 'order_dimensions';
const PROTOCOL = 'protocol';
const CRM_ORDER_FAILED_IDS = 'order_failed_ids';
const CRM_CUSTOMERS_HISTORY_SINCE_ID = 'customer_history';
const CRM_ORDER_HISTORY_DATE = 'order_history_date';
const CRM_CATALOG_BASE_PRICE = 'catalog_base_price';
const CRM_ORDER_DIMENSIONS = 'order_dimensions';
const CANCEL_PROPERTY_CODE = 'INTAROCRM_IS_CANCELED';
const CRM_INTEGRATION_DELIVERY = 'integration_delivery';
const CRM_SHIPMENT_DEDUCTED = 'shipment_deducted';
const SEND_PAYMENT_AMOUNT = 'send_payment_amount';
const CRM_ONLINE_CONSULTANT = 'online_consultant';
const CRM_ONLINE_CONSULTANT_SCRIPT = 'online_consultant_script';
public const MODULE_ID = 'intaro.retailcrm';
public const CRM_API_HOST_OPTION = 'api_host';
public const CRM_API_KEY_OPTION = 'api_key';
public const CRM_ORDER_TYPES_ARR = 'order_types_arr';
public const CRM_DELIVERY_TYPES_ARR = 'deliv_types_arr';
public const CRM_DELIVERY_SERVICES_ARR = 'deliv_services_arr';
public const CRM_PAYMENT_TYPES = 'pay_types_arr';
public const CRM_INTEGRATION_PAYMENT = 'integration_payment';
public const CRM_PAYMENT_STATUSES = 'pay_statuses_arr';
public const CRM_PAYMENT = 'payment_arr'; //order payment Y/N
public const CRM_ORDER_LAST_ID = 'order_last_id';
public const CRM_ORDER_SITES = 'sites_ids';
public const CRM_ORDER_DISCHARGE = 'order_discharge';
public const CRM_SITES_LIST = 'sites_list';
public const CRM_ORDER_PROPS = 'order_props';
public const CRM_LEGAL_DETAILS = 'legal_details';
public const CRM_CUSTOM_FIELDS = 'custom_fields';
public const CRM_CONTRAGENT_TYPE = 'contragent_type';
public const CRM_SITES_LIST_CORPORATE = 'shops-corporate';
public const CRM_ORDER_NUMBERS = 'order_numbers';
public const CRM_CANCEL_ORDER = 'cansel_order';
public const CRM_INVENTORIES_UPLOAD = 'inventories_upload';
public const CRM_STORES = 'stores';
public const CRM_SHOPS = 'shops';
public const CRM_IBLOCKS_INVENTORIES = 'iblocks_inventories';
public const CRM_PRICES_UPLOAD = 'prices_upload';
public const CRM_PRICES = 'prices';
public const CRM_PRICE_SHOPS = 'price_shops';
public const CRM_IBLOCKS_PRICES = 'iblock_prices';
public const CRM_COLLECTOR = 'collector';
public const CRM_COLL_KEY = 'coll_key';
public const CRM_UA = 'ua';
public const CRM_UA_KEYS = 'ua_keys';
public const CRM_DISCOUNT_ROUND = 'discount_round';
public const CRM_CC = 'cc';
public const CRM_CORP_SHOPS = 'shops-corporate';
public const CRM_CORP_NAME = 'nickName-corporate';
public const CRM_CORP_ADDRESS = 'adres-corporate';
public const CRM_API_VERSION = 'api_version';
public const CRM_CURRENCY = 'currency';
public const CRM_ADDRESS_OPTIONS = 'address_options';
public const CRM_DIMENSIONS = 'order_dimensions';
public const PROTOCOL = 'protocol';
public const CRM_ORDER_FAILED_IDS = 'order_failed_ids';
public const CRM_CUSTOMERS_HISTORY_SINCE_ID = 'customer_history';
public const CRM_ORDER_HISTORY_DATE = 'order_history_date';
public const CRM_CATALOG_BASE_PRICE = 'catalog_base_price';
public const CRM_ORDER_DIMENSIONS = 'order_dimensions';
public const CANCEL_PROPERTY_CODE = 'INTAROCRM_IS_CANCELED';
public const CRM_INTEGRATION_DELIVERY = 'integration_delivery';
public const CRM_SHIPMENT_DEDUCTED = 'shipment_deducted';
public const SEND_PAYMENT_AMOUNT = 'send_payment_amount';
public const CRM_ONLINE_CONSULTANT = 'online_consultant';
public const CRM_ONLINE_CONSULTANT_SCRIPT = 'online_consultant_script';
public const CRM_PURCHASE_PRICE_NULL = 'purchasePrice_null';
}

View File

@ -1,910 +0,0 @@
<?php
use Bitrix\Highloadblock as HL;
use Bitrix\Main\Entity;
IncludeModuleLangFile(__FILE__);
class RetailCrmICML
{
public $profileID;
public $iblocks;
public $filename;
public $serverName;
public $defaultServerName;
public $propertiesSKU;
public $propertiesUnitSKU;
public $propertiesProduct;
public $propertiesUnitProduct;
public $highloadblockSkuProperties;
public $highloadblockProductProperties;
public $application;
public $encoding = 'utf-8';
public $encodingDefault = 'utf-8';
public $loadPurchasePrice = false;
public $productPictures;
public $skuPictures;
public $offerPageSize = 50;
protected $fp;
protected $mainSection = 1000000;
protected $pageSize = 500;
protected $protocol;
protected $purchasePriceNull;
protected $isLogged = false;
protected $logFile = '/bitrix/catalog_export/i_crm_load_log.txt';
protected $fpLog;
protected $localizedIBlockProps;
protected $MODULE_ID = 'intaro.retailcrm';
protected $CRM_CATALOG_BASE_PRICE = 'catalog_base_price';
protected $PROTOCOL_OPTION = 'protocol';
protected $CRM_PURCHASE_PRICE_NULL = 'purchasePrice_null';
protected $measurement = array (
'mm' => 1, // 1 mm = 1 mm
'cm' => 10, // 1 cm = 10 mm
'm' => 1000,
'mg' => 0.001, // 0.001 g = 1 mg
'g' => 1,
'kg' => 1000,
);
protected $measurementLink = array (
'mm' => 'mm',
'cm' => 'mm',
'm' => 'mm',
'mg' => 'g',
'g' => 'g',
'kg' => 'g',
);
protected $measure = array (
'pc. 1' => 'pc',
'm' => 'm',
'l' => 'l',
'kg' => 'kg',
);
public function Load()
{
global $USER;
if (!isset($_SESSION["SESS_AUTH"]["USER_ID"]) || !$_SESSION["SESS_AUTH"]["USER_ID"]) {
$USER = new CUser();
}
$this->isLogged = true;
$this->localizedIBlockProps = $this->getLocalizedIBlockProps();
$defaultSite = CSite::GetList($by = "def", $order = "desc", array('DEF' => 'Y'))->Fetch();
$this->encodingDefault = $defaultSite["CHARSET"];
$this->protocol = COption::GetOptionString($this->MODULE_ID, $this->PROTOCOL_OPTION);
$this->purchasePriceNull = COption::GetOptionString($this->MODULE_ID, $this->CRM_PURCHASE_PRICE_NULL);
$this->PrepareSettings();
$this->fp = $this->PrepareFile($this->filename. '.tmp');
if ($this->isLogged) {
$this->fpLog = $this->PrepareFile($this->logFile);
$this->WriteLog("Start Loading");
}
$this->PreWriteCatalog();
$categories = $this->GetCategories();
$this->WriteCategories($categories);
$this->PreWriteOffers();
$this->BuildOffers($categories);
$this->PostWriteOffers();
$this->PostWriteCatalog();
if ($this->isLogged) {
$this->WriteLog("Loading was ended successfully (peek memory usage: " . memory_get_peak_usage() . ")");
}
$this->CloseFile($this->fp);
$this->CloseFile($this->fpLog);
unlink($defaultSite['ABS_DOC_ROOT'] . $this->filename);
rename($defaultSite['ABS_DOC_ROOT'] . $this->filename. '.tmp', $defaultSite['ABS_DOC_ROOT'] . $this->filename);
return true;
}
private function setSiteAddress($block_id)
{
$site = CAllIBlock::GetSite($block_id)->Fetch();
if ($site['SERVER_NAME']) {
$this->serverName = $site['SERVER_NAME'];
} else {
$this->serverName = $this->defaultServerName;
}
}
protected function PrepareSettings()
{
foreach ($this->propertiesSKU as $iblock => $arr) {
foreach ($arr as $id => $sku) {
$this->propertiesSKU[$iblock][$id] = strtoupper($sku);
}
}
foreach ($this->propertiesProduct as $iblock => $arr) {
foreach ($arr as $id => $prod) {
$this->propertiesProduct[$iblock][$id] = strtoupper($prod);
}
}
}
protected function PrepareValue($text)
{
$newText = $this->application->ConvertCharset($text, $this->encodingDefault, $this->encoding);
$newText = strip_tags($newText);
$newText = str_replace("&", "&#x26;", $newText);
return $newText;
}
protected function PrepareFile($filename)
{
$fullFilename = $_SERVER["DOCUMENT_ROOT"] . $filename;
CheckDirPath($fullFilename);
if ($fp = @fopen($fullFilename, "w")){
return $fp;
} else {
return false;
}
}
protected function PreWriteCatalog()
{
@fwrite($this->fp, "<yml_catalog date=\"" . $this->PrepareValue(Date("Y-m-d H:i:s")) . "\">\n
<shop>\n
<name>" . $this->PrepareValue(COption::GetOptionString("main", "site_name", ""))."</name>\n
<company>" . $this->PrepareValue(COption::GetOptionString("main", "site_name", ""))."</company>\n"
);
}
protected function WriteCategories($categories)
{
$stringCategories = "";
@fwrite($this->fp, "<categories>\n");
foreach ($categories as $category) {
$stringCategories .= $this->BuildCategory($category);
}
@fwrite($this->fp, $stringCategories);
@fwrite($this->fp, "</categories>\n");
}
protected function PreWriteOffers()
{
@fwrite($this->fp, "<offers>\n");
}
protected function PostWriteOffers()
{
@fwrite($this->fp, "</offers>\n");
}
protected function WriteOffers($offers)
{
@fwrite($this->fp, $offers);
}
protected function WriteLog($text)
{
if ($this->isLogged) {
@fwrite($this->fpLog, Date("Y:m:d H:i:s") . ": " . $text . "\n");
}
}
protected function PostWriteCatalog()
{
@fwrite($this->fp, "</shop>\n
</yml_catalog>\n");
}
protected function CloseFile($fp)
{
@fclose($fp);
}
protected function GetCategories()
{
$categories = array();
foreach ($this->iblocks as $id) {
$this->setSiteAddress($id);
$filter = array("IBLOCK_ID" => $id);
$dbRes = CIBlockSection::GetList(array("left_margin" => "asc"), $filter);
$hasCategories = false;
while ($arRes = $dbRes->Fetch()) {
$categories[$arRes['ID']] = $arRes;
$categories[$arRes['ID']]['SITE'] = $this->protocol . $this->serverName;
$hasCategories = true;
}
if (!$hasCategories) {
$iblock = CIBlock::GetByID($id)->Fetch();
$arRes = array();
$arRes['ID'] = $this->mainSection + $id;
$arRes['IBLOCK_SECTION_ID'] = 0;
$arRes['NAME'] = sprintf(GetMessage('ROOT_CATEGORY_FOR_CATALOG'), $iblock['NAME']);
$categories[$arRes['ID']] = $arRes;
$categories[$arRes['ID']]['SITE'] = $this->protocol . $this->serverName;
}
}
return $categories;
}
protected function BuildCategory($arCategory)
{
$category =
"<category id=\"" . $this->PrepareValue($arCategory["ID"]) . "\""
. (intval($arCategory["IBLOCK_SECTION_ID"]) > 0 ?
" parentId=\"" . $this->PrepareValue($arCategory["IBLOCK_SECTION_ID"]) . "\""
:"")
. ">\n\t"
. "<name>" . $this->PrepareValue($arCategory["NAME"]) . "</name>\n";
if (CFile::GetPath($arCategory["DETAIL_PICTURE"])) {
$category .= "\t<picture>" . $this->getImageUrl($arCategory["DETAIL_PICTURE"]) . "</picture>\n";
}
if (CFile::GetPath($arCategory["PICTURE"])) {
$category .= "\t<picture>" . $this->getImageUrl($arCategory["PICTURE"]) . "</picture>\n";
}
$category .= "</category>\n";
return $category;
}
protected function BuildOffers(&$allCategories)
{
$basePriceId = $this->getBasePriceId();
foreach ($this->iblocks as $key => $id) {
$this->setSiteAddress($id);
$barcodes = $this->getProductBarcodesByIblock($id);
// Get Info by infoblocks
$iblockData = CIBlock::GetByID($id)->Fetch();
$iblockOffer = CCatalogSKU::GetInfoByProductIBlock($id);
$highloadblockSkuProps = $this->getAvailableHighloadOfferSkuProps($iblockOffer['IBLOCK_ID']);
$highloadblockProductProps = $this->getAvailableHighloadProductProps($id);
$arSelect = $this->buildProductQuery($id);
$arSelectOffer = $this->buildOfferQuery($id, $iblockOffer['SKU_PROPERTY_ID']);
// Set filter
$order = array("id");
$filter = array(
"IBLOCK_ID" => $id,
"ACTIVE" => 'Y',
);
$arNavStatParams = array(
"iNumPage" => 1,
"nPageSize" => $this->pageSize,
);
// Cycle page to page
do {
// Get products on this page
$elems = array();
$dbResProductsIds = CIBlockElement::GetList($order, $filter, false, $arNavStatParams, array('ID'));
while ($obIds = $dbResProductsIds->Fetch()) {
$elems[] = $obIds['ID'];
}
foreach ($elems as $elemId) {
$arFilter = array(
"IBLOCK_ID" => $id,
"ID" => array($elemId)
);
$this->ProcessProductOffers(
$arSelect,
$arSelectOffer,
$allCategories,
$basePriceId,
$id,
$iblockData,
$iblockOffer,
$barcodes,
$highloadblockProductProps,
$highloadblockSkuProps,
$order,
$arFilter
);
}
if ($this->isLogged) {
$this->WriteLog(
count($elems)
. " product(s) has been loaded from " . $id . " IB (memory usage: " . memory_get_usage() . ")"
);
}
$arNavStatParams['iNumPage'] = $dbResProductsIds->NavPageNomer + 1;
} while ($dbResProductsIds->NavPageNomer < $dbResProductsIds->NavPageCount);
}
}
/**
* Process offers for a single product
*
* @param array $arSelect Properties to select for order
* @param array $arSelectOffer Properties to select for offer
* @param array $allCategories Categories to pick data from
* @param string $basePriceId Base price ID
* @param string $iblockId iblock id
* @param array $iblock iblock data
* @param array $iblockOffer offer iblock
* @param array $barcodes Catalog barcodes
* @param array $highloadblockProductProps Product props
* @param array $highloadblockSkuProps SKU props
* @param array $order Order data
* @param array $arFilter filter
*/
protected function ProcessProductOffers(
$arSelect,
$arSelectOffer,
$allCategories,
$basePriceId,
$iblockId,
$iblock,
$iblockOffer,
$barcodes,
$highloadblockProductProps,
$highloadblockSkuProps,
$order,
$arFilter
) {
$dbResProducts = CIBlockElement::GetList($order, $arFilter, false, false, $arSelect);
$products = [];
while ($product = $dbResProducts->GetNext()) {
// Compile products to array
$products[$product['ID']] = $product;
$products[$product['ID']]['offers'] = [];
}
unset($product);
if (!empty($iblockOffer['IBLOCK_ID']) && !empty($products)) {
$arFilterOffer = array(
'IBLOCK_ID' => $iblockOffer['IBLOCK_ID'],
'PROPERTY_' . $iblockOffer['SKU_PROPERTY_ID'] => array_keys($products),
);
// Get all offers for products on this page
$dbResOffers = CIBlockElement::GetList(
array(),
$arFilterOffer,
false,
array('nTopCount' => $this->pageSize * $this->offerPageSize),
$arSelectOffer
);
while ($offer = $dbResOffers->GetNext()) {
// Link offers to products
$products[$offer['PROPERTY_' . $iblockOffer['SKU_PROPERTY_ID'] . '_VALUE']]['offers'][$offer['ID']] = $offer;
}
unset($offer, $dbResOffers);
}
foreach ($products as $product) {
$product['PICTURE'] = $this->getProductPicture($iblockId, $product);
$resPropertiesProduct = $this->getProductProperties($iblockId, $highloadblockProductProps, $product);
$categories = $this->getProductCategories($allCategories, $iblockId, $product['ID']);
$existOffer = false;
if (!empty($iblockOffer['IBLOCK_ID'])) {
foreach ($product['offers'] as $offer) {
$offer['BARCODE'] = isset($barcodes[$offer['ID']]) ? $barcodes[$offer['ID']] : '';
$offer['PRODUCT_ID'] = $product["ID"];
$offer['DETAIL_PAGE_URL'] = $product["DETAIL_PAGE_URL"];
if (CFile::GetPath($offer["DETAIL_PICTURE"])) {
$offer['PICTURE'] = $this->getImageUrl($offer["DETAIL_PICTURE"]);
} elseif (CFile::GetPath($offer["PREVIEW_PICTURE"])) {
$offer['PICTURE'] = $this->getImageUrl($offer["PREVIEW_PICTURE"]);
} elseif (
$this->skuPictures
&& isset($this->skuPictures[$iblockId])
&& CFile::GetPath($offer["PROPERTY_" . $this->skuPictures[$iblockId]['picture'] . "_VALUE"])
) {
$offer['PICTURE'] = $this->getImageUrl($offer["PROPERTY_" . $this->skuPictures[$iblockId]['picture'] . "_VALUE"]);
} else {
$offer['PICTURE'] = $product['PICTURE'];
}
$offer['PRODUCT_NAME'] = $product["NAME"];
$offer['PRODUCT_ACTIVE'] = $product["ACTIVE"];
$offer['PRICE'] = $offer['CATALOG_PRICE_' . $basePriceId];
$offer['PURCHASE_PRICE'] = $offer['CATALOG_PURCHASING_PRICE'];
$offer['QUANTITY'] = $offer["CATALOG_QUANTITY"];
// Get properties of product
foreach ($this->propertiesSKU[$iblockId] as $key => $propSKU) {
if ($propSKU != "") {
if (isset ($offer["PROPERTY_" . $propSKU . "_NAME"])) {
$offer['_PROP_' . $key] = $offer["PROPERTY_" . $propSKU . "_NAME"];
} elseif (isset($offer["PROPERTY_" . $propSKU . "_VALUE"])) {
$offer['_PROP_' . $key] = $offer["PROPERTY_" . $propSKU . "_VALUE"];
} elseif (isset($offer[$propSKU])) {
$offer['_PROP_' . $key] = $offer[$propSKU];
}
if (array_key_exists($key, $this->propertiesUnitSKU[$iblockId])) {
$offer['_PROP_' . $key] *= $this->measurement[$this->propertiesUnitSKU[$iblockId][$key]];
$offer['_PROP_' . $key . "_UNIT"] = $this->measurementLink[$this->propertiesUnitSKU[$iblockId][$key]];
}
if (isset($highloadblockSkuProps[$propSKU])) {
$propVal = $this->getHBprop($highloadblockSkuProps[$propSKU], $offer["PROPERTY_" . $propSKU . "_VALUE"]);
$tableName = $highloadblockSkuProps[$propSKU]['USER_TYPE_SETTINGS']['TABLE_NAME'];
$field = $this->highloadblockSkuProperties[$tableName][$iblockId][$key];
$offer['_PROP_' . $key] = $propVal[$field];
}
}
}
foreach ($resPropertiesProduct as $key => $propProduct) {
if ($this->propertiesProduct[$iblockId][$key] != "" && !isset($offer[$key])) {
$offer['_PROP_' . $key] = $propProduct;
}
}
$this->PutOffer($offer, $categories, $iblock, $allCategories);
$existOffer = true;
}
}
if (!$existOffer) {
$offer['BARCODE'] = isset($barcodes[$product["ID"]]) ? $barcodes[$product["ID"]] : '';
$product['PRODUCT_ID'] = $product["ID"];
$product['PRODUCT_NAME'] = $product["NAME"];
$product['PRODUCT_ACTIVE'] = $product["ACTIVE"];
$product['PRICE'] = $product['CATALOG_PRICE_' . $basePriceId];
$product['PURCHASE_PRICE'] = $product['CATALOG_PURCHASING_PRICE'];
$product['QUANTITY'] = $product["CATALOG_QUANTITY"];
foreach ($resPropertiesProduct as $key => $propProduct) {
if ($this->propertiesProduct[$iblockId][$key] != "" || $this->propertiesProduct[$iblockId][str_replace("_UNIT", "", $key)] != "") {
$product['_PROP_' . $key] = $propProduct;
}
}
$this->PutOffer($product, $categories, $iblock, $allCategories);
}
}
unset($products);
}
protected function getProductPicture($iblockId, array $product)
{
$picture = '';
if (CFile::GetPath($product["DETAIL_PICTURE"])) {
$picture = $this->getImageUrl($product["DETAIL_PICTURE"]);
} elseif (CFile::GetPath($product["PREVIEW_PICTURE"])){
$picture = $this->getImageUrl($product["PREVIEW_PICTURE"]);
} elseif (
$this->productPictures
&& isset($this->productPictures[$iblockId])
&& CFile::GetPath($product["PROPERTY_" . $this->productPictures[$iblockId]['picture'] . "_VALUE"])
) {
$picture = $this->getImageUrl($product["PROPERTY_" . $this->productPictures[$iblockId]['picture'] . "_VALUE"]);
}
return $picture;
}
protected function PutOffer($arOffer, $categories, $iblock, &$allCategories)
{
$offerData = $this->BuildOffer($arOffer, $categories, $iblock, $allCategories);
if ($offerData !== "") {
$this->WriteOffers($offerData);
}
}
protected function BuildOffer($arOffer, $categories, $iblock, &$allCategories)
{
$offer = "";
$offer .= "<offer id=\"" .$this->PrepareValue($arOffer["ID"]) . "\" ".
"productId=\"" . $this->PrepareValue($arOffer["PRODUCT_ID"]) . "\" ".
"quantity=\"" . $this->PrepareValue(DoubleVal($arOffer['QUANTITY'])) . "\">\n";
if ($arOffer['PRODUCT_ACTIVE'] == "N") {
$offer .= "<productActivity>" . $this->PrepareValue($arOffer['PRODUCT_ACTIVE']) . "</productActivity>\n";
}
$keys = array_keys($categories);
if (strpos($arOffer['DETAIL_PAGE_URL'], "#SECTION_PATH#") !== false) {
if (count($categories) != 0) {
$category = $allCategories[$keys[0]];
$path = $category['CODE'];
if (intval($category["IBLOCK_SECTION_ID"] ) != 0) {
while (true) {
$category = $allCategories[$category['IBLOCK_SECTION_ID']];
$path = $category['CODE'] . '/' . $path;
if(intval($category["IBLOCK_SECTION_ID"]) == 0){
break;
}
}
}
}
$arOffer['DETAIL_PAGE_URL'] = str_replace("#SECTION_PATH#", $path, $arOffer['DETAIL_PAGE_URL']);
}
if (isset($arOffer["PICTURE"]) && $arOffer["PICTURE"]) {
$offer .= "<picture>" . $this->PrepareValue($arOffer["PICTURE"]) . "</picture>\n";
}
$offer .= "<url>" . $this->protocol . $this->serverName . $this->PrepareValue($arOffer['DETAIL_PAGE_URL']) . "</url>\n";
$offer .= "<price>" . $this->PrepareValue($arOffer['PRICE']) . "</price>\n";
if ($this->loadPurchasePrice) {
if ($arOffer['PURCHASE_PRICE']) {
$offer .= "<purchasePrice>" . $this->PrepareValue($arOffer['PURCHASE_PRICE']) . "</purchasePrice>\n";
} elseif ("Y" == $this->purchasePriceNull) {
$offer .= "<purchasePrice>0</purchasePrice>\n";
}
}
foreach ($categories as $category) {
$offer .= "<categoryId>" . $category['ID'] . "</categoryId>\n";
}
$offer .= "<name>" . $this->PrepareValue($arOffer["NAME"]) . "</name>\n";
$offer .= "<xmlId>" . $this->PrepareValue($arOffer["EXTERNAL_ID"]) . "</xmlId>\n";
$offer .= "<productName>" . $this->PrepareValue($arOffer["PRODUCT_NAME"]) . "</productName>\n";
foreach ($this->propertiesProduct[$iblock['ID']] as $key => $propProduct) {
if ($propProduct != "" && $arOffer['_PROP_' . $key] != null) {
if ($key === "manufacturer") {
$offer .= "<vendor>" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "</vendor>\n";
} else {
$name = $key;
if (isset($this->localizedIBlockProps[$key])) {
$name = $this->localizedIBlockProps[$key];
}
$offer .= '<param name="' . $this->PrepareValue($name) . '" code="' . $key . '"' . (isset($arOffer['_PROP_' . $key . "_UNIT"]) ? ' unit="' . $arOffer['_PROP_' . $key . "_UNIT"] . '"' : "") . ">" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "</param>\n";
}
}
}
foreach ($this->propertiesSKU[$iblock['ID']] as $key => $propProduct) {
if ($propProduct != "" && $arOffer['_PROP_' . $key] != null) {
if ($key === "manufacturer") {
$offer .= "<vendor>" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "</vendor>\n";
} else {
$name = $key;
if (isset($this->localizedIBlockProps[$key])) {
$name = $this->localizedIBlockProps[$key];
}
$offer .= '<param name="' . $this->PrepareValue($name) . '" code="' . $key . '"' . (isset($arOffer['_PROP_' . $key . "_UNIT"]) ? ' unit="' . $arOffer['_PROP_' . $key . "_UNIT"] . '"' : "") . ">" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "</param>\n";
}
}
}
if (isset($arOffer["MEASURE"]['SYMBOL_INTL'])) {
if ($this->measure[$arOffer["MEASURE"]['SYMBOL_INTL']]) {
$offer .= '<unit code="' . $this->measure[$this->PrepareValue($arOffer["MEASURE"]['SYMBOL_INTL'])] . '" />' . "\n";
} else {
$offer .= '<unit code="' . $this->PrepareValue($arOffer["MEASURE"]['SYMBOL_INTL']) . '" name="' . $this->PrepareValue($arOffer["MEASURE"]['MEASURE_TITLE']) . '" sym="' . $this->PrepareValue($arOffer["MEASURE"]['SYMBOL_RUS']) . '" />' . "\n";
}
} else {
$measure = \Bitrix\Catalog\ProductTable::getCurrentRatioWithMeasure($arOffer["ID"]);
if ($this->measure[$measure[$arOffer["ID"]]["MEASURE"]['SYMBOL_INTL']]) {
$offer .= '<unit code="' . $this->measure[$this->PrepareValue($measure[$arOffer["ID"]]["MEASURE"]['SYMBOL_INTL'])] . '" />' . "\n";
} else {
$offer .= '<unit code="' . $this->PrepareValue($measure[$arOffer["ID"]]["MEASURE"]['SYMBOL_INTL']) . '" name="' . $this->PrepareValue($measure[$arOffer["ID"]]["MEASURE"]['MEASURE_TITLE']) . '" sym="' . $this->PrepareValue($measure[$arOffer["ID"]]["MEASURE"]['SYMBOL_RUS']) . '" />' . "\n";
}
}
if ($arOffer["BARCODE"]) {
$offer.= "<barcode>" . $this->PrepareValue($arOffer["BARCODE"]) . "</barcode>\n";
}
if ((float)$arOffer["CATALOG_VAT"]) {
$vatRate = $arOffer["CATALOG_VAT"];
} else {
$vatRate = 'none';
}
$offer.= "<vatRate>" . $this->PrepareValue($vatRate) . "</vatRate>\n";
$offer.= "</offer>\n";
return $offer;
}
private function getHBprop($hbProp, $xml_id)
{
if (CModule::IncludeModule('highloadblock')) {
$hlblockArr = \Bitrix\Highloadblock\HighloadBlockTable::getList(array(
'filter' => array('=TABLE_NAME' => $hbProp['USER_TYPE_SETTINGS']['TABLE_NAME'])
))->fetch();
$hlblock = HL\HighloadBlockTable::getById($hlblockArr["ID"])->fetch();
$entity = HL\HighloadBlockTable::compileEntity($hlblock);
$entityClass = $entity->getDataClass();
$result = $entityClass::getList(array(
'select' => array('*'),
'filter' => array('UF_XML_ID' => $xml_id)
));
return $result->fetch();
}
return array();
}
/**
* Returns products IDs with barcodes by infoblock id
*
* @param int $iblockId
*
* @return array
*/
private function getProductBarcodesByIblock($iblockId)
{
$barcodes = array();
$dbBarCode = CCatalogStoreBarCode::getList(
array(),
array("IBLOCK_ID" => $iblockId),
false,
false,
array('PRODUCT_ID', 'BARCODE')
);
while ($arBarCode = $dbBarCode->GetNext()) {
if (!empty($arBarCode)) {
$barcodes[$arBarCode['PRODUCT_ID']] = $arBarCode['BARCODE'];
}
}
return $barcodes;
}
/**
* Returns necessary product properties
*
* @param int $iblockId
* @param array $highloadblockProductProps
* @param array $product
*
* @return array
*/
private function getProductProperties($iblockId, $highloadblockProductProps, $product)
{
// Get properties of product
$resPropertiesProduct = array();
foreach ($this->propertiesProduct[$iblockId] as $key => $propProduct) {
$resPropertiesProduct[$key] = "";
if ($propProduct != "") {
if (isset($product["PROPERTY_" . $propProduct . "_NAME"])) {
$resPropertiesProduct[$key] = $product["PROPERTY_" . $propProduct . "_NAME"];
} elseif (isset($product["PROPERTY_" . $propProduct . "_VALUE"])) {
$resPropertiesProduct[$key] = $product["PROPERTY_" . $propProduct . "_VALUE"];
} elseif (isset($product[$propProduct])) {
$resPropertiesProduct[$key] = $product[$propProduct];
}
if (array_key_exists($key, $this->propertiesUnitProduct[$iblockId])) {
$resPropertiesProduct[$key] *= $this->measurement[$this->propertiesUnitProduct[$iblockId][$key]];
$resPropertiesProduct[$key . "_UNIT"] = $this->measurementLink[$this->propertiesUnitProduct[$iblockId][$key]];
}
if (isset($highloadblockProductProps[$propProduct])) {
$propVal = $this->getHBprop($highloadblockProductProps[$propProduct], $product["PROPERTY_" . $propProduct . "_VALUE"]);
$tableName = $highloadblockProductProps[$propProduct]['USER_TYPE_SETTINGS']['TABLE_NAME'];
$field = $this->highloadblockProductProperties[$tableName][$iblockId][$key];
$resPropertiesProduct[$key] = $propVal[$field];
}
}
}
return $resPropertiesProduct;
}
/**
* @param array $allCategories
* @param int $iblockId
* @param string $productId
*
* @return array
*/
private function getProductCategories(&$allCategories, $iblockId, $productId)
{
$categories = array();
$dbResCategories = CIBlockElement::GetElementGroups($productId, true);
while ($arResCategory = $dbResCategories->Fetch()) {
$categories[$arResCategory["ID"]] = array(
'ID' => $arResCategory["ID"],
'NAME' => $arResCategory["NAME"],
);
}
if (count($categories) == 0) {
$catId = $this->mainSection + $iblockId;
$categories[$catId] = $allCategories[$catId];
}
return $categories;
}
private function buildProductQuery($iblockId)
{
$arSelect = array(
"ID",
"LID",
"IBLOCK_ID",
"IBLOCK_SECTION_ID",
"ACTIVE",
"NAME",
"DETAIL_PICTURE",
"PREVIEW_PICTURE",
"DETAIL_PAGE_URL",
"CATALOG_GROUP_" . $this->getBasePriceId()
);
// Set selected properties
foreach ($this->propertiesProduct[$iblockId] as $key => $propProduct) {
if ($this->propertiesProduct[$iblockId][$key] != "") {
$arSelect[] = "PROPERTY_" . $propProduct;
$arSelect[] = "PROPERTY_" . $propProduct . ".NAME";
}
}
if ($this->productPictures && isset($this->productPictures[$iblockId])) {
$arSelect[] = "PROPERTY_" . $this->productPictures[$iblockId]['picture'];
$arSelect[] = "PROPERTY_" . $this->productPictures[$iblockId]['picture'] . ".NAME";
}
return $arSelect;
}
private function buildOfferQuery($iblockId, $skuPropertyId)
{
$arSelectOffer = array(
'ID',
"NAME",
"DETAIL_PAGE_URL",
"DETAIL_PICTURE",
"PREVIEW_PICTURE",
'PROPERTY_' . $skuPropertyId,
"CATALOG_GROUP_" . $this->getBasePriceId()
);
// Set selected properties
foreach ($this->propertiesSKU[$iblockId] as $key => $propSKU) {
if ($this->propertiesSKU[$iblockId][$key] != "") {
$arSelectOffer[] = "PROPERTY_" . $propSKU;
$arSelectOffer[] = "PROPERTY_" . $propSKU . ".NAME";
}
}
if ($this->skuPictures && isset($this->skuPictures[$iblockId])) {
$arSelectOffer[] = "PROPERTY_" . $this->skuPictures[$iblockId]['picture'];
$arSelectOffer[] = "PROPERTY_" . $this->skuPictures[$iblockId]['picture'] . ".NAME";
}
return $arSelectOffer;
}
private function getAvailableHighloadProductProps($iblockId)
{
$highloadblockProductProps = array();
$productProps = CIBlockproperty::GetList(array(), array("IBLOCK_ID" => $iblockId));
while ($arrProductProps = $productProps->Fetch()) {
if ($arrProductProps["USER_TYPE"] == 'directory') {
$highloadblockProductProps[$arrProductProps['CODE']] = $arrProductProps;
}
}
return $highloadblockProductProps;
}
private function getAvailableHighloadOfferSkuProps($iblockId)
{
$highloadblockSkuProps = array();
$skuProps = CIBlockproperty::GetList(array(), array("IBLOCK_ID" => $iblockId));
while ($arrSkuProps = $skuProps->Fetch()) {
if ($arrSkuProps["USER_TYPE"] == 'directory') {
$highloadblockSkuProps[$arrSkuProps['CODE']] = $arrSkuProps;
}
}
return $highloadblockSkuProps;
}
/**
* Returns base price id
*
* @return string
*/
private function getBasePriceId()
{
$basePriceId = COption::GetOptionString(
$this->MODULE_ID,
$this->CRM_CATALOG_BASE_PRICE . '_' . $this->profileID,
0
);
if (!$basePriceId) {
$dbPriceType = CCatalogGroup::GetList(
array(),
array('BASE' => 'Y'),
false,
false,
array('ID')
);
$result = $dbPriceType->GetNext();
$basePriceId = $result['ID'];
}
return $basePriceId;
}
private function getLocalizedIBlockProps()
{
return array(
"article" => GetMessage("PROPERTY_ARTICLE_HEADER_NAME"),
"manufacturer" => GetMessage("PROPERTY_MANUFACTURER_HEADER_NAME"),
"color" => GetMessage("PROPERTY_COLOR_HEADER_NAME"),
"size" => GetMessage("PROPERTY_SIZE_HEADER_NAME"),
"weight" => GetMessage("PROPERTY_WEIGHT_HEADER_NAME"),
"length" => GetMessage("PROPERTY_LENGTH_HEADER_NAME"),
"width" => GetMessage("PROPERTY_WIDTH_HEADER_NAME"),
"height" => GetMessage("PROPERTY_HEIGHT_HEADER_NAME"),
"picture" => GetMessage("PROPERTY_PICTURE_HEADER_NAME")
);
}
/**
* @param $fileId
* @return string
*/
public function getImageUrl($fileId)
{
$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->protocol . $this->serverName . $pathImage;
} else {
return $pathImage;
}
}
}

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

@ -14,7 +14,6 @@ CModule::AddAutoloadClasses(
'RetailCrmUser' => file_exists($server . '/bitrix/php_interface/retailcrm/RetailCrmUser.php') ? '../../php_interface/retailcrm/RetailCrmUser.php' : 'classes/general/user/RetailCrmUser.php',
'RetailCrmOrder' => file_exists($server . '/bitrix/php_interface/retailcrm/RetailCrmOrder.php') ? '../../php_interface/retailcrm/RetailCrmOrder.php' : 'classes/general/order/RetailCrmOrder_' . $version . '.php',
'RetailCrmHistory' => file_exists($server . '/bitrix/php_interface/retailcrm/RetailCrmHistory.php') ? '../../php_interface/retailcrm/RetailCrmHistory.php' : 'classes/general/history/RetailCrmHistory_' . $version . '.php',
'RetailCrmICML' => file_exists($server . '/bitrix/php_interface/retailcrm/RetailCrmICML.php') ? '../../php_interface/retailcrm/RetailCrmICML.php' : 'classes/general/icml/RetailCrmICML.php',
'RetailCrmInventories' => file_exists($server . '/bitrix/php_interface/retailcrm/RetailCrmInventories.php') ? '../../php_interface/retailcrm/RetailCrmInventories.php' : 'classes/general/inventories/RetailCrmInventories.php',
'RetailCrmPrices' => file_exists($server . '/bitrix/php_interface/retailcrm/RetailCrmPrices.php') ? '../../php_interface/retailcrm/RetailCrmPrices.php' : 'classes/general/prices/RetailCrmPrices.php',
'RetailCrmCollector' => file_exists($server . '/bitrix/php_interface/retailcrm/RetailCrmCollector.php') ? '../../php_interface/retailcrm/RetailCrmCollector.php' : 'classes/general/collector/RetailCrmCollector.php',

View File

@ -6,73 +6,74 @@
* Class name: intaro_retailcrm
*/
global $MESS;
use Bitrix\Main\Context;
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;
use Intaro\RetailCrm\Vendor\Symfony\Component\Process\PhpExecutableFinder;
use RetailCrm\Exception\CurlException;
use RetailCrm\Response\ApiResponse;
IncludeModuleLangFile(__FILE__);
if (class_exists('intaro_retailcrm'))
return;
class intaro_retailcrm extends CModule
{
var $MODULE_ID = 'intaro.retailcrm';
var $OLD_MODULE_ID = 'intaro.intarocrm';
var $MODULE_VERSION;
var $MODULE_VERSION_DATE;
var $MODULE_NAME;
var $MODULE_DESCRIPTION;
var $MODULE_GROUP_RIGHTS = 'N';
var $PARTNER_NAME;
var $PARTNER_URI;
var $RETAIL_CRM_API;
var $RETAIL_CRM_EXPORT = 'retailcrm';
var $CRM_API_HOST_OPTION = 'api_host';
var $CRM_API_KEY_OPTION = 'api_key';
var $CRM_SITES_LIST= 'sites_list';
var $CRM_ORDER_TYPES_ARR = 'order_types_arr';
var $CRM_DELIVERY_TYPES_ARR = 'deliv_types_arr';
var $CRM_DELIVERY_SERVICES_ARR = 'deliv_services_arr';
var $CRM_PAYMENT_TYPES = 'pay_types_arr';
var $CRM_PAYMENT_STATUSES = 'pay_statuses_arr';
var $CRM_PAYMENT = 'payment_arr'; //order payment Y/N
var $CRM_ORDER_LAST_ID = 'order_last_id';
var $CRM_ORDER_PROPS = 'order_props';
var $CRM_LEGAL_DETAILS = 'legal_details';
var $CRM_CUSTOM_FIELDS = 'custom_fields';
var $CRM_CONTRAGENT_TYPE = 'contragent_type';
var $CRM_ORDER_DISCHARGE = 'order_discharge';
var $CRM_ORDER_FAILED_IDS = 'order_failed_ids';
var $CRM_ORDER_HISTORY = 'order_history';
var $CRM_CUSTOMER_HISTORY = 'customer_history';
var $CRM_CATALOG_BASE_PRICE = 'catalog_base_price';
public $MODULE_ID = 'intaro.retailcrm';
public $OLD_MODULE_ID = 'intaro.intarocrm';
public $MODULE_VERSION;
public $MODULE_VERSION_DATE;
public $MODULE_NAME;
public $MODULE_DESCRIPTION;
public $MODULE_GROUP_RIGHTS = 'N';
public $PARTNER_NAME;
public $PARTNER_URI;
public $RETAIL_CRM_API;
public $RETAIL_CRM_EXPORT = 'retailcrm';
public $CRM_API_HOST_OPTION = 'api_host';
public $CRM_API_KEY_OPTION = 'api_key';
public $CRM_SITES_LIST = 'sites_list';
public $CRM_ORDER_TYPES_ARR = 'order_types_arr';
public $CRM_DELIVERY_TYPES_ARR = 'deliv_types_arr';
public $CRM_DELIVERY_SERVICES_ARR = 'deliv_services_arr';
public $CRM_PAYMENT_TYPES = 'pay_types_arr';
public $CRM_PAYMENT_STATUSES = 'pay_statuses_arr';
public $CRM_PAYMENT = 'payment_arr'; //order payment Y/N
public $CRM_ORDER_LAST_ID = 'order_last_id';
public $CRM_ORDER_PROPS = 'order_props';
public $CRM_LEGAL_DETAILS = 'legal_details';
public $CRM_CUSTOM_FIELDS = 'custom_fields';
public $CRM_CONTRAGENT_TYPE = 'contragent_type';
public $CRM_ORDER_DISCHARGE = 'order_discharge';
public $CRM_ORDER_FAILED_IDS = 'order_failed_ids';
public $CRM_ORDER_HISTORY = 'order_history';
public $CRM_CUSTOMER_HISTORY = 'customer_history';
public $CRM_CATALOG_BASE_PRICE = 'catalog_base_price';
//var $CRM_CATALOG_IBLOCKS = 'catalog_base_iblocks';
var $CRM_ORDER_NUMBERS = 'order_numbers';
var $CRM_CANSEL_ORDER = 'cansel_order';
var $CRM_CURRENCY = 'currency';
var $CRM_ADDRESS_OPTIONS = 'address_options';
var $CRM_INVENTORIES_UPLOAD = 'inventories_upload';
var $CRM_STORES = 'stores';
var $CRM_SHOPS = 'shops';
var $CRM_IBLOCKS_INVENTORIES = 'iblocks_inventories';
var $CRM_PRICES_UPLOAD = 'prices_upload';
var $CRM_PRICES = 'prices';
var $CRM_PRICE_SHOPS = 'price_shops';
var $CRM_IBLOCKS_PRICES = 'iblock_prices';
var $CRM_COLLECTOR = 'collector';
var $CRM_COLL_KEY = 'coll_key';
var $CRM_UA = 'ua';
var $CRM_UA_KEYS = 'ua_keys';
var $CRM_API_VERSION = 'api_version';
var $HISTORY_TIME = 'history_time';
var $CLIENT_ID = 'client_id';
var $PROTOCOL = 'protocol';
var $INSTALL_PATH;
public $CRM_ORDER_NUMBERS = 'order_numbers';
public $CRM_CANSEL_ORDER = 'cansel_order';
public $CRM_CURRENCY = 'currency';
public $CRM_ADDRESS_OPTIONS = 'address_options';
public $CRM_INVENTORIES_UPLOAD = 'inventories_upload';
public $CRM_STORES = 'stores';
public $CRM_SHOPS = 'shops';
public $CRM_IBLOCKS_INVENTORIES = 'iblocks_inventories';
public $CRM_PRICES_UPLOAD = 'prices_upload';
public $CRM_PRICES = 'prices';
public $CRM_PRICE_SHOPS = 'price_shops';
public $CRM_IBLOCKS_PRICES = 'iblock_prices';
public $CRM_COLLECTOR = 'collector';
public $CRM_COLL_KEY = 'coll_key';
public $CRM_UA = 'ua';
public $CRM_UA_KEYS = 'ua_keys';
public $CRM_API_VERSION = 'api_version';
public $HISTORY_TIME = 'history_time';
public $CLIENT_ID = 'client_id';
public $PROTOCOL = 'protocol';
public $INSTALL_PATH;
function intaro_retailcrm()
{
@ -94,6 +95,7 @@ class intaro_retailcrm extends CModule
if (!class_exists('RetailcrmConstants')) {
require_once dirname(__FILE__) . '/../classes/general/RetailcrmConstants.php';
}
if (!class_exists('RetailcrmConfigProvider')) {
require_once dirname(__FILE__) . '/../classes/general/RetailcrmConfigProvider.php';
}
@ -136,7 +138,33 @@ class intaro_retailcrm extends CModule
include($this->INSTALL_PATH . '/../classes/general/RCrmActions.php');
include($this->INSTALL_PATH . '/../classes/general/user/RetailCrmUser.php');
include($this->INSTALL_PATH . '/../classes/general/events/RetailCrmEvent.php');
include($this->INSTALL_PATH . '/../classes/general/icml/RetailCrmICML.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/offerparam.php');
include($this->INSTALL_PATH . '/../lib/component/agent.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/selectparams.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/unit.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/xmlcategory.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/xmldata.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/xmloffer.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/xmlsetup.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/xmlsetupprops.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/xml/xmlsetuppropscategories.php');
include($this->INSTALL_PATH . '/../lib/icml/icmldirector.php');
include($this->INSTALL_PATH . '/../lib/icml/icmlwriter.php');
include($this->INSTALL_PATH . '/../lib/icml/queryparamsmolder.php');
include($this->INSTALL_PATH . '/../lib/icml/xmlcategorydirector.php');
include($this->INSTALL_PATH . '/../lib/icml/xmlcategoryfactory.php');
include($this->INSTALL_PATH . '/../lib/icml/xmlofferdirector.php');
include($this->INSTALL_PATH . '/../lib/icml/xmlofferbuilder.php');
include($this->INSTALL_PATH . '/../lib/icml/utils/icmlutils.php');
include($this->INSTALL_PATH . '/../lib/repository/catalogrepository.php');
include($this->INSTALL_PATH . '/../lib/repository/filerepository.php');
include($this->INSTALL_PATH . '/../lib/repository/hlrepository.php');
include($this->INSTALL_PATH . '/../lib/repository/measurerepository.php');
include($this->INSTALL_PATH . '/../lib/repository/siterepository.php');
include($this->INSTALL_PATH . '/../lib/service/hl.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/orm/catalogiblockinfo.php');
include($this->INSTALL_PATH . '/../lib/model/bitrix/orm/iblockcatalog.php');
include($this->INSTALL_PATH . '/../classes/general/RetailcrmConstants.php');
include($this->INSTALL_PATH . '/../classes/general/Exception/InvalidJsonException.php');
include($this->INSTALL_PATH . '/../classes/general/Exception/CurlException.php');
include($this->INSTALL_PATH . '/../classes/general/RestNormalizer.php');
@ -367,7 +395,7 @@ class intaro_retailcrm extends CModule
$arResult['paymentStatusesList'] = $this->RETAIL_CRM_API->paymentStatusesList()->paymentStatuses;
$arResult['paymentList'] = $this->RETAIL_CRM_API->statusesList()->statuses;
$arResult['paymentGroupList'] = $this->RETAIL_CRM_API->statusGroupsList()->statusGroups;
} catch (\RetailCrm\Exception\CurlException $e) {
} catch (CurlException $e) {
RCrmActions::eventLog(
'intaro.retailcrm/install/index.php', 'RetailCrm\ApiClient::*List::CurlException',
$e->getCode() . ': ' . $e->getMessage()
@ -477,7 +505,7 @@ class intaro_retailcrm extends CModule
'description' => RCrmActions::toJSON($deliveryType['DESCRIPTION']),
'paymentTypes' => ''
)));
} catch (\RetailCrm\Exception\CurlException $e) {
} catch (CurlException $e) {
$load = false;
RCrmActions::eventLog(
'intaro.crm/install/index.php', 'RetailCrm\ApiClient::deliveryTypeEdit::CurlException',
@ -496,7 +524,7 @@ class intaro_retailcrm extends CModule
'name' => RCrmActions::toJSON($deliveryService['NAME']),
'deliveryType' => $deliveryType['ID']
)));
} catch (\RetailCrm\Exception\CurlException $e) {
} catch (CurlException $e) {
RCrmActions::eventLog(
'intaro.crm/install/index.php', 'RetailCrm\ApiClient::deliveryServiceEdit::CurlException',
$e->getCode() . ': ' . $e->getMessage()
@ -722,7 +750,7 @@ class intaro_retailcrm extends CModule
if ($historyDate = COption::GetOptionString($this->OLD_MODULE_ID, 'order_history_date', 0)) {
try {
$history = $api->ordersHistory(array('startDate' => $historyDate));
} catch (\RetailCrm\Exception\CurlException $e) {
} catch (CurlException $e) {
RCrmActions::eventLog(
'intaro.retailcrm/install/index.php', 'RetailCrm\RestApi::ordersHistory::CurlException',
$e->getCode() . ': ' . $e->getMessage()
@ -903,9 +931,9 @@ class intaro_retailcrm extends CModule
}
if (!isset($_POST['MAX_OFFERS_VALUE'])) {
$maxOffers = "";
$maxOffers = null;
} else {
$maxOffers = $_POST['MAX_OFFERS_VALUE'];
$maxOffers = (int) $_POST['MAX_OFFERS_VALUE'];
}
if (!isset($_POST['SETUP_PROFILE_NAME'])) {
@ -970,146 +998,171 @@ class intaro_retailcrm extends CModule
);
$this->CopyFiles();
if (isset($_POST['LOAD_NOW'])) {
$loader = new RetailCrmICML();
$loader->iblocks = $iblocks;
$loader->propertiesUnitProduct = $propertiesUnitProduct;
$loader->propertiesProduct = $propertiesProduct;
$loader->propertiesUnitSKU = $propertiesUnitSKU;
$loader->propertiesSKU = $propertiesSKU;
if ($hlblockModule === true) {
$loader->highloadblockSkuProperties = $propertiesHbSKU;
$loader->highloadblockProductProperties = $propertiesHbProduct;
}
if ($maxOffers) {
$loader->offerPageSize = $maxOffers;
}
$loader->filename = $filename;
$loader->serverName = \Bitrix\Main\Context::getCurrent()->getServer()->getHttpHost();
$loader->application = $APPLICATION;
$loader->Load();
}
COption::RemoveOption($this->MODULE_ID, $this->CRM_CATALOG_BASE_PRICE);
if ($typeLoading == 'agent' || $typeLoading == 'cron') {
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/bitrix/php_interface/include/catalog_export/' . $this->RETAIL_CRM_EXPORT . '_run.php')) {
$dbProfile = CCatalogExport::GetList(array(), array("FILE_NAME" => $this->RETAIL_CRM_EXPORT));
while ($arProfile = $dbProfile->Fetch()) {
if ($arProfile["DEFAULT_PROFILE"] != "Y") {
CAgent::RemoveAgent("CCatalogExport::PreGenerateExport(" . $arProfile['ID'] . ");", "catalog");
CCatalogExport::Delete($arProfile['ID']);
}
}
}
$ar = $this->GetProfileSetupVars(
$iblocks,
$propertiesProduct,
$propertiesUnitProduct,
$propertiesSKU,
$propertiesUnitSKU,
$propertiesHbSKU,
$propertiesHbProduct,
$filename,
$maxOffers
);
$PROFILE_ID = CCatalogExport::Add(array(
"LAST_USE" => false,
"FILE_NAME" => $this->RETAIL_CRM_EXPORT,
"NAME" => $profileName,
"DEFAULT_PROFILE" => "N",
"IN_MENU" => "N",
"IN_AGENT" => "N",
"IN_CRON" => "N",
"NEED_EDIT" => "N",
"SETUP_VARS" => $ar
));
if (intval($PROFILE_ID) <= 0) {
$arResult['errCode'] = 'ERR_IBLOCK';
return;
}
COption::SetOptionString(
$this->MODULE_ID,
$this->CRM_CATALOG_BASE_PRICE . '_' . $PROFILE_ID,
htmlspecialchars(trim($_POST['price-types']))
);
if ($typeLoading == 'agent') {
$dateAgent = new DateTime();
$intAgent = new DateInterval('PT60S'); // PT60S - 60 sec;
$dateAgent->add($intAgent);
CAgent::AddAgent(
"CCatalogExport::PreGenerateExport(" . $PROFILE_ID . ");", "catalog", "N", 86400, $dateAgent->format('d.m.Y H:i:s'), // date of first check
"Y", // agent is active
$dateAgent->format('d.m.Y H:i:s'), // date of first start
30
);
CCatalogExport::Update($PROFILE_ID, array(
"IN_AGENT" => "Y"
));
} else {
$agent_period = 24;
$agent_php_path = "/usr/local/php/bin/php";
if (!file_exists($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "cron_frame.php")) {
CheckDirPath($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS);
$tmp_file_size = filesize($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS_DEF . "cron_frame.php");
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS_DEF . "cron_frame.php", "rb");
$tmp_data = fread($fp, $tmp_file_size);
fclose($fp);
$tmp_data = str_replace("#DOCUMENT_ROOT#", $_SERVER["DOCUMENT_ROOT"], $tmp_data);
$tmp_data = str_replace("#PHP_PATH#", $agent_php_path, $tmp_data);
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "cron_frame.php", "ab");
fwrite($fp, $tmp_data);
fclose($fp);
}
$cfg_data = "";
if (file_exists($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg")) {
$cfg_file_size = filesize($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg");
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg", "rb");
$cfg_data = fread($fp, $cfg_file_size);
fclose($fp);
}
CheckDirPath($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "logs/");
if ($arProfile["IN_CRON"] == "Y") {
// remove
$cfg_data = preg_replace("#^.*?" . preg_quote(CATALOG_PATH2EXPORTS) . "cron_frame.php +" . $PROFILE_ID . " *>.*?$#im", "", $cfg_data);
} else {
$strTime = "0 */" . $agent_period . " * * * ";
if (strlen($cfg_data) > 0)
$cfg_data .= "\n";
$cfg_data .= $strTime . $agent_php_path . " -f " . $_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "cron_frame.php " . $PROFILE_ID . " >" . $_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "logs/" . $PROFILE_ID . ".txt\n";
}
CCatalogExport::Update($PROFILE_ID, array(
"IN_CRON" => "Y"
));
CheckDirPath($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/");
$cfg_data = preg_replace("#[\r\n]{2,}#im", "\n", $cfg_data);
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg", "wb");
fwrite($fp, $cfg_data);
fclose($fp);
$arRetval = array();
@exec("crontab " . $_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg", $arRetval, $return_var);
if (
file_exists($_SERVER['DOCUMENT_ROOT']
. '/bitrix/php_interface/include/catalog_export/'
. $this->RETAIL_CRM_EXPORT
. '_run.php')
) {
$dbProfile = CCatalogExport::GetList([], ['FILE_NAME' => $this->RETAIL_CRM_EXPORT]);
if ($dbProfile instanceof CDBResult) {
$this->removeExportProfiles($dbProfile);
}
}
$setupVars = $this->getProfileSetupVars(
$iblocks,
$propertiesProduct,
$propertiesUnitProduct,
$propertiesSKU,
$propertiesUnitSKU,
$propertiesHbSKU,
$propertiesHbProduct,
$filename,
$maxOffers
);
$profileId = CCatalogExport::Add([
"LAST_USE" => false,
"FILE_NAME" => $this->RETAIL_CRM_EXPORT,
"NAME" => $profileName,
"DEFAULT_PROFILE" => "N",
"IN_MENU" => "N",
"IN_AGENT" => "N",
"IN_CRON" => "N",
"NEED_EDIT" => "N",
"SETUP_VARS" => $setupVars,
]);
if ((int) $profileId <= 0) {
$arResult['errCode'] = 'ERR_IBLOCK';
return;
}
COption::SetOptionString(
$this->MODULE_ID,
$this->CRM_CATALOG_BASE_PRICE . '_' . $profileId,
htmlspecialchars(trim($_POST['price-types']))
);
$agentId = null;
if ($typeLoading === 'agent') {
$dateAgent = new DateTime();
$intAgent = new DateInterval('PT60S'); // PT60S - 60 sec;
$dateAgent->add($intAgent);
$agentId = CAgent::AddAgent(
'CCatalogExport::PreGenerateExport(' . $profileId . ');',
'catalog',
'N',
86400,
$dateAgent->format('d.m.Y H:i:s'),
'Y',
$dateAgent->format('d.m.Y H:i:s'),
30
);
CCatalogExport::Update($profileId, [
"IN_AGENT" => "Y",
]);
}
if (
isset($_POST['LOAD_NOW'])
&& $agentId === null
) {
CAgent::AddAgent(
'\Intaro\RetailCrm\Component\Agent::preGenerateExport(' . $profileId . ');',
$this->MODULE_ID,
'N',
86400,
$dateAgent->format('d.m.Y H:i:s'),
'Y',
$dateAgent->format('d.m.Y H:i:s')
);
}
if ('cron' === $typeLoading) {
include($this->INSTALL_PATH . '/../lib/vendor/symfony/component/process/phpexecutablefinder.php');
include($this->INSTALL_PATH . '/../lib/vendor/symfony/component/process/executablefinder.php');
$agent_period = 24;
$finder = new PhpExecutableFinder();
$agent_php_path = $finder->find();
if (!file_exists($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "cron_frame.php")) {
CheckDirPath($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS);
$tmp_file_size = filesize($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS_DEF . "cron_frame.php");
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS_DEF . "cron_frame.php", "rb");
$tmp_data = fread($fp, $tmp_file_size);
fclose($fp);
$tmp_data = str_replace("#DOCUMENT_ROOT#", $_SERVER["DOCUMENT_ROOT"], $tmp_data);
$tmp_data = str_replace("#PHP_PATH#", $agent_php_path, $tmp_data);
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "cron_frame.php", "ab");
fwrite($fp, $tmp_data);
fclose($fp);
}
$cfg_data = "";
if (file_exists($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg")) {
$cfg_file_size = filesize($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg");
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg", "rb");
$cfg_data = fread($fp, $cfg_file_size);
fclose($fp);
}
CheckDirPath($_SERVER["DOCUMENT_ROOT"] . CATALOG_PATH2EXPORTS . "logs/");
if ($arProfile["IN_CRON"] == "Y") {
// remove
$cfg_data = preg_replace("#^.*?"
. preg_quote(CATALOG_PATH2EXPORTS)
. "cron_frame.php +"
. $profileId
. " *>.*?$#im", "", $cfg_data);
} else {
$strTime = "0 */" . $agent_period . " * * * ";
if (strlen($cfg_data) > 0) {
$cfg_data .= "\n";
}
$cfg_data .= $strTime
. $agent_php_path
. " -f "
. $_SERVER["DOCUMENT_ROOT"]
. CATALOG_PATH2EXPORTS
. "cron_frame.php "
. $profileId
. " >"
. $_SERVER["DOCUMENT_ROOT"]
. CATALOG_PATH2EXPORTS
. "logs/"
. $profileId
. ".txt\n";
}
CCatalogExport::Update($profileId, [
"IN_CRON" => "Y",
]);
CheckDirPath($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/");
$cfg_data = preg_replace("#[\r\n]{2,}#im", "\n", $cfg_data);
$fp = fopen($_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg", "wb");
fwrite($fp, $cfg_data);
fclose($fp);
$arRetval = [];
@exec("crontab " . $_SERVER["DOCUMENT_ROOT"] . "/bitrix/crontab/crontab.cfg", $arRetval, $return_var);
}
$api_host = COption::GetOptionString($this->MODULE_ID, $this->CRM_API_HOST_OPTION, 0);
$api_key = COption::GetOptionString($this->MODULE_ID, $this->CRM_API_KEY_OPTION, 0);
$api_version = COption::GetOptionString($this->MODULE_ID, $this->CRM_API_VERSION, 0);
@ -1217,17 +1270,19 @@ class intaro_retailcrm extends CModule
UnRegisterModuleDependences("main", "OnBeforeProlog", $this->MODULE_ID, "RetailCrmUa", "add");
UnRegisterModuleDependences("sale", "OnSalePaymentEntitySaved", $this->MODULE_ID, "RetailCrmEvent", "paymentSave");
UnRegisterModuleDependences("sale", "OnSalePaymentEntityDeleted", $this->MODULE_ID, "RetailCrmEvent", "paymentDelete");
if (
CModule::IncludeModule('catalog')
&& file_exists($_SERVER['DOCUMENT_ROOT']
. '/bitrix/php_interface/include/catalog_export/'
. $this->RETAIL_CRM_EXPORT
. '_run.php')
) {
$dbProfile = CCatalogExport::GetList([], ['FILE_NAME' => $this->RETAIL_CRM_EXPORT]);
if (CModule::IncludeModule("catalog")) {
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/bitrix/php_interface/include/catalog_export/' . $this->RETAIL_CRM_EXPORT . '_run.php')) {
$dbProfile = CCatalogExport::GetList(array(), array("FILE_NAME" => $this->RETAIL_CRM_EXPORT));
while ($arProfile = $dbProfile->Fetch()) {
if ($arProfile["DEFAULT_PROFILE"] != "Y") {
CAgent::RemoveAgent("CCatalogExport::PreGenerateExport(" . $arProfile['ID'] . ");", "catalog");
CCatalogExport::Delete($arProfile['ID']);
}
}
if ($dbProfile instanceof CDBResult) {
$this->removeExportProfiles($dbProfile);
}
}
@ -1245,7 +1300,14 @@ class intaro_retailcrm extends CModule
function CopyFiles()
{
CopyDirFiles(
$_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/' . $this->MODULE_ID . '/install/export/', $_SERVER['DOCUMENT_ROOT'] . '/bitrix/php_interface/include/catalog_export/', true, true
$_SERVER['DOCUMENT_ROOT']
. '/bitrix/modules/'
. $this->MODULE_ID
. '/install/export/',
$_SERVER['DOCUMENT_ROOT']
. '/bitrix/php_interface/include/catalog_export/',
true,
true
);
}
@ -1264,7 +1326,7 @@ class intaro_retailcrm extends CModule
rmdir($defaultSite['ABS_DOC_ROOT'] . '/retailcrm/');
}
function GetProfileSetupVars(
function getProfileSetupVars(
$iblocks,
$propertiesProduct,
$propertiesUnitProduct,
@ -1316,7 +1378,7 @@ class intaro_retailcrm extends CModule
try {
$history = $api->$method(array(), $page);
} catch (\RetailCrm\Exception\CurlException $e) {
} catch (CurlException $e) {
RCrmActions::eventLog(
'RetailCrmHistory::' . $method, 'RetailCrm\RestApi::' . $method . '::CurlException',
$e->getCode() . ': ' . $e->getMessage()
@ -1336,7 +1398,7 @@ class intaro_retailcrm extends CModule
while (true) {
try {
$history = $api->$method(array(), $page);
} catch (\RetailCrm\Exception\CurlException $e) {
} catch (CurlException $e) {
RCrmActions::eventLog(
'RetailCrmHistory::' . $method, 'RetailCrm\RestApi::' . $method . '::CurlException',
$e->getCode() . ': ' . $e->getMessage()
@ -1380,7 +1442,7 @@ class intaro_retailcrm extends CModule
$client = new RetailCrm\Http\Client($api_host . '/api/' . $version, array('apiKey' => $api_key));
try {
$result = $client->makeRequest('/reference/sites', 'GET');
} catch (\RetailCrm\Exception\CurlException $e) {
} catch (CurlException $e) {
RCrmActions::eventLog(
'intaro.retailcrm/install/index.php', 'RetailCrm\ApiClient::sitesList',
$e->getCode() . ': ' . $e->getMessage()
@ -1389,7 +1451,7 @@ class intaro_retailcrm extends CModule
$res['errCode'] = 'ERR_' . $e->getCode();
}
if ($result->getStatusCode() == 200) {
if ($result instanceof ApiResponse && $result->getStatusCode() == 200) {
COption::SetOptionString($this->MODULE_ID, $this->CRM_API_VERSION, $version);
$res['sitesList'] = $APPLICATION->ConvertCharsetArray($result->sites, 'utf-8', SITE_CHARSET);
@ -1401,4 +1463,19 @@ class intaro_retailcrm extends CModule
return $res;
}
/**
* Удаляет профили экспорта icml каталага и агент, запускавший этот экспорт
*
* @param \CDBResult $dbProfile
*/
private function removeExportProfiles(CDBResult $dbProfile): void
{
while ($arProfile = $dbProfile->Fetch()) {
if ($arProfile['DEFAULT_PROFILE'] !== 'Y') {
CAgent::RemoveAgent('CCatalogExport::PreGenerateExport(' . $arProfile['ID'] . ');', 'catalog');
CCatalogExport::Delete($arProfile['ID']);
}
}
}
}

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

@ -1,5 +1,6 @@
<?
$arModuleVersion = array(
"VERSION" => "5.6.2",
"VERSION_DATE" => "2021-01-14 16:00:00"
);
<?php
$arModuleVersion = [
'VERSION' => '5.7.0',
'VERSION_DATE' => '2021-05-31 13:00:00',
];

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

@ -14,6 +14,8 @@ $MESS ['RETAILCRM_CURL_ERR'] = 'Для работы модуля интегра
$MESS ['ERR_ARTICLE_IBLOCK'] = 'Не установлены артикулы';
$MESS ['DATE_TIMEZONE_ERR'] = 'Не указана временная зона в настройках php.';
$MESS ['SALE_VERSION_ERR'] = 'Версия модуля \'Интернет-магазин\' должна быть выше 16.';
$MESS['IBLOCK_NOT_SELECTED'] = 'Не выбрано ни одного инфоблока для экспорта';
/*
$MESS ['ORDER_PROPS'] = 'Настройки соответствия полей заказа RetailCRM свойствам заказа 1С-Битрикс';
$MESS ['FIO'] = 'Ф.И.О.';

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);
}
}
}