From 6dbe0457a7e4be99565fd68b9d502f4ad47929fe Mon Sep 17 00:00:00 2001 From: Tomasz Kryszan Date: Wed, 4 Sep 2024 16:44:21 +0200 Subject: [PATCH] IBX-8726: Added support for IsBookmarked criterion (#75) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For more details see https://issues.ibexa.co/browse/IBX-8726 and https://github.com/ibexa/solr/pull/75 Key changes: * Added IsBookmarked criterion visitor * Added location_bookmarked_user_ids search field * [Tests] Added IsBookmarkedTest --------- Co-Authored-By: Konrad Oboza Co-Authored-By: Paweł Niedzielski --- .../ContentDocumentLocationFields.php | 16 ++- .../LocationDocumentBaseFields.php | 10 ++ .../Location/IsBookmarked.php | 57 +++++++++ .../container/solr/criterion_visitors.yml | 6 + .../config/container/solr/field_mappers.yml | 8 +- .../Location/IsBookmarkedTest.php | 108 ++++++++++++++++++ 6 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 src/lib/Query/Location/CriterionVisitor/Location/IsBookmarked.php create mode 100644 tests/lib/Search/Query/Location/CriterionVisitor/Location/IsBookmarkedTest.php diff --git a/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php b/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php index e490b908..737d97cc 100644 --- a/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php +++ b/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php @@ -6,6 +6,7 @@ */ namespace Ibexa\Solr\FieldMapper\ContentFieldMapper; +use Ibexa\Contracts\Core\Persistence\Bookmark\Handler as BookmarkHandler; use Ibexa\Contracts\Core\Persistence\Content; use Ibexa\Contracts\Core\Persistence\Content\Location; use Ibexa\Contracts\Core\Persistence\Content\Location\Handler as LocationHandler; @@ -23,8 +24,13 @@ class ContentDocumentLocationFields extends ContentFieldMapper */ protected $locationHandler; - public function __construct(LocationHandler $locationHandler) - { + private BookmarkHandler $bookmarkHandler; + + public function __construct( + BookmarkHandler $bookmarkHandler, + LocationHandler $locationHandler + ) { + $this->bookmarkHandler = $bookmarkHandler; $this->locationHandler = $locationHandler; } @@ -89,6 +95,12 @@ public function mapFields(Content $content) $locationData['ancestors'], new FieldType\MultipleIdentifierField() ); + + $fields[] = new Field( + 'location_bookmarked_user_ids', + $this->bookmarkHandler->loadUserIdsByLocation($location), + new FieldType\MultipleIdentifierField() + ); } if ($mainLocation !== null) { diff --git a/src/lib/FieldMapper/LocationFieldMapper/LocationDocumentBaseFields.php b/src/lib/FieldMapper/LocationFieldMapper/LocationDocumentBaseFields.php index da8aa62c..d938cd01 100644 --- a/src/lib/FieldMapper/LocationFieldMapper/LocationDocumentBaseFields.php +++ b/src/lib/FieldMapper/LocationFieldMapper/LocationDocumentBaseFields.php @@ -6,6 +6,7 @@ */ namespace Ibexa\Solr\FieldMapper\LocationFieldMapper; +use Ibexa\Contracts\Core\Persistence\Bookmark\Handler as BookmarkHandler; use Ibexa\Contracts\Core\Persistence\Content\Handler as ContentHandler; use Ibexa\Contracts\Core\Persistence\Content\Location; use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as ContentTypeHandler; @@ -26,10 +27,14 @@ class LocationDocumentBaseFields extends LocationFieldMapper protected ContentTypeHandler $contentTypeHandler; + private BookmarkHandler $bookmarkHandler; + public function __construct( + BookmarkHandler $bookmarkHandler, ContentHandler $contentHandler, ContentTypeHandler $contentTypeHandler ) { + $this->bookmarkHandler = $bookmarkHandler; $this->contentHandler = $contentHandler; $this->contentTypeHandler = $contentTypeHandler; } @@ -121,6 +126,11 @@ public function mapFields(Location $location) $contentType->isContainer, new FieldType\BooleanField() ), + new Field( + 'location_bookmarked_user_ids', + $this->bookmarkHandler->loadUserIdsByLocation($location), + new FieldType\MultipleIdentifierField() + ), ]; } diff --git a/src/lib/Query/Location/CriterionVisitor/Location/IsBookmarked.php b/src/lib/Query/Location/CriterionVisitor/Location/IsBookmarked.php new file mode 100644 index 00000000..fc6b36f8 --- /dev/null +++ b/src/lib/Query/Location/CriterionVisitor/Location/IsBookmarked.php @@ -0,0 +1,57 @@ +permissionResolver = $permissionResolver; + } + + public function canVisit(Criterion $criterion): bool + { + return $criterion instanceof Criterion\Location\IsBookmarked + && $criterion->operator === Criterion\Operator::EQ; + } + + public function visit( + Criterion $criterion, + CriterionVisitor $subVisitor = null + ): string { + if (!is_array($criterion->value)) { + throw new LogicException(sprintf( + 'Expected %s Criterion value to be an array, received %s', + Criterion\Location\IsBookmarked::class, + get_debug_type($criterion->value), + )); + } + + $userId = $this->permissionResolver + ->getCurrentUserReference() + ->getUserId(); + + $query = self::SEARCH_FIELD . ':"' . $userId . '"'; + + if (!$criterion->value[0]) { + $query = 'NOT ' . $query; + } + + return $query; + } +} diff --git a/src/lib/Resources/config/container/solr/criterion_visitors.yml b/src/lib/Resources/config/container/solr/criterion_visitors.yml index 399a903f..177cedee 100644 --- a/src/lib/Resources/config/container/solr/criterion_visitors.yml +++ b/src/lib/Resources/config/container/solr/criterion_visitors.yml @@ -299,6 +299,12 @@ services: tags: - { name: ibexa.search.solr.query.location.criterion.visitor } + Ibexa\Solr\Query\Location\CriterionVisitor\Location\IsBookmarked: + arguments: + $permissionResolver: '@Ibexa\Contracts\Core\Repository\PermissionResolver' + tags: + - { name: ibexa.search.solr.query.location.criterion.visitor } + Ibexa\Solr\Query\Location\CriterionVisitor\Factory\LocationFullTextFactory: parent: Ibexa\Solr\Query\Common\CriterionVisitor\Factory\FullTextFactoryAbstract diff --git a/src/lib/Resources/config/container/solr/field_mappers.yml b/src/lib/Resources/config/container/solr/field_mappers.yml index cbf481c8..b29ec413 100644 --- a/src/lib/Resources/config/container/solr/field_mappers.yml +++ b/src/lib/Resources/config/container/solr/field_mappers.yml @@ -40,7 +40,8 @@ services: Ibexa\Solr\FieldMapper\ContentFieldMapper\ContentDocumentLocationFields: arguments: - - '@Ibexa\Contracts\Core\Persistence\Content\Location\Handler' + $bookmarkHandler: '@Ibexa\Contracts\Core\Persistence\Bookmark\Handler' + $locationHandler: '@Ibexa\Contracts\Core\Persistence\Content\Location\Handler' tags: - {name: ibexa.search.solr.field.mapper.content} @@ -57,8 +58,9 @@ services: Ibexa\Solr\FieldMapper\LocationFieldMapper\LocationDocumentBaseFields: arguments: - - '@Ibexa\Contracts\Core\Persistence\Content\Handler' - - '@Ibexa\Contracts\Core\Persistence\Content\Type\Handler' + $bookmarkHandler: '@Ibexa\Contracts\Core\Persistence\Bookmark\Handler' + $contentHandler: '@Ibexa\Contracts\Core\Persistence\Content\Handler' + $contentTypeHandler: '@Ibexa\Contracts\Core\Persistence\Content\Type\Handler' tags: - {name: ibexa.search.solr.field.mapper.location} diff --git a/tests/lib/Search/Query/Location/CriterionVisitor/Location/IsBookmarkedTest.php b/tests/lib/Search/Query/Location/CriterionVisitor/Location/IsBookmarkedTest.php new file mode 100644 index 00000000..aa242b96 --- /dev/null +++ b/tests/lib/Search/Query/Location/CriterionVisitor/Location/IsBookmarkedTest.php @@ -0,0 +1,108 @@ +permissionResolver = $this->createMock(PermissionResolver::class); + $this->visitor = new IsBookmarked($this->permissionResolver); + } + + /** + * @dataProvider provideDataForTestCanVisit + */ + public function testCanVisit( + bool $expected, + Criterion $criterion + ): void { + self::assertSame( + $expected, + $this->visitor->canVisit($criterion) + ); + } + + /** + * @return iterable + */ + public function provideDataForTestCanVisit(): iterable + { + yield 'Not supported criterion' => [ + false, + new Criterion\ContentId(123), + ]; + + yield 'Supported criterion' => [ + true, + new Criterion\Location\IsBookmarked(), + ]; + } + + /** + * @dataProvider provideDataForTestVisit + */ + public function testVisit( + string $expected, + Criterion $criterion + ): void { + $this->mockPermissionResolverGetCurrentUserReference(); + + self::assertSame( + $expected, + $this->visitor->visit($criterion) + ); + } + + /** + * @return iterable + */ + public function provideDataForTestVisit(): iterable + { + yield 'Query for bookmarked locations' => [ + 'location_bookmarked_user_ids_mid:"123"', + new Criterion\Location\IsBookmarked(), + ]; + + yield 'Query for not bookmarked locations' => [ + 'NOT location_bookmarked_user_ids_mid:"123"', + new Criterion\Location\IsBookmarked(false), + ]; + } + + private function mockPermissionResolverGetCurrentUserReference(): void + { + $this->permissionResolver + ->method('getCurrentUserReference') + ->willReturn(new UserReference(self::USER_ID)); + } +}