Skip to content

Commit

Permalink
Add support for fetching data via queries and relations with sudo
Browse files Browse the repository at this point in the history
  • Loading branch information
emodric committed Dec 14, 2023
1 parent c196af6 commit 941aea9
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 14 deletions.
48 changes: 48 additions & 0 deletions bundle/QueryType/QueryExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Ibexa\Core\QueryType\QueryTypeRegistry;
use Ibexa\Contracts\Core\Repository\Repository;
use Netgen\IbexaSearchExtra\Core\Pagination\Pagerfanta\BaseAdapter;
use Netgen\IbexaSiteApi\API\FilterService;
use Netgen\IbexaSiteApi\API\FindService;
use Netgen\IbexaSiteApi\Core\Site\Pagination\Pagerfanta\FilterAdapter;
use Netgen\IbexaSiteApi\Core\Site\Pagination\Pagerfanta\FindAdapter;
use Netgen\IbexaSiteApi\Core\Site\Pagination\Pagerfanta\SudoFilterAdapter;
use Netgen\IbexaSiteApi\Core\Site\Pagination\Pagerfanta\SudoFindAdapter;
use Pagerfanta\Pagerfanta;

/**
Expand All @@ -26,6 +29,7 @@ public function __construct(
private readonly QueryTypeRegistry $queryTypeRegistry,
private readonly FilterService $filterService,
private readonly FindService $findService,
private readonly Repository $repository,
) {
}

Expand All @@ -44,6 +48,21 @@ public function execute(QueryDefinition $queryDefinition): Pagerfanta
return $pager;
}

/**
* Execute the Query with the given $name and return the result using repository sudo.
*/
public function sudoExecute(QueryDefinition $queryDefinition): Pagerfanta
{
$adapter = $this->getSudoPagerAdapter($queryDefinition);
$pager = new Pagerfanta($adapter);

$pager->setNormalizeOutOfRangePages(true);
$pager->setMaxPerPage($queryDefinition->maxPerPage);
$pager->setCurrentPage($queryDefinition->page);

return $pager;
}

/**
* Execute the Query with the given $name and return the result.
*/
Expand All @@ -58,6 +77,24 @@ public function executeRaw(QueryDefinition $queryDefinition): SearchResult
return $this->getContentResult($query, $queryDefinition);
}

/**
* Execute the Query with the given $name and return the result using repository sudo.
*/
public function sudoExecuteRaw(QueryDefinition $queryDefinition): SearchResult
{
$query = $this->getQuery($queryDefinition);

if ($query instanceof LocationQuery) {
return $this->repository->sudo(
fn () => $this->getLocationResult($query, $queryDefinition),
);
}

return $this->repository->sudo(
fn () => $this->getContentResult($query, $queryDefinition),
);
}

private function getPagerAdapter(QueryDefinition $queryDefinition): BaseAdapter
{
$query = $this->getQuery($queryDefinition);
Expand All @@ -69,6 +106,17 @@ private function getPagerAdapter(QueryDefinition $queryDefinition): BaseAdapter
return new FindAdapter($query, $this->findService);
}

private function getSudoPagerAdapter(QueryDefinition $queryDefinition): BaseAdapter
{
$query = $this->getQuery($queryDefinition);

if ($queryDefinition->useFilter) {
return new SudoFilterAdapter($query, $this->filterService, $this->repository);
}

return new SudoFindAdapter($query, $this->findService, $this->repository);
}

