diff --git a/CHANGELOG.md b/CHANGELOG.md index 022e3c31..e1155b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2021-05-31 v.5.7.0 +* Переработан генератор ICML каталога: + - генератор использует потоковую запись в файл через `XMLWriter`; + - по умолчанию опция выгрузки каталога в момент установки модуля отключена; + - код генератора каталога теперь использует автолоадер Битрикса. +* Скидка на позицию больше не учитывается в заказе при установке произвольной цены для позиции. + ## 2021-01-14 v.5.6.2 * Исправлено формирование картинок в ICML при включеном CDN * Убрана некорректная запись внешнего идентификатора платежа для новых платежей по истории diff --git a/composer.json b/composer.json index 46354990..52b2760c 100755 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ ], "require": { "ext-json": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-xmlwriter": "*" }, "require-dev": { "phpunit/phpunit": "^7", diff --git a/composer.lock b/composer.lock index 30588530..66b54f40 100644 --- a/composer.lock +++ b/composer.lock @@ -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" } diff --git a/intaro.retailcrm/classes/general/AddressBuilder.php b/intaro.retailcrm/classes/general/AddressBuilder.php index 8db7ddf0..9416bf2e 100644 --- a/intaro.retailcrm/classes/general/AddressBuilder.php +++ b/intaro.retailcrm/classes/general/AddressBuilder.php @@ -1,4 +1,5 @@ 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("&", "&", $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, "PrepareValue(Date("Y-m-d H:i:s")) . "\">\n - \n - " . $this->PrepareValue(COption::GetOptionString("main", "site_name", ""))."\n - " . $this->PrepareValue(COption::GetOptionString("main", "site_name", ""))."\n" - ); - } - - protected function WriteCategories($categories) - { - $stringCategories = ""; - @fwrite($this->fp, "\n"); - foreach ($categories as $category) { - $stringCategories .= $this->BuildCategory($category); - } - @fwrite($this->fp, $stringCategories); - @fwrite($this->fp, "\n"); - } - protected function PreWriteOffers() - { - @fwrite($this->fp, "\n"); - } - - protected function PostWriteOffers() - { - @fwrite($this->fp, "\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, "\n - \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 = - "PrepareValue($arCategory["ID"]) . "\"" - . (intval($arCategory["IBLOCK_SECTION_ID"]) > 0 ? - " parentId=\"" . $this->PrepareValue($arCategory["IBLOCK_SECTION_ID"]) . "\"" - :"") - . ">\n\t" - . "" . $this->PrepareValue($arCategory["NAME"]) . "\n"; - - if (CFile::GetPath($arCategory["DETAIL_PICTURE"])) { - $category .= "\t" . $this->getImageUrl($arCategory["DETAIL_PICTURE"]) . "\n"; - } - - if (CFile::GetPath($arCategory["PICTURE"])) { - $category .= "\t" . $this->getImageUrl($arCategory["PICTURE"]) . "\n"; - } - - $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 .= "PrepareValue($arOffer["ID"]) . "\" ". - "productId=\"" . $this->PrepareValue($arOffer["PRODUCT_ID"]) . "\" ". - "quantity=\"" . $this->PrepareValue(DoubleVal($arOffer['QUANTITY'])) . "\">\n"; - - if ($arOffer['PRODUCT_ACTIVE'] == "N") { - $offer .= "" . $this->PrepareValue($arOffer['PRODUCT_ACTIVE']) . "\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 .= "" . $this->PrepareValue($arOffer["PICTURE"]) . "\n"; - } - - $offer .= "" . $this->protocol . $this->serverName . $this->PrepareValue($arOffer['DETAIL_PAGE_URL']) . "\n"; - - $offer .= "" . $this->PrepareValue($arOffer['PRICE']) . "\n"; - - if ($this->loadPurchasePrice) { - if ($arOffer['PURCHASE_PRICE']) { - $offer .= "" . $this->PrepareValue($arOffer['PURCHASE_PRICE']) . "\n"; - } elseif ("Y" == $this->purchasePriceNull) { - $offer .= "0\n"; - } - } - - foreach ($categories as $category) { - $offer .= "" . $category['ID'] . "\n"; - } - - $offer .= "" . $this->PrepareValue($arOffer["NAME"]) . "\n"; - - $offer .= "" . $this->PrepareValue($arOffer["EXTERNAL_ID"]) . "\n"; - $offer .= "" . $this->PrepareValue($arOffer["PRODUCT_NAME"]) . "\n"; - - foreach ($this->propertiesProduct[$iblock['ID']] as $key => $propProduct) { - if ($propProduct != "" && $arOffer['_PROP_' . $key] != null) { - if ($key === "manufacturer") { - $offer .= "" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "\n"; - } else { - $name = $key; - - if (isset($this->localizedIBlockProps[$key])) { - $name = $this->localizedIBlockProps[$key]; - } - - $offer .= '" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "\n"; - } - } - } - foreach ($this->propertiesSKU[$iblock['ID']] as $key => $propProduct) { - if ($propProduct != "" && $arOffer['_PROP_' . $key] != null) { - if ($key === "manufacturer") { - $offer .= "" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "\n"; - } else { - $name = $key; - - if (isset($this->localizedIBlockProps[$key])) { - $name = $this->localizedIBlockProps[$key]; - } - - $offer .= '" . $this->PrepareValue($arOffer['_PROP_' . $key]) . "\n"; - } - } - } - if (isset($arOffer["MEASURE"]['SYMBOL_INTL'])) { - if ($this->measure[$arOffer["MEASURE"]['SYMBOL_INTL']]) { - $offer .= '' . "\n"; - } else { - $offer .= '' . "\n"; - } - } else { - $measure = \Bitrix\Catalog\ProductTable::getCurrentRatioWithMeasure($arOffer["ID"]); - - if ($this->measure[$measure[$arOffer["ID"]]["MEASURE"]['SYMBOL_INTL']]) { - $offer .= '' . "\n"; - } else { - $offer .= '' . "\n"; - } - } - - if ($arOffer["BARCODE"]) { - $offer.= "" . $this->PrepareValue($arOffer["BARCODE"]) . "\n"; - } - - if ((float)$arOffer["CATALOG_VAT"]) { - $vatRate = $arOffer["CATALOG_VAT"]; - } else { - $vatRate = 'none'; - } - - $offer.= "" . $this->PrepareValue($vatRate) . "\n"; - $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; - } - } -} diff --git a/intaro.retailcrm/description.ru b/intaro.retailcrm/description.ru index 83198c85..ef06d746 100644 --- a/intaro.retailcrm/description.ru +++ b/intaro.retailcrm/description.ru @@ -1,3 +1,2 @@ -- Исправлено формирование картинок в ICML при включеном CDN -- Убрана некорректная запись внешнего идентификатора платежа для новых платежей по истории -- Добавлена проверка на длину email при отправке в систему \ No newline at end of file +- Переработан генератор ICML каталога; +- Скидка на позицию больше не учитывается в заказе при установке произвольной цены для позиции. \ No newline at end of file diff --git a/intaro.retailcrm/export/export_run.php b/intaro.retailcrm/export/export_run.php index 283faa3b..844ec7d5 100644 --- a/intaro.retailcrm/export/export_run.php +++ b/intaro.retailcrm/export/export_run.php @@ -1,59 +1,65 @@ '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(); -} \ No newline at end of file + 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(); + } +} diff --git a/intaro.retailcrm/export/export_setup.php b/intaro.retailcrm/export/export_setup.php index 10c11f56..fc545a0a 100644 --- a/intaro.retailcrm/export/export_setup.php +++ b/intaro.retailcrm/export/export_setup.php @@ -1,13 +1,16 @@ 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"]; diff --git a/intaro.retailcrm/include.php b/intaro.retailcrm/include.php index cece9049..65786258 100644 --- a/intaro.retailcrm/include.php +++ b/intaro.retailcrm/include.php @@ -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', diff --git a/intaro.retailcrm/install/index.php b/intaro.retailcrm/install/index.php index a0433735..d9ca2f79 100644 --- a/intaro.retailcrm/install/index.php +++ b/intaro.retailcrm/install/index.php @@ -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']); + } + } + } } diff --git a/intaro.retailcrm/install/step5.php b/intaro.retailcrm/install/step5.php index 310f2663..51c99670 100644 --- a/intaro.retailcrm/install/step5.php +++ b/intaro.retailcrm/install/step5.php @@ -495,8 +495,9 @@ if (!empty($oldValues)) {

  - +
