From 9e313d2e1a0ad44692bcaf55cf2476fe42c84a72 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:38:19 +0100 Subject: [PATCH 1/6] [PHPDoc] Improved ContentService doc for API reference (#379) For more details see https://github.com/ibexa/core/pull/379 Key changes: * [PHPDoc] Aligned ContentService PHPDocs with API reference requirements * [PHPStan] Aligned baseline with the changes-baseline*.neon --------- Co-Authored-By: Konrad Oboza --- phpstan-baseline-7.4.neon | 18 +- phpstan-baseline-gte-8.0.neon | 18 +- phpstan-baseline.neon | 78 ++--- src/contracts/Repository/ContentService.php | 312 ++++++++------------ 4 files changed, 168 insertions(+), 258 deletions(-) diff --git a/phpstan-baseline-7.4.neon b/phpstan-baseline-7.4.neon index 702873d5be..9b8ecc1c33 100644 --- a/phpstan-baseline-7.4.neon +++ b/phpstan-baseline-7.4.neon @@ -6,17 +6,17 @@ parameters: path: src/bundle/Core/ApiLoader/StorageConnectionFactory.php - - message: "#^Parameter \\#1 \\$input of function array_filter expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$input of function array_filter expects array, iterable\\ given\\.$#" count: 1 path: src/bundle/Core/Command/CleanupVersionsCommand.php - - message: "#^Parameter \\#1 \\$input of function array_slice expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$input of function array_slice expects array, iterable\\ given\\.$#" count: 1 path: src/bundle/Core/Command/CleanupVersionsCommand.php - - message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" count: 1 path: src/bundle/Core/Command/CleanupVersionsCommand.php @@ -171,7 +171,7 @@ parameters: path: src/lib/MVC/Symfony/Component/Serializer/AbstractPropertyWhitelistNormalizer.php - - message: "#^Parameter \\#1 \\$input of function array_keys expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$input of function array_keys expects array, iterable\\ given\\.$#" count: 1 path: src/lib/MVC/Symfony/FieldType/RelationList/ParameterProvider.php @@ -351,17 +351,17 @@ parameters: path: tests/integration/Core/Repository/BaseTest.php - - message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" count: 4 path: tests/integration/Core/Repository/ContentServiceAuthorizationTest.php - - message: "#^Parameter \\#1 \\$array_arg of function usort expects TArray of array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$array_arg of function usort expects TArray of array, iterable\\ given\\.$#" count: 2 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Parameter \\#1 \\$input of function array_keys expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$input of function array_keys expects array, iterable\\ given\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -381,12 +381,12 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$var of function count expects array\\|Countable, iterable\\ given\\.$#" count: 2 path: tests/integration/Core/Repository/ContentServiceTest.php diff --git a/phpstan-baseline-gte-8.0.neon b/phpstan-baseline-gte-8.0.neon index 51c2930bdc..f4d695d9ca 100644 --- a/phpstan-baseline-gte-8.0.neon +++ b/phpstan-baseline-gte-8.0.neon @@ -6,17 +6,17 @@ parameters: path: src/bundle/Core/ApiLoader/StorageConnectionFactory.php - - message: "#^Parameter \\#1 \\$array of function array_filter expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$array of function array_filter expects array, iterable\\ given\\.$#" count: 1 path: src/bundle/Core/Command/CleanupVersionsCommand.php - - message: "#^Parameter \\#1 \\$array of function array_slice expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$array of function array_slice expects array, iterable\\ given\\.$#" count: 1 path: src/bundle/Core/Command/CleanupVersionsCommand.php - - message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" count: 1 path: src/bundle/Core/Command/CleanupVersionsCommand.php @@ -171,7 +171,7 @@ parameters: path: src/lib/MVC/Symfony/Component/Serializer/AbstractPropertyWhitelistNormalizer.php - - message: "#^Parameter \\#1 \\$array of function array_keys expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$array of function array_keys expects array, iterable\\ given\\.$#" count: 1 path: src/lib/MVC/Symfony/FieldType/RelationList/ParameterProvider.php @@ -306,12 +306,12 @@ parameters: path: tests/integration/Core/Repository/BaseTest.php - - message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" count: 4 path: tests/integration/Core/Repository/ContentServiceAuthorizationTest.php - - message: "#^Parameter \\#1 \\$array of function array_keys expects array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$array of function array_keys expects array, iterable\\ given\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -321,7 +321,7 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Parameter \\#1 \\$array of function usort expects TArray of array, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$array of function usort expects TArray of array, iterable\\ given\\.$#" count: 2 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -336,12 +336,12 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, iterable\\ given\\.$#" count: 2 path: tests/integration/Core/Repository/ContentServiceTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d601a47675..7bfaaed552 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -231,7 +231,7 @@ parameters: path: src/bundle/Core/Command/RegenerateUrlAliasesCommand.php - - message: "#^Cannot access offset int on iterable\\\\.$#" + message: "#^Cannot access offset int on iterable\\\\.$#" count: 1 path: src/bundle/Core/Command/RegenerateUrlAliasesCommand.php @@ -5550,11 +5550,6 @@ parameters: count: 1 path: src/contracts/Persistence/User/Policy.php - - - message: "#^Method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\ContentService\\:\\:loadContentByContentInfo\\(\\) has parameter \\$languages with no value type specified in iterable type array\\.$#" - count: 1 - path: src/contracts/Repository/ContentService.php - - message: "#^Method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\ContentService\\:\\:validate\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" count: 1 @@ -5565,21 +5560,6 @@ parameters: count: 1 path: src/contracts/Repository/ContentService.php - - - message: "#^PHPDoc tag @param for parameter \\$contentId with type mixed is not subtype of native type int\\.$#" - count: 1 - path: src/contracts/Repository/ContentService.php - - - - message: "#^PHPDoc tag @param has invalid value \\(\\\\Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\Language\\|null if not set the draft is created with the initialLanguage code of the source version or if not present with the main language\\.\\)\\: Unexpected token \"if\", expected variable at offset 870$#" - count: 1 - path: src/contracts/Repository/ContentService.php - - - - message: "#^Method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Decorator\\\\ContentServiceDecorator\\:\\:loadContentByContentInfo\\(\\) has parameter \\$languages with no value type specified in iterable type array\\.$#" - count: 1 - path: src/contracts/Repository/Decorator/ContentServiceDecorator.php - - message: "#^Method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Decorator\\\\ContentServiceDecorator\\:\\:validate\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" count: 1 @@ -8606,7 +8586,7 @@ parameters: path: src/lib/Base/Utils/DeprecationWarnerInterface.php - - message: "#^Parameter \\#1 \\$locations of class Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Events\\\\Content\\\\DeleteContentEvent constructor expects array, array\\|iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$locations of class Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Events\\\\Content\\\\DeleteContentEvent constructor expects array, array\\|iterable\\ given\\.$#" count: 1 path: src/lib/Event/ContentService.php @@ -19775,11 +19755,6 @@ parameters: count: 1 path: src/lib/Repository/ContentService.php - - - message: "#^Method Ibexa\\\\Core\\\\Repository\\\\ContentService\\:\\:loadContentByContentInfo\\(\\) has parameter \\$languages with no value type specified in iterable type array\\.$#" - count: 1 - path: src/lib/Repository/ContentService.php - - message: "#^Method Ibexa\\\\Core\\\\Repository\\\\ContentService\\:\\:loadContentByRemoteId\\(\\) has parameter \\$languages with no value type specified in iterable type array\\.$#" count: 1 @@ -19831,7 +19806,7 @@ parameters: path: src/lib/Repository/ContentService.php - - message: "#^Parameter \\#3 \\$prioritizedLanguages of method Ibexa\\\\Core\\\\Repository\\\\Mapper\\\\ContentDomainMapper\\:\\:buildContentDomainObjectFromPersistence\\(\\) expects array\\, array\\\\|null given\\.$#" + message: "#^Parameter \\#3 \\$prioritizedLanguages of method Ibexa\\\\Core\\\\Repository\\\\Mapper\\\\ContentDomainMapper\\:\\:buildContentDomainObjectFromPersistence\\(\\) expects array\\, array\\\\|null given\\.$#" count: 1 path: src/lib/Repository/ContentService.php @@ -20785,11 +20760,6 @@ parameters: count: 1 path: src/lib/Repository/SettingService.php - - - message: "#^Method Ibexa\\\\Core\\\\Repository\\\\SiteAccessAware\\\\ContentService\\:\\:loadContentByContentInfo\\(\\) has parameter \\$languages with no value type specified in iterable type array\\.$#" - count: 1 - path: src/lib/Repository/SiteAccessAware/ContentService.php - - message: "#^Method Ibexa\\\\Core\\\\Repository\\\\SiteAccessAware\\\\ContentService\\:\\:validate\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" count: 1 @@ -27121,52 +27091,62 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 0 on iterable\\\\.$#" + message: "#^Cannot access offset 0 on iterable\\\\.$#" count: 12 path: tests/integration/Core/Repository/ContentServiceTest.php - message: "#^Cannot access offset 0 on iterable\\\\.$#" - count: 11 + count: 7 + path: tests/integration/Core/Repository/ContentServiceTest.php + + - + message: "#^Cannot access offset 0 on iterable\\\\.$#" + count: 4 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 1 on iterable\\\\.$#" + message: "#^Cannot access offset 1 on iterable\\\\.$#" count: 4 path: tests/integration/Core/Repository/ContentServiceTest.php - message: "#^Cannot access offset 1 on iterable\\\\.$#" - count: 3 + count: 2 + path: tests/integration/Core/Repository/ContentServiceTest.php + + - + message: "#^Cannot access offset 1 on iterable\\\\.$#" + count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 2 on iterable\\\\.$#" + message: "#^Cannot access offset 2 on iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 3 on iterable\\\\.$#" + message: "#^Cannot access offset 3 on iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 4 on iterable\\\\.$#" + message: "#^Cannot access offset 4 on iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 5 on iterable\\\\.$#" + message: "#^Cannot access offset 5 on iterable\\\\.$#" count: 2 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset mixed on iterable\\\\.$#" + message: "#^Cannot access offset mixed on iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset mixed on iterable\\\\.$#" + message: "#^Cannot access offset int on iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -27256,7 +27236,7 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\ContentServiceTest\\:\\:testAddRelation\\(\\) should return array\\ but returns iterable\\\\.$#" + message: "#^Method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\ContentServiceTest\\:\\:testAddRelation\\(\\) should return array\\ but returns iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -27371,7 +27351,7 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\ContentServiceTest\\:\\:testCreateContentDraftWithRelations\\(\\) should return array\\ but returns iterable\\\\.$#" + message: "#^Method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\ContentServiceTest\\:\\:testCreateContentDraftWithRelations\\(\\) should return array\\ but returns iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -27871,7 +27851,7 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\ContentServiceTest\\:\\:testLoadVersions\\(\\) should return array\\ but returns iterable\\\\.$#" + message: "#^Method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\ContentServiceTest\\:\\:testLoadVersions\\(\\) should return array\\ but returns iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -30726,7 +30706,7 @@ parameters: path: tests/integration/Core/Repository/FieldType/RelationIntegrationTest.php - - message: "#^Parameter \\#1 \\$relations of method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\FieldType\\\\RelationIntegrationTest\\:\\:normalizeRelations\\(\\) expects array\\, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$relations of method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\FieldType\\\\RelationIntegrationTest\\:\\:normalizeRelations\\(\\) expects array\\, iterable\\ given\\.$#" count: 5 path: tests/integration/Core/Repository/FieldType/RelationIntegrationTest.php @@ -30826,7 +30806,7 @@ parameters: path: tests/integration/Core/Repository/FieldType/RelationListIntegrationTest.php - - message: "#^Parameter \\#1 \\$relations of method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\FieldType\\\\RelationListIntegrationTest\\:\\:normalizeRelations\\(\\) expects array\\, iterable\\ given\\.$#" + message: "#^Parameter \\#1 \\$relations of method Ibexa\\\\Tests\\\\Integration\\\\Core\\\\Repository\\\\FieldType\\\\RelationListIntegrationTest\\:\\:normalizeRelations\\(\\) expects array\\, iterable\\ given\\.$#" count: 5 path: tests/integration/Core/Repository/FieldType/RelationListIntegrationTest.php @@ -56101,7 +56081,7 @@ parameters: path: tests/lib/Repository/Decorator/ContentServiceDecoratorTest.php - - message: "#^Parameter \\#1 \\$contentIds of method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\ContentService\\:\\:loadContentInfoList\\(\\) expects array\\, array\\ given\\.$#" + message: "#^Parameter \\#1 \\$contentIds of method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\ContentService\\:\\:loadContentInfoList\\(\\) expects array\\, array\\ given\\.$#" count: 1 path: tests/lib/Repository/Decorator/ContentServiceDecoratorTest.php diff --git a/src/contracts/Repository/ContentService.php b/src/contracts/Repository/ContentService.php index fb3bccb5c9..481bdc9c8e 100644 --- a/src/contracts/Repository/ContentService.php +++ b/src/contracts/Repository/ContentService.php @@ -37,23 +37,19 @@ interface ContentService * * To load fields use loadContent * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException - if the content with the given id does not exist - * - * @param int $contentId - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if the content with the given id doesn't exist. */ public function loadContentInfo(int $contentId): ContentInfo; /** * Bulk-load ContentInfo items by id's. * - * Note: It does not throw exceptions on load, just skips erroneous (NotFound or Unauthorized) ContentInfo items. + * Note: It doesn't throw exceptions on load, just skips erroneous (NotFound or Unauthorized) ContentInfo items. * - * @param int[] $contentIds + * @param array $contentIds * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo[] list of ContentInfo with Content Ids as keys + * @return array List of ContentInfo with content ids as keys */ public function loadContentInfoList(array $contentIds): iterable; @@ -62,49 +58,39 @@ public function loadContentInfoList(array $contentIds): iterable; * * To load fields use loadContent * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException - if the content with the given remote id does not exist - * - * @param string $remoteId - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read the content. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if the content with the given remote id doesn't exist. */ public function loadContentInfoByRemoteId(string $remoteId): ContentInfo; /** * Loads a version info of the given content object. * - * If no version number is given, the method returns the current version + * If no version number is given, the method returns the current version. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException - if the version with the given number does not exist - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if the version with the given number doesn't exist. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo * @param int|null $versionNo the version number. If not given the current version is returned. - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo */ public function loadVersionInfo(ContentInfo $contentInfo, ?int $versionNo = null): VersionInfo; /** * Loads a version info of the given content object id. * - * If no version number is given, the method returns the current version + * If no version number is given, the method returns the current version. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException - if the version with the given number does not exist - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if the version with the given number doesn't exist. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version. * - * @param int $contentId * @param int|null $versionNo the version number. If not given the current version is returned. - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo */ public function loadVersionInfoById(int $contentId, ?int $versionNo = null): VersionInfo; /** * Bulk-load VersionInfo items by the list of ContentInfo Value Objects. * - * @param array<\Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo> $contentInfoList + * @param array $contentInfoList * * @return array List of VersionInfo items with Content Ids as keys * @@ -119,30 +105,24 @@ public function loadVersionInfoListByContentInfo(array $contentInfoList): array; * * If no version number is given, the method returns the current version * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException - if version with the given number does not exist - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if version with the given number doesn't exist. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * @param array $languages A language priority, filters returned fields and is used as prioritized language code on + * @param array $languages A language priority, filters returned fields and is used as prioritized language code on * returned value object. If not given all languages are returned. - * @param int|null $versionNo the version number. If not given the current version is returned from $contentInfo - * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content + * @param int|null $versionNo The version number. If not given the current version is returned from $contentInfo. + * @param bool $useAlwaysAvailable Add Main language to $languages if true (default) and if {@see ContentInfo::$alwaysAvailable} is true. */ public function loadContentByContentInfo(ContentInfo $contentInfo, array $languages = null, ?int $versionNo = null, bool $useAlwaysAvailable = true): Content; /** * Loads content in the version given by version info. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to load this version. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on * returned value object. If not given all languages are returned. - * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content + * @param bool $useAlwaysAvailable Add Main language to $languages if true (default) and if {@see ContentInfo::$alwaysAvailable} is true. */ public function loadContentByVersionInfo(VersionInfo $versionInfo, array $languages = null, bool $useAlwaysAvailable = true): Content; @@ -151,16 +131,15 @@ public function loadContentByVersionInfo(VersionInfo $versionInfo, array $langua * * If no version number is given, the method returns the current version * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if the content or version with the given id and languages does not exist - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException If the user has no access to read content and in case of un-published content: read versions + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if the content or version with the given id and languages doesn't exist. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user lacks: + * - `content/read` permission for published content, or + * - `content/read` and `content/versionread` permissions for draft content. * - * @param mixed $contentId - * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on + * @param array $languages A language priority, filters returned fields and is used as prioritized language code on * returned value object. If not given all languages are returned. - * @param int|null $versionNo the version number. If not given the current version is returned - * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content + * @param int|null $versionNo The version number. If not given the current version is returned. + * @param bool $useAlwaysAvailable Add Main language to $languages if true (default) and if {@see ContentInfo::$alwaysAvailable} is true. */ public function loadContent(int $contentId, array $languages = null, ?int $versionNo = null, bool $useAlwaysAvailable = true): Content; @@ -169,33 +148,32 @@ public function loadContent(int $contentId, array $languages = null, ?int $versi * * If no version is given, the method returns the current version * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException - if the content or version with the given remote id does not exist - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException If the user has no access to read content and in case of un-published content: read versions + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if the content or version with the given remote id doesn't exist. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user lacks: + * - `content/read` permission for published content, or + * - `content/read` and `content/versionread` permissions for draft content. * - * @param string $remoteId * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on * returned value object. If not given all languages are returned. * @param int|null $versionNo the version number. If not given the current version is returned - * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content + * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if {@see ContentInfo::$alwaysAvailable} is true. */ public function loadContentByRemoteId(string $remoteId, array $languages = null, ?int $versionNo = null, bool $useAlwaysAvailable = true): Content; /** * Bulk-load Content items by the list of ContentInfo Value Objects. * - * Note: it does not throw exceptions on load, just ignores erroneous Content item. + * Note: it doesn't throw exceptions on load, just ignores erroneous Content item. * Moreover, since the method works on pre-loaded ContentInfo list, it is assumed that user is * allowed to access every Content on the list. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo[] $contentInfoList - * @param string[] $languages A language priority, filters returned fields and is used as prioritized language code on + * @param array $contentInfoList + * @param array $languages A language priority, filters returned fields and is used as prioritized language code on * returned value object. If not given all languages are returned. - * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if alwaysAvailable is true, + * @param bool $useAlwaysAvailable Add Main language to \$languages if true (default) and if {@see ContentInfo::$alwaysAvailable} is true, * unless all languages have been asked for. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content[] list of Content items with Content Ids as keys + * @return array List of Content items with Content Ids as keys */ public function loadContentListByContentInfo(array $contentInfoList, array $languages = [], bool $useAlwaysAvailable = true): iterable; @@ -204,49 +182,43 @@ public function loadContentListByContentInfo(array $contentInfoList, array $lang * * If a different userId is given in $contentCreateStruct it is assigned to the given user * but this required special rights for the authenticated user - * (this is useful for content staging where the transfer process does not + * (this is useful for content staging where the transfer process doesn't * have to authenticate with the user which created the content object in the source server). * The user has to publish the draft if it should be visible. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to create the content in the given location. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if there is a provided remote ID which exists in the system or multiple Locations * are under the same parent or if the a field value is not accepted by the field type - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException if a required field is missing or is set to an empty value + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException if a field in the $contentCreateStruct is not valid. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException if a required field is missing or is set to an empty value. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentCreateStruct $contentCreateStruct - * @param \Ibexa\Contracts\Core\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs an array of {@see \Ibexa\Contracts\Core\Repository\Values\Content\LocationCreateStruct} for each location parent under which a location should be created for the content + * @param \Ibexa\Contracts\Core\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs An array of {@see \Ibexa\Contracts\Core\Repository\Values\Content\LocationCreateStruct} for each location parent under which a location should be created for the content. * While optional, it's highly recommended to use Locations for content as a lot of features in the system is usually tied to the tree structure (including default Role policies). * @param string[]|null $fieldIdentifiersToValidate List of field identifiers for partial validation or null * for case of full validation. Empty identifiers array is equal to no validation. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content - the newly created content draft + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content The newly created content draft. */ public function createContent(ContentCreateStruct $contentCreateStruct, array $locationCreateStructs = [], ?array $fieldIdentifiersToValidate = null): Content; /** * Updates the metadata. * - * See {@see ContentMetadataUpdateStruct} of a content object - to update fields use updateContent + * To update fields, use {@see ContentService::updateContent()}. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content meta data - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to update the content metadata. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if the remoteId in $contentMetadataUpdateStruct is set but already exists. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentMetadataUpdateStruct $contentMetadataUpdateStruct - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content the content with the updated attributes + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content The content with the updated attributes. */ public function updateContentMetadata(ContentInfo $contentInfo, ContentMetadataUpdateStruct $contentMetadataUpdateStruct): Content; /** * Deletes a content object including all its versions and locations including their subtrees. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete the content (in one of the locations of the given content object) - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete the content (in one of the locations of the given content object). * - * @return int[] Affected Location Id's (List of Locations of the Content that was deleted) + * @return array Affected Location IDs (List of Location IDs of the Content that was deleted). */ public function deleteContent(ContentInfo $contentInfo): iterable; @@ -257,14 +229,12 @@ public function deleteContent(ContentInfo $contentInfo): iterable; * 4.x: The draft is created with the initialLanguage code of the source version or if not present with the main language. * It can be changed on updating the version. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to create the draft + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the current user is not allowed to create the draft. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo|null $versionInfo - * @param \Ibexa\Contracts\Core\Repository\Values\User\User|null $creator Used as creator of the draft if given - otherwise uses current-user - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Language|null if not set the draft is created with the initialLanguage code of the source version or if not present with the main language. + * @param \Ibexa\Contracts\Core\Repository\Values\User\User|null $creator Used as creator of the draft if given; otherwise uses current user. + * @param \Ibexa\Contracts\Core\Repository\Values\Content\Language|null $language If not set the draft is created with the initialLanguage code of the source version or if not present with the main language. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content - the newly created content draft + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content The newly created content draft. */ public function createContentDraft( ContentInfo $contentInfo, @@ -276,11 +246,11 @@ public function createContentDraft( /** * Counts drafts for a user. * - * If no user is given the number of drafts for the authenticated user are returned + * If no user is given the number of drafts for the authenticated user are returned. * - * @param \Ibexa\Contracts\Core\Repository\Values\User\User $user The user to load drafts for, if defined, otherwise drafts for current-user + * @param \Ibexa\Contracts\Core\Repository\Values\User\User $user The user to load drafts for, if defined, otherwise drafts for current user. * - * @return int The number of drafts ({@see VersionInfo}) owned by the given user + * @return int The number of drafts ({@see VersionInfo}) owned by the given user. * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException */ @@ -289,48 +259,42 @@ public function countContentDrafts(?User $user = null): int; /** * Loads drafts for a user. * - * If no user is given the drafts for the authenticated user are returned + * If no user is given the drafts for the authenticated user are returned. * * @deprecated Please use {@see ContentService::loadContentDraftList()} instead to avoid risking loading too much data. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to load the draft list + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the current user is not allowed to load the draft list * - * @param \Ibexa\Contracts\Core\Repository\Values\User\User $user The user to load drafts for, if defined, otherwise drafts for current-user + * @param \Ibexa\Contracts\Core\Repository\Values\User\User|null $user The user to load drafts for. If `null`, the current user's drafts are loaded. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo[] the drafts ({@see VersionInfo}) owned by the given user + * @return \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo[] The drafts owned by the given user. */ public function loadContentDrafts(?User $user = null): iterable; /** * Loads drafts for a user when content is not in the trash. The list is sorted by modification date. * - * If no user is given the drafts for the authenticated user are returned + * If no user is given the drafts for the authenticated user are returned. * * @since 7.5.5 * - * @param \Ibexa\Contracts\Core\Repository\Values\User\User|null $user The user to load drafts for, if defined, otherwise drafts for current-user - * @param int $offset - * @param int $limit - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\ContentDraftList + * @param \Ibexa\Contracts\Core\Repository\Values\User\User|null $user The user to load drafts for, if defined; otherwise drafts for current user. */ public function loadContentDraftList(?User $user = null, int $offset = 0, int $limit = -1): ContentDraftList; /** * Updates the fields of a draft. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException if a field in the $contentUpdateStruct is not valid - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException if a required field is set to an empty value - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if a field value is not accepted by the field type + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to update this version. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException if a field in the $contentUpdateStruct is not valid. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException if a required field is set to an empty value. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if a field value is not accepted by the field type. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentUpdateStruct $contentUpdateStruct - * @param string[]|null $fieldIdentifiersToValidate List of field identifiers for partial validation or null + * @param array|null $fieldIdentifiersToValidate List of field identifiers for partial validation or null * for case of full validation. Empty identifiers array is equal to no validation. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content the content draft with the updated fields + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content The content draft with the updated fields. */ public function updateContent(VersionInfo $versionInfo, ContentUpdateStruct $contentUpdateStruct, ?array $fieldIdentifiersToValidate = null): Content; @@ -342,18 +306,15 @@ public function updateContent(VersionInfo $versionInfo, ContentUpdateStruct $con * * @todo Introduce null|int ContentType->versionArchiveLimit to be able to let admins override this per type. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish this version. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo - * @param string[] $translations List of language codes of translations which will be included + * @param array $translations List of language codes of translations which will be included * in a published version. - * By default all translations from the current version will be published. - * If the list is provided but does not cover all currently published translations, + * By default, all translations from the current version will be published. + * If the list is provided but doesn't cover all currently published translations, * the missing ones will be copied from the currently published version, * overriding those in the current version. - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content */ public function publishVersion(VersionInfo $versionInfo, array $translations = Language::ALL): Content; @@ -361,23 +322,18 @@ public function publishVersion(VersionInfo $versionInfo, array $translations = L * Removes the given version. * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is in - * published state or is a last version of Content in non draft state - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo + * published state or is a last version of Content in non-draft state. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to remove this version. */ public function deleteVersion(VersionInfo $versionInfo): void; /** * Loads all versions for the given content. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if the given status is invalid - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * @param int|null $status + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to list versions. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if the given status is invalid. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo[] an array of {@see \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo} sorted by creation date + * @return array An array of {@see \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo} sorted by creation date. */ public function loadVersions(ContentInfo $contentInfo, ?int $status = null): iterable; @@ -385,24 +341,20 @@ public function loadVersions(ContentInfo $contentInfo, ?int $status = null): ite * Copies the content to a new location. If no version is given, * all versions are copied, otherwise only the given version. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * @param \Ibexa\Contracts\Core\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct the target location where the content is copied to - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to copy the content to the given location. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content + * @param \Ibexa\Contracts\Core\Repository\Values\Content\LocationCreateStruct $destinationLocationCreateStruct The target location where the content is copied to. */ public function copyContent(ContentInfo $contentInfo, LocationCreateStruct $destinationLocationCreateStruct, ?VersionInfo $versionInfo = null): Content; /** * Loads all outgoing relations for the given version. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version. * - * @deprecated 4.5.7 The "ContentService::loadRelations()" method is deprecated, will be removed in 5.0. + * @deprecated 4.5.7 It will be removed in 5.0. Use {@see ContentService::loadRelationList()} instead. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Relation[] + * @return array */ public function loadRelations(VersionInfo $versionInfo): iterable; @@ -433,9 +385,7 @@ public function countRelations(VersionInfo $versionInfo): int; /** * Counts all incoming relations for the given content object. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * - * @return int The number of reverse relations ({@see \Ibexa\Contracts\Core\Repository\Values\Content\Relation}) + * @return int The number of reverse relations ({@see Relation}). */ public function countReverseRelations(ContentInfo $contentInfo): int; @@ -444,11 +394,9 @@ public function countReverseRelations(ContentInfo $contentInfo): int; * * The relations come only from published versions of the source content objects * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to read this version. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Relation[] + * @return array */ public function loadReverseRelations(ContentInfo $contentInfo): iterable; @@ -456,42 +404,36 @@ public function loadReverseRelations(ContentInfo $contentInfo): iterable; * Loads all incoming relations for a content object. * * The relations come only from published versions of the source content objects. - * If the user is not allowed to read specific version then UnauthorizedRelationListItem is returned - * {@see \Ibexa\Contracts\Core\Repository\Values\Content\RelationList\Item\UnauthorizedRelationListItem} - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * @param int $offset - * @param int $limit - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\RelationList + * If the user is not allowed to read specific version then {@see \Ibexa\Contracts\Core\Repository\Values\Content\RelationList\Item\UnauthorizedRelationListItem} is returned */ public function loadReverseRelationList(ContentInfo $contentInfo, int $offset = 0, int $limit = -1): RelationList; /** - * Adds a relation of type common. + * Adds a common relation. * * The source of the relation is the content and version - * referenced by $versionInfo. + * referenced by $sourceVersion. + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit this version - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft + * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $sourceVersion The source content's version in relation with the destination. + * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $destinationContent The destination of the relation. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $sourceVersion - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $destinationContent the destination of the relation + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Relation The newly created relation. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Relation the newly created relation + * @see Relation::COMMON */ public function addRelation(VersionInfo $sourceVersion, ContentInfo $destinationContent): Relation; /** - * Removes a relation of type COMMON from a draft. + * Removes a common relation from a draft. * - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if there is no relation of type COMMON for the given destination + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed edit this version. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the version is not a draft. + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if there is no relation of type {@see Relation::COMMON} for the given destination. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $sourceVersion - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $destinationContent + * @see Relation::COMMON */ public function deleteRelation(VersionInfo $sourceVersion, ContentInfo $destinationContent): void; @@ -504,12 +446,9 @@ public function deleteRelation(VersionInfo $sourceVersion, ContentInfo $destinat * is the Main Translation of a Content Item. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException if the user is not allowed * to delete the content (in one of the locations of the given Content Item). - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if languageCode argument + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if $languageCode argument * is invalid for the given content. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo - * @param string $languageCode - * * @since 6.13 */ public function deleteTranslation(ContentInfo $contentInfo, string $languageCode): void; @@ -517,7 +456,7 @@ public function deleteTranslation(ContentInfo $contentInfo, string $languageCode /** * Delete specified Translation from a Content Draft. * - * When using together with ContentService::publishVersion() method, make sure to not provide deleted translation + * When using together with {@see ContentService::publishVersion()} method, make sure to not provide deleted translation * in translations array, as it is going to be copied again from published version. * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException if the specified Translation @@ -526,12 +465,12 @@ public function deleteTranslation(ContentInfo $contentInfo, string $languageCode * to edit the Content (in one of the locations of the given Content Object). * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException if languageCode argument * is invalid for the given Draft. - * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if specified Version was not found + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException if specified Version was not found. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft - * @param string $languageCode Language code of the Translation to be removed + * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo Content Version Draft. + * @param string $languageCode Language code of the Translation to be removed. * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content Content Draft w/o the specified Translation + * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content Content Draft without the specified Translation. * * @since 6.12 */ @@ -539,49 +478,38 @@ public function deleteTranslationFromDraft(VersionInfo $versionInfo, string $lan /** * Hides Content by making all the Locations appear hidden. - * It does not persist hidden state on Location object itself. * - * Content hidden by this API can be revealed by revealContent API. + * It doesn't persist hidden state on Location object itself. * - * @see ContentService::revealContent() + * Content hidden by this API can be revealed by {@see ContentService::revealContent()} API. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo + * @see ContentService::revealContent() */ public function hideContent(ContentInfo $contentInfo): void; /** * Reveals Content hidden by hideContent API. + * * Locations which were hidden before hiding Content will remain hidden. * * @see ContentService::hideContent() - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo $contentInfo */ public function revealContent(ContentInfo $contentInfo): void; /** * Instantiates a new content create struct object. * - * alwaysAvailable is set to the ContentType's defaultAlwaysAvailable - * - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType - * @param string $mainLanguageCode - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\ContentCreateStruct + * {@see ContentCreateStruct::$alwaysAvailable} is set to the {@see ContentType::$defaultAlwaysAvailable}. */ public function newContentCreateStruct(ContentType $contentType, string $mainLanguageCode): ContentCreateStruct; /** * Instantiates a new content meta data update struct. - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\ContentMetadataUpdateStruct */ public function newContentMetadataUpdateStruct(): ContentMetadataUpdateStruct; /** * Instantiates a new content update struct. - * - * @return \Ibexa\Contracts\Core\Repository\Values\Content\ContentUpdateStruct */ public function newContentUpdateStruct(): ContentUpdateStruct; @@ -589,7 +517,7 @@ public function newContentUpdateStruct(): ContentUpdateStruct; * Validates given content related ValueObject returning field errors structure as a result. * * @param array $context Additional context parameters to be used by validators. - * @param string[]|null $fieldIdentifiersToValidate List of field identifiers for partial validation or null + * @param array|null $fieldIdentifiersToValidate List of field identifiers for partial validation, or null * for case of full validation. Empty identifiers array is equal to no validation. * * @return array Validation errors grouped by field definition and language code, in format: @@ -600,18 +528,20 @@ public function newContentUpdateStruct(): ContentUpdateStruct; public function validate(ValueObject $object, array $context, ?array $fieldIdentifiersToValidate = null): array; /** - * Fetch Content items from the Repository filtered by the given conditions. + * Fetches Content items from the Repository filtered by the given conditions. * - * @param string[] $languages a list of language codes to be added as additional constraints. + * @param array $languages A list of language codes to be added as additional constraints. * If skipped, by default, unless SiteAccessAware layer has been disabled, languages set * for a SiteAccess in a current context will be used. */ public function find(Filter $filter, ?array $languages = null): ContentList; /** - * Count total number of items returned by {@see ContentService::find()} method. + * Gets the total number of fetchable Content items. + * + * Counts total number of items returned by {@see ContentService::find()} with the same parameters. * - * @param string[] $languages a list of language codes to be added as additional constraints. + * @param array $languages A list of language codes to be added as additional constraints. * If skipped, by default, unless SiteAccessAware layer has been disabled, languages set * for a SiteAccess in a current context will be used. */ From 02200e652f7e377d0a4d024571fbf53d1ff976e2 Mon Sep 17 00:00:00 2001 From: Bartek Wajda Date: Fri, 8 Nov 2024 16:26:12 +0100 Subject: [PATCH 2/6] IBX-8418: Fixed removing orphaned drafts when trashing or deleting its ancestors (#439) For more details see https://issues.ibexa.co/browse/IBX-8418 and https://github.com/ibexa/core/pull/439 Key changes: * Fixed removing orphaned draft when trashing or deleting its parent or ancestor location --- phpstan-baseline.neon | 25 ++-- .../Persistence/Content/Location/Handler.php | 5 + src/contracts/Test/IbexaKernelTestTrait.php | 6 + src/contracts/Test/IbexaTestKernel.php | 1 + src/lib/Persistence/Cache/LocationHandler.php | 14 +++ .../Persistence/Legacy/Content/Handler.php | 1 + .../Legacy/Content/Location/Gateway.php | 7 ++ .../Location/Gateway/DoctrineDatabase.php | 23 ++++ .../Location/Gateway/ExceptionConversion.php | 12 ++ .../Legacy/Content/Location/Handler.php | 5 + .../Legacy/Content/TreeHandler.php | 20 ++++ src/lib/Repository/TrashService.php | 1 + .../ContentService/DeleteContentTest.php | 111 ++++++++++++++++++ .../Persistence/Cache/LocationHandlerTest.php | 1 + .../Legacy/Content/LocationHandlerTest.php | 12 ++ .../Legacy/Content/TreeHandlerTest.php | 55 +++++++++ 16 files changed, 284 insertions(+), 15 deletions(-) create mode 100644 tests/integration/Core/Repository/ContentService/DeleteContentTest.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7bfaaed552..668d9e640a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -19010,11 +19010,6 @@ parameters: count: 1 path: src/lib/Persistence/Legacy/User/Mapper.php - - - message: "#^array\\|string does not accept array\\\\.$#" - count: 1 - path: src/lib/Persistence/Legacy/User/Mapper.php - - message: "#^Method Ibexa\\\\Core\\\\Persistence\\\\Legacy\\\\User\\\\Role\\\\Gateway\\:\\:addPolicyLimitations\\(\\) has parameter \\$limitations with no value type specified in iterable type array\\.$#" count: 1 @@ -27090,23 +27085,18 @@ parameters: count: 5 path: tests/integration/Core/Repository/ContentServiceTest.php - - - message: "#^Cannot access offset 0 on iterable\\\\.$#" - count: 12 - path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 0 on iterable\\\\.$#" count: 7 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 0 on iterable\\\\.$#" - count: 4 + message: "#^Cannot access offset 0 on iterable\\\\.$#" + count: 12 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset 1 on iterable\\\\.$#" + message: "#^Cannot access offset 0 on iterable\\\\.$#" count: 4 path: tests/integration/Core/Repository/ContentServiceTest.php @@ -27115,6 +27105,11 @@ parameters: count: 2 path: tests/integration/Core/Repository/ContentServiceTest.php + - + message: "#^Cannot access offset 1 on iterable\\\\.$#" + count: 4 + path: tests/integration/Core/Repository/ContentServiceTest.php + - message: "#^Cannot access offset 1 on iterable\\\\.$#" count: 1 @@ -27141,12 +27136,12 @@ parameters: path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset mixed on iterable\\\\.$#" + message: "#^Cannot access offset int on iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php - - message: "#^Cannot access offset int on iterable\\\\.$#" + message: "#^Cannot access offset mixed on iterable\\\\.$#" count: 1 path: tests/integration/Core/Repository/ContentServiceTest.php diff --git a/src/contracts/Persistence/Content/Location/Handler.php b/src/contracts/Persistence/Content/Location/Handler.php index 0fbea26996..5eb76c4c78 100644 --- a/src/contracts/Persistence/Content/Location/Handler.php +++ b/src/contracts/Persistence/Content/Location/Handler.php @@ -215,6 +215,11 @@ public function create(CreateStruct $location); */ public function removeSubtree($locationId); + /** + * Removes all draft contents that have no location assigned to them under the given parent location. + */ + public function deleteChildrenDrafts(int $locationId): void; + /** * Set section on all content objects in the subtree. * Only main locations will be updated. diff --git a/src/contracts/Test/IbexaKernelTestTrait.php b/src/contracts/Test/IbexaKernelTestTrait.php index 6b75669022..c40329634d 100644 --- a/src/contracts/Test/IbexaKernelTestTrait.php +++ b/src/contracts/Test/IbexaKernelTestTrait.php @@ -19,6 +19,7 @@ use Ibexa\Contracts\Core\Repository\RoleService; use Ibexa\Contracts\Core\Repository\SearchService; use Ibexa\Contracts\Core\Repository\SectionService; +use Ibexa\Contracts\Core\Repository\TrashService; use Ibexa\Contracts\Core\Repository\URLAliasService; use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Contracts\Core\Test\Persistence\Fixture\FixtureImporter; @@ -169,6 +170,11 @@ protected static function getUrlAliasService(): URLAliasService return self::getServiceByClassName(URLAliasService::class); } + protected static function getTrashService(): TrashService + { + return self::getServiceByClassName(TrashService::class); + } + protected static function setAnonymousUser(): void { $anonymousUserId = 10; diff --git a/src/contracts/Test/IbexaTestKernel.php b/src/contracts/Test/IbexaTestKernel.php index ca18fc32a7..5c89768f84 100644 --- a/src/contracts/Test/IbexaTestKernel.php +++ b/src/contracts/Test/IbexaTestKernel.php @@ -92,6 +92,7 @@ class IbexaTestKernel extends Kernel implements IbexaTestKernelInterface Repository\TokenService::class, Repository\URLAliasService::class, Repository\BookmarkService::class, + Repository\TrashService::class, Handler::class, ]; diff --git a/src/lib/Persistence/Cache/LocationHandler.php b/src/lib/Persistence/Cache/LocationHandler.php index dc07fd9f93..74a50a2039 100644 --- a/src/lib/Persistence/Cache/LocationHandler.php +++ b/src/lib/Persistence/Cache/LocationHandler.php @@ -415,6 +415,20 @@ public function removeSubtree($locationId) return $return; } + public function deleteChildrenDrafts(int $locationId): void + { + $this->logger->logCall(__METHOD__, ['location' => $locationId]); + + $this->persistenceHandler->locationHandler()->deleteChildrenDrafts($locationId); + + $this->cache->invalidateTags([ + $this->cacheIdentifierGenerator->generateTag( + self::LOCATION_PATH_IDENTIFIER, + [$locationId], + ), + ]); + } + /** * {@inheritdoc} */ diff --git a/src/lib/Persistence/Legacy/Content/Handler.php b/src/lib/Persistence/Legacy/Content/Handler.php index 56871b0d27..0acf1ab1e0 100644 --- a/src/lib/Persistence/Legacy/Content/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Handler.php @@ -650,6 +650,7 @@ public function deleteContent($contentId) $this->removeRawContent($contentId); } else { foreach ($contentLocations as $locationId) { + $this->treeHandler->deleteChildrenDrafts($locationId); $this->treeHandler->removeSubtree($locationId); } } diff --git a/src/lib/Persistence/Legacy/Content/Location/Gateway.php b/src/lib/Persistence/Legacy/Content/Location/Gateway.php index 57e7838eeb..5218cad951 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Gateway.php +++ b/src/lib/Persistence/Legacy/Content/Location/Gateway.php @@ -111,6 +111,13 @@ abstract public function loadParentLocationsDataForDraftContent(int $contentId): */ abstract public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array; + /** + * Finds draft contents created under the given parent location. + * + * @return array + */ + abstract public function getSubtreeChildrenDraftContentIds(int $sourceId): array; + abstract public function getSubtreeSize(string $path): int; /** diff --git a/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php index 8341e96196..bfd06ecb2a 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Location/Gateway/DoctrineDatabase.php @@ -237,6 +237,29 @@ public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array : $results; } + /** + * @return array + * + * @throws \Doctrine\DBAL\Exception + * @throws \Doctrine\DBAL\Driver\Exception + */ + public function getSubtreeChildrenDraftContentIds(int $sourceId): array + { + $query = $this->connection->createQueryBuilder(); + $query + ->select('contentobject_id') + ->from('eznode_assignment', 'n') + ->innerJoin('n', 'ezcontentobject', 'c', 'n.contentobject_id = c.id') + ->andWhere('n.parent_node = :parentNode') + ->andWhere('c.status = :status') + ->setParameter(':parentNode', $sourceId, ParameterType::INTEGER) + ->setParameter(':status', ContentInfo::STATUS_DRAFT, ParameterType::INTEGER); + + $statement = $query->execute(); + + return $statement->fetchFirstColumn(); + } + public function getSubtreeSize(string $path): int { $query = $this->createNodeQueryBuilder([$this->dbPlatform->getCountExpression('node_id')]); diff --git a/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php b/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php index 70fe4b717c..e2d3c5db23 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php +++ b/src/lib/Persistence/Legacy/Content/Location/Gateway/ExceptionConversion.php @@ -106,6 +106,18 @@ public function getSubtreeContent(int $sourceId, bool $onlyIds = false): array } } + /** + * @return array + */ + public function getSubtreeChildrenDraftContentIds(int $sourceId): array + { + try { + return $this->innerGateway->getSubtreeChildrenDraftContentIds($sourceId); + } catch (PDOException $e) { + throw DatabaseException::wrap($e); + } + } + public function getSubtreeSize(string $path): int { try { diff --git a/src/lib/Persistence/Legacy/Content/Location/Handler.php b/src/lib/Persistence/Legacy/Content/Location/Handler.php index 758e221c4a..8ce6a52493 100644 --- a/src/lib/Persistence/Legacy/Content/Location/Handler.php +++ b/src/lib/Persistence/Legacy/Content/Location/Handler.php @@ -546,6 +546,11 @@ public function removeSubtree($locationId) $this->treeHandler->removeSubtree($locationId); } + public function deleteChildrenDrafts(int $locationId): void + { + $this->treeHandler->deleteChildrenDrafts($locationId); + } + /** * Set section on all content objects in the subtree. * diff --git a/src/lib/Persistence/Legacy/Content/TreeHandler.php b/src/lib/Persistence/Legacy/Content/TreeHandler.php index 700b067e94..629326589b 100644 --- a/src/lib/Persistence/Legacy/Content/TreeHandler.php +++ b/src/lib/Persistence/Legacy/Content/TreeHandler.php @@ -215,6 +215,26 @@ public function removeSubtree($locationId) $this->locationGateway->deleteNodeAssignment($contentId); } + /** + * Removes draft contents assigned to the given parent location and its descendant locations. + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + */ + public function deleteChildrenDrafts(int $locationId): void + { + $subLocations = $this->locationGateway->getChildren($locationId); + foreach ($subLocations as $subLocation) { + $this->deleteChildrenDrafts($subLocation['node_id']); + } + + // Fetch child draft content ids + $subtreeChildrenDraftIds = $this->locationGateway->getSubtreeChildrenDraftContentIds($locationId); + + foreach ($subtreeChildrenDraftIds as $contentId) { + $this->removeRawContent($contentId); + } + } + /** * Set section on all content objects in the subtree. * diff --git a/src/lib/Repository/TrashService.php b/src/lib/Repository/TrashService.php index e32a0cea27..3ab569d18a 100644 --- a/src/lib/Repository/TrashService.php +++ b/src/lib/Repository/TrashService.php @@ -146,6 +146,7 @@ public function trash(Location $location): ?APITrashItem $this->repository->beginTransaction(); try { + $this->persistenceHandler->locationHandler()->deleteChildrenDrafts($location->id); $spiTrashItem = $this->persistenceHandler->trashHandler()->trashSubtree($location->id); $this->persistenceHandler->urlAliasHandler()->locationDeleted($location->id); $this->repository->commit(); diff --git a/tests/integration/Core/Repository/ContentService/DeleteContentTest.php b/tests/integration/Core/Repository/ContentService/DeleteContentTest.php new file mode 100644 index 0000000000..e1dd1faf7a --- /dev/null +++ b/tests/integration/Core/Repository/ContentService/DeleteContentTest.php @@ -0,0 +1,111 @@ +prepareContentStructure(); + + $contentService->deleteContent($folder->getContentInfo()); + + $contentInfos = $contentService->loadContentInfoList([ + $draft1->getId(), + $draft2->getId(), + $draft3->getId(), + $draftSecondDepth->getId(), + ]); + + self::assertEmpty($contentInfos); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + public function testTrashLocationDeletesChildrenDrafts(): void + { + $trashService = self::getTrashService(); + $contentService = self::getContentService(); + + [$folder, $draft1, $draft2, $draft3, $draftSecondDepth] = $this->prepareContentStructure(); + + $folderMainLocationId = $folder->getVersionInfo()->getContentInfo()->getMainLocationId(); + Assert::assertIsNumeric($folderMainLocationId); + + $locationToTrash = self::getLocationService()->loadLocation($folderMainLocationId); + + $trashService->trash($locationToTrash); + + $contentInfos = $contentService->loadContentInfoList([ + $draft1->getId(), + $draft2->getId(), + $draft3->getId(), + $draftSecondDepth->getId(), + ]); + + self::assertEmpty($contentInfos); + } + + /** + * @return array + */ + private function prepareContentStructure(): array + { + $folder = $this->createFolder(['eng-GB' => 'Folder'], 2); + $folderMainLocationId = $folder->getVersionInfo()->getContentInfo()->getMainLocationId(); + Assert::assertIsNumeric($folderMainLocationId); + + $childFolder = $this->createFolder( + ['eng-GB' => 'Child folder'], + $folderMainLocationId, + ); + $childFolderMainLocationId = $childFolder->getVersionInfo()->getContentInfo()->getMainLocationId(); + Assert::assertIsNumeric($childFolderMainLocationId); + + $secondDepthChildFolder = $this->createFolder( + ['eng-GB' => 'Second depth folder'], + $childFolderMainLocationId, + ); + $secondDepthChildFolderLocationId = $secondDepthChildFolder + ->getVersionInfo() + ->getContentInfo() + ->getMainLocationId() + ; + Assert::assertIsNumeric($secondDepthChildFolderLocationId); + + $draft1 = $this->createFolderDraft(['eng-GB' => 'Folder draft 1'], $folderMainLocationId); + $draft2 = $this->createFolderDraft(['eng-GB' => 'Folder draft 2'], $childFolderMainLocationId); + $draft3 = $this->createFolderDraft(['eng-GB' => 'Folder draft 3'], $childFolderMainLocationId); + $draftSecondDepth = $this->createFolderDraft( + ['eng-GB' => 'Folder draft 4'], + $secondDepthChildFolderLocationId, + ); + + return [ + $folder, + $draft1, + $draft2, + $draft3, + $draftSecondDepth, + ]; + } +} diff --git a/tests/lib/Persistence/Cache/LocationHandlerTest.php b/tests/lib/Persistence/Cache/LocationHandlerTest.php index ed498aa495..c06f71b276 100644 --- a/tests/lib/Persistence/Cache/LocationHandlerTest.php +++ b/tests/lib/Persistence/Cache/LocationHandlerTest.php @@ -67,6 +67,7 @@ public function providerForUnCachedMethods(): array ['c-4', 'ragl-4'], ], ['removeSubtree', [12], [['location_path', [12], false]], null, ['lp-12']], + ['deleteChildrenDrafts', [12], [['location_path', [12], false]], null, ['lp-12']], ['setSectionForSubtree', [12, 2], [['location_path', [12], false]], null, ['lp-12']], ['changeMainLocation', [4, 12], [['content', [4], false]], null, ['c-4']], ['countLocationsByContent', [4]], diff --git a/tests/lib/Persistence/Legacy/Content/LocationHandlerTest.php b/tests/lib/Persistence/Legacy/Content/LocationHandlerTest.php index 6b971543cc..b22dd45d60 100644 --- a/tests/lib/Persistence/Legacy/Content/LocationHandlerTest.php +++ b/tests/lib/Persistence/Legacy/Content/LocationHandlerTest.php @@ -453,6 +453,18 @@ public function testRemoveSubtree() $handler->removeSubtree(42); } + public function testDeleteChildrenDrafts(): void + { + $handler = $this->getLocationHandler(); + + $this->treeHandler + ->expects(self::once()) + ->method('deleteChildrenDrafts') + ->with(42); + + $handler->deleteChildrenDrafts(42); + } + /** * Test for the copySubtree() method. */ diff --git a/tests/lib/Persistence/Legacy/Content/TreeHandlerTest.php b/tests/lib/Persistence/Legacy/Content/TreeHandlerTest.php index a3403d8fb8..9daf814d36 100644 --- a/tests/lib/Persistence/Legacy/Content/TreeHandlerTest.php +++ b/tests/lib/Persistence/Legacy/Content/TreeHandlerTest.php @@ -400,6 +400,61 @@ public function testLoadLocation() $this->assertTrue($location instanceof Location); } + public function testDeleteChildrenDraftsRecursive(): void + { + $locationGatewayMock = $this->getLocationGatewayMock(); + $contentGatewayMock = $this->getContentGatewayMock(); + $contentMapperMock = $this->getContentMapperMock(); + + $locationGatewayMock + ->expects(self::exactly(3)) + ->method('getChildren') + ->willReturnMap([ + [42, [ + ['node_id' => 201], + ['node_id' => 202], + ]], + [201, []], + [202, []], + ]); + + $locationGatewayMock + ->expects(self::exactly(3)) + ->method('getSubtreeChildrenDraftContentIds') + ->willReturnMap([ + [201, [101]], + [202, [102]], + [42, [99]], + ]); + + $contentGatewayMock + ->expects(self::exactly(3)) + ->method('loadContentInfo') + ->willReturnMap([ + [101, ['main_node_id' => 201]], + [102, ['main_node_id' => 202]], + [99, ['main_node_id' => 42]], + ]); + + $contentMapperMock + ->expects(self::exactly(3)) + ->method('extractContentInfoFromRow') + ->willReturnCallback(static function (array $row): ContentInfo { + return new ContentInfo(['mainLocationId' => $row['main_node_id']]); + }); + + $contentGatewayMock + ->expects(self::exactly(3)) + ->method('deleteContent') + ->willReturnCallback(static function (int $contentId): void { + self::assertContains($contentId, [99, 101, 102]); + }); + + $treeHandler = $this->getTreeHandler(); + + $treeHandler->deleteChildrenDrafts(42); + } + /** @var \PHPUnit\Framework\MockObject\MockObject|\Ibexa\Core\Persistence\Legacy\Content\Location\Gateway */ protected $locationGatewayMock; From 9fda0ecd1c9f5e42719d3f92f7ec8bb0b50d0798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 19 Nov 2024 10:12:21 +0100 Subject: [PATCH 3/6] Regenerated PHPStan (#453) --- phpstan-baseline.neon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 668d9e640a..4b3dee324b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -22642,7 +22642,7 @@ parameters: - message: "#^Cannot access offset 0 on array\\\\|bool\\|float\\|int\\|string\\.$#" - count: 2 + count: 1 path: src/lib/Search/Legacy/Content/Location/Gateway/CriterionHandler/Location/IsMainLocation.php - @@ -22702,7 +22702,7 @@ parameters: - message: "#^Cannot access offset 0 on array\\\\|bool\\|float\\|int\\|string\\.$#" - count: 2 + count: 1 path: src/lib/Search/Legacy/Content/Location/Gateway/CriterionHandler/Visibility.php - From 2e22cebee13794446744992a7d89b93c53c12075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Tue, 19 Nov 2024 14:57:41 +0100 Subject: [PATCH 4/6] Fixed issues related to twig/twig 3.15 in tests --- phpstan-baseline.neon | 20 ----- .../Twig/Extension/ContentExtension.php | 44 +++-------- .../Extension/DataAttributesExtension.php | 17 +++-- .../Extension/DeprecationOptionsTrait.php | 46 ++++++++++++ .../Extension/FieldRenderingExtension.php | 28 ++++--- .../Twig/Extension/FileSizeExtension.php | 7 +- .../Twig/Extension/ImageExtension.php | 26 ++++--- .../Extension/QueryRenderingExtension.php | 24 +++--- .../Twig/Extension/RenderContentExtension.php | 13 ++-- .../Twig/Extension/RenderExtension.php | 13 ++-- .../Extension/RenderLocationExtension.php | 13 ++-- .../Twig/Extension/RoutingExtension.php | 29 ++++---- .../Extension/DataAttributesExtensionTest.php | 6 +- .../Twig/Extension/FileSizeExtensionTest.php | 11 +-- .../Twig/Extension/RoutingExtensionTest.php | 6 +- .../ez_data_attributes_serialize.test | 2 +- .../ibexa_data_attributes_serialize.test | 0 .../8.0/ez_data_attributes_serialize.test | 15 ++++ .../8.0/ibexa_data_attributes_serialize.test | 37 ++++++++++ .../ez_file_size/7.4/ez_file_size.test | 44 +++++++++++ .../{ => 7.4}/ibexa_file_size.test | 0 .../ez_file_size/8.0/ez_file_size.test | 44 +++++++++++ .../ez_file_size/8.0/ibexa_file_size.test | 65 ++++++++++++++++ .../functions/ez_file_size/ez_file_size.test | 44 ----------- .../routing_functions/{ => 7.4}/ez_path.test | 36 ++++----- .../routing_functions/{ => 7.4}/ez_route.test | 6 +- .../routing_functions/{ => 7.4}/ez_url.test | 36 ++++----- .../{ => 7.4}/ibexa_path.test | 0 .../{ => 7.4}/ibexa_route.test | 0 .../{ => 7.4}/ibexa_url.test | 0 .../routing_functions/8.0/ez_path.test | 74 +++++++++++++++++++ .../routing_functions/8.0/ez_route.test | 25 +++++++ .../routing_functions/8.0/ez_url.test | 74 +++++++++++++++++++ .../routing_functions/8.0/ibexa_path.test | 55 ++++++++++++++ .../routing_functions/8.0/ibexa_route.test | 21 ++++++ .../routing_functions/8.0/ibexa_url.test | 55 ++++++++++++++ 36 files changed, 719 insertions(+), 217 deletions(-) create mode 100644 src/lib/MVC/Symfony/Templating/Twig/Extension/DeprecationOptionsTrait.php rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/{ => 7.4}/ez_data_attributes_serialize.test (71%) rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/{ => 7.4}/ibexa_data_attributes_serialize.test (100%) create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ez_data_attributes_serialize.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ibexa_data_attributes_serialize.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/7.4/ez_file_size.test rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/{ => 7.4}/ibexa_file_size.test (100%) create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ez_file_size.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ibexa_file_size.test delete mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/ez_file_size.test rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/{ => 7.4}/ez_path.test (57%) rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/{ => 7.4}/ez_route.test (61%) rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/{ => 7.4}/ez_url.test (57%) rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/{ => 7.4}/ibexa_path.test (100%) rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/{ => 7.4}/ibexa_route.test (100%) rename tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/{ => 7.4}/ibexa_url.test (100%) create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_path.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_route.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_url.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_path.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_route.test create mode 100644 tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_url.test diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4b3dee324b..64cf9f0188 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -12945,11 +12945,6 @@ parameters: count: 1 path: src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Templating\\\\Twig\\\\Extension\\\\ContentExtension\\:\\:getFunctions\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Templating\\\\Twig\\\\Extension\\\\ContentExtension\\:\\:getTranslatedField\\(\\) should return Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\Field but returns Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Content\\\\Field\\|null\\.$#" count: 1 @@ -12960,26 +12955,11 @@ parameters: count: 2 path: src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php - - - message: "#^Property Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Templating\\\\Twig\\\\Extension\\\\ContentExtension\\:\\:\\$logger \\(Psr\\\\Log\\\\LoggerInterface\\) does not accept Psr\\\\Log\\\\LoggerInterface\\|null\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Templating\\\\Twig\\\\Extension\\\\CoreExtension\\:\\:getGlobals\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 path: src/lib/MVC/Symfony/Templating/Twig/Extension/CoreExtension.php - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Templating\\\\Twig\\\\Extension\\\\DataAttributesExtension\\:\\:getFilters\\(\\) return type has no value type specified in iterable type array\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtension.php - - - - message: "#^Method Ibexa\\\\Core\\\\MVC\\\\Symfony\\\\Templating\\\\Twig\\\\Extension\\\\DataAttributesExtension\\:\\:serializeDataAttributes\\(\\) has parameter \\$dataAttributes with no value type specified in iterable type array\\.$#" - count: 1 - path: src/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtension.php - - message: "#^Parameter \\#1 \\$string of function htmlspecialchars expects string, string\\|false given\\.$#" count: 1 diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php index 348a7cd7e4..578208acf0 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/ContentExtension.php @@ -16,6 +16,7 @@ use Ibexa\Core\Helper\FieldsGroups\FieldsGroupsList; use Ibexa\Core\Helper\TranslationHelper; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -25,6 +26,8 @@ */ class ContentExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** @var \Ibexa\Contracts\Core\Repository\Repository */ protected $repository; @@ -50,24 +53,19 @@ public function __construct( $this->translationHelper = $translationHelper; $this->fieldHelper = $fieldHelper; $this->fieldsGroupsList = $fieldsGroupsList; - $this->logger = $logger; + $this->logger = $logger ?? new NullLogger(); } /** * Returns a list of functions to add to the existing list. - * - * @return array */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction( 'ez_content_name', [$this, 'getTranslatedContentName'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_content_name', - ] + $this->getDeprecationOptions('ibexa_content_name'), ), new TwigFunction( 'ibexa_content_name', @@ -76,10 +74,7 @@ public function getFunctions() new TwigFunction( 'ez_field_value', [$this, 'getTranslatedFieldValue'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_field_value', - ] + $this->getDeprecationOptions('ibexa_field_value'), ), new TwigFunction( 'ibexa_field_value', @@ -88,10 +83,7 @@ public function getFunctions() new TwigFunction( 'ez_field', [$this, 'getTranslatedField'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_field', - ] + $this->getDeprecationOptions('ibexa_field'), ), new TwigFunction( 'ibexa_field', @@ -100,10 +92,7 @@ public function getFunctions() new TwigFunction( 'ez_field_is_empty', [$this, 'isFieldEmpty'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_field_is_empty', - ] + $this->getDeprecationOptions('ibexa_field_is_empty'), ), new TwigFunction( 'ibexa_has_field', @@ -116,10 +105,7 @@ public function getFunctions() new TwigFunction( 'ez_field_name', [$this, 'getTranslatedFieldDefinitionName'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_field_name', - ] + $this->getDeprecationOptions('ibexa_field_name'), ), new TwigFunction( 'ibexa_field_name', @@ -128,10 +114,7 @@ public function getFunctions() new TwigFunction( 'ez_field_description', [$this, 'getTranslatedFieldDefinitionDescription'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_field_description', - ] + $this->getDeprecationOptions('ibexa_field_description'), ), new TwigFunction( 'ibexa_field_description', @@ -144,10 +127,7 @@ public function getFunctions() new TwigFunction( 'ez_content_field_identifier_first_filled_image', [$this, 'getFirstFilledImageFieldIdentifier'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_content_field_identifier_first_filled_image', - ] + $this->getDeprecationOptions('ibexa_content_field_identifier_first_filled_image'), ), new TwigFunction( 'ibexa_content_field_identifier_first_filled_image', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtension.php index 4e6b1c170c..bef6946beb 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtension.php @@ -16,10 +16,10 @@ */ class DataAttributesExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** * Returns a list of functions to add to the existing list. - * - * @return array */ public function getFilters(): array { @@ -27,11 +27,12 @@ public function getFilters(): array new TwigFilter( 'ez_data_attributes_serialize', [$this, 'serializeDataAttributes'], - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_data_attributes_serialize', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_data_attributes_serialize'), + ), ), new TwigFilter( 'ibexa_data_attributes_serialize', @@ -45,7 +46,7 @@ public function getFilters(): array * Processes an associative list of data attributes and returns them as HTML attributes list * in the form of data-="". * - * @param array $dataAttributes + * @param array $dataAttributes * * @return string */ diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/DeprecationOptionsTrait.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/DeprecationOptionsTrait.php new file mode 100644 index 0000000000..a7c4ca44df --- /dev/null +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/DeprecationOptionsTrait.php @@ -0,0 +1,46 @@ + new DeprecatedCallableInfo('ibexa/core', '4.0', $newFunction), + ]; + } + + // Compatibility with twig/twig prior to 3.15 + return [ + 'deprecated' => '4.0', + 'deprecating_package' => 'ibexa/core', + 'alternative' => $newFunction, + ]; + } +} diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/FieldRenderingExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/FieldRenderingExtension.php index a63c6f8e1b..8a5767b3cb 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/FieldRenderingExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/FieldRenderingExtension.php @@ -22,6 +22,8 @@ */ class FieldRenderingExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** @var \Ibexa\Core\MVC\Symfony\Templating\FieldBlockRendererInterface */ private $fieldBlockRenderer; @@ -66,12 +68,13 @@ public function getFunctions() new TwigFunction( 'ez_render_field', $renderFieldCallable, - [ - 'is_safe' => ['html'], - 'needs_environment' => true, - 'deprecated' => '4.0', - 'alternative' => 'ibexa_render_field', - ] + array_merge( + [ + 'is_safe' => ['html'], + 'needs_environment' => true, + ], + $this->getDeprecationOptions('ibexa_render_field'), + ), ), new TwigFunction( 'ibexa_render_field', @@ -81,12 +84,13 @@ public function getFunctions() new TwigFunction( 'ez_render_field_definition_settings', $renderFieldDefinitionSettingsCallable, - [ - 'is_safe' => ['html'], - 'needs_environment' => true, - 'deprecated' => '4.0', - 'alternative' => 'ibexa_render_field_definition_settings', - ] + array_merge( + [ + 'is_safe' => ['html'], + 'needs_environment' => true, + ], + $this->getDeprecationOptions('ibexa_render_field_definition_settings'), + ), ), new TwigFunction( 'ibexa_render_field_definition_settings', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtension.php index 9c0d700ec7..8198e60095 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtension.php @@ -20,6 +20,8 @@ */ class FileSizeExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** * @param \Symfony\Contracts\Translation\TranslatorInterface $translator */ @@ -77,10 +79,7 @@ public function getFilters() new TwigFilter( 'ez_file_size', [$this, 'sizeFilter'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_file_size', - ] + $this->getDeprecationOptions('ibexa_file_size'), ), new TwigFilter( 'ibexa_file_size', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/ImageExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/ImageExtension.php index 0a85394329..c77507c52c 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/ImageExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/ImageExtension.php @@ -18,6 +18,8 @@ class ImageExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** @var \Ibexa\Contracts\Core\Variation\VariationHandler */ private $imageVariationService; @@ -30,17 +32,18 @@ public function __construct(VariationHandler $imageVariationService, AssetMapper $this->assetMapper = $assetMapper; } - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction( 'ez_image_alias', [$this, 'getImageVariation'], - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_image_alias', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_image_alias'), + ), ), new TwigFunction( 'ibexa_image_alias', @@ -50,11 +53,12 @@ public function getFunctions() new TwigFunction( 'ez_content_field_identifier_image_asset', [$this, 'getImageAssetContentFieldIdentifier'], - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_content_field_identifier_image_asset', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_content_field_identifier_image_asset'), + ), ), new TwigFunction( 'ibexa_content_field_identifier_image_asset', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/QueryRenderingExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/QueryRenderingExtension.php index 989fdb568c..7695463533 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/QueryRenderingExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/QueryRenderingExtension.php @@ -16,6 +16,8 @@ class QueryRenderingExtension extends AbstractExtension { + use DeprecationOptionsTrait; + private const VALID_TYPES = ['content', 'location']; /** @var \Symfony\Component\HttpKernel\Fragment\FragmentHandler */ @@ -49,11 +51,12 @@ public function getFunctions(): array new TwigFunction( 'ez_render_*_query', $typeCallable, - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_render_*_query', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_render_*_query'), + ), ), new TwigFunction( 'ibexa_render_*_query', @@ -63,11 +66,12 @@ public function getFunctions(): array new TwigFunction( 'ez_render_*_query_*', $typeAndRendererCallable, - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_render_*_query_', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_render_*_query_*'), + ), ), new TwigFunction( 'ibexa_render_*_query_*', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderContentExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderContentExtension.php index 9fa3ebaf9f..29143878bb 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderContentExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderContentExtension.php @@ -21,6 +21,8 @@ */ final class RenderContentExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** @var \Ibexa\Core\MVC\Symfony\Templating\RenderContentStrategy */ private $renderContentStrategy; @@ -41,11 +43,12 @@ public function getFunctions(): array new TwigFunction( 'ez_render_content', [$this, 'renderContent'], - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_render_content', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_render_content'), + ), ), new TwigFunction( 'ibexa_render_content', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderExtension.php index c441f8b13c..677b361594 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderExtension.php @@ -22,6 +22,8 @@ */ final class RenderExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** @var \Ibexa\Contracts\Core\MVC\Templating\RenderStrategy */ private $renderStrategy; @@ -42,11 +44,12 @@ public function getFunctions(): array new TwigFunction( 'ez_render', [$this, 'render'], - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_render', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_render'), + ), ), new TwigFunction( 'ibexa_render', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderLocationExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderLocationExtension.php index ef7f24cd4b..3bcb835bec 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderLocationExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/RenderLocationExtension.php @@ -21,6 +21,8 @@ */ final class RenderLocationExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** @var \Ibexa\Core\MVC\Symfony\Templating\RenderLocationStrategy */ private $renderLocationStrategy; @@ -41,11 +43,12 @@ public function getFunctions(): array new TwigFunction( 'ez_render_location', [$this, 'renderLocation'], - [ - 'is_safe' => ['html'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_render_location', - ] + array_merge( + [ + 'is_safe' => ['html'], + ], + $this->getDeprecationOptions('ibexa_render_location'), + ), ), new TwigFunction( 'ibexa_render_location', diff --git a/src/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtension.php b/src/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtension.php index ac5f0dfffd..475e70bfc7 100644 --- a/src/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtension.php +++ b/src/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtension.php @@ -26,6 +26,8 @@ class RoutingExtension extends AbstractExtension { + use DeprecationOptionsTrait; + /** @var \Ibexa\Core\MVC\Symfony\Routing\Generator\RouteReferenceGeneratorInterface */ private $routeReferenceGenerator; @@ -46,10 +48,7 @@ public function getFunctions(): array new TwigFunction( 'ez_route', [$this, 'getRouteReference'], - [ - 'deprecated' => '4.0', - 'alternative' => 'ibexa_route', - ] + $this->getDeprecationOptions('ibexa_route'), ), new TwigFunction( 'ibexa_route', @@ -58,11 +57,12 @@ public function getFunctions(): array new TwigFunction( 'ez_path', [$this, 'getPath'], - [ - 'is_safe_callback' => [$this, 'isUrlGenerationSafe'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_path', - ] + array_merge( + [ + 'is_safe_callback' => [$this, 'isUrlGenerationSafe'], + ], + $this->getDeprecationOptions('ibexa_path'), + ), ), new TwigFunction( 'ibexa_path', @@ -74,11 +74,12 @@ public function getFunctions(): array new TwigFunction( 'ez_url', [$this, 'getUrl'], - [ - 'is_safe_callback' => [$this, 'isUrlGenerationSafe'], - 'deprecated' => '4.0', - 'alternative' => 'ibexa_url', - ] + array_merge( + [ + 'is_safe_callback' => [$this, 'isUrlGenerationSafe'], + ], + $this->getDeprecationOptions('ibexa_url'), + ), ), new TwigFunction( 'ibexa_url', diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtensionTest.php b/tests/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtensionTest.php index 6311fdaf57..95fc219d96 100644 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtensionTest.php +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/DataAttributesExtensionTest.php @@ -22,7 +22,11 @@ public function getExtensions(): array protected function getFixturesDir(): string { - return __DIR__ . '/_fixtures/filters'; + if (\PHP_VERSION_ID < 8_00_00) { + return __DIR__ . '/_fixtures/filters/7.4'; + } + + return __DIR__ . '/_fixtures/filters/8.0'; } } diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtensionTest.php b/tests/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtensionTest.php index b526e49d6f..4506cd4970 100644 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtensionTest.php +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/FileSizeExtensionTest.php @@ -70,12 +70,13 @@ protected function getExtensions() ]; } - /** - * @return string - */ - protected function getFixturesDir() + protected function getFixturesDir(): string { - return __DIR__ . '/_fixtures/functions/ez_file_size'; + if (\PHP_VERSION_ID < 8_00_00) { + return __DIR__ . '/_fixtures/functions/ez_file_size/7.4'; + } + + return __DIR__ . '/_fixtures/functions/ez_file_size/8.0'; } /** diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtensionTest.php b/tests/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtensionTest.php index a5b6324f71..164f750200 100644 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtensionTest.php +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/RoutingExtensionTest.php @@ -40,7 +40,11 @@ protected function getExtensions(): array protected function getFixturesDir(): string { - return __DIR__ . '/_fixtures/routing_functions'; + if (\PHP_VERSION_ID < 8_00_00) { + return __DIR__ . '/_fixtures/routing_functions/7.4'; + } + + return __DIR__ . '/_fixtures/routing_functions/8.0'; } protected function getExampleContent(int $id): APIContent diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/ez_data_attributes_serialize.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/7.4/ez_data_attributes_serialize.test similarity index 71% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/ez_data_attributes_serialize.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/7.4/ez_data_attributes_serialize.test index ec2eefd0f6..4b8b69ec7e 100644 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/ez_data_attributes_serialize.test +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/7.4/ez_data_attributes_serialize.test @@ -1,7 +1,7 @@ --TEST-- "ez_data_attributes_serialize" filter --DEPRECATION-- -Since 4.0: Twig Filter "ez_data_attributes_serialize" is deprecated. Use "ibexa_data_attributes_serialize" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Filter "ez_data_attributes_serialize" is deprecated. Use "ibexa_data_attributes_serialize" instead in index.twig at line 2. --TEMPLATE-- Article --DATA-- diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/ibexa_data_attributes_serialize.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/7.4/ibexa_data_attributes_serialize.test similarity index 100% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/ibexa_data_attributes_serialize.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/7.4/ibexa_data_attributes_serialize.test diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ez_data_attributes_serialize.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ez_data_attributes_serialize.test new file mode 100644 index 0000000000..89b44c15db --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ez_data_attributes_serialize.test @@ -0,0 +1,15 @@ +--TEST-- +"ez_data_attributes_serialize" filter +--DEPRECATION-- +Since ibexa/core 4.0: Twig Filter "ez_data_attributes_serialize" is deprecated; use "ibexa_data_attributes_serialize" instead in index.twig at line 2. +--TEMPLATE-- +Article +--DATA-- +return [ + 'data_attributes' => [ + 'my-attr1' => 'value1', + 'my-attr2' => 'value2,value3', + ] +]; +--EXPECT-- +Article diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ibexa_data_attributes_serialize.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ibexa_data_attributes_serialize.test new file mode 100644 index 0000000000..6681e0066d --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/filters/8.0/ibexa_data_attributes_serialize.test @@ -0,0 +1,37 @@ +--TEST-- +"ibexa_data_attributes_serialize" filter +--TEMPLATE-- +Article +--DATA-- +return [ + 'data_attributes' => [ + 'my-attr1' => 'value1', + 'my-attr2' => 'value2,value3', + ] +]; +--EXPECT-- +Article +--DATA-- +return [ + 'data_attributes' => [ + 'attr' => 'foo" style="background: red', + ] +]; +--EXPECT-- +Article +--DATA-- +return [ + 'data_attributes' => [ + 'attr' => true, + ] +]; +--EXPECT-- +Article +--DATA-- +return [ + 'data_attributes' => [ + 'attr' => ['key1' => 'value1', 'key2' => 'value2'], + ] +]; +--EXPECT-- +Article diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/7.4/ez_file_size.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/7.4/ez_file_size.test new file mode 100644 index 0000000000..2c41efd928 --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/7.4/ez_file_size.test @@ -0,0 +1,44 @@ +--TEST-- +"twig" filter +--DEPRECATION-- +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 4. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 5. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 6. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 7. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 8. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 9. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 10. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 11. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 12. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 13. +--TEMPLATE-- +{{ 10|ez_file_size( 2 ) }} +{{ 1024|ez_file_size( 0 ) }} +{{ 5120|ez_file_size( 3 ) }} +{{ 12288|ez_file_size( 1 ) }} +{{ 155648|ez_file_size( 0 ) }} +{{ 27421583|ez_file_size( 5 ) }} +{{ 129103927|ez_file_size( 4 ) }} +{{ 490163142656|ez_file_size( 1 ) }} +{{ 868383057603765|ez_file_size( 6 ) }} +{{ 889224250749591400|ez_file_size( 10 ) }} +{{ 910565875123441600000|ez_file_size( 2 ) }} +{{ 910565632767581700000000000|ez_file_size( 4 ) }} +--DATA-- +$this->setConfigurationLocale( array( 'wrong local' ), 'eng-GB' ); +return array() +--EXPECT-- +10 B wrong local so we take the default one which is en-GB here +1 kB wrong local so we take the default one which is en-GB here +5 kB wrong local so we take the default one which is en-GB here +12 kB wrong local so we take the default one which is en-GB here +152 kB wrong local so we take the default one which is en-GB here +26.15126 MB wrong local so we take the default one which is en-GB here +123.1231 MB wrong local so we take the default one which is en-GB here +456.5 GB wrong local so we take the default one which is en-GB here +789.78979 TB wrong local so we take the default one which is en-GB here +789.7897897898 PB wrong local so we take the default one which is en-GB here +789.79 EB wrong local so we take the default one which is en-GB here +789789789.7898 EB wrong local so we take the default one which is en-GB here diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/ibexa_file_size.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/7.4/ibexa_file_size.test similarity index 100% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/ibexa_file_size.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/7.4/ibexa_file_size.test diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ez_file_size.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ez_file_size.test new file mode 100644 index 0000000000..a431ea5548 --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ez_file_size.test @@ -0,0 +1,44 @@ +--TEST-- +"twig" filter +--DEPRECATION-- +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 4. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 5. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 6. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 7. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 8. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 9. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 10. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 11. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 12. +Since ibexa/core 4.0: Twig Filter "ez_file_size" is deprecated; use "ibexa_file_size" instead in index.twig at line 13. +--TEMPLATE-- +{{ 10|ez_file_size( 2 ) }} +{{ 1024|ez_file_size( 0 ) }} +{{ 5120|ez_file_size( 3 ) }} +{{ 12288|ez_file_size( 1 ) }} +{{ 155648|ez_file_size( 0 ) }} +{{ 27421583|ez_file_size( 5 ) }} +{{ 129103927|ez_file_size( 4 ) }} +{{ 490163142656|ez_file_size( 1 ) }} +{{ 868383057603765|ez_file_size( 6 ) }} +{{ 889224250749591400|ez_file_size( 10 ) }} +{{ 910565875123441600000|ez_file_size( 2 ) }} +{{ 910565632767581700000000000|ez_file_size( 4 ) }} +--DATA-- +$this->setConfigurationLocale( array( 'wrong local' ), 'eng-GB' ); +return array() +--EXPECT-- +10 B wrong local so we take the default one which is en-GB here +1 kB wrong local so we take the default one which is en-GB here +5 kB wrong local so we take the default one which is en-GB here +12 kB wrong local so we take the default one which is en-GB here +152 kB wrong local so we take the default one which is en-GB here +26.15126 MB wrong local so we take the default one which is en-GB here +123.1231 MB wrong local so we take the default one which is en-GB here +456.5 GB wrong local so we take the default one which is en-GB here +789.78979 TB wrong local so we take the default one which is en-GB here +789.7897897898 PB wrong local so we take the default one which is en-GB here +789.79 EB wrong local so we take the default one which is en-GB here +789789789.7898 EB wrong local so we take the default one which is en-GB here diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ibexa_file_size.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ibexa_file_size.test new file mode 100644 index 0000000000..e8000014ec --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/8.0/ibexa_file_size.test @@ -0,0 +1,65 @@ +--TEST-- +"twig" filter +--TEMPLATE-- +{{ 10|ibexa_file_size( 2 ) }} +{{ 1024|ibexa_file_size( 0 ) }} +{{ 5120|ibexa_file_size( 3 ) }} +{{ 12288|ibexa_file_size( 1 ) }} +{{ 155648|ibexa_file_size( 0 ) }} +{{ 27421583|ibexa_file_size( 5 ) }} +{{ 129103927|ibexa_file_size( 4 ) }} +{{ 490163142656|ibexa_file_size( 1 ) }} +{{ 868383057603765|ibexa_file_size( 6 ) }} +{{ 889224250749591400|ibexa_file_size( 10 ) }} +{{ 910565875123441600000|ibexa_file_size( 2 ) }} +{{ 910565632767581700000000000|ibexa_file_size( 4 ) }} +--DATA-- +$this->setConfigurationLocale( array( 'wrong local' ), 'eng-GB' ); +return array() +--EXPECT-- +10 B wrong local so we take the default one which is en-GB here +1 kB wrong local so we take the default one which is en-GB here +5 kB wrong local so we take the default one which is en-GB here +12 kB wrong local so we take the default one which is en-GB here +152 kB wrong local so we take the default one which is en-GB here +26.15126 MB wrong local so we take the default one which is en-GB here +123.1231 MB wrong local so we take the default one which is en-GB here +456.5 GB wrong local so we take the default one which is en-GB here +789.78979 TB wrong local so we take the default one which is en-GB here +789.7897897898 PB wrong local so we take the default one which is en-GB here +789.79 EB wrong local so we take the default one which is en-GB here +789789789.7898 EB wrong local so we take the default one which is en-GB here +--DATA-- +return array() +--CONFIG-- +$this->locale = "fre-FR"; return array(); +--EXPECT-- +10 B French version +1 kB French version +5 kB French version +12 kB French version +152 kB French version +26,15126 MB French version +123,1231 MB French version +456,5 GB French version +789,78979 TB French version +789,7897897898 PB French version +789,79 EB French version +789789789,7898 EB French version +--DATA-- +return array() +--CONFIG-- +$this->locale = "eng-GB"; return array(); +--EXPECT-- +10 B English version +1 kB English version +5 kB English version +12 kB English version +152 kB English version +26.15126 MB English version +123.1231 MB English version +456.5 GB English version +789.78979 TB English version +789.7897897898 PB English version +789.79 EB English version +789789789.7898 EB English version diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/ez_file_size.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/ez_file_size.test deleted file mode 100644 index 726e6b2c7c..0000000000 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/functions/ez_file_size/ez_file_size.test +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -"twig" filter ---DEPRECATION-- -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 2. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 3. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 4. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 5. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 6. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 7. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 8. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 9. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 10. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 11. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 12. -Since 4.0: Twig Filter "ez_file_size" is deprecated. Use "ibexa_file_size" instead in index.twig at line 13. ---TEMPLATE-- -{{ 10|ez_file_size( 2 ) }} -{{ 1024|ez_file_size( 0 ) }} -{{ 5120|ez_file_size( 3 ) }} -{{ 12288|ez_file_size( 1 ) }} -{{ 155648|ez_file_size( 0 ) }} -{{ 27421583|ez_file_size( 5 ) }} -{{ 129103927|ez_file_size( 4 ) }} -{{ 490163142656|ez_file_size( 1 ) }} -{{ 868383057603765|ez_file_size( 6 ) }} -{{ 889224250749591400|ez_file_size( 10 ) }} -{{ 910565875123441600000|ez_file_size( 2 ) }} -{{ 910565632767581700000000000|ez_file_size( 4 ) }} ---DATA-- -$this->setConfigurationLocale( array( 'wrong local' ), 'eng-GB' ); -return array() ---EXPECT-- -10 B wrong local so we take the default one which is en-GB here -1 kB wrong local so we take the default one which is en-GB here -5 kB wrong local so we take the default one which is en-GB here -12 kB wrong local so we take the default one which is en-GB here -152 kB wrong local so we take the default one which is en-GB here -26.15126 MB wrong local so we take the default one which is en-GB here -123.1231 MB wrong local so we take the default one which is en-GB here -456.5 GB wrong local so we take the default one which is en-GB here -789.78979 TB wrong local so we take the default one which is en-GB here -789.7897897898 PB wrong local so we take the default one which is en-GB here -789.79 EB wrong local so we take the default one which is en-GB here -789789789.7898 EB wrong local so we take the default one which is en-GB here diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_path.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_path.test similarity index 57% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_path.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_path.test index 3a26493fc4..3f14a34393 100644 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_path.test +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_path.test @@ -1,24 +1,24 @@ --TEST-- "ez_path" function --DEPRECATION-- -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 2. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 3. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 4. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 5. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 6. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 7. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 8. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 9. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 10. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 11. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 12. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 13. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 14. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 15. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 16. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 17. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 18. -Since 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 19. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 4. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 5. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 6. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 7. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 8. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 9. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 10. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 11. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 12. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 13. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 14. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 15. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 16. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 17. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 18. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated. Use "ibexa_path" instead in index.twig at line 19. --TEMPLATE-- {{ ez_path(location) }} {{ ez_path(location, {}, true) }} diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_route.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_route.test similarity index 61% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_route.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_route.test index 084741ded6..107dba20c5 100644 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_route.test +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_route.test @@ -1,9 +1,9 @@ --TEST-- "twig" filter --DEPRECATION-- -Since 4.0: Twig Function "ez_route" is deprecated. Use "ibexa_route" instead in index.twig at line 2. -Since 4.0: Twig Function "ez_route" is deprecated. Use "ibexa_route" instead in index.twig at line 3. -Since 4.0: Twig Function "ez_route" is deprecated. Use "ibexa_route" instead in index.twig at line 4. +Since ibexa/core 4.0: Twig Function "ez_route" is deprecated. Use "ibexa_route" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Function "ez_route" is deprecated. Use "ibexa_route" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Function "ez_route" is deprecated. Use "ibexa_route" instead in index.twig at line 4. --TEMPLATE-- {% set route_ref1 = ez_route( "foo_route" ) %} {% set route_ref2 = ez_route( "bar_route", {"some": "thing"} ) %} diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_url.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_url.test similarity index 57% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_url.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_url.test index b63e601cb6..9444e6b7d2 100644 --- a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ez_url.test +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ez_url.test @@ -1,24 +1,24 @@ --TEST-- "ez_url" function --DEPRECATION-- -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 2. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 3. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 4. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 5. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 6. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 7. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 8. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 9. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 10. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 11. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 12. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 13. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 14. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 15. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 16. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 17. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 18. -Since 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 19. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 4. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 5. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 6. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 7. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 8. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 9. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 10. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 11. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 12. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 13. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 14. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 15. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 16. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 17. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 18. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated. Use "ibexa_url" instead in index.twig at line 19. --TEMPLATE-- {{ ez_url(location) }} {{ ez_url(location, {}, true) }} diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ibexa_path.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ibexa_path.test similarity index 100% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ibexa_path.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ibexa_path.test diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ibexa_route.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ibexa_route.test similarity index 100% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ibexa_route.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ibexa_route.test diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ibexa_url.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ibexa_url.test similarity index 100% rename from tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/ibexa_url.test rename to tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/7.4/ibexa_url.test diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_path.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_path.test new file mode 100644 index 0000000000..bc848e675a --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_path.test @@ -0,0 +1,74 @@ +--TEST-- +"ez_path" function +--DEPRECATION-- +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 4. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 5. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 6. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 7. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 8. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 9. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 10. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 11. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 12. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 13. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 14. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 15. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 16. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 17. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 18. +Since ibexa/core 4.0: Twig Function "ez_path" is deprecated; use "ibexa_path" instead in index.twig at line 19. +--TEMPLATE-- +{{ ez_path(location) }} +{{ ez_path(location, {}, true) }} +{{ ez_path(location, {'foo': 'foo'}) }} +{{ ez_path(content) }} +{{ ez_path(content, {}, true) }} +{{ ez_path(content, {'foo': 'foo'}) }} +{{ ez_path(content_info) }} +{{ ez_path(content_info, {}, true) }} +{{ ez_path(content_info, {'foo': 'foo'}) }} +{{ ez_path(content_aware) }} +{{ ez_path(content_aware, {}, true) }} +{{ ez_path(content_aware, {'foo': 'foo'}) }} +{{ ez_path(route_ref) }} +{{ ez_path(route_ref, {}, true) }} +{{ ez_path(route_ref, {'baz': 'baz'}) }} +{{ ez_path(unsupported_object) }} +{{ ez_path(unsupported_object, {}, true) }} +{{ ez_path(unsupported_object, {'baz': 'baz'}) }} +--DATA-- +return [ + 'location' => $this->getExampleLocation(54), + 'content' => $this->getExampleContent(2), + 'content_info' => $this->getExampleContentInfo(2), + 'content_aware' => $this->getExampleContentAware(64), + 'route_ref' => $this->getExampleRouteReference( + 'example_route', + [ + 'foo' => 'foo', + 'bar' => 'bar' + ] + ), + 'unsupported_object' => $this->getExampleUnsupportedObject(), +]; +--EXPECT-- +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","locationId":54},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":64},"$referenceType":1} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":1} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":2} +{"$name":"example_route","$parameters":{"baz":"baz","foo":"foo","bar":"bar"},"$referenceType":1} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":1} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":2} +{"$name":"","$parameters":{"baz":"baz","_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":1} diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_route.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_route.test new file mode 100644 index 0000000000..c264f86f9f --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_route.test @@ -0,0 +1,25 @@ +--TEST-- +"twig" filter +--DEPRECATION-- +Since ibexa/core 4.0: Twig Function "ez_route" is deprecated; use "ibexa_route" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Function "ez_route" is deprecated; use "ibexa_route" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Function "ez_route" is deprecated; use "ibexa_route" instead in index.twig at line 4. +--TEMPLATE-- +{% set route_ref1 = ez_route( "foo_route" ) %} +{% set route_ref2 = ez_route( "bar_route", {"some": "thing"} ) %} +{% set route_ref3 = ez_route( "route_66", {"direction": "highway to hell"} ) %} +{{ route_ref1.route }} +{{ route_ref1.get( "param", "test" ) }} +{{ route_ref2.route }} +{{ route_ref2.get( "some" ) }} +{{ route_ref3.route }} +{{ route_ref3.get( "direction", "highway to hell" ) }} +--DATA-- +return array(); +--EXPECT-- +foo_route +test +bar_route +thing +route_66 +highway to hell diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_url.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_url.test new file mode 100644 index 0000000000..3dd6fdabbb --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ez_url.test @@ -0,0 +1,74 @@ +--TEST-- +"ez_url" function +--DEPRECATION-- +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 2. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 3. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 4. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 5. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 6. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 7. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 8. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 9. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 10. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 11. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 12. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 13. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 14. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 15. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 16. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 17. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 18. +Since ibexa/core 4.0: Twig Function "ez_url" is deprecated; use "ibexa_url" instead in index.twig at line 19. +--TEMPLATE-- +{{ ez_url(location) }} +{{ ez_url(location, {}, true) }} +{{ ez_url(location, {'foo': 'foo'}) }} +{{ ez_url(content) }} +{{ ez_url(content, {}, true) }} +{{ ez_url(content, {'foo': 'foo'}) }} +{{ ez_url(content_info) }} +{{ ez_url(content_info, {}, true) }} +{{ ez_url(content_info, {'foo': 'foo'}) }} +{{ ez_url(content_aware) }} +{{ ez_url(content_aware, {}, true) }} +{{ ez_url(content_aware, {'foo': 'foo'}) }} +{{ ez_url(route_ref) }} +{{ ez_url(route_ref, {}, true) }} +{{ ez_url(route_ref, {'baz': 'baz'}) }} +{{ ez_url(unsupported_object) }} +{{ ez_url(unsupported_object, {}, true) }} +{{ ez_url(unsupported_object, {'baz': 'baz'}) }} +--DATA-- +return [ + 'location' => $this->getExampleLocation(54), + 'content' => $this->getExampleContent(2), + 'content_info' => $this->getExampleContentInfo(2), + 'content_aware' => $this->getExampleContentAware(64), + 'route_ref' => $this->getExampleRouteReference( + 'example_route', + [ + 'foo' => 'foo', + 'bar' => 'bar' + ] + ), + 'unsupported_object' => $this->getExampleUnsupportedObject(), +]; +--EXPECT-- +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","locationId":54},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":64},"$referenceType":0} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":0} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":3} +{"$name":"example_route","$parameters":{"baz":"baz","foo":"foo","bar":"bar"},"$referenceType":0} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":0} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":3} +{"$name":"","$parameters":{"baz":"baz","_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":0} diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_path.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_path.test new file mode 100644 index 0000000000..c23a9a9676 --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_path.test @@ -0,0 +1,55 @@ +--TEST-- +"ibexa_path" function +--TEMPLATE-- +{{ ibexa_path(location) }} +{{ ibexa_path(location, {}, true) }} +{{ ibexa_path(location, {'foo': 'foo'}) }} +{{ ibexa_path(content) }} +{{ ibexa_path(content, {}, true) }} +{{ ibexa_path(content, {'foo': 'foo'}) }} +{{ ibexa_path(content_info) }} +{{ ibexa_path(content_info, {}, true) }} +{{ ibexa_path(content_info, {'foo': 'foo'}) }} +{{ ibexa_path(content_aware) }} +{{ ibexa_path(content_aware, {}, true) }} +{{ ibexa_path(content_aware, {'foo': 'foo'}) }} +{{ ibexa_path(route_ref) }} +{{ ibexa_path(route_ref, {}, true) }} +{{ ibexa_path(route_ref, {'baz': 'baz'}) }} +{{ ibexa_path(unsupported_object) }} +{{ ibexa_path(unsupported_object, {}, true) }} +{{ ibexa_path(unsupported_object, {'baz': 'baz'}) }} +--DATA-- +return [ + 'location' => $this->getExampleLocation(54), + 'content' => $this->getExampleContent(2), + 'content_info' => $this->getExampleContentInfo(2), + 'content_aware' => $this->getExampleContentAware(64), + 'route_ref' => $this->getExampleRouteReference( + 'example_route', + [ + 'foo' => 'foo', + 'bar' => 'bar' + ] + ), + 'unsupported_object' => $this->getExampleUnsupportedObject(), +]; +--EXPECT-- +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","locationId":54},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":1} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":2} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":64},"$referenceType":1} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":1} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":2} +{"$name":"example_route","$parameters":{"baz":"baz","foo":"foo","bar":"bar"},"$referenceType":1} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":1} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":2} +{"$name":"","$parameters":{"baz":"baz","_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":1} diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_route.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_route.test new file mode 100644 index 0000000000..f03cc5af17 --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_route.test @@ -0,0 +1,21 @@ +--TEST-- +"twig" filter +--TEMPLATE-- +{% set route_ref1 = ibexa_route( "foo_route" ) %} +{% set route_ref2 = ibexa_route( "bar_route", {"some": "thing"} ) %} +{% set route_ref3 = ibexa_route( "route_66", {"direction": "highway to hell"} ) %} +{{ route_ref1.route }} +{{ route_ref1.get( "param", "test" ) }} +{{ route_ref2.route }} +{{ route_ref2.get( "some" ) }} +{{ route_ref3.route }} +{{ route_ref3.get( "direction", "highway to hell" ) }} +--DATA-- +return array(); +--EXPECT-- +foo_route +test +bar_route +thing +route_66 +highway to hell diff --git a/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_url.test b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_url.test new file mode 100644 index 0000000000..f737c8d3ab --- /dev/null +++ b/tests/lib/MVC/Symfony/Templating/Twig/Extension/_fixtures/routing_functions/8.0/ibexa_url.test @@ -0,0 +1,55 @@ +--TEST-- +"ibexa_url" function +--TEMPLATE-- +{{ ibexa_url(location) }} +{{ ibexa_url(location, {}, true) }} +{{ ibexa_url(location, {'foo': 'foo'}) }} +{{ ibexa_url(content) }} +{{ ibexa_url(content, {}, true) }} +{{ ibexa_url(content, {'foo': 'foo'}) }} +{{ ibexa_url(content_info) }} +{{ ibexa_url(content_info, {}, true) }} +{{ ibexa_url(content_info, {'foo': 'foo'}) }} +{{ ibexa_url(content_aware) }} +{{ ibexa_url(content_aware, {}, true) }} +{{ ibexa_url(content_aware, {'foo': 'foo'}) }} +{{ ibexa_url(route_ref) }} +{{ ibexa_url(route_ref, {}, true) }} +{{ ibexa_url(route_ref, {'baz': 'baz'}) }} +{{ ibexa_url(unsupported_object) }} +{{ ibexa_url(unsupported_object, {}, true) }} +{{ ibexa_url(unsupported_object, {'baz': 'baz'}) }} +--DATA-- +return [ + 'location' => $this->getExampleLocation(54), + 'content' => $this->getExampleContent(2), + 'content_info' => $this->getExampleContentInfo(2), + 'content_aware' => $this->getExampleContentAware(64), + 'route_ref' => $this->getExampleRouteReference( + 'example_route', + [ + 'foo' => 'foo', + 'bar' => 'bar' + ] + ), + 'unsupported_object' => $this->getExampleUnsupportedObject(), +]; +--EXPECT-- +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"locationId":54},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","locationId":54},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":2},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":2},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":0} +{"$name":"ibexa.url.alias","$parameters":{"contentId":64},"$referenceType":3} +{"$name":"ibexa.url.alias","$parameters":{"foo":"foo","contentId":64},"$referenceType":0} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":0} +{"$name":"example_route","$parameters":{"foo":"foo","bar":"bar"},"$referenceType":3} +{"$name":"example_route","$parameters":{"baz":"baz","foo":"foo","bar":"bar"},"$referenceType":0} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":0} +{"$name":"","$parameters":{"_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":3} +{"$name":"","$parameters":{"baz":"baz","_route_object":{"foo":"foo","bar":"bar"}},"$referenceType":0} From c38bd010b1d69a45a25360d6288d0a4cf3b94b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Niedzielski?= Date: Wed, 20 Nov 2024 02:28:52 +0100 Subject: [PATCH 5/6] Introduced StructValidator to unpack validation errors for structs --- src/bundle/Core/Resources/config/services.yml | 11 ++ .../AbstractValidationStructWrapper.php | 36 +++++ src/contracts/Validation/StructValidator.php | 34 +++++ .../Validation/StructWrapperValidator.php | 94 +++++++++++++ .../Validation/ValidationFailedException.php | 60 ++++++++ .../ValidationStructWrapperInterface.php | 19 +++ tests/lib/Validation/StructValidatorTest.php | 94 +++++++++++++ .../Validation/StructWrapperValidatorTest.php | 128 ++++++++++++++++++ .../ValidationFailedExceptionTest.php | 60 ++++++++ 9 files changed, 536 insertions(+) create mode 100644 src/contracts/Validation/AbstractValidationStructWrapper.php create mode 100644 src/contracts/Validation/StructValidator.php create mode 100644 src/contracts/Validation/StructWrapperValidator.php create mode 100644 src/contracts/Validation/ValidationFailedException.php create mode 100644 src/contracts/Validation/ValidationStructWrapperInterface.php create mode 100644 tests/lib/Validation/StructValidatorTest.php create mode 100644 tests/lib/Validation/StructWrapperValidatorTest.php create mode 100644 tests/lib/Validation/ValidationFailedExceptionTest.php diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml index 0af04b30a3..93db51df29 100644 --- a/src/bundle/Core/Resources/config/services.yml +++ b/src/bundle/Core/Resources/config/services.yml @@ -370,3 +370,14 @@ services: $entityManagers: '%doctrine.entity_managers%' Ibexa\Bundle\Core\Translation\Policy\PolicyTranslationDefinitionProvider: ~ + + Ibexa\Contracts\Core\Validation\StructValidator: + arguments: + $validator: '@validator' + + Ibexa\Contracts\Core\Validation\StructWrapperValidator: + decorates: 'validator' + # Decorator priority is higher than debug.validator to ensure profiler receives struct errors + decoration_priority: 500 + arguments: + $inner: '@.inner' diff --git a/src/contracts/Validation/AbstractValidationStructWrapper.php b/src/contracts/Validation/AbstractValidationStructWrapper.php new file mode 100644 index 0000000000..5c1c29dc56 --- /dev/null +++ b/src/contracts/Validation/AbstractValidationStructWrapper.php @@ -0,0 +1,36 @@ +struct = $struct; + } + + /** + * @phpstan-return T + */ + final public function getStruct(): object + { + return $this->struct; + } +} diff --git a/src/contracts/Validation/StructValidator.php b/src/contracts/Validation/StructValidator.php new file mode 100644 index 0000000000..b0073afbf4 --- /dev/null +++ b/src/contracts/Validation/StructValidator.php @@ -0,0 +1,34 @@ +validator = $validator; + } + + /** + * @throws \Ibexa\Contracts\Core\Validation\ValidationFailedException + * + * @param string[] $groups + */ + public function assertValidStruct(string $name, object $struct, array $groups): void + { + $errors = $this->validator->validate($struct, null, ['Default', ...$groups]); + if ($errors->count() > 0) { + throw new ValidationFailedException($name, $errors); + } + } +} diff --git a/src/contracts/Validation/StructWrapperValidator.php b/src/contracts/Validation/StructWrapperValidator.php new file mode 100644 index 0000000000..5a80f5d5fa --- /dev/null +++ b/src/contracts/Validation/StructWrapperValidator.php @@ -0,0 +1,94 @@ +inner = $inner; + } + + public function getMetadataFor($value): MetadataInterface + { + return $this->inner->getMetadataFor($value); + } + + public function hasMetadataFor($value): bool + { + return $this->inner->hasMetadataFor($value); + } + + public function validate($value, $constraints = null, $groups = null): ConstraintViolationListInterface + { + $result = $this->inner->validate($value, $constraints, $groups); + + if (!$value instanceof ValidationStructWrapperInterface) { + return $result; + } + + $unwrappedErrors = new ConstraintViolationList(); + + foreach ($result as $error) { + $path = $error->getPropertyPath(); + $root = $error->getRoot(); + if (str_starts_with($path, 'struct.')) { + $path = substr($path, strlen('struct.')); + $root = $value->getStruct(); + } + + $unwrappedError = new ConstraintViolation( + $error->getMessage(), + $error->getMessageTemplate(), + $error->getParameters(), + $root, + $path, + $error->getInvalidValue(), + $error->getPlural(), + $error->getCode(), + $error->getConstraint(), + $error->getCause() + ); + + $unwrappedErrors->add($unwrappedError); + } + + return $unwrappedErrors; + } + + public function validateProperty(object $object, string $propertyName, $groups = null): ConstraintViolationListInterface + { + return $this->inner->validatePropertyValue($object, $propertyName, $groups); + } + + public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null): ConstraintViolationListInterface + { + return $this->inner->validatePropertyValue($objectOrClass, $propertyName, $groups); + } + + public function startContext(): ContextualValidatorInterface + { + return $this->inner->startContext(); + } + + public function inContext(ExecutionContextInterface $context): ContextualValidatorInterface + { + return $this->inner->inContext($context); + } +} diff --git a/src/contracts/Validation/ValidationFailedException.php b/src/contracts/Validation/ValidationFailedException.php new file mode 100644 index 0000000000..d7ca210e42 --- /dev/null +++ b/src/contracts/Validation/ValidationFailedException.php @@ -0,0 +1,60 @@ +createMessage($argumentName, $errors), 0, $previous); + + $this->errors = $errors; + } + + public function getErrors(): ConstraintViolationListInterface + { + return $this->errors; + } + + private function createMessage(string $argumentName, ConstraintViolationListInterface $errors): string + { + if ($errors->count() === 0) { + throw new \InvalidArgumentException(sprintf( + 'Cannot create %s with empty validation error list.', + self::class, + )); + } + + if ($errors->count() === 1) { + return sprintf( + "Argument '%s->%s' is invalid: %s", + $argumentName, + $errors->get(0)->getPropertyPath(), + $errors->get(0)->getMessage() + ); + } + + return sprintf( + "Argument '%s->%s' is invalid: %s and %d more errors", + $argumentName, + $errors->get(0)->getPropertyPath(), + $errors->get(0)->getMessage(), + $errors->count() - 1 + ); + } +} diff --git a/src/contracts/Validation/ValidationStructWrapperInterface.php b/src/contracts/Validation/ValidationStructWrapperInterface.php new file mode 100644 index 0000000000..85bb3062b0 --- /dev/null +++ b/src/contracts/Validation/ValidationStructWrapperInterface.php @@ -0,0 +1,19 @@ +validator = $this->createMock(ValidatorInterface::class); + $this->structValidator = new StructValidator($this->validator); + } + + public function testAssertValidStructWithValidStruct(): void + { + $struct = new stdClass(); + $errors = $this->createMock(ConstraintViolationListInterface::class); + $errors->method('count')->willReturn(0); + + $this->validator + ->expects(self::once()) + ->method('validate') + ->with( + $struct, + null, + ['Default', 'group'] + )->willReturn($errors); + + $this->structValidator->assertValidStruct('struct', new stdClass(), ['group']); + } + + public function testAssertValidStructWithInvalidStruct(): void + { + $errors = $this->createExampleConstraintViolationList( + $this->createExampleConstraintViolation() + ); + + $this->validator + ->method('validate') + ->with( + new stdClass(), + null, + ['Default', 'group'] + )->willReturn($errors); + + try { + $this->structValidator->assertValidStruct('struct', new stdClass(), ['group']); + } catch (ValidationFailedException $e) { + self::assertSame("Argument 'struct->property' is invalid: validation error", $e->getMessage()); + self::assertSame($errors, $e->getErrors()); + } + } + + private function createExampleConstraintViolation(): ConstraintViolationInterface + { + return new ConstraintViolation( + 'validation error', + null, + [], + '', + 'property', + 'example' + ); + } + + private function createExampleConstraintViolationList( + ConstraintViolationInterface $error + ): ConstraintViolationListInterface { + return new ConstraintViolationList([$error]); + } +} diff --git a/tests/lib/Validation/StructWrapperValidatorTest.php b/tests/lib/Validation/StructWrapperValidatorTest.php new file mode 100644 index 0000000000..ea75c6beed --- /dev/null +++ b/tests/lib/Validation/StructWrapperValidatorTest.php @@ -0,0 +1,128 @@ +validator = $this->createMock(ValidatorInterface::class); + $this->structValidator = new StructWrapperValidator($this->validator); + } + + public function testAssertValidStructWithValidStruct(): void + { + $struct = new stdClass(); + $initialErrors = $this->createMock(ConstraintViolationListInterface::class); + $initialErrors->method('count')->willReturn(0); + + $this->validator + ->expects(self::once()) + ->method('validate') + ->with( + $struct, + null, + ['Default', 'group'] + )->willReturn($initialErrors); + + $errors = $this->structValidator->validate(new stdClass(), null, ['Default', 'group']); + self::assertSame($initialErrors, $errors); + self::assertCount(0, $errors); + } + + public function testAssertValidStructWithInvalidStruct(): void + { + $initialError = $this->createExampleConstraintViolation(); + $initialErrors = $this->createExampleConstraintViolationList($initialError); + + $this->validator + ->method('validate') + ->with( + new stdClass(), + null, + ['Default', 'group'] + )->willReturn($initialErrors); + + $errors = $this->structValidator->validate(new stdClass(), null, ['Default', 'group']); + self::assertSame($initialErrors, $errors); + self::assertCount(1, $errors); + + $error = $errors->get(0); + self::assertSame($initialError, $error); + self::assertSame('validation error', $error->getMessage()); + self::assertSame('struct.property', $error->getPropertyPath()); + } + + public function testAssertValidStructWithInvalidWrapperStruct(): void + { + $initialError = $this->createExampleConstraintViolation(); + $initialErrors = $this->createExampleConstraintViolationList($initialError); + + $wrapper = $this->createMock(ValidationStructWrapperInterface::class); + + $struct = new stdClass(); + $wrapper->expects(self::once()) + ->method('getStruct') + ->willReturn($struct); + + $this->validator + ->method('validate') + ->with( + $wrapper, + null, + ['Default', 'group'] + )->willReturn($initialErrors); + + $errors = $this->structValidator->validate($wrapper, null, ['Default', 'group']); + self::assertNotSame($initialErrors, $errors); + self::assertCount(1, $errors); + + $error = $errors->get(0); + self::assertNotSame($error, $initialError); + self::assertSame('validation error', $error->getMessage()); + self::assertSame($struct, $error->getRoot()); + self::assertSame('property', $error->getPropertyPath()); + } + + private function createExampleConstraintViolation(): ConstraintViolationInterface + { + return new ConstraintViolation( + 'validation error', + null, + [], + '', + 'struct.property', + 'example' + ); + } + + private function createExampleConstraintViolationList( + ConstraintViolationInterface ...$errors + ): ConstraintViolationListInterface { + return new ConstraintViolationList($errors); + } +} diff --git a/tests/lib/Validation/ValidationFailedExceptionTest.php b/tests/lib/Validation/ValidationFailedExceptionTest.php new file mode 100644 index 0000000000..fbdfe3f02f --- /dev/null +++ b/tests/lib/Validation/ValidationFailedExceptionTest.php @@ -0,0 +1,60 @@ +__property_path__' is invalid: __error__", + $exception->getMessage(), + ); + self::assertSame($errors, $exception->getErrors()); + } + + public function testMultipleErrors(): void + { + $errors = new ConstraintViolationList([ + new ConstraintViolation('__error_1__', null, [], null, '__property_path_1__', null), + new ConstraintViolation('__error_2__', null, [], null, '__property_path_2__', null), + ]); + + $exception = new ValidationFailedException('__argument_name__', $errors); + + self::assertSame( + "Argument '__argument_name__->__property_path_1__' is invalid: __error_1__ and 1 more errors", + $exception->getMessage(), + ); + self::assertSame($errors, $exception->getErrors()); + } + + public function testEmptyErrorList(): void + { + $errors = new ConstraintViolationList([]); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf( + 'Cannot create %s with empty validation error list.', + ValidationFailedException::class, + )); + new ValidationFailedException('__argument_name__', $errors); + } +} From eed0ceab7ebf5d83bb161346c0065f29ea507685 Mon Sep 17 00:00:00 2001 From: Thorsten Reiter <34274688+reithor@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:17:30 +0100 Subject: [PATCH 6/6] IBX-8566: Fixed postgres language limit (#454) For more details see https://issues.ibexa.co/browse/IBX-8566 and https://github.com/ibexa/core/pull/454 Key changes: * Implemented PostgresqlGateway * Implemented SharedGateway\AbstractGateway to avoid code duplication * [Tests] Added integration test coverage for the use case * [Tests] Aligned tests with the changes * Fixed PHP deprecated error for $exp overflow for the last language --- .../Content/Gateway/DoctrineDatabase.php | 11 +-- .../Legacy/Content/Language/MaskGenerator.php | 3 +- src/lib/Persistence/Legacy/Content/Mapper.php | 3 +- .../DatabasePlatform/AbstractGateway.php | 60 ++++++++++++++++ .../DatabasePlatform/FallbackGateway.php | 25 +------ .../DatabasePlatform/PostgresqlGateway.php | 34 +++++++++ .../DatabasePlatform/SqliteGateway.php | 5 +- .../Legacy/SharedGateway/Gateway.php | 5 ++ .../legacy/shared_gateway.yaml | 6 ++ .../MaxLanguagesContentServiceTest.php | 69 +++++++++++++++++++ .../Repository/_fixtures/max_languages.yaml | 62 +++++++++++++++++ .../SharedGateway/GatewayFactoryTest.php | 2 +- tests/lib/Persistence/Legacy/TestCase.php | 2 +- 13 files changed, 246 insertions(+), 41 deletions(-) create mode 100644 src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/AbstractGateway.php create mode 100644 src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/PostgresqlGateway.php create mode 100644 tests/integration/Core/Repository/ContentService/MaxLanguagesContentServiceTest.php create mode 100644 tests/integration/Core/Repository/_fixtures/max_languages.yaml diff --git a/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php b/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php index 29d1569b52..3931637596 100644 --- a/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php +++ b/src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php @@ -1357,16 +1357,7 @@ public function setName(int $contentId, int $version, string $name, string $lang */ private function getSetNameLanguageMaskSubQuery(): string { - return << 0 ) - THEN (:language_id | 1) - ELSE :language_id - END - FROM ezcontentobject - WHERE id = :content_id) - SQL; + return $this->sharedGateway->getSetNameLanguageMaskSubQuery(); } public function deleteContent(int $contentId): void diff --git a/src/lib/Persistence/Legacy/Content/Language/MaskGenerator.php b/src/lib/Persistence/Legacy/Content/Language/MaskGenerator.php index c51c84f85f..d82714cda5 100644 --- a/src/lib/Persistence/Legacy/Content/Language/MaskGenerator.php +++ b/src/lib/Persistence/Legacy/Content/Language/MaskGenerator.php @@ -150,7 +150,8 @@ public function extractLanguageIdsFromMask($languageMask): array $result = []; // Decomposition of $languageMask into its binary components. - while ($exp <= $languageMask) { + // check if $exp has not overflown and became float (happens for the last possible language in the mask) + while (is_int($exp) && $exp <= $languageMask) { if ($languageMask & $exp) { $result[] = $exp; } diff --git a/src/lib/Persistence/Legacy/Content/Mapper.php b/src/lib/Persistence/Legacy/Content/Mapper.php index aa6a670a66..64456bd400 100644 --- a/src/lib/Persistence/Legacy/Content/Mapper.php +++ b/src/lib/Persistence/Legacy/Content/Mapper.php @@ -535,7 +535,8 @@ private function extractLanguageCodesFromMask(int $languageMask, array $allLangu $result = []; // Decomposition of $languageMask into its binary components to extract language codes - while ($exp <= $languageMask) { + // check if $exp has not overflown and became float (happens for the last possible language in the mask) + while (is_int($exp) && $exp <= $languageMask) { if ($languageMask & $exp) { if (isset($allLanguages[$exp])) { $result[] = $allLanguages[$exp]->languageCode; diff --git a/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/AbstractGateway.php b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/AbstractGateway.php new file mode 100644 index 0000000000..9989c7ecac --- /dev/null +++ b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/AbstractGateway.php @@ -0,0 +1,60 @@ +connection = $connection; + } + + public function getColumnNextIntegerValue( + string $tableName, + string $columnName, + string $sequenceName + ): ?int { + return null; + } + + /** + * Return a language sub select query for setName. + * + * The query generates the proper language mask at the runtime of the INSERT/UPDATE query + * generated by setName. + * + * @see setName + */ + public function getSetNameLanguageMaskSubQuery(): string + { + return << 0 ) + THEN (:language_id | 1) + ELSE :language_id + END + FROM ezcontentobject + WHERE id = :content_id) + SQL; + } + + public function getLastInsertedId(string $sequenceName): int + { + return (int)$this->connection->lastInsertId($sequenceName); + } +} diff --git a/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/FallbackGateway.php b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/FallbackGateway.php index 7a7a483c54..13330f23db 100644 --- a/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/FallbackGateway.php +++ b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/FallbackGateway.php @@ -8,31 +8,8 @@ namespace Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform; -use Doctrine\DBAL\Connection; -use Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway; - -final class FallbackGateway implements Gateway +final class FallbackGateway extends AbstractGateway { - /** @var \Doctrine\DBAL\Connection */ - private $connection; - - public function __construct(Connection $connection) - { - $this->connection = $connection; - } - - public function getColumnNextIntegerValue( - string $tableName, - string $columnName, - string $sequenceName - ): ?int { - return null; - } - - public function getLastInsertedId(string $sequenceName): int - { - return (int)$this->connection->lastInsertId($sequenceName); - } } class_alias(FallbackGateway::class, 'eZ\Publish\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\FallbackGateway'); diff --git a/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/PostgresqlGateway.php b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/PostgresqlGateway.php new file mode 100644 index 0000000000..d7889d127f --- /dev/null +++ b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/PostgresqlGateway.php @@ -0,0 +1,34 @@ + 0 ) + THEN (cast(:language_id as BIGINT) | 1) + ELSE :language_id + END + FROM ezcontentobject + WHERE id = :content_id) + SQL; + } +} diff --git a/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/SqliteGateway.php b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/SqliteGateway.php index b92fa7ea03..0825754e1b 100644 --- a/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/SqliteGateway.php +++ b/src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/SqliteGateway.php @@ -9,9 +9,8 @@ namespace Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform; use Ibexa\Core\Base\Exceptions\DatabaseException; -use Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway; -final class SqliteGateway implements Gateway +final class SqliteGateway extends AbstractGateway { /** * Error code 7 for a fatal error - taken from an existing driver implementation. @@ -21,7 +20,7 @@ final class SqliteGateway implements Gateway private const DB_INT_MAX = 2147483647; /** @var array */ - private $lastInsertedIds = []; + private array $lastInsertedIds = []; public function getColumnNextIntegerValue( string $tableName, diff --git a/src/lib/Persistence/Legacy/SharedGateway/Gateway.php b/src/lib/Persistence/Legacy/SharedGateway/Gateway.php index 9b947b146c..eb674d3de3 100644 --- a/src/lib/Persistence/Legacy/SharedGateway/Gateway.php +++ b/src/lib/Persistence/Legacy/SharedGateway/Gateway.php @@ -41,6 +41,11 @@ public function getColumnNextIntegerValue( * It returns integer as all the IDs in the Ibexa Legacy Storage are (big)integers */ public function getLastInsertedId(string $sequenceName): int; + + /** + * Return a language sub select query for setName. + */ + public function getSetNameLanguageMaskSubQuery(): string; } class_alias(Gateway::class, 'eZ\Publish\Core\Persistence\Legacy\SharedGateway\Gateway'); diff --git a/src/lib/Resources/settings/storage_engines/legacy/shared_gateway.yaml b/src/lib/Resources/settings/storage_engines/legacy/shared_gateway.yaml index 5fdbe8c133..e72873d48a 100644 --- a/src/lib/Resources/settings/storage_engines/legacy/shared_gateway.yaml +++ b/src/lib/Resources/settings/storage_engines/legacy/shared_gateway.yaml @@ -6,12 +6,18 @@ services: bind: $connection: '@ibexa.persistence.connection' + Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\AbstractGateway: ~ + Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\FallbackGateway: ~ Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\SqliteGateway: tags: - { name: ibexa.storage.legacy.gateway.shared, platform: sqlite } + Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\PostgresqlGateway: + tags: + - { name: ibexa.storage.legacy.gateway.shared, platform: postgresql } + Ibexa\Core\Persistence\Legacy\SharedGateway\GatewayFactory: arguments: $fallbackGateway: '@Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\FallbackGateway' diff --git a/tests/integration/Core/Repository/ContentService/MaxLanguagesContentServiceTest.php b/tests/integration/Core/Repository/ContentService/MaxLanguagesContentServiceTest.php new file mode 100644 index 0000000000..1885328cce --- /dev/null +++ b/tests/integration/Core/Repository/ContentService/MaxLanguagesContentServiceTest.php @@ -0,0 +1,69 @@ + */ + private static array $languagesRawList = []; + + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + self::$languagesRawList = Yaml::parseFile(dirname(__DIR__) . '/_fixtures/max_languages.yaml'); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + */ + protected function setUp(): void + { + parent::setUp(); + + $this->prepareMaxLanguages(); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + public function testCreateContent(): void + { + $names = array_merge(...array_map( + static fn (array $languageData): array => [ + $languageData['languageCode'] => $languageData['name'] . ' name', + ], + self::$languagesRawList + )); + $this->createFolder($names); + } + + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException + */ + private function prepareMaxLanguages(): void + { + $languageService = self::getLanguageService(); + + foreach (self::$languagesRawList as $languageData) { + $languageCreateStruct = $languageService->newLanguageCreateStruct(); + $languageCreateStruct->languageCode = $languageData['languageCode']; + $languageCreateStruct->name = $languageData['name']; + $languageService->createLanguage($languageCreateStruct); + } + } +} diff --git a/tests/integration/Core/Repository/_fixtures/max_languages.yaml b/tests/integration/Core/Repository/_fixtures/max_languages.yaml new file mode 100644 index 0000000000..72b8cc8fef --- /dev/null +++ b/tests/integration/Core/Repository/_fixtures/max_languages.yaml @@ -0,0 +1,62 @@ +- { languageCode: alb-SQ, name: Albanian } +- { languageCode: ara-AR, name: Arabic } +- { languageCode: aze-AZ, name: Azerbaijani } +- { languageCode: bos-BS, name: Bosnian } +- { languageCode: cha-CH, name: Chamorro } +- { languageCode: chi-ZH, name: Chinese } +- { languageCode: cze-CS, name: Czech } +- { languageCode: dan-DA, name: Danish } +- { languageCode: dut-NL, name: Dutch (Flemish) } +#- { languageCode: eng-GB, name: English (United Kingdom) } # Pre-exists in the initial db fixture +#- { languageCode: eng-US, name: English (United States) } # Pre-exists in the initial db fixture +- { languageCode: eng-AU, name: English (Australia) } +- { languageCode: epo-EO, name: Esperanto } +- { languageCode: esp-ES, name: Spanish } +- { languageCode: esp-MX, name: Spanish (Mexico) } +- { languageCode: est-ET, name: Estonian } +- { languageCode: fas-FA, name: Persian (Farsi) } +- { languageCode: fin-FI, name: Finnish } +- { languageCode: fre-FR, name: French (France) } +- { languageCode: fre-BE, name: French (Belgium) } +- { languageCode: fre-CA, name: French (Canada) } +- { languageCode: fre-CH, name: French (Switzerland) } +- { languageCode: fre-LU, name: French (Luxembourg) } +- { languageCode: geo-KA, name: Georgian } +#- { languageCode: ger-DE, name: German (Germany) } # Pre-exists in the initial db fixture +- { languageCode: ger-AT, name: German (Austria) } +- { languageCode: ger-CH, name: German (Switzerland) } +- { languageCode: ger-LI, name: German (Liechtenstein) } +- { languageCode: ger-LU, name: German (Luxembourg) } +- { languageCode: gle-GA, name: Irish } +- { languageCode: gla-GD, name: Scottish (Gaelic) } +- { languageCode: gre-EL, name: Greek } +- { languageCode: hin-HI, name: Hebrew } +- { languageCode: heb-HE, name: Hebrew } +- { languageCode: hrv-HR, name: Croatian } +- { languageCode: hun-HU, name: Hungarian } +- { languageCode: ind-ID, name: Indonesian } +- { languageCode: isl-IS, name: Icelandic } +- { languageCode: ita-IT, name: Italian } +- { languageCode: jpn-JA, name: Japanese } +- { languageCode: kor-KO, name: Korean } +- { languageCode: lat-LA, name: Latin } +- { languageCode: lav-LV, name: Latvian } +- { languageCode: lit-LT, name: Lithuanian } +- { languageCode: mao-MI, name: Maori (New Zealand) } +- { languageCode: may-MS, name: Malay } +- { languageCode: nor-NO, name: Norwegian } +- { languageCode: pol-PL, name: Polish } +- { languageCode: por-PT, name: Portuguese (Portugal) } +- { languageCode: por-BR, name: Portuguese (Brazil) } +- { languageCode: rum-RO, name: Romanian } +- { languageCode: slo-SK, name: Slovak } +- { languageCode: swe-SV, name: Swedish } +- { languageCode: bul-BG, name: Bulgarian } +- { languageCode: swa-SW, name: Swahili (Swahili) } +- { languageCode: tha-TH, name: Thai } +- { languageCode: tib-BO, name: Tibetan } +- { languageCode: tlh-TL, name: Klingon } +- { languageCode: tur-TR, name: Turkish } +- { languageCode: ukr-UK, name: Ukrainian } +- { languageCode: wel-CY, name: Welsh (Swahili) } +- { languageCode: yid-YI, name: Yiddish } diff --git a/tests/lib/Persistence/Legacy/SharedGateway/GatewayFactoryTest.php b/tests/lib/Persistence/Legacy/SharedGateway/GatewayFactoryTest.php index 090c366bf9..a75aab39c8 100644 --- a/tests/lib/Persistence/Legacy/SharedGateway/GatewayFactoryTest.php +++ b/tests/lib/Persistence/Legacy/SharedGateway/GatewayFactoryTest.php @@ -30,7 +30,7 @@ final class GatewayFactoryTest extends TestCase public function setUp(): void { $gateways = [ - 'sqlite' => new SqliteGateway(), + 'sqlite' => new SqliteGateway($this->createMock(Connection::class)), ]; $this->factory = new GatewayFactory( diff --git a/tests/lib/Persistence/Legacy/TestCase.php b/tests/lib/Persistence/Legacy/TestCase.php index 887861cfe7..72b9593222 100644 --- a/tests/lib/Persistence/Legacy/TestCase.php +++ b/tests/lib/Persistence/Legacy/TestCase.php @@ -114,7 +114,7 @@ final public function getSharedGateway(): SharedGateway\Gateway $factory = new SharedGateway\GatewayFactory( new SharedGateway\DatabasePlatform\FallbackGateway($connection), [ - 'sqlite' => new SharedGateway\DatabasePlatform\SqliteGateway(), + 'sqlite' => new SharedGateway\DatabasePlatform\SqliteGateway($connection), ] );