private function getLocationResult(LocationQuery $query, QueryDefinition $queryDefinition): SearchResult
{
if ($queryDefinition->useFilter) {
Expand Down
1 change: 1 addition & 0 deletions bundle/Resources/config/services/query_type.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ services:
- '@Ibexa\Core\QueryType\ArrayQueryTypeRegistry'
- '@netgen.ibexa_site_api.filter_service'
- '@netgen.ibexa_site_api.find_service'
- '@ibexa.api.repository'
10 changes: 10 additions & 0 deletions bundle/Templating/Twig/Extension/QueryExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@ public function getFunctions(): array
[QueryRuntime::class, 'executeQuery'],
['needs_context' => true],
),
new TwigFunction(
'ng_sudo_query',
[QueryRuntime::class, 'sudoExecuteQuery'],
['needs_context' => true],
),
new TwigFunction(
'ng_raw_query',
[QueryRuntime::class, 'executeRawQuery'],
['needs_context' => true],
),
new TwigFunction(
'ng_sudo_raw_query',
[QueryRuntime::class, 'sudoExecuteRawQuery'],
['needs_context' => true],
),
];
}
}
14 changes: 14 additions & 0 deletions bundle/Templating/Twig/Extension/QueryRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,27 @@ public function executeQuery(mixed $context, string $name): Pagerfanta
);
}

public function sudoExecuteQuery(mixed $context, string $name): Pagerfanta
{
return $this->queryExecutor->sudoExecute(
$this->getQueryDefinitionCollection($context)->get($name),
);
}

public function executeRawQuery(mixed $context, string $name): SearchResult
{
return $this->queryExecutor->executeRaw(
$this->getQueryDefinitionCollection($context)->get($name),
);
}

public function sudoExecuteRawQuery(mixed $context, string $name): SearchResult
{
return $this->queryExecutor->sudoExecuteRaw(
$this->getQueryDefinitionCollection($context)->get($name),
);
}