+
 
- " class="adm-btn-save"> + " + class="adm-btn-save">
- " class="adm-btn-save"> + " class="adm-btn-save">
- - diff --git a/intaro.retailcrm/install/version.php b/intaro.retailcrm/install/version.php index bb562d48..6a763cfc 100644 --- a/intaro.retailcrm/install/version.php +++ b/intaro.retailcrm/install/version.php @@ -1,5 +1,6 @@ - "5.6.2", - "VERSION_DATE" => "2021-01-14 16:00:00" -); + '5.7.0', + 'VERSION_DATE' => '2021-05-31 13:00:00', +]; diff --git a/intaro.retailcrm/lang/en/export/export_run.php b/intaro.retailcrm/lang/en/export/export_run.php new file mode 100644 index 00000000..a1ace4af --- /dev/null +++ b/intaro.retailcrm/lang/en/export/export_run.php @@ -0,0 +1,3 @@ +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; + } +} diff --git a/intaro.retailcrm/lib/icml/icmlwriter.php b/intaro.retailcrm/lib/icml/icmlwriter.php new file mode 100644 index 00000000..c6ff60db --- /dev/null +++ b/intaro.retailcrm/lib/icml/icmlwriter.php @@ -0,0 +1,227 @@ +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); + } + } +} diff --git a/intaro.retailcrm/lib/icml/queryparamsmolder.php b/intaro.retailcrm/lib/icml/queryparamsmolder.php new file mode 100644 index 00000000..81833955 --- /dev/null +++ b/intaro.retailcrm/lib/icml/queryparamsmolder.php @@ -0,0 +1,80 @@ + $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, + ]; + } +} diff --git a/intaro.retailcrm/lib/icml/xmlcategorydirector.php b/intaro.retailcrm/lib/icml/xmlcategorydirector.php new file mode 100644 index 00000000..6bc48831 --- /dev/null +++ b/intaro.retailcrm/lib/icml/xmlcategorydirector.php @@ -0,0 +1,140 @@ +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()); + } + } +} diff --git a/intaro.retailcrm/lib/icml/xmlcategoryfactory.php b/intaro.retailcrm/lib/icml/xmlcategoryfactory.php new file mode 100644 index 00000000..1d784a4a --- /dev/null +++ b/intaro.retailcrm/lib/icml/xmlcategoryfactory.php @@ -0,0 +1,43 @@ +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; + } +} diff --git a/intaro.retailcrm/lib/icml/xmlofferbuilder.php b/intaro.retailcrm/lib/icml/xmlofferbuilder.php new file mode 100644 index 00000000..0d3d2628 --- /dev/null +++ b/intaro.retailcrm/lib/icml/xmlofferbuilder.php @@ -0,0 +1,520 @@ +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; + } +} diff --git a/intaro.retailcrm/lib/icml/xmlofferdirector.php b/intaro.retailcrm/lib/icml/xmlofferdirector.php new file mode 100644 index 00000000..bd41ca14 --- /dev/null +++ b/intaro.retailcrm/lib/icml/xmlofferdirector.php @@ -0,0 +1,206 @@ +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 ?? '') + ); + } +} diff --git a/intaro.retailcrm/lib/lang/ru/icml/xmlofferbuilder.php b/intaro.retailcrm/lib/lang/ru/icml/xmlofferbuilder.php new file mode 100644 index 00000000..925632b4 --- /dev/null +++ b/intaro.retailcrm/lib/lang/ru/icml/xmlofferbuilder.php @@ -0,0 +1,13 @@ + + *
  • IBLOCK_ID int mandatory + *
  • YANDEX_EXPORT bool optional default 'N' + *
  • SUBSCRIPTION bool optional default 'N' + *
  • VAT_ID int optional + *
  • PRODUCT_IBLOCK_ID int mandatory + *
  • SKU_PROPERTY_ID int mandatory + *
  • VAT reference to {@link \Bitrix\Catalog\CatalogVatTable} + *
  • IBLOCK reference to {@link \Bitrix\Iblock\IblockTable} + *
  • PRODUCT_IBLOCK reference to {@link \Bitrix\Iblock\IblockTable} + *
  • SKU_PROPERTY reference to {@link \Bitrix\Iblock\IblockPropertyTable} + * + * + * @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; + } + } +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/offerparam.php b/intaro.retailcrm/lib/model/bitrix/xml/offerparam.php new file mode 100644 index 00000000..e1d59b4f --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/offerparam.php @@ -0,0 +1,34 @@ + + * @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; +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/selectparams.php b/intaro.retailcrm/lib/model/bitrix/xml/selectparams.php new file mode 100644 index 00000000..f807af46 --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/selectparams.php @@ -0,0 +1,54 @@ + + * @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; +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/unit.php b/intaro.retailcrm/lib/model/bitrix/xml/unit.php new file mode 100644 index 00000000..b2abebaf --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/unit.php @@ -0,0 +1,51 @@ + + * @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; + } +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/xmlcategory.php b/intaro.retailcrm/lib/model/bitrix/xml/xmlcategory.php new file mode 100644 index 00000000..dc97ebe0 --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/xmlcategory.php @@ -0,0 +1,39 @@ + + * @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; +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/xmldata.php b/intaro.retailcrm/lib/model/bitrix/xml/xmldata.php new file mode 100644 index 00000000..c402bb8d --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/xmldata.php @@ -0,0 +1,39 @@ + + * @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; +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/xmloffer.php b/intaro.retailcrm/lib/model/bitrix/xml/xmloffer.php new file mode 100644 index 00000000..9f619b0e --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/xmloffer.php @@ -0,0 +1,131 @@ + + * @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; + } +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/xmlsetup.php b/intaro.retailcrm/lib/model/bitrix/xml/xmlsetup.php new file mode 100644 index 00000000..7b7a53f9 --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/xmlsetup.php @@ -0,0 +1,74 @@ + + * @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; +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/xmlsetupprops.php b/intaro.retailcrm/lib/model/bitrix/xml/xmlsetupprops.php new file mode 100644 index 00000000..618d42f6 --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/xmlsetupprops.php @@ -0,0 +1,55 @@ + + * @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; +} diff --git a/intaro.retailcrm/lib/model/bitrix/xml/xmlsetuppropscategories.php b/intaro.retailcrm/lib/model/bitrix/xml/xmlsetuppropscategories.php new file mode 100644 index 00000000..e3a3c778 --- /dev/null +++ b/intaro.retailcrm/lib/model/bitrix/xml/xmlsetuppropscategories.php @@ -0,0 +1,65 @@ + + * @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; +} diff --git a/intaro.retailcrm/lib/repository/catalogrepository.php b/intaro.retailcrm/lib/repository/catalogrepository.php new file mode 100644 index 00000000..3c6f26a2 --- /dev/null +++ b/intaro.retailcrm/lib/repository/catalogrepository.php @@ -0,0 +1,179 @@ +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; + } +} diff --git a/intaro.retailcrm/lib/repository/filerepository.php b/intaro.retailcrm/lib/repository/filerepository.php new file mode 100644 index 00000000..8d087486 --- /dev/null +++ b/intaro.retailcrm/lib/repository/filerepository.php @@ -0,0 +1,67 @@ +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 ?? ''; + } +} diff --git a/intaro.retailcrm/lib/repository/hlrepository.php b/intaro.retailcrm/lib/repository/hlrepository.php new file mode 100644 index 00000000..801a3541 --- /dev/null +++ b/intaro.retailcrm/lib/repository/hlrepository.php @@ -0,0 +1,56 @@ +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; + } +} diff --git a/intaro.retailcrm/lib/repository/measurerepository.php b/intaro.retailcrm/lib/repository/measurerepository.php new file mode 100644 index 00000000..f268423b --- /dev/null +++ b/intaro.retailcrm/lib/repository/measurerepository.php @@ -0,0 +1,29 @@ +Fetch()) { + $measures[$measure['ID']] = $measure; + } + + return $measures; + } +} diff --git a/intaro.retailcrm/lib/repository/siterepository.php b/intaro.retailcrm/lib/repository/siterepository.php new file mode 100644 index 00000000..fd7f528d --- /dev/null +++ b/intaro.retailcrm/lib/repository/siterepository.php @@ -0,0 +1,29 @@ + 'Y']); + + while ($ar = $rsSites->Fetch()) { + if ($ar['DEF'] === 'Y') { + return RetailcrmConfigProvider::getProtocol() . $ar['SERVER_NAME']; + } + } + + return null; + } +} diff --git a/intaro.retailcrm/lib/service/hl.php b/intaro.retailcrm/lib/service/hl.php new file mode 100644 index 00000000..5c4eefac --- /dev/null +++ b/intaro.retailcrm/lib/service/hl.php @@ -0,0 +1,76 @@ +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; + } + } +} diff --git a/intaro.retailcrm/lib/vendor/symfony/component/process/LICENSE b/intaro.retailcrm/lib/vendor/symfony/component/process/LICENSE new file mode 100644 index 00000000..768dcaea --- /dev/null +++ b/intaro.retailcrm/lib/vendor/symfony/component/process/LICENSE @@ -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. \ No newline at end of file diff --git a/intaro.retailcrm/lib/vendor/symfony/component/process/executablefinder.php b/intaro.retailcrm/lib/vendor/symfony/component/process/executablefinder.php new file mode 100644 index 00000000..ff6e4950 --- /dev/null +++ b/intaro.retailcrm/lib/vendor/symfony/component/process/executablefinder.php @@ -0,0 +1,86 @@ + + * + * 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 + * @author Johannes M. Schmitt + */ +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; + } +} diff --git a/intaro.retailcrm/lib/vendor/symfony/component/process/phpexecutablefinder.php b/intaro.retailcrm/lib/vendor/symfony/component/process/phpexecutablefinder.php new file mode 100644 index 00000000..c77b2197 --- /dev/null +++ b/intaro.retailcrm/lib/vendor/symfony/component/process/phpexecutablefinder.php @@ -0,0 +1,99 @@ + + * + * 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 + * @author Johannes M. Schmitt + */ +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; + } +} diff --git a/tests/classes/general/icml/RetailCrmICMLTest.php b/tests/classes/general/icml/RetailCrmICMLTest.php deleted file mode 100644 index 06a5ff94..00000000 --- a/tests/classes/general/icml/RetailCrmICMLTest.php +++ /dev/null @@ -1,37 +0,0 @@ -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); - } - } -}