Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/catalog/lib/model/ |
Upload File : |
<?php namespace Bitrix\Catalog\Model; use Bitrix\Catalog\v2\Integration\Seo\Entity\ExportedProductTable; use Bitrix\Main; use Bitrix\Main\ORM; use Bitrix\Main\Loader; use Bitrix\Main\Localization\Loc; use Bitrix\Catalog; use Bitrix\Iblock; class Product extends Entity { /** @var string Need to recalculate parent product available */ protected const ACTION_CHANGE_PARENT_AVAILABLE = 'SKU_AVAILABLE'; /** @var string Need to check parent product type */ protected const ACTION_CHANGE_PARENT_TYPE = 'PARENT_TYPE'; /** @var string Need to recalculate sets with current product */ protected const ACTION_RECALCULATE_SETS = 'SETS'; /** @var string Need to send notifications about available products */ protected const ACTION_SEND_NOTIFICATIONS = 'SUBSCRIPTION'; /** @var null|bool Enable offers automation */ private static ?bool $separateSkuMode = null; /** @var null|bool Sale exists */ private static ?bool $saleIncluded = null; /** @var null|string Query for update element timestamp */ private static ?string $queryElementDate = null; /** * Returns product tablet name. * * @return string */ public static function getTabletClassName(): string { return '\Bitrix\Catalog\ProductTable'; } /** * Returns product default fields list for caching. * * @return array */ protected static function getDefaultCachedFieldList(): array { return [ 'ID', 'TYPE', 'AVAILABLE', 'QUANTITY', 'QUANTITY_TRACE' => 'QUANTITY_TRACE_ORIG', 'CAN_BUY_ZERO' => 'CAN_BUY_ZERO_ORIG', 'SUBSCRIBE' => 'SUBSCRIBE_ORIG' ]; } /** * Delete product item. Use instead of DataManager method. * * @param int $id * @return ORM\Data\DeleteResult */ public static function delete($id): ORM\Data\DeleteResult { return parent::deleteNoDemands($id); } /** * Check and modify fields before add product. Need for product automation. * * @param ORM\Data\AddResult $result * @param int|null $id * @param array &$data * @return void */ protected static function prepareForAdd(ORM\Data\AddResult $result, $id, array &$data): void { if (isset($data['fields']['ID'])) $id = $data['fields']['ID']; $id = (int)$id; if ($id <= 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PRODUCT_ID') )); return; } $iblockId = 0; if (isset($data['external_fields']['IBLOCK_ID'])) $iblockId = (int)$data['external_fields']['IBLOCK_ID']; if ($iblockId <= 0) $iblockId = \CIBlockElement::GetIBlockByID($id); if (empty($iblockId)) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_ELEMENT_NOT_EXISTS') )); return; } $iblockData = \CCatalogSku::GetInfoByIBlock($iblockId); if (empty($iblockData)) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_SIMPLE_IBLOCK') )); return; } $data['external_fields']['IBLOCK_ID'] = $iblockId; $allowedTypes = self::getProductTypes($iblockData['CATALOG_TYPE']); $fields = $data['fields']; parent::prepareForAdd($result, $id, $fields); if (!$result->isSuccess()) return; if (self::$separateSkuMode === null) { self::$separateSkuMode = Main\Config\Option::get('catalog', 'show_catalog_tab_with_offers') === 'Y'; } static $defaultValues = null, $blackList = null, $paymentPeriods = null, $tripleFields = null, $booleanFields = null, $nullFields = null, $sizeFields = null; if ($defaultValues === null) { $defaultValues = [ 'QUANTITY' => 0, 'QUANTITY_RESERVED' => 0, 'QUANTITY_TRACE' => Catalog\ProductTable::STATUS_DEFAULT, 'CAN_BUY_ZERO' => Catalog\ProductTable::STATUS_DEFAULT, 'WEIGHT' => 0, 'PRICE_TYPE' => Catalog\ProductTable::PAYMENT_TYPE_SINGLE, 'RECUR_SCHEME_LENGTH' => null, 'RECUR_SCHEME_TYPE' => Catalog\ProductTable::PAYMENT_PERIOD_DAY, 'TRIAL_PRICE_ID' => null, 'WITHOUT_ORDER' => Catalog\ProductTable::STATUS_NO, 'SELECT_BEST_PRICE' => Catalog\ProductTable::STATUS_NO, 'VAT_ID' => null, 'VAT_INCLUDED' => Catalog\ProductTable::STATUS_NO, 'BARCODE_MULTI' => Catalog\ProductTable::STATUS_NO, 'SUBSCRIBE' => Catalog\ProductTable::STATUS_DEFAULT, 'BUNDLE' => Catalog\ProductTable::STATUS_NO, 'PURCHASING_PRICE' => null, 'PURCHASING_CURRENCY' => null, 'TMP_ID' => null, 'MEASURE' => null, 'WIDTH' => null, 'LENGTH' => null, 'HEIGHT' => null, ]; $blackList = [ 'NEGATIVE_AMOUNT_TRACE' => true ]; $paymentPeriods = Catalog\ProductTable::getPaymentPeriods(false); $tripleFields = ['QUANTITY_TRACE', 'CAN_BUY_ZERO', 'SUBSCRIBE']; $booleanFields = ['WITHOUT_ORDER', 'SELECT_BEST_PRICE', 'VAT_INCLUDED', 'BARCODE_MULTI', 'BUNDLE']; $nullFields = ['MEASURE', 'TRIAL_PRICE_ID', 'VAT_ID', 'RECUR_SCHEME_LENGTH']; $sizeFields = ['WIDTH', 'LENGTH', 'HEIGHT']; } $defaultValues['TYPE'] = self::getDefaultProductType($iblockData['CATALOG_TYPE']); $fields = array_merge($defaultValues, array_diff_key($fields, $blackList)); $fields['TYPE'] = (int)$fields['TYPE']; if (!isset($allowedTypes[$fields['TYPE']])) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_BAD_PRODUCT_TYPE') )); return; } $isSpecialType = ( $fields['TYPE'] === Catalog\ProductTable::TYPE_EMPTY_SKU || $fields['TYPE'] === Catalog\ProductTable::TYPE_FREE_OFFER ); if ($isSpecialType) { $fields['AVAILABLE'] = Catalog\ProductTable::STATUS_NO; $fields['QUANTITY'] = 0; $fields['QUANTITY_RESERVED'] = 0; $fields['QUANTITY_TRACE'] = Catalog\ProductTable::STATUS_YES; $fields['CAN_BUY_ZERO'] = Catalog\ProductTable::STATUS_NO; } $isService = $fields['TYPE'] === Catalog\ProductTable::TYPE_SERVICE; if ($isService) { $fields['QUANTITY_RESERVED'] = 0; $fields['QUANTITY_TRACE'] = Catalog\ProductTable::STATUS_NO; $fields['CAN_BUY_ZERO'] = Catalog\ProductTable::STATUS_YES; } if (is_string($fields['QUANTITY']) && !is_numeric($fields['QUANTITY'])) { $result->addError(new ORM\EntityError( Loc::getMessage( 'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_NUMERIC_FIELD', ['#FIELD#' => 'QUANTITY'] ) )); } if ($isService) { $fields['QUANTITY'] = (int)$fields['QUANTITY']; if ($fields['QUANTITY'] !== 1) { $fields['QUANTITY'] = 0; } } else { $fields['QUANTITY'] = (float)$fields['QUANTITY']; } if (is_string($fields['QUANTITY_RESERVED']) && !is_numeric($fields['QUANTITY_RESERVED'])) { $result->addError(new ORM\EntityError( Loc::getMessage( 'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_NUMERIC_FIELD', ['#FIELD#' => 'QUANTITY_RESERVED'] ) )); } $fields['QUANTITY_RESERVED'] = (float)$fields['QUANTITY_RESERVED']; if ($fields['QUANTITY_RESERVED'] < 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO'), 'BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO' )); } foreach ($tripleFields as $fieldName) { if ( $fields[$fieldName] != Catalog\ProductTable::STATUS_NO && $fields[$fieldName] != Catalog\ProductTable::STATUS_YES ) $fields[$fieldName] = $defaultValues[$fieldName]; } foreach ($booleanFields as $fieldName) { if ($fields[$fieldName] != Catalog\ProductTable::STATUS_YES) $fields[$fieldName] = $defaultValues[$fieldName]; } foreach ($nullFields as $fieldName) { if ($fields[$fieldName] !== null) { $fields[$fieldName] = (int)$fields[$fieldName]; if ($fields[$fieldName] <= 0) $fields[$fieldName] = null; } } foreach ($sizeFields as $fieldName) { if ($fields[$fieldName] !== null) { $fields[$fieldName] = (float)$fields[$fieldName]; if ($fields[$fieldName] <= 0) $fields[$fieldName] = null; } } unset($fieldName); if ( $fields['PRICE_TYPE'] != Catalog\ProductTable::PAYMENT_TYPE_REGULAR && $fields['PRICE_TYPE'] != Catalog\ProductTable::PAYMENT_TYPE_TRIAL ) $fields['PRICE_TYPE'] = $defaultValues['PRICE_TYPE']; if (!in_array($fields['RECUR_SCHEME_TYPE'], $paymentPeriods, true)) $fields['RECUR_SCHEME_TYPE'] = $defaultValues['RECUR_SCHEME_TYPE']; if (is_string($fields['WEIGHT']) && !is_numeric($fields['WEIGHT'])) { $result->addError(new ORM\EntityError( Loc::getMessage( 'BX_CATALOG_MODEL_PRODUCT_ERR_BAD_NUMERIC_FIELD', ['#FIELD#' => 'WEIGHT'] ) )); } $fields['WEIGHT'] = (float)$fields['WEIGHT']; if ($fields['TMP_ID'] !== null) $fields['TMP_ID'] = mb_substr($fields['TMP_ID'], 0, 40); /* purchasing price */ $purchasingCurrency = null; $purchasingPrice = static::checkPriceValue($fields['PURCHASING_PRICE']); if ($purchasingPrice !== null) { $purchasingCurrency = static::checkPriceCurrency($fields['PURCHASING_CURRENCY']); if ($purchasingCurrency === null) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PURCHASING_CURRENCY') )); $purchasingPrice = null; } } $fields['PURCHASING_PRICE'] = $purchasingPrice; $fields['PURCHASING_CURRENCY'] = $purchasingCurrency; unset($purchasingCurrency, $purchasingPrice); /* purchasing price end */ if (array_key_exists('AVAILABLE', $fields)) { if ( $fields['AVAILABLE'] != Catalog\ProductTable::STATUS_YES && $fields['AVAILABLE'] != Catalog\ProductTable::STATUS_NO ) { unset($fields['AVAILABLE']); } else { if (!$isSpecialType) { if ($isService) { //TODO: remove this hack after reservation resource $fields['QUANTITY'] = $fields['AVAILABLE'] !== Catalog\ProductTable::STATUS_YES ? 0 : 1; } else { $data['actions'][self::ACTION_CHANGE_PARENT_AVAILABLE] = true; } } } } if ($result->isSuccess()) { $fields['ID'] = $id; $fields['NEGATIVE_AMOUNT_TRACE'] = $fields['CAN_BUY_ZERO']; $fields['TIMESTAMP_X'] = new Main\Type\DateTime(); if (!isset($fields['AVAILABLE'])) { self::calculateAvailable($fields, $data['actions']); if ($fields['AVAILABLE'] === null) $fields['AVAILABLE'] = Catalog\ProductTable::STATUS_NO; } if ($fields['TYPE'] === Catalog\ProductTable::TYPE_OFFER) { $data['actions'][self::ACTION_CHANGE_PARENT_TYPE] = true; } } if ($result->isSuccess()) $data['fields'] = $fields; unset($fields); } /** * Check and modify fields before update product. Need for product automation. * * @param ORM\Data\UpdateResult $result * @param int $id * @param array &$data * @return void */ protected static function prepareForUpdate(ORM\Data\UpdateResult $result, $id, array &$data): void { $id = (int)$id; if ($id <= 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PRODUCT_ID') )); return; } $iblockId = 0; if (isset($data['external_fields']['IBLOCK_ID'])) $iblockId = (int)$data['external_fields']['IBLOCK_ID']; if ($iblockId <= 0) $iblockId = \CIBlockElement::GetIBlockByID($id); if (empty($iblockId)) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_ELEMENT_NOT_EXISTS') )); return; } $iblockData = \CCatalogSku::GetInfoByIBlock($iblockId); if (empty($iblockData)) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_SIMPLE_IBLOCK') )); return; } $data['external_fields']['IBLOCK_ID'] = $iblockId; $fields = $data['fields']; parent::prepareForUpdate($result, $id, $fields); if (!$result->isSuccess()) return; if (self::$separateSkuMode === null) { self::$separateSkuMode = Main\Config\Option::get('catalog', 'show_catalog_tab_with_offers') === 'Y'; } static $quantityFields = null, $paymentPeriods = null, $tripleFields = null, $booleanFields = null, $nullFields = null, $sizeFields = null, $blackList = null; if ($quantityFields === null) { $quantityFields = ['QUANTITY', 'QUANTITY_RESERVED']; $paymentPeriods = Catalog\ProductTable::getPaymentPeriods(false); $tripleFields = ['QUANTITY_TRACE', 'CAN_BUY_ZERO', 'SUBSCRIBE']; $booleanFields = ['WITHOUT_ORDER', 'SELECT_BEST_PRICE', 'VAT_INCLUDED', 'BARCODE_MULTI', 'BUNDLE', 'AVAILABLE']; $nullFields = ['MEASURE', 'TRIAL_PRICE_ID', 'VAT_ID', 'RECUR_SCHEME_LENGTH']; $sizeFields = ['WIDTH', 'LENGTH', 'HEIGHT']; $blackList = [ 'ID' => true, 'NEGATIVE_AMOUNT_TRACE' => true ]; } $fields = array_diff_key($fields, $blackList); $allowedTypes = self::getProductTypes($iblockData['CATALOG_TYPE']); if (array_key_exists('TYPE', $fields)) { $fields['TYPE'] = (int)$fields['TYPE']; if (!isset($allowedTypes[$fields['TYPE']])) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_BAD_PRODUCT_TYPE') )); return; } } foreach ($quantityFields as $fieldName) { if (array_key_exists($fieldName, $fields)) { if ($fields[$fieldName] === null) unset($fields[$fieldName]); else $fields[$fieldName] = (float)$fields[$fieldName]; } } foreach ($tripleFields as $fieldName) { if (array_key_exists($fieldName, $fields)) { if ( $fields[$fieldName] != Catalog\ProductTable::STATUS_NO && $fields[$fieldName] != Catalog\ProductTable::STATUS_YES && $fields[$fieldName] != Catalog\ProductTable::STATUS_DEFAULT ) unset($fields[$fieldName]); } } if (isset($fields['SUBSCRIBE'])) $data['actions'][self::ACTION_SEND_NOTIFICATIONS] = true; foreach ($booleanFields as $fieldName) { if (array_key_exists($fieldName, $fields)) { if ( $fields[$fieldName] != Catalog\ProductTable::STATUS_NO && $fields[$fieldName] != Catalog\ProductTable::STATUS_YES ) unset($fields[$fieldName]); } } foreach ($nullFields as $fieldName) { if (isset($fields[$fieldName])) { $fields[$fieldName] = (int)$fields[$fieldName]; if ($fields[$fieldName] <= 0) { $fields[$fieldName] = null; } } } foreach ($sizeFields as $fieldName) { if (isset($fields[$fieldName])) { $fields[$fieldName] = (float)$fields[$fieldName]; if ($fields[$fieldName] <= 0) { $fields[$fieldName] = null; } } } unset($fieldName); if (array_key_exists('PRICE_TYPE', $fields)) { if ( $fields['PRICE_TYPE'] != Catalog\ProductTable::PAYMENT_TYPE_REGULAR && $fields['PRICE_TYPE'] != Catalog\ProductTable::PAYMENT_TYPE_TRIAL && $fields['PRICE_TYPE'] != Catalog\ProductTable::PAYMENT_TYPE_SINGLE ) unset($fields['PRICE_TYPE']); } if (array_key_exists('RECUR_SCHEME_TYPE', $fields)) { if (!in_array($fields['RECUR_SCHEME_TYPE'], $paymentPeriods, true)) unset($fields['RECUR_SCHEME_TYPE']); } if (array_key_exists('WEIGHT', $fields)) { if ($fields['WEIGHT'] === null) { unset($fields['WEIGHT']); } else { $fields['WEIGHT'] = (float)$fields['WEIGHT']; $data['actions'][self::ACTION_RECALCULATE_SETS] = true; } } if (isset($fields['TMP_ID'])) { $fields['TMP_ID'] = mb_substr($fields['TMP_ID'], 0, 40); } if (array_key_exists('QUANTITY_RESERVED', $fields) && (float)$fields['QUANTITY_RESERVED'] < 0) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO'), 'BX_CATALOG_MODEL_PRODUCT_ERR_QUANTITY_RESERVE_LESS_ZERO' )); } /* purchasing price */ $existPurchasingPrice = array_key_exists('PURCHASING_PRICE', $fields); $existPurchasingCurrency = array_key_exists('PURCHASING_CURRENCY', $fields); if ($existPurchasingPrice) { $fields['PURCHASING_PRICE'] = static::checkPriceValue($fields['PURCHASING_PRICE']); if ($fields['PURCHASING_PRICE'] === null) { $fields['PURCHASING_CURRENCY'] = null; $existPurchasingCurrency = false; } } if ($existPurchasingCurrency) { $fields['PURCHASING_CURRENCY'] = static::checkPriceCurrency($fields['PURCHASING_CURRENCY']); if ($fields['PURCHASING_CURRENCY'] === null) { $result->addError(new ORM\EntityError( Loc::getMessage('BX_CATALOG_MODEL_PRODUCT_ERR_WRONG_PURCHASING_CURRENCY') )); } } /* purchasing price end */ if ($result->isSuccess()) { if (isset($fields['CAN_BUY_ZERO'])) $fields['NEGATIVE_AMOUNT_TRACE'] = $fields['CAN_BUY_ZERO']; $fields['TIMESTAMP_X'] = new Main\Type\DateTime(); if (isset($fields['AVAILABLE'])) { $copyFields = isset($fields['TYPE']) ? $fields : array_merge(static::getCacheItem($id, true), $fields) ; $copyFields['TYPE'] = (int)$copyFields['TYPE']; $isService = $copyFields['TYPE'] === Catalog\ProductTable::TYPE_SERVICE; if ($isService) { //TODO: remove this hack after reservation resource $fields['QUANTITY'] = $fields['AVAILABLE'] !== Catalog\ProductTable::STATUS_YES ? 0 : 1; } if (!$isService) { $data['actions'][self::ACTION_CHANGE_PARENT_AVAILABLE] = true; } $data['actions'][self::ACTION_RECALCULATE_SETS] = true; $data['actions'][self::ACTION_SEND_NOTIFICATIONS] = true; } else { $needCalculateAvailable = (isset($fields['TYPE']) || isset($fields['QUANTITY']) || isset($fields['QUANTITY_TRACE']) || isset($fields['CAN_BUY_ZERO']) ); if ($needCalculateAvailable) { $needCache = (!isset($fields['TYPE']) || !isset($fields['QUANTITY']) || !isset($fields['QUANTITY_TRACE']) || !isset($fields['CAN_BUY_ZERO']) ); $copyFields = ( $needCache ? array_merge(static::getCacheItem($id, true), $fields) : $fields ); $copyFields['TYPE'] = (int)$copyFields['TYPE']; self::calculateAvailable($copyFields, $data['actions']); if ($copyFields['AVAILABLE'] !== null) $fields['AVAILABLE'] = $copyFields['AVAILABLE']; unset($copyFields, $needCache); } unset($needCalculateAvailable); } $data['fields'] = $fields; } unset($fields); } /** * Run core automation after add product. * * @param int $id * @param array $data * @return void */ protected static function runAddExternalActions($id, array $data): void { switch ($data['fields']['TYPE']) { case Catalog\ProductTable::TYPE_OFFER: if ( isset($data['actions'][self::ACTION_CHANGE_PARENT_AVAILABLE]) || isset($data['actions'][self::ACTION_CHANGE_PARENT_TYPE]) ) { Catalog\Product\Sku::calculateComplete( $id, $data['external_fields']['IBLOCK_ID'], Catalog\ProductTable::TYPE_OFFER ); } break; case Catalog\ProductTable::TYPE_SKU: if (isset($data['actions'][self::ACTION_CHANGE_PARENT_AVAILABLE])) { Catalog\Product\Sku::calculateComplete( $id, $data['external_fields']['IBLOCK_ID'], Catalog\ProductTable::TYPE_SKU ); } break; } } /** * Run core automation after update product. * * @param int $id * @param array $data * @return void */ protected static function runUpdateExternalActions($id, array $data): void { $product = self::getCacheItem($id); if (isset($data['actions'][self::ACTION_CHANGE_PARENT_AVAILABLE])) { switch ($product['TYPE']) { case Catalog\ProductTable::TYPE_OFFER: if (isset($data['actions']['SKU_AVAILABLE'])) { Catalog\Product\Sku::calculateComplete( $id, $data['external_fields']['IBLOCK_ID'], Catalog\ProductTable::TYPE_OFFER ); } self::updateElementModificationTime($id); break; case Catalog\ProductTable::TYPE_SKU: if (isset($data['actions']['SKU_AVAILABLE'])) { Catalog\Product\Sku::calculateComplete( $id, $data['external_fields']['IBLOCK_ID'], Catalog\ProductTable::TYPE_SKU ); } break; case Catalog\ProductTable::TYPE_PRODUCT: case Catalog\ProductTable::TYPE_SET: self::updateElementModificationTime($id); break; } } if (isset($data['actions'][self::ACTION_SEND_NOTIFICATIONS])) { self::checkSubscription($id, $product); } if (isset($data['actions'][self::ACTION_RECALCULATE_SETS])) { \CCatalogProductSet::recalculateSetsByProduct($id); } $changeAvailable = ( isset($product['AVAILABLE']) && isset($product[self::PREFIX_OLD.'AVAILABLE']) && $product[self::PREFIX_OLD.'AVAILABLE'] != $product['AVAILABLE'] ); if ($changeAvailable) { // clear public components cache \CIBlock::clearIblockTagCache($data['external_fields']['IBLOCK_ID']); // send old event $eventId = 'OnProductQuantityTrace'; if ( Main\Config\Option::get('catalog', 'enable_processing_deprecated_events') === 'Y' && Event::existEventHandlersById($eventId) ) { $description = [ 'ID' => $product['ID'], 'ELEMENT_IBLOCK_ID' => $data['external_fields']['IBLOCK_ID'], 'IBLOCK_ID' => $data['external_fields']['IBLOCK_ID'], 'TYPE' => $product['TYPE'], 'AVAILABLE' => $product['AVAILABLE'], 'CAN_BUY_ZERO' => $product['CAN_BUY_ZERO'], 'NEGATIVE_AMOUNT_TRACE' => $product['CAN_BUY_ZERO'], 'QUANTITY_TRACE' => $product['QUANTITY_TRACE'], 'QUANTITY' => $product['QUANTITY'], 'OLD_QUANTITY' => $product[self::PREFIX_OLD.'QUANTITY'] ?? $product['QUANTITY'], ]; $description['DELTA'] = $description['QUANTITY'] - $description['OLD_QUANTITY']; $handlerData = [ $product['ID'], $description, ]; unset($description); $eventManager = Main\EventManager::getInstance(); $handlerList = $eventManager->findEventHandlers('catalog', $eventId); foreach ($handlerList as $handler) { $handler['FROM_MODULE_ID'] = 'catalog'; $handler['MESSAGE_ID'] = $eventId; ExecuteModuleEventEx($handler, $handlerData); } unset($handler, $handlerList); unset($handlerData); } } unset($product); } /** * Run core automation after delete product. * * @param int $id * @return void */ protected static function runDeleteExternalActions($id): void { Catalog\PriceTable::deleteByProduct($id); Catalog\MeasureRatioTable::deleteByProduct($id); Catalog\ProductGroupAccessTable::deleteByProduct($id); Catalog\StoreProductTable::deleteByProduct($id); Catalog\SubscribeTable::onIblockElementDelete($id); ExportedProductTable::deleteProduct($id); //TODO: replace this code $conn = Main\Application::getConnection(); $helper = $conn->getSqlHelper(); $conn->queryExecute( 'delete from '.$helper->quote('b_catalog_product_sets'). ' where '.$helper->quote('ITEM_ID').' = '.$id.' or '.$helper->quote('OWNER_ID').' = '.$id ); unset($helper, $conn); } /** * Sending messages that the product has become available. * @internal * * @param $id * @param array $product * @return void */ private static function checkSubscription($id, array $product): void { if ( isset($product[self::PREFIX_OLD.'AVAILABLE']) && Catalog\SubscribeTable::checkPermissionSubscribe($product['SUBSCRIBE']) ) { if ( $product[self::PREFIX_OLD.'AVAILABLE'] == Catalog\ProductTable::STATUS_NO && $product['AVAILABLE'] == Catalog\ProductTable::STATUS_YES ) { Catalog\SubscribeTable::runAgentToSendNotice($id); } elseif ( $product[self::PREFIX_OLD.'AVAILABLE'] == Catalog\ProductTable::STATUS_YES && $product['AVAILABLE'] == Catalog\ProductTable::STATUS_NO ) { Catalog\SubscribeTable::runAgentToSendRepeatedNotice($id); } if ( $product[self::PREFIX_OLD.'QUANTITY'] <= 0 && $product['QUANTITY'] > 0 ) { if (self::$saleIncluded === null) self::$saleIncluded = Loader::includeModule('sale'); if (self::$saleIncluded) \CSaleBasket::ProductSubscribe($id, 'catalog'); } } } private static function checkPriceValue($price): ?float { $result = null; if ($price !== null) { if (is_string($price)) { if ($price !== '' && is_numeric($price)) { $price = (float)$price; if (is_finite($price)) $result = $price; } } elseif ( is_int($price) || (is_float($price) && is_finite($price)) ) { $result = $price; } } return $result; } private static function checkPriceCurrency($currency): ?string { $result = null; if (is_string($currency) && $currency !== '') { $result = $currency; } return $result; } private static function calculateAvailable(array &$fields, array &$actions) { $result = null; switch ($fields['TYPE']) { case Catalog\ProductTable::TYPE_PRODUCT: case Catalog\ProductTable::TYPE_FREE_OFFER: $result = Catalog\ProductTable::calculateAvailable($fields); $actions[self::ACTION_RECALCULATE_SETS] = true; $actions[self::ACTION_SEND_NOTIFICATIONS] = true; break; case Catalog\ProductTable::TYPE_OFFER: $result = Catalog\ProductTable::calculateAvailable($fields); if (!self::$separateSkuMode) $actions[self::ACTION_CHANGE_PARENT_AVAILABLE] = true; $actions[self::ACTION_RECALCULATE_SETS] = true; $actions[self::ACTION_SEND_NOTIFICATIONS] = true; break; case Catalog\ProductTable::TYPE_SKU: if (self::$separateSkuMode) $result = Catalog\ProductTable::calculateAvailable($fields); else $actions[self::ACTION_CHANGE_PARENT_AVAILABLE] = true; break; case Catalog\ProductTable::TYPE_SET: $result = Catalog\ProductTable::calculateAvailable($fields); break; case Catalog\ProductTable::TYPE_EMPTY_SKU: $result = Catalog\ProductTable::STATUS_NO; break; case Catalog\ProductTable::TYPE_SERVICE: if (isset($fields['QUANTITY'])) { $result = ($fields['QUANTITY'] > 0 ? Catalog\ProductTable::STATUS_YES : Catalog\ProductTable::STATUS_NO); $actions[self::ACTION_SEND_NOTIFICATIONS] = true; } } $fields['AVAILABLE'] = $result; } //TODO: remove after create \Bitrix\Catalog\Model\CatalogIblock private static function getProductTypes($catalogType): array { $result = []; switch ($catalogType) { case \CCatalogSku::TYPE_CATALOG: $result = [ Catalog\ProductTable::TYPE_PRODUCT => true, Catalog\ProductTable::TYPE_SET => true, Catalog\ProductTable::TYPE_SERVICE => true, ]; break; case \CCatalogSku::TYPE_OFFERS: $result = [ Catalog\ProductTable::TYPE_OFFER => true, Catalog\ProductTable::TYPE_FREE_OFFER => true ]; break; case \CCatalogSku::TYPE_FULL: $result = [ Catalog\ProductTable::TYPE_PRODUCT => true, Catalog\ProductTable::TYPE_SET => true, Catalog\ProductTable::TYPE_SKU => true, Catalog\ProductTable::TYPE_EMPTY_SKU => true, Catalog\ProductTable::TYPE_SERVICE => true, ]; break; case \CCatalogSku::TYPE_PRODUCT: $result = [ Catalog\ProductTable::TYPE_SKU => true, Catalog\ProductTable::TYPE_EMPTY_SKU => true ]; break; } return $result; } //TODO: remove after create \Bitrix\Catalog\Model\CatalogIblock private static function getDefaultProductType($catalogType): ?int { $result = null; switch ($catalogType) { case \CCatalogSku::TYPE_CATALOG: case \CCatalogSku::TYPE_FULL: $result = Catalog\ProductTable::TYPE_PRODUCT; break; case \CCatalogSku::TYPE_OFFERS: $result = Catalog\ProductTable::TYPE_OFFER; break; case \CCatalogSku::TYPE_PRODUCT: $result = Catalog\ProductTable::TYPE_SKU; break; } return $result; } private static function updateElementModificationTime(int $elementId): void { $conn = Main\Application::getConnection(); if (self::$queryElementDate === null) { $helper = $conn->getSqlHelper(); self::$queryElementDate = 'update ' . $helper->quote(Iblock\ElementTable::getTableName()) . ' set ' . $helper->quote('TIMESTAMP_X') . ' = ' . $helper->getCurrentDateTimeFunction() . ' where ' . $helper->quote('ID') . '='; } $conn->queryExecute(self::$queryElementDate . $elementId); } }