Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Classes/AssetExtraction/NullAssetExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);

namespace Flowpack\SimpleSearch\ContentRepositoryAdaptor\AssetExtraction;

use Flowpack\SimpleSearch\ContentRepositoryAdaptor\NotImplementedException;
use Neos\ContentRepository\Search\AssetExtraction\AssetExtractorInterface;
use Neos\ContentRepository\Search\Dto\AssetContent;
use Neos\Media\Domain\Model\AssetInterface;

class NullAssetExtractor implements AssetExtractorInterface
{
public function extract(AssetInterface $asset): AssetContent
{
throw new NotImplementedException('AssetExtractor is not implemented in SimpleSearchAdaptor.');
}
}
2 changes: 1 addition & 1 deletion Classes/Command/NodeIndexCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ protected function traverseNodes(NodeInterface $currentNode): void
try {
$this->nodeIndexer->indexNode($currentNode, null, false);
} catch (NodeException|IndexingException|EelException $exception) {
throw new Exception(sprintf('Error during indexing of node %s (%s)', $currentNode->getPath(), $currentNode->getIdentifier()), 1579170291, $exception);
throw new Exception(sprintf('Error during indexing of node %s (%s)', $currentNode->findNodePath(), (string) $currentNode->getNodeAggregateIdentifier()), 1579170291, $exception);
}
$this->indexedNodes++;
foreach ($currentNode->findChildNodes() as $childNode) {
Expand Down
14 changes: 7 additions & 7 deletions Classes/Indexer/NodeIndexer.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

namespace Flowpack\SimpleSearch\ContentRepositoryAdaptor\Indexer;

use Flowpack\SimpleSearch\Domain\Service\IndexInterface;
use Flowpack\SimpleSearch\Search\QueryBuilderInterface;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Repository\WorkspaceRepository;
use Neos\ContentRepository\Domain\Service\ContentDimensionPresetSourceInterface;
Expand Down Expand Up @@ -118,7 +118,7 @@ public function getIndexClient(): IndexInterface
* @throws IndexingException
* @throws Exception
*/
public function indexNode(NodeInterface $node, $targetWorkspaceName = null, $indexVariants = true)
public function indexNode(NodeInterface $node, $targetWorkspaceName = null, $indexVariants = true): void
{
if ($indexVariants === true) {
$this->indexAllNodeVariants($node);
Expand Down Expand Up @@ -161,7 +161,7 @@ public function indexNode(NodeInterface $node, $targetWorkspaceName = null, $ind
* @param NodeInterface $node
* @return void
*/
public function removeNode(NodeInterface $node)
public function removeNode(NodeInterface $node): void
{
$identifier = $this->generateUniqueNodeIdentifier($node);
$this->indexClient->removeData($identifier);
Expand All @@ -170,7 +170,7 @@ public function removeNode(NodeInterface $node)
/**
* @return void
*/
public function flush()
public function flush(): void
{
$this->indexedNodeData = [];
}
Expand All @@ -182,7 +182,7 @@ public function flush()
*/
protected function indexAllNodeVariants(NodeInterface $node): void
{
$nodeIdentifier = $node->getIdentifier();
$nodeIdentifier = (string) $node->getNodeAggregateIdentifier();

$allIndexedVariants = $this->indexClient->executeStatement(
$this->queryBuilder->getFindIdentifiersByNodeIdentifierQuery('identifier'),
Expand Down Expand Up @@ -248,13 +248,13 @@ protected function findFulltextRoot(NodeInterface $node): ?NodeInterface
return null;
}

$currentNode = $node->getParent();
$currentNode = $node->findParentNode();
while ($currentNode !== null) {
if (in_array($currentNode->getNodeType()->getName(), $this->fulltextRootNodeTypes, true)) {
return $currentNode;
}

$currentNode = $currentNode->getParent();
$currentNode = $currentNode->findParentNode();
}

return null;
Expand Down
8 changes: 8 additions & 0 deletions Classes/NotImplementedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);

namespace Flowpack\SimpleSearch\ContentRepositoryAdaptor;

class NotImplementedException extends \Exception
{
}
292 changes: 292 additions & 0 deletions Classes/Search/AbstractQueryBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
<?php
declare(strict_types=1);

namespace Flowpack\SimpleSearch\ContentRepositoryAdaptor\Search;

use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Search\Search\QueryBuilderInterface;
use Neos\Eel\ProtectedContextAwareInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\Exception\IllegalObjectTypeException;
use Psr\Log\LoggerInterface;

/**
* Abstract Query Builder for Content Repository searches
*/
abstract class AbstractQueryBuilder implements QueryBuilderInterface, ProtectedContextAwareInterface
{
/**
* @Flow\Inject
* @var LoggerInterface
*/
protected $logger;

/**
* The node inside which searching should happen
*
* @var NodeInterface
*/
protected $contextNode;

/**
* Optional message for a log entry on execution of this Query.
*
* @var string
*/
protected $logMessage;

/**
* Should this Query be logged?
*
* @var boolean
*/
protected $queryLogEnabled = false;

abstract protected function getSimpleSearchQueryBuilder(): \Flowpack\SimpleSearch\Search\QueryBuilderInterface;

public function sortDesc(string $propertyName): QueryBuilderInterface
{
$this->getSimpleSearchQueryBuilder()->sortDesc($propertyName);
return $this;
}

public function sortAsc(string $propertyName): QueryBuilderInterface
{
$this->getSimpleSearchQueryBuilder()->sortAsc($propertyName);
return $this;
}

public function limit($limit): QueryBuilderInterface
{
$this->getSimpleSearchQueryBuilder()->limit($limit);
return $this;
}

public function from($from): QueryBuilderInterface
{
$this->getSimpleSearchQueryBuilder()->from($from);
return $this;
}

public function fulltext(string $searchWord, array $options = []): QueryBuilderInterface
{
$this->getSimpleSearchQueryBuilder()->fulltext($searchWord);
return $this;
}

/**
* @param NodeInterface $contextNode
* @return MysqlQueryBuilder
* @throws IllegalObjectTypeException
*/
public function query(NodeInterface $contextNode): QueryBuilderInterface
{
$this->getSimpleSearchQueryBuilder()->customCondition("(__parentPath LIKE '%#" . $contextNode->findNodePath() . "#%' OR __path LIKE '" . $contextNode->findNodePath() . "')");
$this->getSimpleSearchQueryBuilder()->like('__path', (string) $contextNode->findNodePath());
$this->getSimpleSearchQueryBuilder()->like('__workspace', "#" . $contextNode->getContext()->getWorkspace()->getName() . "#");
$this->getSimpleSearchQueryBuilder()->like('__dimensionshash', "#" . md5(json_encode($contextNode->getContext()->getDimensions())) . "#");
$this->contextNode = $contextNode;

return $this;
}

/**
* @param string $nodeIdentifierPlaceholder
* @return string
*/
abstract public function getFindIdentifiersByNodeIdentifierQuery(string $nodeIdentifierPlaceholder);

/**
* HIGH-LEVEL API
*/

/**
* Filter by node type, taking inheritance into account.
*
* @param string $nodeType the node type to filter for
* @return QueryBuilderInterface
*/
public function nodeType($nodeType): QueryBuilderInterface
{
$this->getSimpleSearchQueryBuilder()->like('__typeAndSuperTypes', "#" . $nodeType . "#");

return $this;
}

/**
* add an exact-match query for a given property
*
* @param string $propertyName
* @param mixed $propertyValue
* @return QueryBuilderInterface
*/
public function exactMatch($propertyName, $propertyValue): QueryBuilderInterface
{
if ($propertyValue instanceof NodeInterface) {
$propertyValue = (string) $propertyValue->getNodeAggregateIdentifier();
}

$this->getSimpleSearchQueryBuilder()->exactMatch($propertyName, $propertyValue);
return $this;
}

/**
* add an like query for a given property
*
* @param string $propertyName
* @param mixed $propertyValue
* @return QueryBuilderInterface
*/
public function like(string $propertyName, $propertyValue): QueryBuilderInterface
{
if ($propertyValue instanceof NodeInterface) {
$propertyValue = (string) $propertyValue->getNodeAggregateIdentifier();
}

$this->getSimpleSearchQueryBuilder()->like($propertyName, $propertyValue);
return $this;
}

/**
* add a greater than query for a given property
*
* @param string $propertyName
* @param mixed $propertyValue
* @return QueryBuilderInterface
*/
public function greaterThan($propertyName, $propertyValue)
{
if ($propertyValue instanceof NodeInterface) {
$propertyValue = (string) $propertyValue->getNodeAggregateIdentifier();
}

$this->getSimpleSearchQueryBuilder()->greaterThan($propertyName, $propertyValue);
return $this;
}

/**
* add a greater than or equal query for a given property
*
* @param string $propertyName
* @param mixed $propertyValue
* @return QueryBuilderInterface
*/
public function greaterThanOrEqual($propertyName, $propertyValue)
{
if ($propertyValue instanceof NodeInterface) {
$propertyValue = (string) $propertyValue->getNodeAggregateIdentifier();
}

$this->getSimpleSearchQueryBuilder()->greaterThanOrEqual($propertyName, $propertyValue);
return $this;
}

/**
* add a less than query for a given property
*
* @param string $propertyName
* @param mixed $propertyValue
* @return QueryBuilderInterface
*/
public function lessThan($propertyName, $propertyValue)
{
if ($propertyValue instanceof NodeInterface) {
$propertyValue = (string) $propertyValue->getNodeAggregateIdentifier();
}

$this->getSimpleSearchQueryBuilder()->lessThan($propertyName, $propertyValue);
return $this;
}

/**
* add a less than query for a given property
*
* @param string $propertyName
* @param mixed $propertyValue
* @return QueryBuilderInterface
*/
public function lessThanOrEqual($propertyName, $propertyValue)
{
if ($propertyValue instanceof NodeInterface) {
$propertyValue = (string) $propertyValue->getNodeAggregateIdentifier();
}

$this->getSimpleSearchQueryBuilder()->lessThanOrEqual($propertyName, $propertyValue);
return $this;
}

/**
* Execute the query and return the list of nodes as result
*
* @return array<\Neos\ContentRepository\Domain\Model\NodeInterface>
*/
public function execute(): \Traversable
{
// Adding implicit sorting by __sortIndex (as last fallback) as we can expect it to be there for nodes.
$this->getSimpleSearchQueryBuilder()->sortAsc("__sortIndex");

$timeBefore = microtime(true);
$result = $this->getSimpleSearchQueryBuilder()->execute();
$timeAfterwards = microtime(true);

if ($this->queryLogEnabled === true) {
$this->logger->debug('Query Log (' . $this->logMessage . '): -- execution time: ' . (($timeAfterwards - $timeBefore) * 1000) . ' ms -- Total Results: ' . count($result));
}

$nodes = [];
foreach ($result as $hit) {
$nodePath = $hit['__path'];
$node = $this->contextNode->getNode($nodePath);
if ($node instanceof NodeInterface) {
$nodes[(string) $node->getNodeAggregateIdentifier()] = $node;
}
}

return (new \ArrayObject(array_values($nodes)))->getIterator();
}

/**
* Log the current request for debugging after it has been executed.
*
* @param string $message an optional message to identify the log entry
* @return AbstractQueryBuilder
*/
public function log($message = null)
{
$this->queryLogEnabled = true;
$this->logMessage = $message;

return $this;
}

/**
* Return the total number of hits for the query.
*
* @return integer
*/
public function count(): int
{
$timeBefore = microtime(true);
$count = $this->getSimpleSearchQueryBuilder()->count();
$timeAfterwards = microtime(true);

if ($this->queryLogEnabled === true) {
$this->logger->debug('Query Log (' . $this->logMessage . '): -- execution time: ' . (($timeAfterwards - $timeBefore) * 1000) . ' ms -- Total Results: ' . $count);
}

return $count;
}

/**
* @param string $methodName
* @return boolean
*/
public function allowsCallOfMethod($methodName)
{
if ($methodName !== 'getFindIdentifiersByNodeIdentifierQuery') {
// query must be called first to establish a context and starting point.
return !($this->contextNode === null && $methodName !== 'query');
}
return false;
}
}
Loading