Skip to content

Commit

Permalink
language element query param
Browse files Browse the repository at this point in the history
Resolves #14631
  • Loading branch information
brandonkelly committed Mar 21, 2024
1 parent 4260566 commit dcbd9a6
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
- Element conditions can now include condition rules for Time fields. ([#14616](https://github.com/craftcms/cms/discussions/14616))

### Development
- Added the `language` element query param, which filters the resulting elements based on their sites’ languages. ([#14631](https://github.com/craftcms/cms/discussions/14631))
- GraphQL responses now include full exception details, when Dev Mode is enabled or an admin is signed in with the “Show full exception views when Dev Mode is disabled” preference enabled. ([#14527](https://github.com/craftcms/cms/issues/14527))

### Extensibility
- Added `craft\services\Sites::getSitesByLanguage()`.
- Added `craft\web\ErrorHandler::exceptionAsArray()`.
- Added `craft\web\ErrorHandler::showExceptionDetails()`.

Expand Down
31 changes: 31 additions & 0 deletions src/elements/db/ElementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,37 @@ public function siteId($value): self
return $this;
}

/**
* @inheritdoc
* @uses $siteId
* @return static
*/
public function language($value): self
{
if (is_string($value)) {
$sites = Craft::$app->getSites()->getSitesByLanguage($value);
if (empty($sites)) {
throw new InvalidArgumentException("Invalid language: $value");
}
$this->siteId = array_map(fn(Site $site) => $site->id, $sites);
} else {
if ($not = (strtolower(reset($value)) === 'not')) {
array_shift($value);
}
$this->siteId = [];
foreach (Craft::$app->getSites()->getAllSites() as $site) {
if (in_array($site->language, $value, true) === !$not) {
$this->siteId[] = $site->id;
}
}
if (empty($this->siteId)) {
throw new InvalidArgumentException('Invalid language param: [' . ($not ? 'not, ' : '') . implode(', ', $value) . ']');
}
}

return $this;
}

/**
* @inheritdoc
* @return static
Expand Down
38 changes: 38 additions & 0 deletions src/elements/db/ElementQueryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,44 @@ public function site(mixed $value): self;
*/
public function siteId(mixed $value): self;

/**
* Determines which site(s) the {elements} should be queried in, based on their language.
*
* Possible values include:
*
* | Value | Fetches {elements}…
* | - | -
* | `'en'` | from sites with a language of `en`.
* | `['en-GB', 'en-US']` | from sites with a language of `en-GB` or `en-US`.
* | `['not', 'en-GB', 'en-US']` | not in sites with a language of `en-GB` or `en-US`.
*
* ::: tip
* Elements that belong to multiple sites will be returned multiple times by default. If you
* only want unique elements to be returned, use [[unique()]] in conjunction with this.
* :::
*
* ---
*
* ```twig
* {# Fetch {elements} from English sites #}
* {% set {elements-var} = {twig-method}
* .language('en')
* .all() %}
* ```
*
* ```php
* // Fetch {elements} from English sites
* ${elements-var} = {php-method}
* ->language('en')
* ->all();
* ```
*
* @param mixed $value The property value
* @return static
* @since 4.9.0
*/
public function language(mixed $value): self;

/**
* Determines whether only elements with unique IDs should be returned by the query.
*
Expand Down
5 changes: 5 additions & 0 deletions src/gql/base/ElementArguments.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ public static function getArguments(): array
'type' => Type::int(),
'description' => 'Sets the offset for paginated results.',
],
'language' => [
'name' => 'language',
'type' => Type::listOf(Type::string()),
'description' => 'Determines which site(s) the elements should be queried in, based on their language.',
],
'limit' => [
'name' => 'limit',
'type' => Type::int(),
Expand Down
14 changes: 14 additions & 0 deletions src/services/Sites.php
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,19 @@ public function getSiteByHandle(string $siteHandle, ?bool $withDisabled = null):
return ArrayHelper::firstWhere($this->_allSites($withDisabled), 'handle', $siteHandle, true);
}

/**
* Returns sites by their language.
*
* @param string $language
* @param bool|null $withDisabled
* @return Site[]
* @since 4.9.0
*/
public function getSitesByLanguage(string $language, ?bool $withDisabled = null): array
{
return ArrayHelper::where($this->_allSites($withDisabled), 'language', $language, true);
}

/**
* Saves a site.
*
Expand Down Expand Up @@ -1122,6 +1135,7 @@ public function refreshSites(): void
{
$this->_allSitesById = null;
$this->_enabledSitesById = null;
$this->_editableSiteIds = null;
$this->_loadAllSites();
Craft::$app->getIsMultiSite(true);
}
Expand Down
161 changes: 161 additions & 0 deletions tests/unit/services/SitesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php
/**
* @link https://craftcms.com/
* @copyright Copyright (c) Pixel & Tonic, Inc.
* @license https://craftcms.github.io/license/
*/

namespace crafttests\unit\services;

use Craft;
use craft\elements\User;
use craft\test\TestCase;
use crafttests\fixtures\SitesFixture;
use UnitTester;

/**
* Unit tests for the security service
*
* @author Pixel & Tonic, Inc. <[email protected]>
* @since 4.9.0
*/
class SitesTest extends TestCase
{
/**
* @var UnitTester
*/
protected UnitTester $tester;

/**
* @return array
*/
public function _fixtures(): array
{
return [
'sites' => [
'class' => SitesFixture::class,
],
];
}

/**
* @dataProvider getSitesByGroupIdDataProvider
* @param int $expectedCount
* @param int $groupId
*/
public function testGetSitesByGroupId(int $expectedCount, int $groupId): void
{
$sites = Craft::$app->getSites()->getSitesByGroupId($groupId);
self::assertEquals($expectedCount, count($sites));
}

public function getSitesByGroupIdDataProvider(): array
{
return [
[4, 1],
[0, 9999],
];
}

/**
*
*/
public function testGetTotalSites(): void
{
self::assertEquals(4, Craft::$app->getSites()->getTotalSites());
}

/**
*
*/
public function testGetTotalEditableSites(): void
{
$userSession = Craft::$app->getUser();
$sitesService = Craft::$app->getSites();
$originalUser = $userSession->getIdentity(false);

$userSession->setIdentity(null);
$sitesService->refreshSites();
self::assertEquals(0, Craft::$app->getSites()->getTotalEditableSites());

$admin = User::find()->admin()->one();
$userSession->setIdentity($admin);
$sitesService->refreshSites();
self::assertEquals(4, Craft::$app->getSites()->getTotalEditableSites());

$userSession->setIdentity($originalUser);
$sitesService->refreshSites();
}

/**
* @dataProvider getSiteByIdDataProvider
* @param bool $expectedNotEmpty
* @param int $id
*/
public function testGetSiteById(bool $expectedNotEmpty, int $id): void
{
$sites = Craft::$app->getSites()->getSiteById($id);
self::assertEquals($expectedNotEmpty, !empty($sites));
}

/**
* @return array
*/
public function getSiteByIdDataProvider(): array
{
return [
[true, 1000],
[true, 1001],
[true, 1002],
[false, 999999],
];
}

/**
* @dataProvider getSiteByHandleDataProvider
* @param bool $expectedNotEmpty
* @param string $handle
*/
public function testGetSiteByHandle(bool $expectedNotEmpty, string $handle): void
{
$sites = Craft::$app->getSites()->getSiteByHandle($handle);
self::assertEquals($expectedNotEmpty, !empty($sites));
}

/**
* @return array
*/
public function getSiteByHandleDataProvider(): array
{
return [
[true, 'testSite1'],
[true, 'testSite2'],
[true, 'testSite3'],
[false, 'fakeSiteHandle'],
];
}

/**
* @dataProvider getSitesByLanguageDataProvider
* @param int $expectedCount
* @param string $language
*/
public function testGetSitesByLanguage(int $expectedCount, string $language): void
{
$sites = Craft::$app->getSites()->getSitesByLanguage($language);
self::assertEquals($expectedCount, count($sites));
}

/**
* @return array
*/
public function getSitesByLanguageDataProvider(): array
{
return [
[2, 'en-US'],
[2, 'nl'],
[0, 'en-us'],
[0, 'en'],
];
}
}

0 comments on commit dcbd9a6

Please sign in to comment.