403Webshell
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/components/bitrix/catalog.product.grid/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/components/bitrix/catalog.product.grid/class.php
<?php

use Bitrix\Main;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Catalog;
use Bitrix\Catalog\Access\ActionDictionary;
use Bitrix\Catalog\Config\Feature;
use Bitrix\Catalog\Config\State;
use Bitrix\Catalog\Grid\Column\PriceProvider;
use Bitrix\Catalog\Grid\Column\ProductProvider;
use Bitrix\Catalog\Grid\ProductGrid;
use Bitrix\Catalog\GroupTable;
use Bitrix\Catalog\ProductTable;
use Bitrix\Catalog\StoreProductTable;
use Bitrix\Catalog\Url\AdminPage\CatalogBuilder;
use Bitrix\Catalog\Url\ShopBuilder;
use Bitrix\Crm\Order\Import\Instagram;
use Bitrix\Iblock;
use Bitrix\Iblock\Grid\Column\ElementPropertyProvider;
use Bitrix\Iblock\Grid\RowType;
use Bitrix\Main\Config\Option;
use Bitrix\Main\Context;
use Bitrix\Main\Grid\Export\ExcelExporter;
use Bitrix\Main\Grid\Settings;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Main\SystemException;
use Bitrix\Main\Web\Uri;
use Bitrix\UI;
use Bitrix\UI\Buttons\SettingsButton;
use Bitrix\UI\Buttons\Split\CreateButton;
use Bitrix\UI\Toolbar\ButtonLocation;
use Bitrix\UI\Toolbar\Facade\Toolbar;

if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true)
{
	die();
}

