vendor/pimcore/portal-engine/src/Service/SearchIndex/IndexQueueService.php line 380

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under following license:
  6.  * - Pimcore Commercial License (PCL)
  7.  *
  8.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  9.  *  @license    http://www.pimcore.org/license     PCL
  10.  */
  11. namespace Pimcore\Bundle\PortalEngineBundle\Service\SearchIndex;
  12. use Carbon\Carbon;
  13. use Pimcore\Bundle\PortalEngineBundle\Enum\Index\DatabaseConfig;
  14. use Pimcore\Bundle\PortalEngineBundle\Service\SearchIndex\Asset\IndexService as AssetIndexService;
  15. use Pimcore\Bundle\PortalEngineBundle\Service\SearchIndex\DataObject\IndexService as DataObjectIndexService;
  16. use Pimcore\Db;
  17. use Pimcore\Db\ConnectionInterface;
  18. use Pimcore\Model\Asset;
  19. use Pimcore\Model\DataObject\AbstractObject;
  20. use Pimcore\Model\DataObject\ClassDefinition;
  21. use Pimcore\Model\DataObject\Concrete;
  22. use Pimcore\Model\Element\ElementInterface;
  23. use Pimcore\Model\Element\Tag;
  24. use Psr\Log\LoggerInterface;
  25. /**
  26.  * Class IndexQueueService
  27.  *
  28.  * @package Pimcore\Bundle\PortalEngineBundle\Service\SearchIndex
  29.  */
  30. class IndexQueueService
  31. {
  32.     /** @var LoggerInterface */
  33.     protected $logger;
  34.     /** @var ElasticSearchConfigService */
  35.     protected $elasticSearchConfigService;
  36.     /** @var DataObjectIndexService */
  37.     protected $dataObjectIndexService;
  38.     /** @var AssetIndexService */
  39.     protected $assetIndexService;
  40.     /** @var bool */
  41.     protected $performIndexRefresh false;
  42.     /**
  43.      * IndexQueueService constructor.
  44.      *
  45.      * @param LoggerInterface $logger
  46.      * @param ElasticSearchConfigService $elasticSearchConfigService
  47.      * @param DataObjectIndexService $dataObjectIndexService
  48.      * @param AssetIndexService $assetIndexService
  49.      */
  50.     public function __construct(LoggerInterface $loggerElasticSearchConfigService $elasticSearchConfigServiceDataObjectIndexService $dataObjectIndexServiceAssetIndexService $assetIndexService)
  51.     {
  52.         $this->logger $logger;
  53.         $this->elasticSearchConfigService $elasticSearchConfigService;
  54.         $this->dataObjectIndexService $dataObjectIndexService;
  55.         $this->assetIndexService $assetIndexService;
  56.     }
  57.     /**
  58.      * @return Db\Connection|ConnectionInterface
  59.      */
  60.     protected function getDb()
  61.     {
  62.         return Db::get();
  63.     }
  64.     /**
  65.      * @param ElementInterface|Concrete|Asset $element
  66.      * @param string $operation
  67.      * @param bool $doIndexElement Index given element directly instead of add to queue
  68.      *
  69.      * @return $this
  70.      */
  71.     public function updateIndexQueue(ElementInterface $elementstring $operationbool $doIndexElement false)
  72.     {
  73.         try {
  74.             if (!$this->isOperationValid($operation)) {
  75.                 throw new \Exception(sprintf('operation %s not valid'$operation));
  76.             }
  77.             $oldFullPath $element instanceof Asset\Folder $this->getCurrentIndexFullPath($element) : null;
  78.             if ($doIndexElement) {
  79.                 $this->doHandleIndexData($element$operation);
  80.             }
  81.             /** @var string $elementType */
  82.             $elementType $this->getElementType($element);
  83.             /** @var int $currentQueueTableOperationTime */
  84.             $currentQueueTableOperationTime $this->getCurrentQueueTableOperationTime();
  85.             if ($element instanceof AbstractObject) {
  86.                 /** @var string $tableName */
  87.                 $tableName 'objects';
  88.                 $or $doIndexElement '' sprintf('o_id = %s OR'$this->getDb()->quote($element->getId()));
  89.                 $sql "SELECT o_id, %s, o_className, %s, %s FROM %s WHERE (%s o_path LIKE %s) and o_type != 'folder'";
  90.                 $selectQuery sprintf($sql,
  91.                     $this->getDb()->quote($elementType),
  92.                     $this->getDb()->quote($operation),
  93.                     $this->getDb()->quote($currentQueueTableOperationTime),
  94.                     $this->getDb()->quoteIdentifier($tableName),
  95.                     $or,
  96.                     $this->getDb()->quote($element->getRealFullPath() . '/%')
  97.                 );
  98.             } else {
  99.                 $tableName 'assets';
  100.                 $or $doIndexElement '' sprintf('id = %s OR'$this->getDb()->quote($element->getId()));
  101.                 $sql 'SELECT id, %s, %s, %s, %s FROM %s WHERE %s path LIKE %s';
  102.                 $selectQuery sprintf($sql,
  103.                     $this->getDb()->quote($elementType),
  104.                     $this->getDb()->quote($this->getElementIndexName($element)),
  105.                     $this->getDb()->quote($operation),
  106.                     $this->getDb()->quote($currentQueueTableOperationTime),
  107.                     $this->getDb()->quoteIdentifier($tableName),
  108.                     $or,
  109.                     $this->getDb()->quote($element->getRealFullPath() . '/%')
  110.                 );
  111.             }
  112.             if (!$doIndexElement || !($element instanceof Asset) || $element instanceof Asset\Folder) {
  113.                 $this->getDb()->executeQuery(sprintf('INSERT INTO %s (%s) %s ON DUPLICATE KEY UPDATE operation = VALUES(operation), operationTime = VALUES(operationTime)',
  114.                     DatabaseConfig::QUEUE_TABLE_NAME,
  115.                     implode(',', ['elementId''elementType''elementIndexName''operation''operationTime']),
  116.                     $selectQuery
  117.                 ));
  118.             }
  119.             if ($element instanceof Asset) {
  120.                 $this->updateAssetDependencies($element);
  121.             }
  122.             if ($element instanceof Asset\Folder && !empty($oldFullPath) && $oldFullPath !== $element->getRealFullPath()) {
  123.                 $this->rewriteChildrenIndexPaths($element$oldFullPath);
  124.             }
  125.         } catch (\Exception $e) {
  126.             $this->logger->warning('Update indexQueue in database-table' DatabaseConfig::QUEUE_TABLE_NAME ' failed! Error: ' $e->getMessage());
  127.         }
  128.         return $this;
  129.     }
  130.     /**
  131.      * @param bool $dispatch
  132.      * @param int $limit
  133.      *
  134.      * @return array
  135.      */
  136.     public function getUnhandledIndexQueueEntries(bool $dispatch falseint $limit 100000)
  137.     {
  138.         $unhandledIndexQueueEntries = [];
  139.         try {
  140.             if ($dispatch === true) {
  141.                 $dispatchId time();
  142.                 $workerId uniqid();
  143.                 $this->getDb()->executeQuery('UPDATE ' DatabaseConfig::QUEUE_TABLE_NAME ' SET dispatched = ?, workerId = ? WHERE (ISNULL(dispatched) OR dispatched < ?) LIMIT ' intval($limit),
  144.                     [$dispatchId$workerId$dispatchId 3000]);
  145.                 $unhandledIndexQueueEntries $this->getDb()->executeQuery('SELECT elementId, elementType, elementIndexName, operation, operationTime FROM ' DatabaseConfig::QUEUE_TABLE_NAME ' WHERE workerId = ? LIMIT ' intval($limit), [$workerId])->fetchAllAssociative(); // @phpstan-ignore-line
  146.             } else {
  147.                 $unhandledIndexQueueEntries $this->getDb()->executeQuery('SELECT elementId, elementType, elementIndexName, operation, operationTime FROM ' DatabaseConfig::QUEUE_TABLE_NAME ' ORDER BY operationTime LIMIT ' intval($limit))->fetchAllAssociative(); // @phpstan-ignore-line
  148.             }
  149.         } catch (\Exception $e) {
  150.             $this->logger->info('getUnhandledIndexQueueEntries failed! Error: ' $e->getMessage());
  151.         }
  152.         return $unhandledIndexQueueEntries;
  153.     }
  154.     /**
  155.      * @param array $entry
  156.      *
  157.      * @return $this
  158.      */
  159.     public function handleIndexQueueEntry($entry)
  160.     {
  161.         try {
  162.             $this->logger->info(DatabaseConfig::QUEUE_TABLE_NAME ' updating index for element ' $entry['elementId'] . ' and type ' $entry['elementType']);
  163.             /** @var AbstractObject|Asset|null $element */
  164.             $element $this->getElement($entry['elementId'], $entry['elementType']);
  165.             if ($element) {
  166.                 $this->doHandleIndexData($element$entry['operation']);
  167.             }
  168.             //delete handled entry from queue table
  169.             $this->getDb()->executeQuery('DELETE FROM ' DatabaseConfig::QUEUE_TABLE_NAME ' WHERE elementId = ? AND elementType = ? AND operation = ? AND operationTime = ?', [
  170.                 $entry['elementId'],
  171.                 $entry['elementType'],
  172.                 $entry['operation'],
  173.                 $entry['operationTime']
  174.             ]);
  175.         } catch (\Exception $e) {
  176.             $this->logger->info('handleIndexQueueEntry failed! Error: ' $e->getMessage());
  177.         }
  178.         return $this;
  179.     }
  180.     /**
  181.      * @param ClassDefinition $classDefinition
  182.      *
  183.      * @return $this
  184.      */
  185.     public function updateDataObjects($classDefinition)
  186.     {
  187.         $dataObjectTableName 'object_' $classDefinition->getId();
  188.         /** @var string $selectQuery */
  189.         $selectQuery sprintf("SELECT oo_id, '%s', '%s', '%s', '%s' FROM %s",
  190.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT,
  191.             $classDefinition->getName(),
  192.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  193.             $this->getCurrentQueueTableOperationTime(),
  194.             $dataObjectTableName
  195.         );
  196.         $this->updateBySelectQuery($selectQuery);
  197.         return $this;
  198.     }
  199.     /**
  200.      * @return $this
  201.      */
  202.     public function updateAssets()
  203.     {
  204.         /** @var string $selectQuery */
  205.         $selectQuery sprintf("SELECT id, '%s', '%s', '%s', '%s' FROM %s",
  206.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET,
  207.             'asset',
  208.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  209.             $this->getCurrentQueueTableOperationTime(),
  210.             'assets'
  211.         );
  212.         $this->updateBySelectQuery($selectQuery);
  213.         return $this;
  214.     }
  215.     /**
  216.      * @return $this
  217.      */
  218.     public function updateByTag(Tag $tag)
  219.     {
  220.         //assets
  221.         $selectQuery sprintf("SELECT id, '%s', '%s', '%s', '%s' FROM assets where id in (select cid from tags_assignment where ctype='asset' and tagid = %s)",
  222.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET,
  223.             'asset',
  224.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  225.             $this->getCurrentQueueTableOperationTime(),
  226.             $tag->getId()
  227.         );
  228.         $this->updateBySelectQuery($selectQuery);
  229.         //data objects
  230.         $selectQuery sprintf("SELECT o_id, '%s', o_className, '%s', '%s' FROM objects where o_id in (select cid from tags_assignment where ctype='object' and tagid = %s)",
  231.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT,
  232.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  233.             $this->getCurrentQueueTableOperationTime(),
  234.             $tag->getId()
  235.         );
  236.         $this->updateBySelectQuery($selectQuery);
  237.         return $this;
  238.     }
  239.     /**
  240.      * @param ElementInterface $element
  241.      *
  242.      * @return string|null
  243.      *
  244.      * @throws \Exception
  245.      */
  246.     protected function getCurrentIndexFullPath(ElementInterface $element)
  247.     {
  248.         if ($indexService $this->getIndexServiceByElement($element)) {
  249.             $indexName $this->elasticSearchConfigService->getIndexName($this->getElementIndexName($element));
  250.             return $indexService->getCurrentIndexFullPath($element$indexName);
  251.         }
  252.         return null;
  253.     }
  254.     /**
  255.      * Directly update children paths in elasticsearch for assets as otherwise you might get strange results if you rename a folder in the portal engine frontend.
  256.      *
  257.      * @param ElementInterface $element
  258.      * @param string $oldFullPath
  259.      *
  260.      * @throws \Exception
  261.      */
  262.     protected function rewriteChildrenIndexPaths(ElementInterface $elementstring $oldFullPath)
  263.     {
  264.         if ($element instanceof Asset && $indexService $this->getIndexServiceByElement($element)) {
  265.             $indexName $this->elasticSearchConfigService->getIndexName($this->getElementIndexName($element));
  266.             $indexService->rewriteChildrenIndexPaths($element$indexName$oldFullPath);
  267.         }
  268.     }
  269.     protected function updateBySelectQuery(string $selectQuery)
  270.     {
  271.         try {
  272.             $this->getDb()->executeQuery(sprintf('INSERT INTO %s (%s) %s ON DUPLICATE KEY UPDATE operation = VALUES(operation), operationTime = VALUES(operationTime)',
  273.                 DatabaseConfig::QUEUE_TABLE_NAME,
  274.                 implode(',', ['elementId''elementType''elementIndexName''operation''operationTime']),
  275.                 $selectQuery
  276.             ));
  277.         } catch (\Exception $e) {
  278.             $this->logger->debug($e->getMessage());
  279.         }
  280.     }
  281.     /**
  282.      * @param ElementInterface $element
  283.      *
  284.      * @return $this
  285.      */
  286.     public function refreshIndexByElement(ElementInterface $element)
  287.     {
  288.         try {
  289.             /** @var string $indexName */
  290.             $indexName $this->elasticSearchConfigService->getIndexName($this->getElementIndexName($element));
  291.             switch ($element) {
  292.                 case $element instanceof AbstractObject:
  293.                     $this->dataObjectIndexService->refreshIndex($indexName);
  294.                     break;
  295.                 case $element instanceof Asset:
  296.                     $this->assetIndexService->refreshIndex($indexName);
  297.                     break;
  298.             }
  299.         } catch (\Exception $e) {
  300.             $this->logger->debug($e->getMessage());
  301.         }
  302.         return $this;
  303.     }
  304.     /**
  305.      * @param Asset $asset
  306.      *
  307.      * @return $this
  308.      */
  309.     protected function updateAssetDependencies(Asset $asset)
  310.     {
  311.         foreach ($asset->getDependencies()->getRequiredBy() as $requiredByEntry) {
  312.             /** @var ElementInterface $element */
  313.             $element null;
  314.             if ('object' === $requiredByEntry['type']) {
  315.                 $element AbstractObject::getById($requiredByEntry['id']);
  316.             }
  317.             if ('asset' === $requiredByEntry['type']) {
  318.                 $element Asset::getById($requiredByEntry['id']);
  319.             }
  320.             if ($element) {
  321.                 $this->updateIndexQueue($elementDatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE);
  322.             }
  323.         }
  324.         return $this;
  325.     }
  326.     /**
  327.      * @param ElementInterface $element
  328.      * @param string $operation
  329.      *
  330.      * @return $this
  331.      *
  332.      * @throws \Exception
  333.      */
  334.     protected function doHandleIndexData(ElementInterface $elementstring $operation)
  335.     {
  336.         /** @var AbstractIndexService $indexService */
  337.         $indexService $this->getIndexServiceByElement($element);
  338.         /** @var bool $indexServicePerformIndexRefreshBackup */
  339.         $indexServicePerformIndexRefreshBackup $indexService->isPerformIndexRefresh();
  340.         $indexService->setPerformIndexRefresh($this->performIndexRefresh);
  341.         switch ($operation) {
  342.             case DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE:
  343.                 $this->doUpdateIndexData($element);
  344.                 break;
  345.             case DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_DELETE:
  346.                 $this->doDeleteFromIndex($element);
  347.                 break;
  348.         }
  349.         $indexService->setPerformIndexRefresh($indexServicePerformIndexRefreshBackup);
  350.         return $this;
  351.     }
  352.     /**
  353.      * @param ElementInterface $element
  354.      *
  355.      * @return AbstractIndexService|AssetIndexService|DataObjectIndexService|null
  356.      */
  357.     protected function getIndexServiceByElement(ElementInterface $element)
  358.     {
  359.         /** @var AbstractIndexService $indexService */
  360.         $indexService null;
  361.         switch ($element) {
  362.             case $element instanceof AbstractObject:
  363.                 $indexService $this->dataObjectIndexService;
  364.                 break;
  365.             case $element instanceof Asset:
  366.                 $indexService $this->assetIndexService;
  367.                 break;
  368.         }
  369.         return $indexService;
  370.     }
  371.     /**
  372.      * @param ElementInterface $element
  373.      *
  374.      * @return $this
  375.      */
  376.     protected function doUpdateIndexData(ElementInterface $element)
  377.     {
  378.         $this
  379.             ->getIndexServiceByElement($element)
  380.             ->doUpdateIndexData($element);
  381.         return $this;
  382.     }
  383.     /**
  384.      * @param ElementInterface $element
  385.      *
  386.      * @return $this
  387.      *
  388.      * @throws \Exception
  389.      */
  390.     protected function doDeleteFromIndex(ElementInterface $element)
  391.     {
  392.         /** @var int $elementId */
  393.         $elementId $element->getId();
  394.         /** @var string $elementIndexName */
  395.         $elementIndexName $this->getElementIndexName($element);
  396.         $this
  397.             ->getIndexServiceByElement($element)
  398.             ->doDeleteFromIndex($elementId$elementIndexName);
  399.         return $this;
  400.     }
  401.     /**
  402.      * @param string $operation
  403.      *
  404.      * @return bool
  405.      */
  406.     protected function isOperationValid($operation)
  407.     {
  408.         return in_array($operation, [
  409.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  410.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_DELETE
  411.         ]);
  412.     }
  413.     /**
  414.      * Get current timestamp + milliseconds
  415.      *
  416.      * @return int
  417.      */
  418.     protected function getCurrentQueueTableOperationTime()
  419.     {
  420.         /** @var Carbon $carbonNow */
  421.         $carbonNow Carbon::now();
  422.         return (int)($carbonNow->getTimestamp() . str_pad((string)$carbonNow->milli3'0')); // @phpstan-ignore-line
  423.     }
  424.     /**
  425.      * @param int $id
  426.      * @param string $type
  427.      *
  428.      * @return Asset|AbstractObject|null
  429.      *
  430.      * @throws \Exception
  431.      */
  432.     protected function getElement($id$type)
  433.     {
  434.         switch ($type) {
  435.             case DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET:
  436.                 return Asset::getById($id);
  437.             case DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT:
  438.                 return AbstractObject::getById($id);
  439.             default:
  440.                 throw new \Exception('elementType ' $type ' not supported');
  441.         }
  442.     }
  443.     /**
  444.      * @param ElementInterface $element
  445.      *
  446.      * @return string
  447.      *
  448.      * @throws \Exception
  449.      */
  450.     protected function getElementType($element)
  451.     {
  452.         switch ($element) {
  453.             case $element instanceof AbstractObject:
  454.                 return DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT;
  455.             case $element instanceof Asset:
  456.                 return DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET;
  457.             default:
  458.                 throw new \Exception('element ' get_class($element) . ' not supported');
  459.         }
  460.     }
  461.     /**
  462.      * @param ElementInterface $element
  463.      *
  464.      * @return string
  465.      *
  466.      * @throws \Exception
  467.      */
  468.     protected function getElementIndexName($element)
  469.     {
  470.         switch ($element) {
  471.             case $element instanceof Concrete:
  472.                 return $element->getClassName();
  473.             case $element instanceof Asset:
  474.                 return 'asset';
  475.             default:
  476.                 throw new \Exception('element ' get_class($element) . ' not supported');
  477.         }
  478.     }
  479.     /**
  480.      * @return bool
  481.      */
  482.     public function isPerformIndexRefresh(): bool
  483.     {
  484.         return $this->performIndexRefresh;
  485.     }
  486.     /**
  487.      * @param bool $performIndexRefresh
  488.      *
  489.      * @return IndexQueueService
  490.      */
  491.     public function setPerformIndexRefresh(bool $performIndexRefresh): self
  492.     {
  493.         $this->performIndexRefresh $performIndexRefresh;
  494.         return $this;
  495.     }
  496. }