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/yandex.market/lib/export/run/steps/ |
Upload File : |
<?php namespace Yandex\Market\Export\Run\Steps; use Bitrix\Main; use Yandex\Market; Main\Localization\Loc::loadMessages(__FILE__); /** * @method Market\Export\Xml\Tag\Base getTag($type = null) */ class Offer extends Base { const ELEMENT_TYPE_PRODUCT = 1; /** @noinspection PhpUnused */ const ELEMENT_TYPE_SET = 2; const ELEMENT_TYPE_SKU = 3; const ELEMENT_TYPE_OFFER = 4; /** @noinspection PhpUnused */ const ELEMENT_TYPE_FREE_OFFER = 5; const ELEMENT_TYPE_EMPTY_SKU = 6; public function getName() { return Market\Export\Run\Manager::STEP_OFFER; } public function getReadyCount() { $dataClass = $this->getStorageDataClass(); $context = $this->getContext(); $readyFilter = $this->getStorageReadyFilter($context); $result = 0; $query = $dataClass::getList([ 'filter' => $readyFilter, 'select' => [ 'CNT' ], 'runtime' => [ new Main\Entity\ExpressionField('CNT', 'COUNT(1)') ] ]); if ($row = $query->fetch()) { $result = (int)$row['CNT']; } return $result; } public function getSuccessCount($context = null) { if ($context === null) { $context = $this->getContext(); } $dataClass = $this->getStorageDataClass(); $readyFilter = $this->getStorageReadyFilter($context, true); $readyFilter['=STATUS'] = static::STORAGE_STATUS_SUCCESS; $result = 0; $query = $dataClass::getList([ 'filter' => $readyFilter, 'select' => [ 'CNT' ], 'runtime' => [ new Main\Entity\ExpressionField('CNT', 'COUNT(1)') ] ]); if ($row = $query->fetch()) { $result = (int)$row['CNT']; } return $result; } public function getCount($offset = null, $isNeedAll = null) { $result = new Market\Result\StepCount(); $offsetObject = new Market\Data\Run\Offset($offset); foreach ($this->getIblockConfigList($isNeedAll) as $iblockConfig) { if (!$offsetObject->tick('iblock')) { continue; } $counterManager = new Market\Export\Run\Counter\Manager(); do { try { $isNeedRepeatCount = false; $counter = null; $previousFilterSum = 0; foreach ($iblockConfig['FILTER_LIST'] as $sourceFilter) { $filterContext = $sourceFilter['CONTEXT'] + $iblockConfig['CONTEXT']; $filterBuilder = new Market\Export\Routine\QueryBuilder\Filter(); $exportFilter = $filterBuilder->boot($sourceFilter['FILTER'], $filterContext); $queryFilters = $this->compileQueryFilters($filterBuilder, $exportFilter, [], $filterContext); $filterCount = 0; $isIblockConfigFilter = ($sourceFilter['ID'] === null); if ($isIblockConfigFilter) { $totalCount = 0; foreach ($queryFilters as $queryFilter) { $totalCount += $this->queryTotalCount($queryFilter, $filterContext); } $filterCount = $totalCount - $previousFilterSum; if ($this->isCatalogTypeCompatibility($filterContext)) { $result->addCountWarning($iblockConfig['ID'], new Market\Error\Base( Market\Config::getLang('EXPORT_RUN_STEP_OFFER_COUNT_CATALOG_TYPE_COMPATIBILITY') )); } } else if (!empty($sourceFilter['FILTER'])) { if ($counter === null) { $counter = $counterManager->getCounter(); $counter->start(); } foreach ($queryFilters as $queryFilter) { $filterCount += $this->queryCount($queryFilter, $filterContext, $counter); } $previousFilterSum += $filterCount; } if ($isIblockConfigFilter) // is iblock link { $result->setCount($iblockConfig['ID'], $filterCount); } else { $result->setCount($iblockConfig['ID'] . ':' . $sourceFilter['ID'], $filterCount); } $filterBuilder->release($sourceFilter['FILTER'], $filterContext); if ($offsetObject->get('filter') !== null && $offsetObject->tick('filter')) { break; } } if ($counter !== null) { $counter->finish(); } } catch (Main\SystemException $exception) { $counterManager->invalidateCounter(); if ($counterManager->hasCounter()) { $isNeedRepeatCount = true; } else { $result->addCountWarning($iblockConfig['ID'], new Market\Error\Base( Market\Config::getLang('EXPORT_RUN_STEP_OFFER_COUNT_FAILED') )); } } } while ($isNeedRepeatCount); if ($offset !== null) { break; } } return $result; } public function run($action, $offset = null) { $result = new Market\Result\Step(); $this->setRunAction($action); $formatTag = $this->getTag(); $isTimeExpired = false; $offsetObject = new Market\Data\Run\Offset($offset); foreach ($this->getIblockConfigList() as $iblockConfig) { if (!$offsetObject->tick('iblock')) { continue; } $tagDescriptionList = $iblockConfig['TAG_DESCRIPTION_LIST']; $iblockContext = $iblockConfig['CONTEXT']; $iblockLimit = (isset($iblockConfig['LIMIT']) ? (int)$iblockConfig['LIMIT'] : null); $iblockReadyCount = 0; $hasIblockLimit = ($iblockLimit > 0); $isExceededIblockLimit = false; $changesFilter = null; if ($action === 'change') { $changes = $this->getChanges(); $changesFilter = $this->getQueryChangesFilter($changes, $iblockContext); if ($changesFilter === null) { continue; } // changed other entity } $formatTag->extendTagDescriptionList($tagDescriptionList, $iblockContext); $selectBuilder = new Market\Export\Routine\QueryBuilder\Select(); $sourceSelect = (new Market\Export\Param\TagMap($tagDescriptionList))->getSourceSelect(); $sourceSelect = $selectBuilder->boot($sourceSelect, $iblockContext); $querySelect = $selectBuilder->compile($sourceSelect, $iblockContext); $this->applySelectMap($tagDescriptionList, $iblockContext); if ($hasIblockLimit && $offsetObject->get('filter') !== null) { $iblockReadyCount = $this->getSuccessCount($iblockContext); $isExceededIblockLimit = ($iblockReadyCount >= $iblockLimit); if ($isExceededIblockLimit) { continue; } } foreach ($iblockConfig['FILTER_LIST'] as $filterConfig) { if (!$offsetObject->tick('filter')) { continue; } $filterContext = $filterConfig['CONTEXT'] + $iblockContext; $filterBuilder = new Market\Export\Routine\QueryBuilder\Filter(); $exportFilter = $filterBuilder->boot($filterConfig['FILTER'], $filterContext); $queryFilters = $this->compileQueryFilters($filterBuilder, $exportFilter, $sourceSelect, $filterContext, $changesFilter); foreach ($queryFilters as $queryFilter) { if (!$offsetObject->tick('query')) { continue; } $filterResult = $this->exportIblockFilter( $queryFilter, $sourceSelect, $querySelect, $tagDescriptionList, $filterContext, $offsetObject->get('element'), $iblockLimit, $iblockReadyCount ); $iblockReadyCount += $filterResult['SUCCESS_COUNT']; if ($filterResult['OFFSET'] !== null) { $isTimeExpired = true; $offsetObject->set('element', $filterResult['OFFSET']); } else if ($hasIblockLimit && $iblockReadyCount >= $iblockLimit) { $isExceededIblockLimit = true; $isTimeExpired = $this->getProcessor()->isTimeExpired(); $offsetObject->next('iblock'); break; } else { $isTimeExpired = $this->getProcessor()->isTimeExpired(); $offsetObject->next('query'); } if ($isTimeExpired) { break; } } $filterBuilder->release($exportFilter, $filterContext); if ($isExceededIblockLimit || $isTimeExpired) { break; } } $selectBuilder->release($sourceSelect, $iblockContext); if ($isTimeExpired) { break; } } if ($isTimeExpired) { $result->setOffset((string)$offsetObject); $result->setTotal(1); if ($this->getParameter('progressCount') === true) { $result->setReadyCount($this->getReadyCount()); } } return $result; } public function getFormatTag(Market\Export\Xml\Format\Reference\Base $format, $type = null) { return $format->getOffer(); } public function getFormatTagParentName(Market\Export\Xml\Format\Reference\Base $format) { return $format->getOfferParentName(); } protected function getOfferTag() { $step = $this->getProcessor()->getStep(Market\Export\Run\Manager::STEP_OFFER); return $step->getTag(); } protected function getOfferPrimarySource(Market\Export\IblockLink\Model $iblockLink, array $context) { /** @var Market\Export\Xml\Tag\Base $offerTag */ $offerTag = $this->getOfferTag(); $offerName = $offerTag->getName(); $primaryName = $this->getTagPrimaryName($offerTag); $tagDescription = $iblockLink->getTagDescription($offerName); $tagDescription = $offerTag->extendTagDescription($tagDescription, $context); $result = null; if ($tagDescription !== null && isset($tagDescription['ATTRIBUTES'][$primaryName])) { $result = $tagDescription['ATTRIBUTES'][$primaryName]; } return $result; } protected function useHashCollision() { return $this->getFormat()->useOfferHashCollision(); } protected function usePrimaryCollision($context) { return !empty($context['USE_PRIMARY_COLLISION']) && $this->useTagPrimary(); } /** * �������� �� ���������� �������������� �� ���� * * @param $tagDescriptionList array[] * @param $context array * * @return bool */ protected function resolvePrimaryCollision($tagDescriptionList, $context) { $tagType = isset($context['ELEMENT_TYPE']) ? $context['ELEMENT_TYPE'] : null; $tag = $this->getTag($tagType); $result = false; if ($tag !== null) { $tagName = $tag->getName(); $primaryName = $this->getTagPrimaryName($tag); foreach ($tagDescriptionList as $tagDescription) { if ( $tagDescription['TAG'] === $tagName && isset( $tagDescription['ATTRIBUTES'][$primaryName]['TYPE'], $tagDescription['ATTRIBUTES'][$primaryName]['FIELD'] ) ) { $primarySource = $tagDescription['ATTRIBUTES'][$primaryName]['TYPE']; $primaryField = $tagDescription['ATTRIBUTES'][$primaryName]['FIELD']; if ($primaryField === 'ID') { $sourcesWithoutCollision = [ Market\Export\Entity\Manager::TYPE_IBLOCK_OFFER_FIELD => true, ]; if (!$context['HAS_OFFER']) { $sourcesWithoutCollision[Market\Export\Entity\Manager::TYPE_IBLOCK_ELEMENT_FIELD] = true; } $result = !isset($sourcesWithoutCollision[$primarySource]); } else { $result = true; } break; } } } return $result; } protected function getTagResultHash($xmlContent, Market\Result\XmlValue $tagValues = null) { if ($xmlContent === null) { return ''; } return $this->calculateXmlContentHash( $xmlContent, $tagValues !== null ? $tagValues->getType() : null ); } public function calculateXmlContentHash($xmlContent, $tagType = null) { if ($this->useHashCollision()) { $tag = $this->getTag($tagType); $primaryName = $this->getTagPrimaryName($tag); $xmlContent = preg_replace('/^(<[^ ]+) ' . $primaryName . '="[^"]*?"/', '$1', $xmlContent); // remove id attr for check tag contents $xmlContent = preg_replace_callback('/(<url>.*?\?)(.*?)(#.*)?(<\/url>)/', static function($matches) { $utmMarker = 'utm_'; $queryString = $matches[2]; if (Market\Data\TextString::getPosition($queryString, $utmMarker) !== false) { $glue = '&'; $isChanged = false; $queryParameters = explode($glue, $queryString); foreach ($queryParameters as $queryParameterIndex => $queryParameter) { if (Market\Data\TextString::getPosition($queryParameter, $utmMarker) === 0) { $isChanged = true; unset($queryParameters[$queryParameterIndex]); } } if ($isChanged) { $queryString = implode($glue, $queryParameters); } } return $matches[1] . $queryString . $matches[3] . $matches[4]; }, $xmlContent); // remove utm from url } return md5($xmlContent); } protected function getStorageDataClass() { return Market\Export\Run\Storage\OfferTable::class; } protected function getStorageChangesFilter($changes, $context) { $isNeedFull = false; $result = []; if (!empty($changes)) { foreach ($changes as $changeType => $entityIds) { switch ($changeType) { case Market\Export\Run\Manager::ENTITY_TYPE_OFFER: $dataClass = $this->getStorageDataClass(); $elementFilter = []; $parentFilter = []; $query = $dataClass::getList([ 'filter' => [ '=SETUP_ID' => $context['SETUP_ID'], [ 'LOGIC' => 'OR', [ '=ELEMENT_ID' => $entityIds ], [ '=PARENT_ID' => $entityIds ] ] ], 'select' => [ 'ELEMENT_ID', 'PARENT_ID' ] ]); while ($row = $query->fetch()) { $parentId = (int)$row['PARENT_ID']; if ($parentId > 0) { $parentFilter[$parentId] = true; } else { $elementFilter[] = (int)$row['ELEMENT_ID']; } } $hasParentFilter = !empty($parentFilter); $hasElementFilter = !empty($elementFilter); if ($hasParentFilter || $hasElementFilter) { if ($hasParentFilter) { $result[] = [ '=PARENT_ID' => array_keys($parentFilter) ]; } if ($hasElementFilter) { $result[] = [ '=ELEMENT_ID' => $elementFilter ]; } } break; case Market\Export\Run\Manager::ENTITY_TYPE_CATEGORY: $result[] = [ '=CATEGORY_ID' => $entityIds ]; break; case Market\Export\Run\Manager::ENTITY_TYPE_CURRENCY: $result[] = [ '=CURRENCY_ID' => $entityIds ]; break; default: $isNeedFull = true; break; } if ($isNeedFull) { break; } } } if ($isNeedFull) { $result = []; } else if (empty($result)) { $result = null; } else if (count($result) > 1) { $result['LOGIC'] = 'OR'; } return $result; } protected function getStorageAdditionalData($tagResult, $tagValues, $element, $context, $data) { $categoryId = $tagValues->getTagValue('categoryId') ?: ''; $currencyId = $tagValues->getTagValue('currencyId') ?: ''; return [ 'PARENT_ID' => isset($element['PARENT_ID']) ? $element['PARENT_ID'] : '', 'IBLOCK_LINK_ID' => isset($context['IBLOCK_LINK_ID']) ? $context['IBLOCK_LINK_ID'] : '', 'FILTER_ID' => isset($context['FILTER_ID']) ? $context['FILTER_ID'] : '', 'CATEGORY_ID' => $categoryId, 'CURRENCY_ID' => $currencyId ]; } protected function getDataLogEntityType() { return Market\Logger\Table::ENTITY_TYPE_EXPORT_RUN_OFFER; } protected function isAllowPublicDelete() { return true; } protected function getIgnoredTypeChanges() { return [ Market\Export\Run\Manager::ENTITY_TYPE_PROMO => true, Market\Export\Run\Manager::ENTITY_TYPE_GIFT => true, Market\Export\Run\Manager::ENTITY_TYPE_COLLECTION => true, ]; } /** * �������� ������� �� ���������� * * @param bool|null $isNeedAll * * @return array */ protected function getIblockConfigList($isNeedAll = null) { $setup = $this->getSetup(); $result = []; /** @var Market\Export\IblockLink\Model $iblockLink */ foreach ($setup->getIblockLinkCollection() as $iblockLink) { $iblockContext = $iblockLink->getContext(); $result[] = [ 'ID' => $iblockLink->getInternalId(), 'EXPORT_ALL' => $iblockLink->isExportAll(), 'TAG_DESCRIPTION_LIST' => $iblockLink->getTagDescriptionList(), 'FILTER_LIST' => $this->getSourceFilterList($iblockLink, $isNeedAll), 'CONTEXT' => $iblockContext, ]; } return $result; } /** * ������� ��������� �� ������ * * @param $queryFilter * @param $sourceSelect * @param $querySelect * @param $tagDescriptionList * @param $context * @param int|null $queryOffset * @param int|null $limit * @param int $successCount * * @return array 'OFFSET' => ������ �� ��������� �������, 'SUCCESS_COUNT' => ��������� ������ �������� ����� */ protected function exportIblockFilter($queryFilter, $sourceSelect, $querySelect, $tagDescriptionList, $context, $queryOffset = null, $limit = null, $successCount = 0) { $elementFetcher = $this->createElementFetcher($context); $collectionLink = new Offer\CollectionLink($this->getSetup(), $this->getFormat(), $this->getRunAction()); $queryDistinct = !empty($queryFilter['DISTINCT']) ? $queryFilter['DISTINCT'] : null; $useDistinct = ($queryDistinct !== null); $hasLimit = ($limit > 0); $processChunkSize = ($hasLimit ? $limit : 500); $result = [ 'OFFSET' => null, 'SUCCESS_COUNT' => 0 ]; $context['USE_DISTINCT'] = $useDistinct; $context['USE_PRIMARY_COLLISION'] = $this->resolvePrimaryCollision($tagDescriptionList, $context); do { $queryResult = $elementFetcher->load($queryFilter, $querySelect, $context, $queryOffset); $queryOffset = (int)$queryResult['OFFSET']; $this->processExportElementList($queryResult['ELEMENT'], $queryResult['PARENT'], $context); foreach ($this->chunkElementList($queryResult['ELEMENT'], $processChunkSize, $useDistinct) as $elementChunk) { $parentChunk = array_intersect_key($queryResult['PARENT'], array_column($elementChunk, 'PARENT_ID', 'PARENT_ID')); $sourceValueList = $this->extractElementListValues($sourceSelect, $elementChunk, $parentChunk, $context); $sourceValueList = $collectionLink->extend(array_column($elementChunk, 'ID'), $sourceValueList); // write $writeLimit = ($hasLimit ? $limit - $successCount : null); $tagValuesList = $this->buildTagValuesList($tagDescriptionList, $sourceValueList, $context); $writeData = [ 'PARENT_LIST' => $parentChunk, 'SOURCE_VALUE' => $sourceValueList, ]; $this->extendData($tagValuesList, $elementChunk, $context, $writeData); if ($useDistinct) { $tagValuesList = $this->resolveDistinctGroups($queryDistinct, $elementChunk, $sourceValueList, $tagValuesList, $context); } $writeResultList = $this->writeData($tagValuesList, $elementChunk, $context, $writeData, $writeLimit); $written = array_filter($writeResultList, static function(array $writeResult) { return $writeResult['STATUS'] === static::STORAGE_STATUS_SUCCESS; }); $collectionLink->commit(array_column($written, 'ID', 'ID')); $successCount += count($written); $result['SUCCESS_COUNT'] += count($written); if ($hasLimit && $successCount >= $limit) { break; } } if ($hasLimit && $successCount >= $limit) { $result['OFFSET'] = null; break; } if ($queryResult['HAS_NEXT'] && $this->getProcessor()->isTimeExpired()) { $result['OFFSET'] = $queryOffset; break; } } while ($queryResult['HAS_NEXT']); return $result; } protected function createElementFetcher(array $context) { $fetcher = new Market\Export\Routine\QueryBuilder\ElementFetcher(); if (empty($context['IGNORE_EXCLUDE'])) { $fetcher->exclude( $this->getStorageDataClass(), $this->getStorageReadyFilter($context), 'ELEMENT_ID', $context['HAS_OFFER'] && !empty($context['USE_DISTINCT']) ? 'PARENT_ID' : null ); } return $fetcher; } protected function processExportElementList(&$elementList, &$parentList, $context) { // nothing by default } protected function applySelectMap(&$tagDescriptionList, $iblockContext) { if (!empty($iblockContext['SELECT_MAP'])) { $selectMap = $iblockContext['SELECT_MAP']; $innerTypes = [ 'ATTRIBUTES', 'SETTINGS' ]; foreach ($tagDescriptionList as &$tagDescription) { if (isset($tagDescription['VALUE'])) { $valueSourceMap = $tagDescription['VALUE']; if (isset($selectMap[$valueSourceMap['TYPE']][$valueSourceMap['FIELD']])) { $tagDescription['VALUE']['FIELD'] = $selectMap[$valueSourceMap['TYPE']][$valueSourceMap['FIELD']]; } } foreach ($innerTypes as $innerType) { if (isset($tagDescription[$innerType])) { foreach ($tagDescription[$innerType] as &$innerSourceMap) { if (is_array($innerSourceMap) && isset($selectMap[$innerSourceMap['TYPE']][$innerSourceMap['FIELD']])) { $innerSourceMap['FIELD'] = $selectMap[$innerSourceMap['TYPE']][$innerSourceMap['FIELD']]; } } unset($innerSourceMap); } } if (isset($tagDescription['CHILDREN']) && is_array($tagDescription['CHILDREN'])) { $this->applySelectMap($tagDescription['CHILDREN'], $iblockContext); } } unset($tagDescription); } } /** @noinspection DuplicatedCode */ protected function getQueryChangesFilter($changes, $context) { $changesFilter = []; $isNeedFull = false; foreach ($changes as $changeType => $entityIds) { $entityType = null; $entityFilter = null; switch ($changeType) { case Market\Export\Run\Manager::ENTITY_TYPE_OFFER: case Market\Export\Run\Manager::ENTITY_TYPE_GIFT: if (!isset($context['OFFER_IBLOCK_ID'])) { $entityType = 'ELEMENT'; $entityFilter = [ 'ID' => $entityIds ]; } else { // no support for only one offer change $elementIdsMap = array_flip($entityIds); $queryOffers = \CIBlockElement::GetList( array(), array( 'IBLOCK_ID' => $context['OFFER_IBLOCK_ID'], 'ID' => $entityIds ), false, false, array( 'IBLOCK_ID', 'ID', 'PROPERTY_' . $context['OFFER_PROPERTY_ID'] ) ); while ($offer = $queryOffers->Fetch()) { $offerId = (int)$offer['ID']; $offerElementId = (int)$offer['PROPERTY_' . $context['OFFER_PROPERTY_ID'] . '_VALUE']; if ($offerElementId > 0 && !isset($elementIdsMap[$offerElementId])) { $elementIdsMap[$offerElementId] = true; } if (isset($elementIdsMap[$offerId])) { unset($elementIdsMap[$offerId]); } } $entityType = 'ELEMENT'; $entityFilter = [ 'ID' => !empty($elementIdsMap) ? array_keys($elementIdsMap) : -1 ]; } break; case Market\Export\Run\Manager::ENTITY_TYPE_CATEGORY: $entityType = 'ELEMENT'; $entityFilter = [ 'SECTION_ID' => $entityIds, 'INCLUDE_SUBSECTIONS' => 'Y' ]; break; case Market\Export\Run\Manager::ENTITY_TYPE_PROMO: /** @noinspection TypeUnsafeArraySearchInspection */ if (isset($context['PROMO_ID']) && in_array($context['PROMO_ID'], (array)$entityIds)) { $isNeedFull = true; } break; default: // unsupported change, need full refresh $isNeedFull = true; break; } if ($isNeedFull) { $changesFilter = []; break; } if (isset($entityType, $entityFilter)) { if (!isset($changesFilter[$entityType])) { $changesFilter[$entityType] = []; } else if (count($changesFilter[$entityType]) === 1) { $changesFilter[$entityType]['LOGIC'] = 'OR'; } $changesFilter[$entityType][] = $entityFilter; } } if (!$isNeedFull && empty($changesFilter)) { $changesFilter = null; } return $changesFilter; } protected function queryCount($queryFilter, $queryContext, Market\Export\Run\Counter\Base $counter) { $countContext = $queryContext; $countContext['PAGE_SIZE'] = (int)(Market\Config::getOption('export_count_offer_page_size') ?: 100); $countContext['CATALOG_TYPE_COMPATIBILITY'] = $queryContext['HAS_OFFER'] && $this->isCatalogTypeCompatibility($queryContext); $countContext['USE_DISTINCT'] = !empty($queryFilter['DISTINCT']); return $counter->count($queryFilter, $countContext); } protected function queryTotalCount($queryFilter, $queryContext) { $hasOffers = isset($queryContext['OFFER_PROPERTY_ID']); $isOnlyOffers = !empty($queryContext['OFFER_ONLY']); $result = 0; // element count if (!$isOnlyOffers) { $elementFilter = $queryFilter['ELEMENT']; if ($hasOffers) { $catalogTypeFieldName = Market\Export\Entity\Catalog\Provider::useCatalogShortFields() ? 'TYPE' : 'CATALOG_TYPE'; $elementFilter['!' . $catalogTypeFieldName] = static::ELEMENT_TYPE_SKU; } $result += (int)\CIBlockElement::GetList([], $elementFilter, []); } // offers count if ($hasOffers) { $result += (int)\CIBlockElement::GetList([], $queryFilter['OFFERS'], []); } return $result; } protected function compileQueryFilters( Market\Export\Routine\QueryBuilder\Filter $filterBuilder, array $sourceFilter, array $sourceSelect, array $context, array $changesFilter = null ) { $queryFilters = $filterBuilder->compile($sourceFilter, $sourceSelect, $context, $changesFilter); $queryFilters = $this->applyFewQueryFiltersModifications($queryFilters, $context); return $queryFilters; } protected function applyFewQueryFiltersModifications(array $queryFilters, array $filterContext) { foreach ($queryFilters as &$queryFilter) { $queryFilter = $this->applyQueryFilterModifications($queryFilter, $filterContext); } unset($queryFilter); return $queryFilters; } /** * �������� ����� ��-������� �� ������-������ ������� * * @param $queryFilter * @param $queryContext * * @return array */ protected function applyQueryFilterModifications($queryFilter, $queryContext) { $result = $queryFilter; $result = $this->modifyQueryFilterBySectionActive($result, $queryContext); return $result; } /** * �������� ����� �� ���������� ������� * * @param $queryFilter * @param $queryContext * * @return array */ protected function modifyQueryFilterBySectionActive($queryFilter, $queryContext) { if ( isset($queryFilter['ELEMENT']) && empty($queryContext['FILTER_MANUAL']['ELEMENT']['SECTION_ACTIVE']) && !$this->hasQuerySectionFilter($queryFilter['ELEMENT']) ) { if ($this->isIblockElementSectionRequired($queryContext)) { $queryFilter['ELEMENT'][] = [ 'SECTION_GLOBAL_ACTIVE' => 'Y', ]; } else { $queryFilter['ELEMENT'][] = [ 'LOGIC' => 'OR', [ 'SECTION_ID' => 0 ], [ 'SECTION_GLOBAL_ACTIVE' => 'Y' ], ]; } } return $queryFilter; } /** * ��� ����� �� ������� * * @param $elementFilter * * @return bool */ protected function hasQuerySectionFilter($elementFilter) { $result = false; if (is_array($elementFilter)) { foreach ($elementFilter as $fieldName => $filter) { if ($fieldName === 'SUBSECTION' || Market\Data\TextString::getPosition($fieldName, 'SECTION_') === 0) { $result = true; } else if (is_numeric($fieldName) && (!isset($filter['LOGIC']) || $filter['LOGIC'] !== 'OR')) { $result = $this->hasQuerySectionFilter($filter); } if ($result === true) { break; } } } return $result; } /** * @param array $context * * @return bool */ protected function isIblockElementSectionRequired($context) { $iblockLink = $this->getContextIblockLink($context); $result = false; if ($iblockLink !== null) { $tagDescription = $iblockLink->getTagDescription('categoryId'); $result = ( isset($tagDescription['VALUE']['TYPE'], $tagDescription['VALUE']['FIELD']) && $tagDescription['VALUE']['TYPE'] === Market\Export\Entity\Manager::TYPE_IBLOCK_ELEMENT_FIELD && $tagDescription['VALUE']['FIELD'] === 'IBLOCK_SECTION_ID' ); } return $result; } /** * @param array $context * * @return Market\Export\IblockLink\Model|null */ protected function getContextIblockLink($context) { $result = null; if (isset($context['IBLOCK_LINK'])) { $result = $context['IBLOCK_LINK']; } else if (isset($context['IBLOCK_LINK_ID'])) { $result = $this->getSetup()->getIblockLinkCollection()->getItemById($context['IBLOCK_LINK_ID']); } return $result; } /** * ����� �� ������ ��������� * * @param $queryContext array * @param $isNeedFull bool * * @return array */ protected function getStorageReadyFilter($queryContext, $isNeedFull = false) { $filter = [ '=SETUP_ID' => $queryContext['SETUP_ID'] ]; if (isset($queryContext['IBLOCK_LINK_ID'])) { $filter['=IBLOCK_LINK_ID'] = $queryContext['IBLOCK_LINK_ID']; } if (!$isNeedFull) { switch ($this->getRunAction()) { case 'change': case 'refresh': $filter['>=TIMESTAMP_X'] = $this->getParameter('initTimeUTC'); break; } } return $filter; } /** * ���������� ������ "������� �� ���������" �� ��������� �������� * * @param \Yandex\Market\Export\IblockLink\Model $iblockLink * @param $isNeedAll bool|null * * @return array */ protected function getSourceFilterList(Market\Export\IblockLink\Model $iblockLink, $isNeedAll = null) { $result = []; $filterCollection = $iblockLink->getFilterCollection(); $isFirstFilter = true; $commonContext = [ 'CAN_IGNORE_OFFER_SUBQUERY' => ($this->getName() === Market\Export\Run\Manager::STEP_OFFER), ]; /** @var \Yandex\Market\Export\Filter\Model $filterModel */ foreach ($filterCollection as $filterModel) { $sourceFilter = $filterModel->getSourceFilter(); $result[] = [ 'ID' => $filterModel->getInternalId(), 'FILTER' => $sourceFilter, 'CONTEXT' => $filterModel->getContext() + [ 'IGNORE_EXCLUDE' => $isFirstFilter ] + $commonContext, ]; $isFirstFilter = false; } if ($isNeedAll === null) { $isNeedAll = $iblockLink->isExportAll(); } if ($isNeedAll) { $result[] = [ 'ID' => null, 'FILTER' => [], 'CONTEXT' => [ 'IGNORE_EXCLUDE' => $isFirstFilter ] + $commonContext ]; } return $result; } /** * ��������� �������� ������ �� ����� � ����� ����������� ����������� * * @param $elementList * @param $limit * @param $useDistinct * * @return array * @noinspection DuplicatedCode */ protected function chunkElementList($elementList, $limit, $useDistinct) { if ($useDistinct) { $result = []; $chunk = []; $chunkSize = 0; $parentElements = []; $parentElementsCount = 0; $currentParentId = null; // sort by PARENT_ID uasort($elementList, static function($aElement, $bElement) { $aParentId = isset($aElement['PARENT_ID']) ? $aElement['PARENT_ID'] : null; $bParentId = isset($bElement['PARENT_ID']) ? $bElement['PARENT_ID'] : null; if ($aParentId === $bParentId) { return $aElement['ID'] < $bElement['ID'] ? -1 : 1; } return $aParentId < $bParentId ? -1 : 1; }); // build chunks foreach ($elementList as $elementId => $element) { $parentId = isset($element['PARENT_ID']) ? $element['PARENT_ID'] : null; if ($parentId === null) { $chunk[$elementId] = $element; ++$chunkSize; if ($chunkSize >= $limit) { $result[] = $chunk; $chunk = []; $chunkSize = 0; } } else { if ($parentId !== $currentParentId) { if ($chunkSize + $parentElementsCount < $limit) { $chunk += $parentElements; $chunkSize += $parentElementsCount; } else if ($parentElementsCount > $chunkSize) { $result[] = $parentElements; } else { $result[] = $chunk; $chunk = $parentElements; $chunkSize = $parentElementsCount; } $parentElements = []; $parentElementsCount = 0; $currentParentId = $parentId; } $parentElements[$elementId] = $element; ++$parentElementsCount; } } if ($parentElementsCount > 0) { if ($chunkSize + $parentElementsCount < $limit) { $chunk += $parentElements; $chunkSize += $parentElementsCount; } else if ($parentElementsCount > $chunkSize) { $result[] = $parentElements; } else { $result[] = $chunk; $chunk = $parentElements; $chunkSize = $parentElementsCount; } } if ($chunkSize > 0) { $result[] = $chunk; } } else { $result = array_chunk($elementList, $limit, true); } return $result; } /** * ������� ������ �� ��������� �� ������ ���������� ������� � ���� ���� * * @param $sourceSelect * @param $elementList * @param $parentList * @param $queryContext * * @return array */ protected function extractElementListValues($sourceSelect, $elementList, $parentList, $queryContext) { $sourceFetcher = new Market\Export\Routine\QueryBuilder\SourceFetcher(); $result = $sourceFetcher->load($sourceSelect, $elementList, $parentList, $queryContext); $conflictList = $this->getProcessor()->getConflicts(); foreach ($result as &$elementValues) { foreach ($conflictList as $sourceConflicts) { foreach ($sourceConflicts as $fieldName => $conflictAction) { if (isset($elementValues[$fieldName])) { $elementValues[$fieldName] = $this->applyValueConflict($elementValues[$fieldName], $conflictAction); } } } } unset($elementValues); return $result; } /** * ������ ������� ���� �� ������� * * @param $type * * @return \Yandex\Market\Export\Entity\Reference\Source * @throws \Bitrix\Main\ObjectNotFoundException */ protected function getSource($type) { return Market\Export\Entity\Manager::getSource($type); } /** * ���� CATALOG_TYPE �������� ������� �������� "����� �� ����� ������� ����������" */ protected function isCatalogTypeCompatibility($context) { return Market\Export\Entity\Catalog\Provider::isCatalogTypeCompatibility($context); } /** * ���������� ����� ����� �� ������� * * @param array $rules * @param array $elementList * @param array $sourceValuesList * @param Market\Result\XmlValue[] $tagValuesList * @param array $context * * @return Market\Result\XmlValue[] */ protected function resolveDistinctGroups($rules, $elementList, $sourceValuesList, $tagValuesList, $context) { $elementGroups = $this->getElementGroups($elementList); $elementsWithGroup = $this->flattenElementGroups($elementGroups, 1); if (!empty($elementsWithGroup)) { $elementsDistinctValues = $this->getDistinctValues($elementsWithGroup, $rules, $sourceValuesList, $tagValuesList, $context); $sortedGroups = $this->sortDistinctGroups($elementGroups, $elementsDistinctValues, $rules); $result = $this->applyDistinctGroups($tagValuesList, $sortedGroups); } else { $result = $tagValuesList; } return $result; } /** * ����������� ����������� �� ������������ �������� * * @param array $elementList * * @return int[][] */ protected function getElementGroups($elementList) { $result = []; foreach ($elementList as $elementId => $element) { if (isset($element['PARENT_ID'])) { $groupId = $element['PARENT_ID']; if (!isset($result[$groupId])) { $result[$groupId] = []; } $result[$groupId][] = $elementId; } } return $result; } /** * ������ ��������� � ������, ��������� ��������� � ����� ���� ����������� ��������� * * @param int[][] $groups * @param int $minimalGroupCount * * @return int[] */ protected function flattenElementGroups($groups, $minimalGroupCount = 0) { $result = []; foreach ($groups as $group) { if ($minimalGroupCount === 0 || count($group) > $minimalGroupCount) { foreach ($group as $elementId) { $result[] = $elementId; } } } return $result; } /** * �������� ������� ����� �� ���������� * * @param int[] $elementIds * @param array $rules * @param array $sourceValuesList * @param Market\Result\XmlValue[] $tagValuesList * @param array $context * * @return array[] */ protected function getDistinctValues($elementIds, $rules, $sourceValuesList, $tagValuesList, $context) { $result = []; $exportTag = $this->getTag(); foreach ($rules as $ruleIndex => $rule) { $ruleValues = array_fill_keys($elementIds, null); if (isset($rule['TAG'])) { $useAttribute = false; $ruleNode = ( $exportTag->getName() === $rule['TAG'] ? $exportTag : $exportTag->getChild($rule['TAG']) ); if (isset($rule['ATTRIBUTE'])) { $useAttribute = true; $ruleNode = $ruleNode !== null ? $ruleNode->getAttribute($rule['ATTRIBUTE']) : null; } foreach ($elementIds as $elementId) { $tagValues = $tagValuesList[$elementId]; if ($useAttribute) { $ruleValue = $tagValues->getTagAttribute($rule['TAG'], $rule['ATTRIBUTE']); } else { $ruleValue = $tagValues->getTagValue($rule['TAG']); } if (!$this->isEmptyXmlValue($ruleValue)) { if ($ruleNode === null) { $ruleValues[$elementId] = $ruleValue; } else { $ruleValue = $ruleNode->compareValue($ruleValue, $context, $tagValues); if ($ruleValue === null) { continue; } $ruleValues[$elementId] = $ruleValue; } } } } else { foreach ($elementIds as $elementId) { if (!isset($sourceValuesList[$elementId][$rule['SOURCE']][$rule['FIELD']])) { continue; } $ruleValue = $sourceValuesList[$elementId][$rule['SOURCE']][$rule['FIELD']]; if (!$this->isEmptyXmlValue($ruleValue)) { $ruleValues[$elementId] = $ruleValue; } } } $result[$ruleIndex] = $ruleValues; } return $result; } /** * ���������� ����� * * @param int[][] $groups * @param array[] $elementValues * @param array $rules * * @return int[][] */ protected function sortDistinctGroups($groups, $elementValues, $rules) { $sortSigns = $this->getDistinctRulesSign($rules); foreach ($groups as &$group) { usort($group, static function($aElementId, $bElementId) use ($elementValues, $sortSigns) { foreach ($sortSigns as $ruleIndex => $sortSign) { $aValue = $elementValues[$ruleIndex][$aElementId]; $bValue = $elementValues[$ruleIndex][$bElementId]; if ($aValue !== $bValue) { if ($aValue === null) { $result = 1; } else if ($bValue === null) { $result = -1; } else { $result = ($aValue < $bValue ? -1 : 1) * $sortSign; } return $result; } } return $aElementId < $bElementId ? -1 : 1; // all equal }); } unset($group); return $groups; } /** * ���� �������� �� ����������� ����� * * @param array $rules * * @return int[] */ protected function getDistinctRulesSign($rules) { $result = []; foreach ($rules as $ruleIndex => $rule) { if (Market\Data\TextString::toLower($rule['ORDER']) === 'desc') { $result[$ruleIndex] = -1; } else { $result[$ruleIndex] = 1; } } return $result; } /** * ���������� ���������� ����������� � ����� * * @param Market\Result\XmlValue[] $tagValuesList * @param array $groups * * @return Market\Result\XmlValue[] */ protected function applyDistinctGroups($tagValuesList, $groups) { $priority = []; // apply distinct and fill priority foreach ($groups as $groupId => $group) { foreach ($group as $index => $elementId) { $tagValues = $tagValuesList[$elementId]; $tagValues->setDistinct($groupId); $priority[$elementId] = $index; } } // sort tagValuesList uksort($tagValuesList, static function($aElementId, $bElementId) use ($priority) { $aPriority = isset($priority[$aElementId]) ? $priority[$aElementId] : 0; $bPriority = isset($priority[$bElementId]) ? $priority[$bElementId] : 0; if ($aPriority === $bPriority) { return $aElementId < $bElementId ? -1 : 1; } return $aPriority < $bPriority ? -1 : 1; }); return $tagValuesList; } }