Skip to content
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

Add support for fetching data via queries and relations with sudo #25

Merged
merged 1 commit into from
Dec 22, 2023
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
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
Loading