| Current Path : /home/rtorresani/www/vendor/magento/module-catalog/Model/Indexer/Product/Price/ |
| Current File : //home/rtorresani/www/vendor/magento/module-catalog/Model/Indexer/Product/Price/AbstractAction.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Catalog\Model\Indexer\Product\Price;
use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice;
use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
use Magento\Directory\Model\Currency;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Indexer\DimensionalIndexerInterface;
use Magento\Framework\Search\Request\Dimension;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\Website;
/**
* Abstract action reindex class
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
abstract class AbstractAction
{
/**
* Default Product Type Price indexer resource model
*
* @var DefaultPrice
*/
protected $_defaultIndexerResource;
/**
* @var AdapterInterface
*/
protected $_connection;
/**
* Core config model
*
* @var ScopeConfigInterface
*/
protected $_config;
/**
* @var StoreManagerInterface
*/
protected $_storeManager;
/**
*
* @var CurrencyFactory
*/
protected $_currencyFactory;
/**
* @var TimezoneInterface
*/
protected $_localeDate;
/**
* @var DateTime
*/
protected $_dateTime;
/**
* @var Type
*/
protected $_catalogProductType;
/**
*
* @var Factory
*/
protected $_indexerPriceFactory;
/**
* @var array|null
*/
protected $_indexers;
/**
* @var TierPrice
*/
private $tierPriceIndexResource;
/**
* @var DimensionCollectionFactory
*/
private $dimensionCollectionFactory;
/**
* @var TableMaintainer
*/
private $tableMaintainer;
/**
* @param ScopeConfigInterface $config
* @param StoreManagerInterface $storeManager
* @param CurrencyFactory $currencyFactory
* @param TimezoneInterface $localeDate
* @param DateTime $dateTime
* @param Type $catalogProductType
* @param Factory $indexerPriceFactory
* @param DefaultPrice $defaultIndexerResource
* @param TierPrice|null $tierPriceIndexResource
* @param DimensionCollectionFactory|null $dimensionCollectionFactory
* @param TableMaintainer|null $tableMaintainer
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
ScopeConfigInterface $config,
StoreManagerInterface $storeManager,
CurrencyFactory $currencyFactory,
TimezoneInterface $localeDate,
DateTime $dateTime,
Type $catalogProductType,
Factory $indexerPriceFactory,
DefaultPrice $defaultIndexerResource,
TierPrice $tierPriceIndexResource = null,
DimensionCollectionFactory $dimensionCollectionFactory = null,
TableMaintainer $tableMaintainer = null
) {
$this->_config = $config;
$this->_storeManager = $storeManager;
$this->_currencyFactory = $currencyFactory;
$this->_localeDate = $localeDate;
$this->_dateTime = $dateTime;
$this->_catalogProductType = $catalogProductType;
$this->_indexerPriceFactory = $indexerPriceFactory;
$this->_defaultIndexerResource = $defaultIndexerResource;
$this->_connection = $this->_defaultIndexerResource->getConnection();
$this->tierPriceIndexResource = $tierPriceIndexResource ?? ObjectManager::getInstance()->get(
TierPrice::class
);
$this->dimensionCollectionFactory = $dimensionCollectionFactory ?? ObjectManager::getInstance()->get(
DimensionCollectionFactory::class
);
$this->tableMaintainer = $tableMaintainer ?? ObjectManager::getInstance()->get(
TableMaintainer::class
);
}
/**
* Execute action for given ids
*
* @param array|int $ids
* @return void
*/
abstract public function execute($ids);
// phpcs:disable
/**
* Synchronize data between index storage and original storage
*
* @param array $processIds
* @return AbstractAction
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @deprecated 102.0.6
* @see Used only for backward compatibility for indexer, which not support indexation by dimensions
*/
protected function _syncData(array $processIds = [])
{
// for backward compatibility split data from old idx table on dimension tables
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$insertSelect = $this->getConnection()->select()->from(
['ip_tmp' => $this->_defaultIndexerResource->getIdxTable()]
);
foreach ($dimensions as $dimension) {
if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) {
$insertSelect->where('ip_tmp.website_id = ?', $dimension->getValue());
}
if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) {
$insertSelect->where('ip_tmp.customer_group_id = ?', $dimension->getValue());
}
}
$query = $insertSelect->insertFromSelect($this->tableMaintainer->getMainTableByDimensions($dimensions));
$this->getConnection()->query($query);
}
return $this;
}
// phpcs:enable
/**
* Prepare website current dates table
*
* @return AbstractAction
*
* @throws LocalizedException
* @throws NoSuchEntityException
*/
protected function _prepareWebsiteDateTable()
{
$baseCurrency = $this->_config->getValue(Currency::XML_PATH_CURRENCY_BASE);
$select = $this->getConnection()->select()->from(
['cw' => $this->_defaultIndexerResource->getTable('store_website')],
['website_id']
)->join(
['csg' => $this->_defaultIndexerResource->getTable('store_group')],
'cw.default_group_id = csg.group_id',
['store_id' => 'default_store_id']
)->where(
'cw.website_id != 0'
);
$data = [];
foreach ($this->getConnection()->fetchAll($select) as $item) {
/** @var $website Website */
$website = $this->_storeManager->getWebsite($item['website_id']);
if ($website->getBaseCurrencyCode() != $baseCurrency) {
$rate = $this->_currencyFactory->create()->load(
$baseCurrency
)->getRate(
$website->getBaseCurrencyCode()
);
if (!$rate) {
$rate = 1;
}
} else {
$rate = 1;
}
/** @var $store Store */
$store = $this->_storeManager->getStore($item['store_id']);
if ($store) {
$timestamp = $this->_localeDate->scopeTimeStamp($store);
$data[] = [
'website_id' => $website->getId(),
'website_date' => $this->_dateTime->formatDate($timestamp, false),
'rate' => $rate,
'default_store_id' => $store->getId()
];
}
}
$table = $this->_defaultIndexerResource->getTable('catalog_product_index_website');
$this->_emptyTable($table);
if ($data) {
foreach ($data as $row) {
$this->getConnection()->insertOnDuplicate($table, $row, array_keys($row));
}
}
return $this;
}
/**
* Prepare tier price index table
*
* @param int|array $entityIds the entity ids limitation
* @return AbstractAction
*/
protected function _prepareTierPriceIndex($entityIds = null)
{
$this->tierPriceIndexResource->reindexEntity((array)$entityIds);
return $this;
}
/**
* Retrieve price indexers per product type
*
* @param bool $fullReindexAction
*
* @return PriceInterface[]
*
* @throws LocalizedException
*/
public function getTypeIndexers($fullReindexAction = false)
{
if ($this->_indexers === null) {
$this->_indexers = [];
$types = $this->_catalogProductType->getTypesByPriority();
foreach ($types as $typeId => $typeInfo) {
$modelName = isset(
$typeInfo['price_indexer']
) ? $typeInfo['price_indexer'] : get_class($this->_defaultIndexerResource);
$indexer = $this->_indexerPriceFactory->create(
$modelName,
[
'fullReindexAction' => $fullReindexAction
]
);
// left setters for backward compatibility
if ($indexer instanceof DefaultPrice) {
$indexer->setTypeId(
$typeId
)->setIsComposite(
!empty($typeInfo['composite'])
);
}
$this->_indexers[$typeId] = $indexer;
}
}
return $this->_indexers;
}
/**
* Retrieve Price indexer by Product Type
*
* @param string $productTypeId
* @return PriceInterface
*
* @throws InputException
* @throws LocalizedException
*/
protected function _getIndexer($productTypeId)
{
$this->getTypeIndexers();
if (!isset($this->_indexers[$productTypeId])) {
throw new InputException(__('Unsupported product type "%1".', $productTypeId));
}
return $this->_indexers[$productTypeId];
}
/**
* Copy data from source table to destination
*
* @param string $sourceTable
* @param string $destTable
* @param null|string $where
* @return void
*/
protected function _insertFromTable($sourceTable, $destTable, $where = null)
{
$columns = array_keys($this->getConnection()->describeTable($destTable));
$select = $this->getConnection()->select()->from($sourceTable, $columns);
if ($where) {
$select->where($where);
}
$query = $this->getConnection()->insertFromSelect(
$select,
$destTable,
$columns,
AdapterInterface::INSERT_ON_DUPLICATE
);
$this->getConnection()->query($query);
}
/**
* Removes all data from the table
*
* @param string $table
* @return void
*/
protected function _emptyTable($table)
{
$this->getConnection()->delete($table);
}
/**
* Refresh entities index
*
* @param array $changedIds
* @return array Affected ids
*
* @throws InputException
* @throws LocalizedException
* @throws NoSuchEntityException
*/
protected function _reindexRows($changedIds = [])
{
$this->_prepareWebsiteDateTable();
$productsTypes = $this->getProductsTypes($changedIds);
$parentProductsTypes = $this->getParentProductsTypes($changedIds);
$changedIds = array_unique(array_merge($changedIds, ...array_values($parentProductsTypes)));
$productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes);
$typeIndexers = array_filter(
$this->getTypeIndexers(),
function ($type) use ($productsTypes) {
return isset($productsTypes[$type]) && !empty($productsTypes[$type]);
},
ARRAY_FILTER_USE_KEY
);
if (empty($typeIndexers)) {
$this->deleteIndexData($changedIds);
return $changedIds;
}
foreach ($typeIndexers as $productType => $indexer) {
$entityIds = $productsTypes[$productType];
if ($indexer instanceof DimensionalIndexerInterface) {
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$this->tableMaintainer->createMainTmpTable($dimensions);
$temporaryTable = $this->tableMaintainer->getMainTmpTable($dimensions);
$this->_emptyTable($temporaryTable);
$indexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false));
$mainTable = $this->tableMaintainer->getMainTableByDimensions($dimensions);
$this->_insertFromTable($temporaryTable, $mainTable);
$this->deleteOutdatedData($entityIds, $temporaryTable, $mainTable);
}
} else {
// handle 3d-party indexers for backward compatibility
$this->deleteIndexData($changedIds);
$this->_emptyTable($this->_defaultIndexerResource->getIdxTable());
$this->_copyRelationIndexData($entityIds);
$indexer->reindexEntity($entityIds);
$this->_syncData($entityIds);
}
}
return $changedIds;
}
/**
* Delete records that do not exist anymore
*
* @param array $entityIds
* @param string $temporaryTable
* @param string $mainTable
* @return void
*/
private function deleteOutdatedData(array $entityIds, string $temporaryTable, string $mainTable): void
{
$joinCondition = [
'tmp_table.entity_id = main_table.entity_id',
'tmp_table.customer_group_id = main_table.customer_group_id',
'tmp_table.website_id = main_table.website_id',
];
$select = $this->getConnection()->select()
->from(['main_table' => $mainTable], null)
->joinLeft(['tmp_table' => $temporaryTable], implode(' AND ', $joinCondition), null)
->where('tmp_table.entity_id IS NULL')
->where('main_table.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE);
$query = $select->deleteFromSelect('main_table');
$this->getConnection()->query($query);
}
/**
* Delete Index data index for list of entities
*
* @param array $entityIds
* @return void
*/
private function deleteIndexData(array $entityIds)
{
$entityIds = array_unique(array_map('intval', $entityIds));
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$select = $this->getConnection()->select()->from(
['index_price' => $this->tableMaintainer->getMainTableByDimensions($dimensions)],
null
)->where('index_price.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE);
$query = $select->deleteFromSelect('index_price');
$this->getConnection()->query($query);
}
}
// phpcs:disable
/**
* Copy relations product index from primary index to temporary index table by parent entity
*
* @param null|array $parentIds
* @param array $excludeIds
* @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
* @deprecated 102.0.6
* @see Used only for backward compatibility for do not broke custom indexer implementation
* which do not work by dimensions.
* For indexers, which support dimensions all composite products read data directly from main price indexer table
* or replica table for partial or full reindex correspondingly.
*/
protected function _copyRelationIndexData($parentIds, $excludeIds = null)
{
$linkField = $this->getProductIdFieldName();
$select = $this->getConnection()->select()->from(
$this->_defaultIndexerResource->getTable('catalog_product_relation'),
['child_id']
)->join(
['e' => $this->_defaultIndexerResource->getTable('catalog_product_entity')],
'e.' . $linkField . ' = parent_id',
[]
)->where(
'e.entity_id IN(?)',
$parentIds,
\Zend_Db::INT_TYPE
);
if (!empty($excludeIds)) {
$select->where('child_id NOT IN(?)', $excludeIds, \Zend_Db::INT_TYPE);
}
$children = $this->getConnection()->fetchCol($select);
if ($children) {
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$select = $this->getConnection()->select()->from(
$this->getIndexTargetTableByDimension($dimensions)
)->where(
'entity_id IN(?)',
$children,
\Zend_Db::INT_TYPE
);
$query = $select->insertFromSelect($this->_defaultIndexerResource->getIdxTable(), [], false);
$this->getConnection()->query($query);
}
}
return $this;
}
// phpcs:enable
/**
* Retrieve index table by dimension that will be used for write operations.
*
* This method is used during both partial and full reindex to identify the table.
*
* @param Dimension[] $dimensions
*
* @return string
*/
private function getIndexTargetTableByDimension(array $dimensions)
{
$indexTargetTable = $this->getIndexTargetTable();
if ($indexTargetTable === self::getIndexTargetTable()) {
$indexTargetTable = $this->tableMaintainer->getMainTableByDimensions($dimensions);
}
if ($indexTargetTable === self::getIndexTargetTable() . '_replica') {
$indexTargetTable = $this->tableMaintainer->getMainReplicaTable($dimensions);
}
return $indexTargetTable;
}
/**
* Retrieve index table that will be used for write operations.
*
* This method is used during both partial and full reindex to identify the table.
*
* @return string
*/
protected function getIndexTargetTable()
{
return $this->_defaultIndexerResource->getTable('catalog_product_index_price');
}
/**
* Get product Id field name
*
* @return string
*/
protected function getProductIdFieldName()
{
$table = $this->_defaultIndexerResource->getTable('catalog_product_entity');
$indexList = $this->getConnection()->getIndexList($table);
return $indexList[$this->getConnection()->getPrimaryKeyName($table)]['COLUMNS_LIST'][0];
}
/**
* Get products types.
*
* @param array $changedIds
* @return array
*/
private function getProductsTypes(array $changedIds = [])
{
$select = $this->getConnection()->select()->from(
$this->_defaultIndexerResource->getTable('catalog_product_entity'),
['entity_id', 'type_id']
);
if ($changedIds) {
$select->where('entity_id IN (?)', $changedIds, \Zend_Db::INT_TYPE);
}
$pairs = $this->getConnection()->fetchPairs($select);
$byType = [];
foreach ($pairs as $productId => $productType) {
$byType[$productType][$productId] = $productId;
}
return $byType;
}
/**
* Get parent products types
*
* Used for add composite products to reindex if we have only simple products in changed ids set
*
* @param array $productsIds
* @return array
*/
private function getParentProductsTypes(array $productsIds)
{
$select = $this->getConnection()->select()->from(
['l' => $this->_defaultIndexerResource->getTable('catalog_product_relation')],
''
)->join(
['e' => $this->_defaultIndexerResource->getTable('catalog_product_entity')],
'e.' . $this->getProductIdFieldName() . ' = l.parent_id',
['e.entity_id as parent_id', 'type_id']
)->where(
'l.child_id IN(?)',
$productsIds,
\Zend_Db::INT_TYPE
);
$pairs = $this->getConnection()->fetchPairs($select);
$byType = [];
foreach ($pairs as $productId => $productType) {
$byType[$productType][$productId] = (int)$productId;
}
return $byType;
}
/**
* Get connection
*
* @return AdapterInterface
*/
private function getConnection()
{
return $this->_defaultIndexerResource->getConnection();
}
}