class CatalogProductGridComponent extends \CBitrixComponent
	implements Main\Engine\Contract\Controllerable, Main\Errorable
{
	use Main\ErrorableImplementation;

	protected const ERROR_CODE_MODULE_IS_ABSENT = -1;
	protected const ERROR_CODE_BAD_PARAMETER = -2;
	protected const ERROR_CODE_ACCESS = -3;
	protected const ACCESS_LEVEL_IBLOCK = 'iblock_admin_display';
	protected const DOM_ID_MASK = '/[^a-zA-Z0-9_]/';

	protected Catalog\Access\AccessController $accessController;

	protected ?int $iblockId = null;
	protected array $iblock;
	protected string $iblockListMode;

	protected array $catalog;
	protected string $catalogType;
	protected bool $useOffers;

	protected bool $useGridFilter;

	protected Main\UI\PageNavigation $navigation;
	protected array $rows;
	protected array $sectionIds;
	protected array $elementIds;
	protected array $parentIds;
	protected ?Catalog\Component\SkuTree $skuTree;

	protected string $gridId;
	protected ProductGrid $grid;
	protected ShopBuilder $urlBuilder;
	protected ExcelExporter $excelExporter;
	protected array $selectedVariationMap;

	private string $productNavString;

	public function __construct($component = null)
	{
		parent::__construct($component);

		$this->errorCollection = new Main\ErrorCollection();
		$this->accessController = Catalog\Access\AccessController::getCurrent();
	}

	public function configureActions(): array
	{
		return [];
	}

	/** @noinspection PhpParameterNameChangedDuringInheritanceInspection */
	/**
	 * @param array|mixed $params
	 *	keys are case sensitive:
	 *		<ul>
	 *		<li>int IBLOCK_ID Information block identifier (required).
	 *		<li>string LIST_MODE Combined or separated product grid (optional, see Iblock\IblockTable::LIST_MODE_*).
	 *		<li>string USE_FILTER Use grid filter (Y/N, default Y).
	 *		<li>string GRID_ID Dom grid id (optional).
	 *		<li>string FILTER_ID Dom filter id (optional).
	 *		<li>string NAVIGATION_ID Dom navigation bar id (optional).
	 *		<li>string BASE_LINK Url for ajax actions (optional).
	 *		</ul>
	 *
	 * @return array|null
	 */
	public function onPrepareComponentParams($params): ?array
	{
		if ($this->checkModules()->hasErrors())
		{
			return null;
		}

		$params = (is_array($params) ? $params : []);

		$params['IBLOCK_ID'] = (int)($params['IBLOCK_ID'] ?? 0);
		if ($this->setIblockId($params['IBLOCK_ID'])->hasErrors())
		{
			return null;
		}

		$params['LIST_MODE'] = (string)($params['LIST_MODE'] ?? '');
		if ($this->setListMode($params['LIST_MODE'])->hasErrors())
		{
			return null;
		}
		$params['USE_FILTER'] = (string)($params['USE_FILTER'] ?? 'Y');
		$this->setUseFilter($params['USE_FILTER'] !== 'N');

		$params = $this->prepareDomIdParameters($params);

		$params['BASE_LINK'] = (string)($params['BASE_LINK'] ?? '');
		$params['SHOW_ALL_BUTTON'] ??= 'N';

		$params['SKU_SELECTOR_ENABLE'] = (string)($params['SKU_SELECTOR_ENABLE'] ?? 'Y');
		if ($params['SKU_SELECTOR_ENABLE'] !== 'N')
		{
			$params['SKU_SELECTOR_ENABLE'] = 'Y';
		}

		$params['USE_NEW_CARD'] ??= State::isProductCardSliderEnabled() ? 'Y' : 'N';
		if (isset($params['USE_NEW_CARD']) && $params['USE_NEW_CARD'] !== 'Y')
		{
			$params['USE_NEW_CARD'] = 'N';
		}

		if (isset($params['URL_BUILDER']) && ($params['URL_BUILDER'] instanceof CatalogBuilder) === false)
		{
			throw new SystemException('Parameter "URL_BUILDER" must implement ' . CatalogBuilder::class);
		}

		return parent::onPrepareComponentParams($params);
	}

	protected function checkModules(): self
	{
		if (!Loader::includeModule('iblock'))
		{
			$this->errorCollection[] = new Main\Error(
				Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_MODULE_IBLOCK_IS_ABSENT'),
				self::ERROR_CODE_MODULE_IS_ABSENT
			);

			return $this;
		}
		if (!Loader::includeModule('catalog'))
		{
			$this->errorCollection[] = new Main\Error(
				Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_MODULE_CATALOG_IS_ABSENT'),
				self::ERROR_CODE_MODULE_IS_ABSENT
			);

			return $this;
		}

		Loader::includeModule('fileman');

		return $this;
	}

	protected function showErrors(): void
	{
		$description =
			Loader::includeModule('bitrix24')
				? null
				: ''
		;
		foreach ($this->getErrors() as $error)
		{
			$this->includeErrorComponent($error->getMessage(), $description);
		}
	}

	protected function showCatalogStub(): void
	{
		$this->arResult['STUB_REDIRECT'] = Loader::includeModule('crm') ? '/crm/' : '/';

		$this->includeComponentTemplate('stub');
	}

	protected function includeErrorComponent(string $errorMessage, string $description = null): void
	{
		UI\Toolbar\Facade\Toolbar::deleteFavoriteStar();

		global $APPLICATION;

		$APPLICATION->IncludeComponent(
			'bitrix:ui.info.error',
			'',
			[
				'TITLE' => $errorMessage,
				'DESCRIPTION' => $description,
				'IS_HTML' => 'Y',
			]
		);
	}

	protected function getIblockId(): ?int
	{
		return $this->iblockId;
	}

	protected function hasIblock(): bool
	{
		return $this->getIblockId() !== null;
	}

	protected function setIblockId(int $iblockId): self
	{
		if ($iblockId <= 0)
		{
			$this->errorCollection[] = new Main\Error(
				Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_IBLOCK_ID_IS_ABSENT'),
				self::ERROR_CODE_BAD_PARAMETER
			);

			return $this;
		}

		// TODO: change \CIBlock::GetArrayByID to new d7 api
		$iblock = \CIBlock::GetArrayByID($iblockId);
		if (!is_array($iblock))
		{
			$this->errorCollection[] = new Main\Error(
				Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_BAD_IBLOCK_ID'),
				self::ERROR_CODE_BAD_PARAMETER
			);

			return $this;
		}

		if (!\CIBlockRights::UserHasRightTo($iblockId, $iblockId, self::ACCESS_LEVEL_IBLOCK))
		{
			$this->errorCollection[] = new Main\Error(
				Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_IBLOCK_ACCESS_DENIED'),
				self::ERROR_CODE_ACCESS
			);

			return $this;
		}

		$this->iblockId = $iblockId;
		$this->iblock = $iblock;

		return $this->initCatalog();
	}

	protected function initCatalog(): self
	{
		// TODO: cnange \CCatalogSku::GetInfoByIBlock to new d7 api
		$catalog = \CCatalogSku::GetInfoByIBlock($this->iblockId);
		if (!is_array($catalog))
		{
			$this->errorCollection[] = new Main\Error(
				Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_IBLOCK_IS_NOT_CATALOG'),
				self::ERROR_CODE_BAD_PARAMETER
			);

			return $this;
		}


		$this->catalog = $catalog;
		$this->catalogType = $catalog['CATALOG_TYPE'];
		$this->useOffers = (
			$this->catalogType === \CCatalogSku::TYPE_FULL
			|| $this->catalogType === \CCatalogSku::TYPE_PRODUCT
		);
		if ($this->useOffers)
		{
			if (!\CIBlockRights::UserHasRightTo(
					$this->catalog['IBLOCK_ID'],
					$this->catalog['IBLOCK_ID'],
					self::ACCESS_LEVEL_IBLOCK
			))
			{
				$this->errorCollection[] = new Main\Error(
					Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_IBLOCK_OFFERS_ACCESS_DENIED'),
					self::ERROR_CODE_ACCESS
				);
			}
		}

		return $this;
	}

	protected function hasOffersIblockId(): bool
	{
		return $this->useOffers;
	}

	protected function getOffersIblockId(): ?int
	{
		return ($this->hasOffersIblockId() ? $this->catalog['IBLOCK_ID'] : null);
	}

	protected function setListMode(string $listMode): self
	{
		if (
			$listMode === Iblock\IblockTable::LIST_MODE_SEPARATE
			|| $listMode === Iblock\IblockTable::LIST_MODE_COMBINED
		)
		{
			$this->iblockListMode = $listMode;
		}

		return $this;
	}

	protected function getListMode(): string
	{
		if (!isset($this->iblockListMode))
		{
			if (
				$this->hasIblock()
				&& (
					$this->iblock['LIST_MODE'] === Iblock\IblockTable::LIST_MODE_SEPARATE
					|| $this->iblock['LIST_MODE'] === Iblock\IblockTable::LIST_MODE_COMBINED
				)
			)
			{
				$this->iblockListMode = $this->iblock['LIST_MODE'];
			}
			else
			{
				$this->iblockListMode =
					Main\Config\Option::get('iblock', 'combined_list_mode') === 'Y'
						? Iblock\IblockTable::LIST_MODE_COMBINED
						: Iblock\IblockTable::LIST_MODE_SEPARATE
				;
			}
		}

		return $this->iblockListMode;
	}

	protected function isCombinedListMode(): bool
	{
		return $this->getListMode() === Iblock\IblockTable::LIST_MODE_COMBINED;
	}

	protected function isUsedSkuSelector(): bool
	{
		if (!$this->hasOffersIblockId())
		{
			return false;
		}
		if ($this->arParams['SKU_SELECTOR_ENABLE'] !== 'Y')
		{
			return false;
		}

		return true;
	}

	/**
	 * @return bool
	 */
	protected function isUsedGridFilter(): bool
	{
		return $this->useGridFilter;
	}

	protected function setUseFilter(bool $state): self
	{
		$this->useGridFilter = $state;

		return $this;
	}

	/**
	 * @return array
	 */
	protected function getVisibleColumnIds(): array
	{
		return $this->grid->getVisibleColumnsIds();
	}

	/**
	 * @param string $gridId
	 * @return string
	 */
	protected static function createFilterId(string $gridId): string
	{
		return $gridId . '_FILTER';
	}

	/**
	 * @param string $gridId
	 * @return string
	 */
	protected static function createNavigationId(string $gridId): string
	{
		return $gridId . '_NAVIGATION';
	}

	protected function prepareDomIdParameters(array $params): array
	{
		$params['GRID_ID'] = (string)($params['GRID_ID'] ?? '');
		$params['GRID_ID'] = preg_replace(self::DOM_ID_MASK, '', $params['GRID_ID']);

		$params['NAVIGATION_ID'] = (string)($params['NAVIGATION_ID'] ?? '');
		$params['NAVIGATION_ID'] = preg_replace(self::DOM_ID_MASK, '', $params['NAVIGATION_ID']) ?: null;

		return $params;
	}

	// region Rights

	protected function checkPermissions(): self
	{
		if (!$this->accessController->check(ActionDictionary::ACTION_CATALOG_READ))
		{
			$this->errorCollection[] = new Main\Error(
				Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_ERR_ACCESS_LEVEL_IS_ABSENT'),
				self::ERROR_CODE_ACCESS
			);

			return $this;
		}

		return $this;
	}

	protected function allowAdd(): bool
	{
		return $this->accessController->check(ActionDictionary::ACTION_PRODUCT_ADD);
	}

	protected function allowEditPrice(): bool
	{
		return $this->accessController->check(ActionDictionary::ACTION_PRICE_EDIT);
	}

	protected function allowInstagramImport(): bool
	{
		return
			Loader::includeModule('crm')
			&& Instagram::isAvailable()
			&& $this->accessController->check(ActionDictionary::ACTION_CATALOG_IMPORT_EXECUTION)
		;
	}

	protected function allowExcelExport(): bool
	{
		return $this->accessController->check(ActionDictionary::ACTION_CATALOG_EXPORT_EXECUTION);
	}

	// endregion

	protected function init(): self
	{
		$this->initSkuTree();
		$this->initGrid();
		$this->initGridFilter();

		return $this;
	}

	protected function getSelectedSectionId(): int
	{
		return $this->arParams['SECTION_ID'] ?? 0;
	}

	protected function initSkuTree(): void
	{
		if ($this->isUsedSkuSelector())
		{
			$this->skuTree = Catalog\v2\IoC\ServiceContainer::make(
				'sku.tree',
				[
					'iblockId' => $this->getIblockId(),
				]
			);
		}
		else
		{
			$this->skuTree = null;
		}
	}

	protected function initGrid(): void
	{
		$settings = new \Bitrix\Catalog\Grid\Settings\ProductSettings([
			'ID' => $this->getGridId(),
			'IBLOCK_ID' => $this->getIblockId(),
		]);
		$settings->setListMode($this->getListMode());
		$settings->setUrlBuilder($this->getUrlBuilder());
		$settings->setSelectedProductOfferIds($this->getSelectedVariationMap());
		$settings->setSkuSelectorEnable($this->isUsedSkuSelector());
		$settings->setNewCardEnabled($this->arParams['USE_NEW_CARD'] === 'Y');

		if ($this->isExcelExport())
		{
			$settings->setMode(Settings::MODE_EXCEL);
		}

		$this->grid = new ProductGrid($settings);

		if ($this->isUsedGridFilter())
		{
			$this->grid->setUseFilter(true);

			// process only direct requests so as not to conflict with the filter, which is updated by ajax
			if (!$this->request->isAjaxRequest() && !$this->isExcelExport())
			{
				$selectedSectionId = $this->getSelectedSectionId();
				if ($selectedSectionId >= 0)
				{
					$options = new \Bitrix\Main\UI\Filter\Options(
						$this->grid->getFilter()->getEntityDataProvider()->getSettings()->getID()
					);

					$presetId = $options->getCurrentFilterId();
					if ($presetId !== \Bitrix\Main\UI\Filter\Options::TMP_FILTER)
					{
						$presetId = \Bitrix\Main\UI\Filter\Options::TMP_FILTER;
						$options->setCurrentFilterPresetId($presetId);
						$options->setCurrentPreset($presetId);
					}

					$settings = $options->getFilterSettings($presetId);
					$settings['fields'] = [
						'SECTION_ID' => $selectedSectionId,
					];

					$options->setFilterSettings($presetId, $settings, true, false);
					$options->save();
				}
			}
		}

		$this->grid->initPagination(
			0,
			$this->getNavigationId()
		);
	}

	/**
	 * @return string
	 */
	protected function getGridId(): string
	{
		$this->gridId ??= $this->arParams['GRID_ID'] ?: (new ReflectionClass($this))->getShortName();

		return $this->gridId;
	}

	/**
	 * @return string|null
	 */
	protected function getNavigationId(): ?string
	{
		return $this->arParams['NAVIGATION_ID'] ?? null;
	}

	protected function isShowAllRecords(): bool
	{
		return $this->arParams['SHOW_ALL_BUTTON'] === 'Y';
	}

	/**
	 * @return array
	 */
	protected function getGridNavigationParams(): array
	{
		$pagination = $this->grid->getPagination();
		if (isset($pagination))
		{
			return [
				'nPageSize' => $pagination->getPageSize(),
				'iPageNum' => $pagination->getCurrentPage(),
			];
		}

		return [];
	}

	protected function initGridFilter(): void
	{
		if (!$this->isUsedGridFilter() || !$this->hasIblock())
		{
			return;
		}

		// toolbar
		$options = \Bitrix\Main\Filter\Component\ComponentParams::get(
			$this->grid->getFilter(),
			[
				'GRID_ID' => $this->grid->getId(),
				'USE_CHECKBOX_LIST_FOR_SETTINGS_POPUP' => \Bitrix\Main\ModuleManager::isModuleInstalled('ui'),
				'ENABLE_FIELDS_SEARCH' => 'Y',
				'CONFIG' => [
					'popupWidth' => 800,
				],
			]
		);
		\Bitrix\UI\Toolbar\Facade\Toolbar::addFilter($options);
	}

	private function getUpdateVariationFieldNames(): array
	{
		$result = [
			'VAT_ID',
			'VAT_INCLUDED',
			'MEASURE_RATIO',
			'MEASURE',
			'PURCHASING_PRICE',
			'PURCHASING_CURRENCY',
			'QUANTITY_TRACE',
			'QUANTITY',
			'QUANTITY_RESERVED',
			'WIDTH',
			'LENGTH',
			'WEIGHT',
			'HEIGHT',
			'CAN_BUY_ZERO',
			'BARCODE',
			'MORE_PHOTO',
		];

		foreach (GroupTable::getTypeList() as $priceType)
		{
			$result[] = PriceProvider::getPriceTypeColumnId($priceType['ID']);
			$result[] = PriceProvider::getCurrencyPriceTypeId($priceType['ID']);
		}

		return $result;
	}

	private function setSelectedVariationMap(array $value): void
	{
		$this->selectedVariationMap = $value;
	}

	private function setSelectedVariationMapFromProductIds(array $productIds): void
	{
		if (!isset($this->skuTree))
		{
			return;
		}

		$selectedVariationMap = $this->getSelectedVariationMap();

		// load from sku tree, to get correct order of offers.
		$tree = $this->skuTree->load($productIds);
		foreach ($tree as $productId => $item)
		{
			if (!isset($selectedVariationMap[$productId]) && !empty($item['OFFERS']))
			{
				$firstOffer = reset($item['OFFERS']);
				if (is_array($firstOffer))
				{
					$selectedVariationMap[$productId] = (int)$firstOffer['ID'];
				}
			}
		}

		if (empty($selectedVariationMap))
		{
			return;
		}

		$this->setSelectedVariationMap($selectedVariationMap);

		// updates it in the grid if it is already initialized.
		if (isset($this->grid))
		{
			$this->grid->getSettings()->setSelectedProductOfferIds($selectedVariationMap);
		}
	}

	private function getSelectedVariationMap(): array
	{
		if (!isset($this->selectedVariationMap))
		{
			$this->selectedVariationMap = [];

			$request = Context::getCurrent()->getRequest();

			$requestedProductId = (int)($request->get('productId') ?? 0);
			$requestedVariationId = (int)($request->get('variationId') ?? 0);

			if ($requestedProductId > 0 && $requestedVariationId > 0)
			{
				$this->selectedVariationMap = [
					$requestedProductId => $requestedVariationId,
				];
			}

		}

		return $this->selectedVariationMap;
	}

	protected function prepareResult(): void
	{
		$additional = [
			'NAV_STRING' => $this->productNavString,
			'USE_CHECKBOX_LIST_FOR_SETTINGS_POPUP' => \Bitrix\Main\ModuleManager::isModuleInstalled('ui'),
			'ENABLE_FIELDS_SEARCH' => 'Y',
			'CONFIG' => [
				'popupWidth' => 800,
			],
		];
		$this->arResult = [
			'GRID' => \Bitrix\Main\Grid\Component\ComponentParams::get($this->grid, $additional),
			'SKU_FIELD_NAMES' => $this->getUpdateVariationFieldNames(),
			'SKU_PRODUCT_MAP' => $this->getSelectedVariationMap(),
			'CAN_EDIT_PRICE' => $this->allowEditPrice(),
			'URL_TO_ADD_PRODUCT' => $this->getUrlBuilder()->getProductDetailUrl(0),
		];
	}

	// endregion

	// region Data

	protected function getDataOrder(): array
	{
		return $this->grid->getOrmOrder();
	}

	protected function getDataFilter(): array
	{
		$filter = $this->grid->getOrmFilter();
		if (isset($filter['SECTION_ID']) && isset($filter['INCLUDE_SUBSECTIONS']))
		{
			if ((int)$filter['SECTION_ID'] === 0 && $filter['INCLUDE_SUBSECTIONS'] === 'Y')
			{
				unset($filter['SECTION_ID'], $filter['INCLUDE_SUBSECTIONS']);
			}
		}

		return $filter;
	}

	// region Viewed column tools
	protected function isViewElementCount():bool
	{
		return in_array('ELEMENT_CNT', $this->getVisibleColumnIds(), true);
	}

	protected function isViewProductBlock(): bool
	{
		return in_array('PRODUCT', $this->getVisibleColumnIds(), true);
	}

	// endregion

	protected function loadRows(): void
	{
		$this->initRows();

		if ($this->isCombinedListMode())
		{
			$iterator = $this->getProductWithSectionsIterator();
		}
		else
		{
			$iterator = $this->getProductIterator();
		}

		if (!$this->isExcelExport())
		{
			$params = $this->getGridNavigationParams();

			$iterator->NavStart(
				$params['nPageSize'],
				$this->isShowAllRecords(),
				false
			);
		}

		while ($row = $iterator->Fetch())
		{
			$rowType = $row['TYPE'] ?? Iblock\Grid\RowType::ELEMENT;
			$index = Iblock\Grid\RowType::getIndex($rowType, $row['ID']);

			$this->rows[$index] = [
				'ID' => $row['ID'],
				'ROW_TYPE' => $rowType,
			];

			if ($rowType === Iblock\Grid\RowType::SECTION)
			{
				$this->sectionIds[] = (int)$row['ID'];
			}
			else
			{
				$this->elementIds[] = (int)$row['ID'];
			}
		}

		$this->productNavString = $this->getProductImplicitNavigationData($iterator);

		$this->internalLoadSections();
		$this->internalLoadElements();
		$this->internalLoadSku();
		$this->internalLoadProductQuantities();

		$this->grid->setRawRows($this->rows);

		$pagination = $this->grid->getPagination();
		$pagination->setRecordCount($iterator->SelectedRowsCount());
		unset($pagination);

		unset($iterator);
	}

	protected function initRows(): void
	{
		$this->rows = [];
		$this->sectionIds = [];
		$this->elementIds = [];
		$this->parentIds = [];
	}

	protected function getProductWithSectionsIterator(): \CDBResult
	{
		$filter = $this->getDataFilter();
		$getCount = $this->isViewElementCount();
		if ($getCount)
		{
			$filter['CNT_ALL'] = 'Y';
			$filter['ELEMENT_SUBSECTIONS'] = 'N';
		}

		$iterator = \CIBlockSection::GetMixedList(
			$this->getDataOrder(),
			$filter,
			$getCount,
			[
				'ID',
				'IBLOCK_ID',
			]
		);
		unset($getCount, $filter);

		return $iterator;
	}

	protected function getProductIterator(): \CDBResult
	{
		return \CIBlockElement::GetList(
			$this->getDataOrder(),
			$this->getDataFilter(),
			false,
			false,
			[
				'ID',
				'IBLOCK_ID',
			]
		);
	}

	protected function getDataSelect(): array
	{
		return $this->grid->getOrmSelect();
	}

	protected function internalLoadSections(): void
	{
		if (empty($this->sectionIds))
		{
			return;
		}

		//TODO: replace to full code
		$select = $this->getDataSelect();
		$changePreviewPicture = in_array('PREVIEW_PICTURE', $select, true);

		//TODO: replace to full code
		$filter = [
			'IBLOCK_ID' => $this->getIblockId(),
			'CHECK_PERMISSIONS' => 'N',
		];

		foreach (array_chunk($this->sectionIds, CATALOG_PAGE_SIZE) as $pageIds)
		{
			$filter['ID'] = $pageIds;

			$iterator = \CIBlockSection::GetList(
				array(),
				$filter,
				false,
				$select
			);
			while ($row = $iterator->Fetch())
			{
				if ($changePreviewPicture)
				{
					$row['PREVIEW_PICTURE'] = $row['PICTURE'];
				}
				$index = Iblock\Grid\RowType::getIndex(Iblock\Grid\RowType::SECTION, $row['ID']);
				$this->rows[$index] += $row;
			}
			unset($row, $iterator);
		}
	}

	protected function internalLoadElements(): void
	{
		if (empty($this->elementIds))
		{
			return;
		}

		//TODO: replace to full code
		$select = $this->getDataSelect();

		//TODO: replace to full code
		$filter = [
			'IBLOCK_ID' => $this->getIblockId(),
			'CHECK_PERMISSIONS' => 'N',
			'SHOW_NEW' => 'Y',
		];

		foreach (array_chunk($this->elementIds, CATALOG_PAGE_SIZE) as $pageIds)
		{
			$filter['ID'] = $pageIds;

			$iterator = \CIBlockElement::GetList(
				array(),
				$filter,
				false,
				false,
				$select
			);
			while ($row = $iterator->Fetch())
			{
				$row['ID'] = (int)$row['ID'];
				$index = Iblock\Grid\RowType::getIndex(Iblock\Grid\RowType::ELEMENT, $row['ID']);
				$this->rows[$index] += $row;

				if ((int)$row['TYPE'] === Catalog\ProductTable::TYPE_SKU)
				{
					$this->parentIds[] = $row['ID'];
				}
			}
			unset($row, $iterator);
		}

		$this->internalLoadElementsProperties();
	}

	protected function internalLoadElementsProperties(): void
	{
		if (empty($this->elementIds))
		{
			return;
		}

		$propertyColumnsToIds = ElementPropertyProvider::getPropertyIdsFromColumnsIds(
			$this->grid->getVisibleColumnsIds()
		);
		if (empty($propertyColumnsToIds))
		{
			return;
		}

		$multiple = $this->getPropertyMultipleState($propertyColumnsToIds);

		$emptyPropertyValue = $this->getPropertyEmptyValue();

		$emptyProperties = [];
		foreach ($propertyColumnsToIds as $columnId => $propertyId)
		{
			if ($multiple[$propertyId])
			{
				$emptyProperties[$columnId] = [
					$emptyPropertyValue
				];
			}
			else
			{
				$emptyProperties[$columnId] = $emptyPropertyValue;
			}
		}

		$iblockId = $this->getIblockId();
		foreach (array_chunk($this->elementIds, CATALOG_PAGE_SIZE) as $pageIds)
		{
			$properties = [];
			CIBlockElement::GetPropertyValuesArray(
				$properties,
				$iblockId,
				[
					'ID' => $pageIds,
				],
				[
					'ID' => $propertyColumnsToIds,
				],
				[
					'USE_PROPERTY_ID' => 'Y',
					'GET_RAW_DATA' => 'Y',
					'PROPERTY_FIELDS' => [
						'ID',
						'PROPERTY_TYPE',
					],
				]
			);

			foreach ($pageIds as $elementId)
			{
				$index = Iblock\Grid\RowType::getIndex(Iblock\Grid\RowType::ELEMENT, $elementId);
				if (isset($properties[$elementId]))
				{
					foreach ($propertyColumnsToIds as $columnId => $propertyId)
					{
						if (isset($properties[$elementId][$propertyId]))
						{
							$values = $properties[$elementId][$propertyId];
							$isList = $values['PROPERTY_TYPE'] === Iblock\PropertyTable::TYPE_LIST;
							if ($multiple[$propertyId])
							{
								if (is_array($values['VALUE']))
								{
									$list = [];
									foreach (array_keys($values['VALUE']) as $valueIndex)
									{
										$row = [
											'VALUE' => $values['VALUE'][$valueIndex],
											'DESCRIPTION' => $values['DESCRIPTION'][$valueIndex] ?? null,
										];
										if ($isList)
										{
											$row['VALUE_ENUM_ID'] = $values['VALUE_ENUM_ID'][$valueIndex];
										}
										$list[] = $row;
									}
									unset($valueIndex);
									$this->rows[$index][$columnId] = $list;
									unset($list);
								}
								else
								{
									$this->rows[$index][$columnId] = [
										$emptyPropertyValue
									];
								}
							}
							else
							{
								$this->rows[$index][$columnId] = [
									'VALUE' => $values['VALUE'],
									'DESCRIPTION' => $values['DESCRIPTION'] ?? null,
								];
								if ($isList)
								{
									$this->rows[$index][$columnId]['VALUE_ENUM_ID'] = $values['VALUE_ENUM_ID'];
								}
							}
							unset($values);
						}
						else
						{
							if ($multiple[$propertyId])
							{
								$this->rows[$index][$columnId] = [
									$emptyPropertyValue
								];
							}
							else
							{
								$this->rows[$index][$columnId] = $emptyPropertyValue;
							}
						}
					}
				}
				else
				{
					$this->rows[$index] += $emptyProperties;
				}
			}
			unset($pageIds);
		}
	}

	private function getPropertyMultipleState(array $propertyIds): array
	{
		$multiple = array_fill_keys($propertyIds, false);
		$iterator = Iblock\PropertyTable::getList([
			'select' => [
				'ID',
				'MULTIPLE',
			],
			'filter' => [
				'@ID' => $propertyIds,
			],
			'cache' => [
				86400,
			],
		]);
		while ($row = $iterator->fetch())
		{
			$row['ID'] = (int)$row['ID'];
			$multiple[$row['ID']] = $row['MULTIPLE'] === 'Y';
		}
		unset($row, $iterator);

		return $multiple;
	}

	private function getPropertyEmptyValue(): array
	{
		return [
			'VALUE' => null,
			'DESCRIPTION' => null,
		];
	}

	protected function internalLoadProductQuantities(): void
	{
		if (
			empty($this->rows)
			|| !ProductProvider::needSummaryStoreAmountByPermissions()
		)
		{
			return;
		}

		$isShowedQuantitiesColumns =
			in_array('QUANTITY', $this->getVisibleColumnIds(), true)
			|| in_array('QUANTITY_RESERVED', $this->getVisibleColumnIds(), true)
		;
		if (!$isShowedQuantitiesColumns)
		{
			return;
		}

		// zeroing values
		$productsAndOffersIds = [];
		$variationMap = $this->getSelectedVariationMap();

		foreach ($this->rows as &$row)
		{
			$type = (string)$row['ROW_TYPE'];
			if ($type === RowType::SECTION)
			{
				continue;
			}

			$row['QUANTITY'] = null;
			$row['QUANTITY_RESERVED'] = null;

			$productId = (int)$row['ID'];
			$productsAndOffersIds[] = $productId;
			if (isset($variationMap[$productId]))
			{
				$productsAndOffersIds[] = $variationMap[$productId];
			}
		}
		unset($row, $variationMap);

		// fill actual
		$isNoAccessToAnyStores = $this->accessController->getAllowedDefaultStoreId() === null;
		if ($isNoAccessToAnyStores || empty($productsAndOffersIds))
		{
			return;
		}

		$query = StoreProductTable::query()
			->setSelect([
				'PRODUCT_ID',
				new ExpressionField('SUM_QUANTITY', 'SUM(%s - %s)', ['AMOUNT', 'QUANTITY_RESERVED']),
				new ExpressionField('SUM_QUANTITY_RESERVED', 'SUM(%s)', 'QUANTITY_RESERVED'),
			])
			->setGroup('PRODUCT_ID')
			->setFilter([
				'@PRODUCT_ID' => $productsAndOffersIds,
			])
		;

		$accessFilter = $this->accessController->getEntityFilter(ActionDictionary::ACTION_STORE_VIEW, StoreProductTable::class);
		if (!empty($accessFilter))
		{
			$query->addFilter(null, $accessFilter);
		}

		$productVariationMap = array_flip($this->getSelectedVariationMap());
		foreach ($query->exec() as $storeQuantityRow)
		{
			$productId = (int)$storeQuantityRow['PRODUCT_ID'];
			if (isset($productVariationMap[$productId]))
			{
				$productId = $productVariationMap[$productId];
			}

			$index = RowType::getIndex(RowType::ELEMENT, $productId);
			if (empty($this->rows[$index]))
			{
				continue;
			}

			$this->rows[$index]['QUANTITY'] = (float)$storeQuantityRow['SUM_QUANTITY'];
			$this->rows[$index]['QUANTITY_RESERVED'] = (float)$storeQuantityRow['SUM_QUANTITY_RESERVED'];
		}
	}

	protected function internalLoadSku(): void
	{
		if (!$this->hasOffersIblockId())
		{
			return;
		}
		if (!isset($this->skuTree))
		{
			return;
		}
		if (empty($this->parentIds))
		{
			return;
		}
		if (!$this->isViewProductBlock())
		{
			return;
		}

		$this->setSelectedVariationMapFromProductIds($this->parentIds);
		$selectedVariationMap = $this->getSelectedVariationMap();
		if (empty($selectedVariationMap))
		{
			return;
		}

		$offersSelect = [
			'ID',
			'PROPERTY_CML2_LINK',
			... $this->getUpdateVariationFieldNames()
		];
		$offersFilter = [
			'ID' => $selectedVariationMap,
			'IBLOCK_ID' => $this->getOffersIblockId(),
			'CHECK_PERMISSIONS' => 'N',
			'SHOW_NEW' => 'Y',
		];
		$iterator = \CIBlockElement::GetList(
			['SORT' => 'ASC', 'ID' => 'ASC'],
			$offersFilter,
			false,
			false,
			$offersSelect
		);

		while ($row = $iterator->Fetch())
		{
			$productId = (int)$row['PROPERTY_CML2_LINK_VALUE'];
			unset(
				$row['ID'],
				$row['PROPERTY_CML2_LINK_VALUE'],
				$row['PROPERTY_CML2_LINK_VALUE_ID'],
				$row['SORT'],
			);

			$index = Iblock\Grid\RowType::getIndex(Iblock\Grid\RowType::ELEMENT, $productId);
			if (isset($this->rows[$index]))
			{
				$this->rows[$index] = array_merge($this->rows[$index], $row);
			}
		}
		unset($iterator);
	}

	public function executeComponent(): void
	{
		if ($this->hasErrors())
		{
			$this->showErrors();

			return;
		}
		if ($this->checkPermissions()->hasErrors())
		{
			$this->showErrors();

			return;
		}

		if (State::isExternalCatalog())
		{
			$this->showCatalogStub();

			return;
		}

		if ($this->init()->hasErrors())
		{
			$this->showErrors();

			return;
		}

		$this->grid->processRequest();

		$this->loadRows();
		$this->prepareResult();

		if ($this->allowExcelExport())
		{
			$this->processExcelExport();
		}

		$this->initToolbar();
		$this->includeComponentTemplate();

		$this->clear();
	}

	/**
	 * @return bool
	 */
	public function isExcelExport(): bool
	{
		return $this->getExcelExporter()->isExportRequest($this->request);
	}

	protected function processExcelExport(): void
	{
		if ($this->isExcelExport())
		{
			$this->getExcelExporter()->process(
				$this->grid,
				$this->arParams['EXPORT_FILE_NAME'] ?? 'export'
			);
		}
	}

	protected function clear(): void
	{
		//unset($this->rightList);

		unset($this->rows);
		unset($this->elementIds, $this->sectionIds);
	}

	private function getExcelExporter(): ExcelExporter
	{
		$this->excelExporter ??= new ExcelExporter();

		return $this->excelExporter;
	}

	private function getUrlBuilder(): ShopBuilder
	{
		if (!isset($this->urlBuilder))
		{
			$paramsBuilder = $this->arParams['URL_BUILDER'] ?? null;
			if ($paramsBuilder instanceof ShopBuilder)
			{
				$this->urlBuilder = $paramsBuilder;
			}
			else
			{
				$this->urlBuilder = new ShopBuilder();
			}

			$this->urlBuilder->setIblockId($this->getIblockId());
			$this->urlBuilder->setUrlParams([]); // hack for append slider URL params
		}

		return $this->urlBuilder;
	}

	private function initToolbar(): void
	{
		$this->initToolbarCreateButtons();
		$this->initToolbarSettings();
	}

	private function getCreateButtonMenuItemsForNewCard(): array
	{
		$options = [
			'IBLOCK_SECTION_ID' => $this->getSelectedSectionId(),
		];

		return [
			[
				'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_PRODUCT_TEXT'),
				'href' => $this->getUrlBuilder()->getProductDetailUrl(0, $options),
			],
			[
				'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_SERVICE_TEXT'),
				'href' => $this->getUrlBuilder()->getProductDetailUrl(0, $options + [
					'productTypeId' => ProductTable::TYPE_SERVICE,
				]),
			],
			[
				'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_SECTION_TEXT'),
				'href' => $this->getUrlBuilder()->getSectionDetailUrl(0, $options),
			],
		];
	}

	private function getCreateButtonMenuItemsForOldCard(): array
	{
		$options = [
			'IBLOCK_SECTION_ID' => $this->getSelectedSectionId(),
		];

		$result = [
			[
				'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_PRODUCT_TEXT'),
				'href' => $this->getUrlBuilder()->getElementDetailUrl(0, $options),
			],
		];

		if ($this->catalogType === CCatalogSku::TYPE_FULL)
		{
			$result[] = [
				'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_SKU_TEXT'),
				'href' => $this->getUrlBuilder()->getElementDetailUrl(0, $options + [
					'PRODUCT_TYPE' => CCatalogAdminTools::TAB_SKU,
				]),
			];
		}

		$result[] = [
			'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_SERVICE_TEXT'),
			'href' => $this->getUrlBuilder()->getElementDetailUrl(0, $options + [
				'PRODUCT_TYPE' => CCatalogAdminTools::TAB_SERVICE,
			]),
		];

		if (Feature::isProductSetsEnabled())
		{
			if ($this->catalogType !== CCatalogSku::TYPE_OFFERS)
			{
				$result[] = [
					'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_SET_TEXT'),
					'href' => $this->getUrlBuilder()->getElementDetailUrl(0, $options + [
						'PRODUCT_TYPE' => CCatalogAdminTools::TAB_SET,
					]),
				];
			}

			$result[] = [
				'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_GROUP_TEXT'),
				'href' => $this->getUrlBuilder()->getElementDetailUrl(0, $options + [
					'PRODUCT_TYPE' => CCatalogAdminTools::TAB_GROUP,
				]),
			];
		}

		$result[] = [
			'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_ADD_SECTION_TEXT'),
			'href' => $this->getUrlBuilder()->getSectionDetailUrl(0, $options),
		];

		return $result;
	}

	private function initToolbarCreateButtons(): void
	{
		$productLimits = null;
		if ($this->hasIblock())
		{
			$productLimits = Catalog\Config\State::getExceedingProductLimit(
				$this->getIblockId(),
				$this->getSelectedSectionId() ?: null
			);
		}

		if (empty($productLimits))
		{
			$createButton = new CreateButton();

			// flag create button for js ext `catalog.iblock-product-list`
			$createButton->addDataAttribute('grid-create-button', $this->getGridId());
			$createButton->addDataAttribute('toolbar-collapsed-icon', UI\Buttons\Icon::ADD);

			$menuItems = [];
			if ($this->allowAdd())
			{
				if ($this->arParams['USE_NEW_CARD'] === 'Y')
				{
					$menuItems = $this->getCreateButtonMenuItemsForNewCard();
				}
				else
				{
					$menuItems = $this->getCreateButtonMenuItemsForOldCard();
				}
			}

			if ($this->allowInstagramImport())
			{
				$menuItems[] = [
					'text' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_IMPORT_INSTAGRAM_TEXT'),
					'title' => Loc::getMessage('CATALOG_PRODUCT_GRID_CMP_TOOLBAR_BUTTONS_IMPORT_INSTAGRAM_TITLE'),
					'href' => Option::get('crm', 'path_to_order_import_instagram'),
				];
			}

			if (empty($menuItems))
			{
				$createButton->setDisabled();
				$createButton->getAttributeCollection()['id'] = 'create_new_product_button_access_denied';
			}
			else
			{
				$createButton->getMainButton()->setLink(
					$menuItems[0]['href']
				);
				$createButton->setMenu([
					'items' => $menuItems,
					'closeByEsc' => true,
					'angle' => true,
					'offsetLeft' => 20,
				]);
				$createButton->getAttributeCollection()->addJsonOption(
					'menuTarget',
					\Bitrix\UI\Buttons\Split\Type::MENU
				);
			}

		}
		else
		{
			if (isset($productLimits['HELP_MESSAGE']['TYPE'], $productLimits['HELP_MESSAGE']['LINK']))
			{
				$createButton = new \Bitrix\UI\Buttons\CreateButton();
				if ($productLimits['HELP_MESSAGE']['TYPE'] === 'ONCLICK')
				{
					$createButton->getAttributeCollection()['onclick'] = $productLimits['HELP_MESSAGE']['LINK'];
				}
				else
				{
					$createButton->setLink($productLimits['HELP_MESSAGE']['LINK']);
				}
			}
		}

		if (isset($createButton))
		{
			Toolbar::addButton($createButton, ButtonLocation::AFTER_TITLE);
		}
	}

	private function initToolbarSettings(): void
	{
		$items = $this->getUrlBuilder()->getContextMenuItems(
			CatalogBuilder::PAGE_ELEMENT_LIST
		);
		$menu = array_map(
			static function (array $item)
			{
				$result = [
					'text' => $item['TEXT'] ?? null,
				];

				if (isset($item['TITLE']))
				{
					$result['title'] = $item['TITLE'];
				}

				if (isset($item['LINK']))
				{
					$result['href'] = $item['LINK'];
				}

				if (isset($item['ONCLICK']))
				{
					$result['onclick'] = [
						'code' => $item['ONCLICK'],
					];
				}

				return $result;
			},
			$items
		);

		if ($this->allowExcelExport())
		{
			$menu[] = $this->getExportExcelDropdownItem();
		}

		$settingsButton = new SettingsButton();
		$settingsButton->setMenu([
			'items' => $menu,
		]);

		Toolbar::addButton($settingsButton, ButtonLocation::RIGHT);
	}

	private function getExportExcelDropdownItem(): array
	{
		$button = $this->getExcelExporter()->getControl($this->request);

		$link = new Uri($button->getLink());
		$link->deleteParams([
			'SECTION_ID',
			'find_section_section',
			'apply_filter',
		]);
		// TODO: remove this code after stable main 23.800.0
		$link->addParams([
			'ncc' => 1,
		]);

		return [
			'text' => $button->getText(),
			// 'href' => $button->getLink(),
			'href' => (string)$link,
		];
	}

	public function showDetailPageSlider(): void
	{
		$this->getUrlBuilder()->showDetailPageSlider();
	}

	private function getProductImplicitNavigationData(\CDBResult $iterator): string
	{
		$navComponentObject = null;
		$navComponentParameters = [];
		if ($this->arParams['BASE_LINK'] !== '')
		{
			$navComponentParameters["BASE_LINK"] = \CHTTP::urlAddParams(
				$this->arParams['BASE_LINK'],
				[],
				['encode' => true]
			);
		}
		return (string)$iterator->GetPageNavStringEx(
			$navComponentObject,
			'',
			'grid',
			true,
			null,
			$navComponentParameters
		);
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit