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

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