diff --git a/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php b/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php index e5d2f664..003071fc 100644 --- a/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php +++ b/src/lib/FieldMapper/ContentFieldMapper/ContentDocumentLocationFields.php @@ -7,6 +7,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; @@ -24,8 +25,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; } @@ -90,6 +96,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 4776324d..1041357f 100644 --- a/src/lib/FieldMapper/LocationFieldMapper/LocationDocumentBaseFields.php +++ b/src/lib/FieldMapper/LocationFieldMapper/LocationDocumentBaseFields.php @@ -7,6 +7,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; @@ -27,10 +28,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; } @@ -122,6 +127,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)); + } +}