/**
* Returns the QueryDefinitionCollection variable from the given $context.
*
Expand Down
40 changes: 32 additions & 8 deletions docs/reference/query_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,18 +509,18 @@ Values that will be provided for evaluation in your custom expression function i
Templating
--------------------------------------------------------------------------------

Configured queries will be available in Twig templates, through ``ng_query`` or ``ng_raw_query``.
Configured queries will be available in Twig templates, through ``ng_query``, ``ng_sudo_query``, ``ng_raw_query`` or ``ng_sudo_raw_query``.
The difference it that the former will return a ``Pagerfanta`` instance, while the latter will
return an instance of ``SearchResult``. That also means ``ng_query`` will use ``max_per_page`` and
``page`` parameters to configure the pager, while ``ng_raw_query`` ignores them and executes the
return an instance of ``SearchResult``. That also means ``ng_query`` and ``ng_sudo_query`` will use ``max_per_page`` and
``page`` parameters to configure the pager, while ``ng_raw_query`` and ``ng_sudo_raw_query`` ignore them and execute the
configured query directly.

.. note::

Queries are only executed as you access them through ``ng_query`` or ``ng_raw_query``. If you
don't call those functions on any of the configured queries, none of them will be executed.
Queries are only executed as you access them through ``ng_query``, ``ng_sudo_query``, ``ng_raw_query`` or ``ng_sudo_raw_query``.
If you don't call those functions on any of the configured queries, none of them will be executed.

Both ``ng_query`` and ``ng_raw_query`` accept a single argument. This is the identifier of the
``ng_query``, ``ng_sudo_query``, ``ng_raw_query`` and ``ng_sudo_raw_query`` accept a single argument. This is the identifier of the
query, which is the key under the ``queries`` section, under which the query is configured.

Example usage of ``ng_query``:
Expand All @@ -537,6 +537,20 @@ Example usage of ``ng_query``:
{{ pagerfanta( images, 'twitter_bootstrap' ) }}
Example usage of ``ng_sudo_query``:

.. code-block:: twig
{% set users = ng_sudo_query( 'users' ) %}
<p>Total users: {{ users.nbResults }}</p>
{% for user in users %}
<p>{{ user.content.name }}</p>
{% endfor %}
{{ pagerfanta( users, 'twitter_bootstrap' ) }}
Example usage of ``ng_raw_query``:

.. code-block:: twig
Expand All @@ -547,13 +561,23 @@ Example usage of ``ng_raw_query``:
<p>{{ categoryHit.valueObject.content.name }}: {{ categoryHit.valueObject.score }}</p>
{% endfor %}
Example usage of ``ng_sudo_raw_query``:

.. code-block:: twig
{% set searchResult = ng_sudo_raw_query( 'users' ) %}
{% for userHit in searchResult.searchHits %}
<p>{{ userHit.valueObject.content.name }}: {{ userHit.valueObject.score }}</p>
{% endfor %}
.. note::

You can't execute named queries. They are only available for referencing in concrete query
configuration for a particular view.

.. hint::

Execution of queries is **not cached**. If you call ``ng_query`` or ``ng_raw_query`` on the same
query multiple times, the same query will be executed multiple times. If you need to access the
Execution of queries is **not cached**. If you call ``ng_query``, ``ng_sudo_query``, ``ng_raw_query`` or ``ng_sudo_raw_query``
on the same query multiple times, the same query will be executed multiple times. If you need to access the
query result multiple times, store it in a variable and access the variable instead.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
Defines the maximum number of items to return.
If ``null`` is used as a value, the limit will be set to the default value.

.. note:: This parameter will not be used if you execute the query from Twig using ``ng_query`` function.
.. note:: This parameter will not be used if you execute the query from Twig using ``ng_query`` or ``ng_sudo_query`` functions.
In that case ``Pargerfanta`` pager is used with semantic parameters ``page`` and ``max_per_page``.
To execute the query directly use ``ng_raw_query`` Twig function instead.
To execute the query directly use ``ng_raw_query`` or ``ng_sudo_raw_query`` Twig functions instead.

- **value type**: ``integer``, ``null``
- **value format**: ``single``
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
Defines the offset for search hits, used for paging the results.
If ``null`` is used as a value, the offset will be set to the default value.

.. note:: This parameter will not be used if you execute the query from Twig using ``ng_query`` function.
.. note:: This parameter will not be used if you execute the query from Twig using ``ng_query`` or ``ng_sudo_query`` functions.
In that case ``Pargerfanta`` pager is used with semantic parameters ``page`` and ``max_per_page``.
To execute the query directly use ``ng_raw_query`` Twig function instead.
To execute the query directly use ``ng_raw_query`` or ``ng_sudo_raw_query`` Twig functions instead.

- **value type**: ``integer``, ``null``
- **value format**: ``single``
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/templating.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ Site API provides four Twig functions for content rendering:
<img src="{{ ng_image_alias( content.fields.image, 'large' ).uri }}" />
``ng_render_field`` and ``ng_image_alias`` are shown in more detail in the examples below. There are
two other Twig functions, ``ng_query`` and ``ng_raw_query``. These are used with Query Types and are
documented separately on :doc:`Query Types reference</reference/query_types>` documentation page.
four other Twig functions, ``ng_query``, ``ng_sudo_query``, ``ng_raw_query`` and ``ng_sudo_raw_query``. These are used
with Query Types and are documented separately on :doc:`Query Types reference</reference/query_types>` documentation page.

Basic usage
-----------
Expand Down
54 changes: 54 additions & 0 deletions lib/API/Values/Content.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,25 @@ abstract public function filterLocations(int $maxPerPage = 25, int $currentPage
*/
abstract public function getFieldRelation(string $fieldDefinitionIdentifier): ?self;

/**
* Return single related Content from $fieldDefinitionIdentifier field using repository sudo.
*/
abstract public function getSudoFieldRelation(string $fieldDefinitionIdentifier): ?self;

/**
* Return all related Content from $fieldDefinitionIdentifier.
*
* @return \Netgen\IbexaSiteApi\API\Values\Content[]
*/
abstract public function getFieldRelations(string $fieldDefinitionIdentifier, int $limit = 25): array;

