Skip to content

Added Column search criterion #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: 4.6
Choose a base branch
from
Draft
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
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"EzSystems\\EzPlatformMatrixFieldtypeBundle\\": "src/bundle/",
"EzSystems\\EzPlatformMatrixFieldtype\\": "src/lib/",
"Ibexa\\FieldTypeMatrix\\": "src/lib/",
"Ibexa\\Bundle\\FieldTypeMatrix\\": "src/bundle/"
"Ibexa\\Bundle\\FieldTypeMatrix\\": "src/bundle/",
"Ibexa\\Contracts\\FieldTypeMatrix\\": "src/contracts/"
}
},
"autoload-dev": {
Expand Down Expand Up @@ -45,6 +46,7 @@
"ibexa/http-cache": "~4.6.0@dev",
"ibexa/design-engine": "~4.6.0@dev",
"ibexa/code-style": "^1.0",
"ibexa/solr": "~4.6.0@dev",
"friendsofphp/php-cs-fixer": "^3.0",
"phpunit/phpunit": "^9.5"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ public function load(array $configs, ContainerBuilder $container)

$loader->load('default_parameters.yaml');
$loader->load('services.yaml');

if ($container->hasExtension('ibexa_solr')) {
$loader->load('services/solr.yaml');
}

if ($container->hasExtension('ibexa_elasticsearch')) {
$loader->load('services/elasticsearch.yaml');
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ imports:
- { resource: services/fieldtype.yaml }
- { resource: services/command.yaml }
- { resource: services/graphql.yaml }
- { resource: services/search.yaml }
14 changes: 14 additions & 0 deletions src/bundle/Resources/config/services/elasticsearch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

Ibexa\FieldTypeMatrix\Search\Elasticsearch\IndexSubscriber:
tags:
- { name: kernel.event_subscriber }

Ibexa\FieldTypeMatrix\Search\Elasticsearch\Criterion\ColumnCriterionVisitor:
tags:
- { name: ibexa.search.elasticsearch.query.content.criterion.visitor }
- { name: ibexa.search.elasticsearch.query.location.criterion.visitor }
7 changes: 7 additions & 0 deletions src/bundle/Resources/config/services/search.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

Ibexa\FieldTypeMatrix\Search\Common\IndexDataProvider: ~
14 changes: 14 additions & 0 deletions src/bundle/Resources/config/services/solr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

Ibexa\FieldTypeMatrix\Search\Solr\ContentFieldMapper:
tags:
- { name: ibexa.search.solr.field.mapper.content }

Ibexa\FieldTypeMatrix\Search\Solr\ColumnCriterionVisitor:
tags:
- { name: ibexa.search.solr.query.content.criterion.visitor }
- { name: ibexa.search.solr.query.location.criterion.visitor }
51 changes: 51 additions & 0 deletions src/contracts/Search/Criterion/Column.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\FieldTypeMatrix\Search\Criterion;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator\Specifications;

final class Column extends Criterion
{
private string $fieldDefIdentifier;

private string $column;

public function __construct(
string $fieldDefIdentifier,
string $column,
string $value,
string $operator = Operator::EQ
) {
parent::__construct(null, $operator, $value);

$this->fieldDefIdentifier = $fieldDefIdentifier;
$this->column = $column;
}

public function getFieldDefIdentifier(): string
{
return $this->fieldDefIdentifier;
}

public function getColumn(): string
{
return $this->column;
}

public function getSpecifications(): array
{
return [
new Specifications(Operator::IN, Specifications::FORMAT_ARRAY),
new Specifications(Operator::EQ, Specifications::FORMAT_SINGLE),
new Specifications(Operator::CONTAINS, Specifications::FORMAT_SINGLE),
];
}
}
2 changes: 2 additions & 0 deletions src/lib/FieldType/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

class Type extends FieldType
{
public const FIELD_TYPE_IDENTIFIER = 'matrix';

/**
* {@inheritdoc}
*/
Expand Down
66 changes: 66 additions & 0 deletions src/lib/Search/Common/IndexDataProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\FieldTypeMatrix\Search\Common;

use Ibexa\Contracts\Core\Persistence\Content as SPIContent;
use Ibexa\Contracts\Core\Persistence\Content\Field;
use Ibexa\Contracts\Core\Persistence\Content\Type\FieldDefinition;
use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as ContentTypeHandler;
use Ibexa\Contracts\Core\Search;
use Ibexa\FieldTypeMatrix\FieldType\Type;

final class IndexDataProvider
{
private ContentTypeHandler $contentTypeHandler;

public function __construct(ContentTypeHandler $contentTypeHandler)
{
$this->contentTypeHandler = $contentTypeHandler;
}

public function getSearchData(SPIContent $content): array
{
$searchFields = [];

$contentType = $this->contentTypeHandler->load(
$content->versionInfo->contentInfo->contentTypeId
);

foreach ($content->fields as $field) {
$definition = $this->findDefintion($contentType, $field);
if ($definition === null || $definition->fieldType !== Type::FIELD_TYPE_IDENTIFIER) {
continue;
}

$columns = array_column($definition->fieldTypeConstraints->fieldSettings['columns'], 'identifier');

$data = $field->value->data;
foreach ($data['entries'] as $column => $value) {
$searchFields[] = new Search\Field(
$definition->identifier . '_col_' . $columns[$column] . '_value',
$value,
new Search\FieldType\MultipleStringField()
);
}
}

return $searchFields;
}

private function findDefintion(SPIContent\Type $contentType, Field $field): ?FieldDefinition
{
foreach ($contentType->fieldDefinitions as $definition) {
if ($field->fieldDefinitionId === $definition->id) {
return $definition;
}
}

return null;
}
}
50 changes: 50 additions & 0 deletions src/lib/Search/Elasticsearch/Criterion/ColumnCriterionVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\FieldTypeMatrix\Search\Elasticsearch\Criterion;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Elasticsearch\Query\CriterionVisitor;
use Ibexa\Contracts\Elasticsearch\Query\LanguageFilter;
use Ibexa\Contracts\FieldTypeMatrix\Search\Criterion\Column;
use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\BoolQuery;
use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\TermsQuery;
use Ibexa\Elasticsearch\ElasticSearch\QueryDSL\WildcardQuery;

final class ColumnCriterionVisitor implements CriterionVisitor
{
public function supports(Criterion $criterion, LanguageFilter $languageFilter): bool
{
return $criterion instanceof Column;
}

/**
* @param \Ibexa\Contracts\FieldTypeMatrix\Search\Criterion\Column $criterion
*/
public function visit(CriterionVisitor $dispatcher, Criterion $criterion, LanguageFilter $languageFilter): array
{
$name = $criterion->getFieldDefIdentifier() . '_col_' . $criterion->getColumn() . '_value_ms';

if ($criterion->operator === Criterion\Operator::CONTAINS) {
$bool = new BoolQuery();
foreach ((array) $criterion->value as $value) {
$wildcard = new WildcardQuery();
$wildcard->withField($name);
$wildcard->withValue('*' . $value . '*');

$bool->addShould($wildcard);
}
} else {
$terms = new TermsQuery();
$terms->withField($name);
$terms->withValue((array)$criterion->value);

return $terms->toArray();
}
}
}
60 changes: 60 additions & 0 deletions src/lib/Search/Elasticsearch/IndexSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\FieldTypeMatrix\Search\Elasticsearch;

use Ibexa\Contracts\Core\Persistence\Content;
use Ibexa\Contracts\Core\Persistence\Content\Handler as ContentHandler;
use Ibexa\Contracts\Core\Search\Document;
use Ibexa\Contracts\Elasticsearch\Mapping\Event\ContentIndexCreateEvent;
use Ibexa\Contracts\Elasticsearch\Mapping\Event\LocationIndexCreateEvent;
use Ibexa\FieldTypeMatrix\Search\Common\IndexDataProvider;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class IndexSubscriber implements EventSubscriberInterface
{
private ContentHandler $contentHandler;

private IndexDataProvider $indexDataProvider;

public function __construct(ContentHandler $contentHandler, IndexDataProvider $indexDataProvider)
{
$this->contentHandler = $contentHandler;
$this->indexDataProvider = $indexDataProvider;
}

public static function getSubscribedEvents(): array
{
return [
ContentIndexCreateEvent::class => 'onContentIndexCreate',
LocationIndexCreateEvent::class => 'onLocationIndexCreate',
];
}

public function onContentIndexCreate(ContentIndexCreateEvent $event): void
{
$this->appendSearchFields($event->getDocument(), $event->getContent());
}

public function onLocationIndexCreate(LocationIndexCreateEvent $event): void
{
$content = $this->contentHandler->load(
$event->getLocation()->contentId
);

$this->appendSearchFields($event->getDocument(), $content);
}

private function appendSearchFields(Document $document, Content $content): void
{
$data = $this->indexDataProvider->getSearchData($content);
foreach ($data as $field) {
$document->fields[] = $field;
}
}
}
33 changes: 33 additions & 0 deletions src/lib/Search/Solr/ContentFieldMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\FieldTypeMatrix\Search\Solr;

use EzSystems\EzPlatformMatrixFieldtype\Search\Common\IndexDataProvider;
use Ibexa\Contracts\Core\Persistence\Content as SPIContent;
use Ibexa\Contracts\Solr\FieldMapper\ContentFieldMapper as BaseContentFieldMapper;

final class ContentFieldMapper extends BaseContentFieldMapper
{
private IndexDataProvider $indexDataProvider;

public function __construct(IndexDataProvider $indexDataProvider)
{
$this->indexDataProvider = $indexDataProvider;
}

public function accept(SPIContent $content): bool
{
return true;
}

public function mapFields(SPIContent $content): array
{
return $this->indexDataProvider->getSearchData($content);
}
}
41 changes: 41 additions & 0 deletions src/lib/Search/Solr/Criterion/ColumnCriterionVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\FieldTypeMatrix\Search\Solr;

use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\Operator;
use Ibexa\Contracts\FieldTypeMatrix\Search\Criterion\Column;
use Ibexa\Contracts\Solr\Query\CriterionVisitor;

final class ColumnCriterionVisitor extends CriterionVisitor
{
public function canVisit(Criterion $criterion): bool
{
return $criterion instanceof Column;
}

/**
* @param \Ibexa\Contracts\FieldTypeMatrix\Search\Criterion\Column $criterion
*/
public function visit(Criterion $criterion, CriterionVisitor $subVisitor = null): string
{
$name = $criterion->getFieldDefIdentifier() . '_col_' . $criterion->getColumn() . '_value_ms';

$queries = [];
foreach ((array)$criterion->value as $value) {
if ($criterion->operator === Operator::CONTAINS) {
$queries[] = $name . ':*' . $this->escapeExpressions($value) . '*';
} else {
$queries[] = $name . ':"' . $this->escapeQuote($value, true) . '"';
}
}

return '(' . implode(' OR ', $queries) . ')';
}
}
Loading