diff --git a/module/CLI/src/Command/Api/GenerateKeyCommand.php b/module/CLI/src/Command/Api/GenerateKeyCommand.php index 12adcd57..c89c4fbf 100644 --- a/module/CLI/src/Command/Api/GenerateKeyCommand.php +++ b/module/CLI/src/Command/Api/GenerateKeyCommand.php @@ -9,6 +9,7 @@ use Shlinkio\Shlink\CLI\ApiKey\RoleResolverInterface; use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ShlinkTable; use Shlinkio\Shlink\Rest\ApiKey\Role; +use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -99,7 +100,7 @@ class GenerateKeyCommand extends Command $io = new SymfonyStyle($input, $output); $io->success(sprintf('Generated API key: "%s"', $apiKey->toString())); - if (! $apiKey->isAdmin()) { + if (! ApiKey::isAdmin($apiKey)) { ShlinkTable::default($io)->render( ['Role name', 'Role metadata'], $apiKey->mapRoles(fn (Role $role, array $meta) => [$role->value, arrayToString($meta, 0)]), diff --git a/module/CLI/src/Command/Api/ListKeysCommand.php b/module/CLI/src/Command/Api/ListKeysCommand.php index 59f5b534..c7e31819 100644 --- a/module/CLI/src/Command/Api/ListKeysCommand.php +++ b/module/CLI/src/Command/Api/ListKeysCommand.php @@ -59,7 +59,7 @@ class ListKeysCommand extends Command $rowData[] = sprintf($messagePattern, $this->getEnabledSymbol($apiKey)); } $rowData[] = $expiration?->toAtomString() ?? '-'; - $rowData[] = $apiKey->isAdmin() ? 'Admin' : implode("\n", $apiKey->mapRoles( + $rowData[] = ApiKey::isAdmin($apiKey) ? 'Admin' : implode("\n", $apiKey->mapRoles( fn (Role $role, array $meta) => empty($meta) ? $role->toFriendlyName() diff --git a/module/Core/src/Tag/Repository/TagRepository.php b/module/Core/src/Tag/Repository/TagRepository.php index c10fce61..62429d95 100644 --- a/module/Core/src/Tag/Repository/TagRepository.php +++ b/module/Core/src/Tag/Repository/TagRepository.php @@ -17,8 +17,8 @@ use Shlinkio\Shlink\Rest\ApiKey\Role; use Shlinkio\Shlink\Rest\ApiKey\Spec\WithApiKeySpecsEnsuringJoin; use Shlinkio\Shlink\Rest\Entity\ApiKey; -use function Functional\map; use function Functional\each; +use function Functional\map; use const PHP_INT_MAX; @@ -50,13 +50,13 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito $tagsSubQb = $conn->createQueryBuilder(); // For admins and when no API key is present, we'll return tags which are not linked to any short URL - $joiningMethod = $apiKey === null || $apiKey->isAdmin() ? 'leftJoin' : 'join'; + $joiningMethod = ApiKey::isAdmin($apiKey) ? 'leftJoin' : 'join'; $tagsSubQb ->select('t.id', 't.name', 'COUNT(DISTINCT s.id) AS short_urls_count') ->from('tags', 't') + ->groupBy('t.id', 't.name') ->{$joiningMethod}('t', 'short_urls_in_tags', 'st', $tagsSubQb->expr()->eq('st.tag_id', 't.id')) - ->{$joiningMethod}('st', 'short_urls', 's', $tagsSubQb->expr()->eq('st.short_url_id', 's.id')) - ->groupBy('t.id', 't.name'); + ->{$joiningMethod}('st', 'short_urls', 's', $tagsSubQb->expr()->eq('st.short_url_id', 's.id')); $searchTerm = $filtering?->searchTerm; if ($searchTerm !== null) { @@ -115,7 +115,7 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito ->setMaxResults($filtering?->limit ?? PHP_INT_MAX) ->setFirstResult($filtering?->offset ?? 0); - $orderByTag = $orderField == null || $orderField === OrderableField::TAG->value; + $orderByTag = $orderField === null || $orderField === OrderableField::TAG->value; if ($orderByTag) { $mainQb->orderBy('t.name', $orderDir ?? 'ASC'); } else { diff --git a/module/Core/src/Tag/TagService.php b/module/Core/src/Tag/TagService.php index d50ced75..ea9a4e8b 100644 --- a/module/Core/src/Tag/TagService.php +++ b/module/Core/src/Tag/TagService.php @@ -59,7 +59,7 @@ class TagService implements TagServiceInterface */ public function deleteTags(array $tagNames, ?ApiKey $apiKey = null): void { - if ($apiKey !== null && ! $apiKey->isAdmin()) { + if (! ApiKey::isAdmin($apiKey)) { throw ForbiddenTagOperationException::forDeletion(); } @@ -75,7 +75,7 @@ class TagService implements TagServiceInterface */ public function renameTag(TagRenaming $renaming, ?ApiKey $apiKey = null): Tag { - if ($apiKey !== null && ! $apiKey->isAdmin()) { + if (! ApiKey::isAdmin($apiKey)) { throw ForbiddenTagOperationException::forRenaming(); } diff --git a/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php b/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php index 1f8c2fd3..9a8f8056 100644 --- a/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php +++ b/module/Rest/src/ApiKey/Spec/WithApiKeySpecsEnsuringJoin.php @@ -11,14 +11,14 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey; class WithApiKeySpecsEnsuringJoin extends BaseSpecification { - public function __construct(private ?ApiKey $apiKey, private string $fieldToJoin = 'shortUrls') + public function __construct(private readonly ?ApiKey $apiKey, private readonly string $fieldToJoin = 'shortUrls') { parent::__construct(); } protected function getSpec(): Specification { - return $this->apiKey === null || $this->apiKey->isAdmin() ? Spec::andX() : Spec::andX( + return $this->apiKey === null || ApiKey::isAdmin($this->apiKey) ? Spec::andX() : Spec::andX( Spec::join($this->fieldToJoin, 's'), $this->apiKey->spec($this->fieldToJoin), ); diff --git a/module/Rest/src/Entity/ApiKey.php b/module/Rest/src/Entity/ApiKey.php index 57fecdd0..88cfa27e 100644 --- a/module/Rest/src/Entity/ApiKey.php +++ b/module/Rest/src/Entity/ApiKey.php @@ -114,9 +114,12 @@ class ApiKey extends AbstractEntity return Spec::andX(...$specs); } - public function isAdmin(): bool + /** + * @return ($apiKey is null ? true : boolean) + */ + public static function isAdmin(?ApiKey $apiKey): bool { - return $this->roles->isEmpty(); + return $apiKey === null || $apiKey->roles->isEmpty(); } public function hasRole(Role $role): bool