/**
* Return all related Content from $fieldDefinitionIdentifier using repository sudo.
*
* @return \Netgen\IbexaSiteApi\API\Values\Content[]
*/
abstract public function getSudoFieldRelations(string $fieldDefinitionIdentifier, int $limit = 25): array;

/**
* Return related Content from $fieldDefinitionIdentifier field,
* optionally limited by a list of $contentTypeIdentifiers.
Expand All @@ -113,18 +125,45 @@ abstract public function filterFieldRelations(
int $currentPage = 1,
): Pagerfanta;

/**
* Return related Content from $fieldDefinitionIdentifier field using repository sudo,
* optionally limited by a list of $contentTypeIdentifiers.
*
* @param string[] $contentTypeIdentifiers
*
* @return \Pagerfanta\Pagerfanta Pagerfanta instance iterating over Site API Content items
*/
abstract public function filterSudoFieldRelations(
string $fieldDefinitionIdentifier,
array $contentTypeIdentifiers = [],
int $maxPerPage = 25,
int $currentPage = 1,
): Pagerfanta;

/**
* Return single related Location from $fieldDefinitionIdentifier field.
*/
abstract public function getFieldRelationLocation(string $fieldDefinitionIdentifier): ?Location;

/**
* Return single related Location from $fieldDefinitionIdentifier field using repository sudo.
*/
abstract public function getSudoFieldRelationLocation(string $fieldDefinitionIdentifier): ?Location;

/**
* Return all related Locations from $fieldDefinitionIdentifier.
*
* @return \Netgen\IbexaSiteApi\API\Values\Location[]
*/
abstract public function getFieldRelationLocations(string $fieldDefinitionIdentifier, int $limit = 25): array;

/**
* Return all related Locations from $fieldDefinitionIdentifier using repository sudo.
*
* @return \Netgen\IbexaSiteApi\API\Values\Location[]
*/
abstract public function getSudoFieldRelationLocations(string $fieldDefinitionIdentifier, int $limit = 25): array;

/**
* Return related Locations from $fieldDefinitionIdentifier field,
* optionally limited by a list of $contentTypeIdentifiers.
Expand All @@ -139,4 +178,19 @@ abstract public function filterFieldRelationLocations(
int $maxPerPage = 25,
int $currentPage = 1,
): Pagerfanta;

/**
* Return related Locations from $fieldDefinitionIdentifier field using repository sudo,
* optionally limited by a list of $contentTypeIdentifiers.
*
* @param string[] $contentTypeIdentifiers
*
* @return \Pagerfanta\Pagerfanta Pagerfanta instance iterating over Site API Locations
*/
abstract public function filterSudoFieldRelationLocations(
string $fieldDefinitionIdentifier,
array $contentTypeIdentifiers = [],
int $maxPerPage = 25,
int $currentPage = 1,
): Pagerfanta;
}
39 changes: 39 additions & 0 deletions lib/Core/Site/Pagination/Pagerfanta/SudoFilterAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Netgen\IbexaSiteApi\Core\Site\Pagination\Pagerfanta;

use Ibexa\Contracts\Core\Repository\Repository;
use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery;
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Search\SearchResult;
use Netgen\IbexaSearchExtra\Core\Pagination\Pagerfanta\BaseAdapter;
use Netgen\IbexaSiteApi\API\FilterService;

/**
* Pagerfanta adapter performing search using FilterService and Repository sudo.
*/
final class SudoFilterAdapter extends BaseAdapter
{
public function __construct(
Query $query,
private readonly FilterService $filterService,
private readonly Repository $repository,
) {
parent::__construct($query);
}

protected function executeQuery(Query $query): SearchResult
{
if ($query instanceof LocationQuery) {
return $this->repository->sudo(
fn () => $this->filterService->filterLocations($query),
);
}

return $this->repository->sudo(
fn () => $this->filterService->filterContent($query),
);
}
}
Loading

0 comments on commit 941aea9

Please sign in to comment.