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

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"$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.                     $elementType,
  92.                     $operation,
  93.                     $currentQueueTableOperationTime,
  94.                     $tableName,
  95.                     $or,
  96.                     $element->getRealFullPath() . '/%'
  97.                 );
  98.             } else {
  99.                 $tableName 'assets';
  100.                 $or $doIndexElement '' sprintf("id = '%s' OR"$element->getId());
  101.                 $sql "SELECT id, '%s', '%s', '%s', '%s' FROM %s WHERE %s path LIKE '%s'";
  102.                 $selectQuery sprintf($sql,
  103.                     $elementType,
  104.                     $this->getElementIndexName($element),
  105.                     $operation,
  106.                     $currentQueueTableOperationTime,
  107.                     $tableName,
  108.                     $or,
  109.                     $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.      * @return mixed[]
  132.      */
  133.     public function getUnhandledIndexQueueEntries()
  134.     {
  135.         /** @var array $unhandledIndexQueueEntries */
  136.         $unhandledIndexQueueEntries = [];
  137.         try {
  138.             $unhandledIndexQueueEntries $this->getDb()->executeQuery('SELECT elementId, elementType, elementIndexName, operation, operationTime FROM ' DatabaseConfig::QUEUE_TABLE_NAME ' ORDER BY operationTime')->fetchAllAssociative(); // @phpstan-ignore-line
  139.         } catch (\Exception $e) {
  140.             $this->logger->info('getUnhandledIndexQueueEntries failed! Error: ' $e->getMessage());
  141.         }
  142.         return $unhandledIndexQueueEntries;
  143.     }
  144.     /**
  145.      * @param array $entry
  146.      *
  147.      * @return $this
  148.      */
  149.     public function handleIndexQueueEntry($entry)
  150.     {
  151.         try {
  152.             $this->logger->info(DatabaseConfig::QUEUE_TABLE_NAME ' updating index for element ' $entry['elementId'] . ' and type ' $entry['elementType']);
  153.             /** @var AbstractObject|Asset|null $element */
  154.             $element $this->getElement($entry['elementId'], $entry['elementType']);
  155.             if ($element) {
  156.                 $this->doHandleIndexData($element$entry['operation']);
  157.             }
  158.             //delete handled entry from queue table
  159.             $this->getDb()->executeQuery('DELETE FROM ' DatabaseConfig::QUEUE_TABLE_NAME ' WHERE elementId = ? AND elementType = ? AND operation = ? AND operationTime = ?', [
  160.                 $entry['elementId'],
  161.                 $entry['elementType'],
  162.                 $entry['operation'],
  163.                 $entry['operationTime']
  164.             ]);
  165.         } catch (\Exception $e) {
  166.             $this->logger->info('handleIndexQueueEntry failed! Error: ' $e->getMessage());
  167.         }
  168.         return $this;
  169.     }
  170.     /**
  171.      * @param ClassDefinition $classDefinition
  172.      *
  173.      * @return $this
  174.      */
  175.     public function updateDataObjects($classDefinition)
  176.     {
  177.         $dataObjectTableName 'object_' $classDefinition->getId();
  178.         /** @var string $selectQuery */
  179.         $selectQuery sprintf("SELECT oo_id, '%s', '%s', '%s', '%s' FROM %s",
  180.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT,
  181.             $classDefinition->getName(),
  182.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  183.             $this->getCurrentQueueTableOperationTime(),
  184.             $dataObjectTableName
  185.         );
  186.         $this->updateBySelectQuery($selectQuery);
  187.         return $this;
  188.     }
  189.     /**
  190.      * @return $this
  191.      */
  192.     public function updateAssets()
  193.     {
  194.         /** @var string $selectQuery */
  195.         $selectQuery sprintf("SELECT id, '%s', '%s', '%s', '%s' FROM %s",
  196.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET,
  197.             'asset',
  198.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  199.             $this->getCurrentQueueTableOperationTime(),
  200.             'assets'
  201.         );
  202.         $this->updateBySelectQuery($selectQuery);
  203.         return $this;
  204.     }
  205.     /**
  206.      * @return $this
  207.      */
  208.     public function updateByTag(Tag $tag)
  209.     {
  210.         //assets
  211.         $selectQuery sprintf("SELECT id, '%s', '%s', '%s', '%s' FROM assets where id in (select cid from tags_assignment where ctype='asset' and tagid = %s)",
  212.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET,
  213.             'asset',
  214.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  215.             $this->getCurrentQueueTableOperationTime(),
  216.             $tag->getId()
  217.         );
  218.         $this->updateBySelectQuery($selectQuery);
  219.         //data objects
  220.         $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)",
  221.             DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT,
  222.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  223.             $this->getCurrentQueueTableOperationTime(),
  224.             $tag->getId()
  225.         );
  226.         $this->updateBySelectQuery($selectQuery);
  227.         return $this;
  228.     }
  229.     /**
  230.      * @param ElementInterface $element
  231.      *
  232.      * @return string|null
  233.      *
  234.      * @throws \Exception
  235.      */
  236.     protected function getCurrentIndexFullPath(ElementInterface $element)
  237.     {
  238.         if ($indexService $this->getIndexServiceByElement($element)) {
  239.             $indexName $this->elasticSearchConfigService->getIndexName($this->getElementIndexName($element));
  240.             return $indexService->getCurrentIndexFullPath($element$indexName);
  241.         }
  242.         return null;
  243.     }
  244.     /**
  245.      * 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.
  246.      *
  247.      * @param ElementInterface $element
  248.      * @param string $oldFullPath
  249.      *
  250.      * @throws \Exception
  251.      */
  252.     protected function rewriteChildrenIndexPaths(ElementInterface $elementstring $oldFullPath)
  253.     {
  254.         if ($element instanceof Asset && $indexService $this->getIndexServiceByElement($element)) {
  255.             $indexName $this->elasticSearchConfigService->getIndexName($this->getElementIndexName($element));
  256.             $indexService->rewriteChildrenIndexPaths($element$indexName$oldFullPath);
  257.         }
  258.     }
  259.     protected function updateBySelectQuery(string $selectQuery)
  260.     {
  261.         try {
  262.             $this->getDb()->executeQuery(sprintf('INSERT INTO %s (%s) %s ON DUPLICATE KEY UPDATE operation = VALUES(operation), operationTime = VALUES(operationTime)',
  263.                 DatabaseConfig::QUEUE_TABLE_NAME,
  264.                 implode(',', ['elementId''elementType''elementIndexName''operation''operationTime']),
  265.                 $selectQuery
  266.             ));
  267.         } catch (\Exception $e) {
  268.             $this->logger->debug($e->getMessage());
  269.         }
  270.     }
  271.     /**
  272.      * @param ElementInterface $element
  273.      *
  274.      * @return $this
  275.      */
  276.     public function refreshIndexByElement(ElementInterface $element)
  277.     {
  278.         try {
  279.             /** @var string $indexName */
  280.             $indexName $this->elasticSearchConfigService->getIndexName($this->getElementIndexName($element));
  281.             switch ($element) {
  282.                 case $element instanceof AbstractObject:
  283.                     $this->dataObjectIndexService->refreshIndex($indexName);
  284.                     break;
  285.                 case $element instanceof Asset:
  286.                     $this->assetIndexService->refreshIndex($indexName);
  287.                     break;
  288.             }
  289.         } catch (\Exception $e) {
  290.             $this->logger->debug($e->getMessage());
  291.         }
  292.         return $this;
  293.     }
  294.     /**
  295.      * @param Asset $asset
  296.      *
  297.      * @return $this
  298.      */
  299.     protected function updateAssetDependencies(Asset $asset)
  300.     {
  301.         foreach ($asset->getDependencies()->getRequiredBy() as $requiredByEntry) {
  302.             /** @var ElementInterface $element */
  303.             $element null;
  304.             if ('object' === $requiredByEntry['type']) {
  305.                 $element AbstractObject::getById($requiredByEntry['id']);
  306.             }
  307.             if ('asset' === $requiredByEntry['type']) {
  308.                 $element Asset::getById($requiredByEntry['id']);
  309.             }
  310.             if ($element) {
  311.                 $this->updateIndexQueue($elementDatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE);
  312.             }
  313.         }
  314.         return $this;
  315.     }
  316.     /**
  317.      * @param ElementInterface $element
  318.      * @param string $operation
  319.      *
  320.      * @return $this
  321.      *
  322.      * @throws \Exception
  323.      */
  324.     protected function doHandleIndexData(ElementInterface $elementstring $operation)
  325.     {
  326.         /** @var AbstractIndexService $indexService */
  327.         $indexService $this->getIndexServiceByElement($element);
  328.         /** @var bool $indexServicePerformIndexRefreshBackup */
  329.         $indexServicePerformIndexRefreshBackup $indexService->isPerformIndexRefresh();
  330.         $indexService->setPerformIndexRefresh($this->performIndexRefresh);
  331.         switch ($operation) {
  332.             case DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE:
  333.                 $this->doUpdateIndexData($element);
  334.                 break;
  335.             case DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_DELETE:
  336.                 $this->doDeleteFromIndex($element);
  337.                 break;
  338.         }
  339.         $indexService->setPerformIndexRefresh($indexServicePerformIndexRefreshBackup);
  340.         return $this;
  341.     }
  342.     /**
  343.      * @param ElementInterface $element
  344.      *
  345.      * @return AbstractIndexService|AssetIndexService|DataObjectIndexService|null
  346.      */
  347.     protected function getIndexServiceByElement(ElementInterface $element)
  348.     {
  349.         /** @var AbstractIndexService $indexService */
  350.         $indexService null;
  351.         switch ($element) {
  352.             case $element instanceof AbstractObject:
  353.                 $indexService $this->dataObjectIndexService;
  354.                 break;
  355.             case $element instanceof Asset:
  356.                 $indexService $this->assetIndexService;
  357.                 break;
  358.         }
  359.         return $indexService;
  360.     }
  361.     /**
  362.      * @param ElementInterface $element
  363.      *
  364.      * @return $this
  365.      */
  366.     protected function doUpdateIndexData(ElementInterface $element)
  367.     {
  368.         $this
  369.             ->getIndexServiceByElement($element)
  370.             ->doUpdateIndexData($element);
  371.         return $this;
  372.     }
  373.     /**
  374.      * @param ElementInterface $element
  375.      *
  376.      * @return $this
  377.      *
  378.      * @throws \Exception
  379.      */
  380.     protected function doDeleteFromIndex(ElementInterface $element)
  381.     {
  382.         /** @var int $elementId */
  383.         $elementId $element->getId();
  384.         /** @var string $elementIndexName */
  385.         $elementIndexName $this->getElementIndexName($element);
  386.         $this
  387.             ->getIndexServiceByElement($element)
  388.             ->doDeleteFromIndex($elementId$elementIndexName);
  389.         return $this;
  390.     }
  391.     /**
  392.      * @param string $operation
  393.      *
  394.      * @return bool
  395.      */
  396.     protected function isOperationValid($operation)
  397.     {
  398.         return in_array($operation, [
  399.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_UPDATE,
  400.             DatabaseConfig::QUEUE_TABLE_COLUMN_OPERATION_DELETE
  401.         ]);
  402.     }
  403.     /**
  404.      * Get current timestamp + milliseconds
  405.      *
  406.      * @return int
  407.      */
  408.     protected function getCurrentQueueTableOperationTime()
  409.     {
  410.         /** @var Carbon $carbonNow */
  411.         $carbonNow Carbon::now();
  412.         return (int)($carbonNow->getTimestamp() . str_pad((string)$carbonNow->milli3'0')); // @phpstan-ignore-line
  413.     }
  414.     /**
  415.      * @param int $id
  416.      * @param string $type
  417.      *
  418.      * @return Asset|AbstractObject|null
  419.      *
  420.      * @throws \Exception
  421.      */
  422.     protected function getElement($id$type)
  423.     {
  424.         switch ($type) {
  425.             case DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET:
  426.                 return Asset::getById($id);
  427.             case DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT:
  428.                 return AbstractObject::getById($id);
  429.             default:
  430.                 throw new \Exception('elementType ' $type ' not supported');
  431.         }
  432.     }
  433.     /**
  434.      * @param ElementInterface $element
  435.      *
  436.      * @return string
  437.      *
  438.      * @throws \Exception
  439.      */
  440.     protected function getElementType($element)
  441.     {
  442.         switch ($element) {
  443.             case $element instanceof AbstractObject:
  444.                 return DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_DATA_OBJECT;
  445.             case $element instanceof Asset:
  446.                 return DatabaseConfig::QUEUE_TABLE_COLUMN_ELEMENT_TYPE_ASSET;
  447.             default:
  448.                 throw new \Exception('element ' get_class($element) . ' not supported');
  449.         }
  450.     }
  451.     /**
  452.      * @param ElementInterface $element
  453.      *
  454.      * @return string
  455.      *
  456.      * @throws \Exception
  457.      */
  458.     protected function getElementIndexName($element)
  459.     {
  460.         switch ($element) {
  461.             case $element instanceof Concrete:
  462.                 return $element->getClassName();
  463.             case $element instanceof Asset:
  464.                 return 'asset';
  465.             default:
  466.                 throw new \Exception('element ' get_class($element) . ' not supported');
  467.         }
  468.     }
  469.     /**
  470.      * @return bool
  471.      */
  472.     public function isPerformIndexRefresh(): bool
  473.     {
  474.         return $this->performIndexRefresh;
  475.     }
  476.     /**
  477.      * @param bool $performIndexRefresh
  478.      *
  479.      * @return IndexQueueService
  480.      */
  481.     public function setPerformIndexRefresh(bool $performIndexRefresh): self
  482.     {
  483.         $this->performIndexRefresh $performIndexRefresh;
  484.         return $this;
  485.     }
  486. }