diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 4a7e083e..2da020b3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -90,6 +90,7 @@ jobs: env: CLI_PHP: ${{ (matrix.php == '8.1' && matrix.prestashop_version == 'develop') && '-d extension=pcov.so -d pcov.enabled=1 -d pcov.directory=./modules/ps_apiresources/ -d pcov.exclude="~(tests|vendor)~" -d memory_limit=-1' || ' ' }} CLI_PHPUNIT: ${{ (matrix.php == '8.1' && matrix.prestashop_version == 'develop') && ' ' || ' --no-coverage' }} + PS_VERSION: ${{ matrix.prestashop_version }} run : | USER_ID=$(id -u) GROUP_ID=$(id -g) docker exec prestashop-prestashop-git-1 \ php ${{env.CLI_PHP}} vendor/bin/phpunit -c modules/ps_apiresources/tests/Integration/phpunit-ci.xml${{ env.CLI_PHPUNIT }} diff --git a/src/ApiPlatform/Resources/Theme/Theme.php b/src/ApiPlatform/Resources/Theme/Theme.php new file mode 100644 index 00000000..4bb08873 --- /dev/null +++ b/src/ApiPlatform/Resources/Theme/Theme.php @@ -0,0 +1,75 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +declare(strict_types=1); + +namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\Theme; + +use ApiPlatform\Metadata\ApiResource; +use PrestaShop\PrestaShop\Core\Domain\Theme\Command\AdaptThemeToRTLLanguagesCommand; +use PrestaShop\PrestaShop\Core\Domain\Theme\Command\DeleteThemeCommand; +use PrestaShop\PrestaShop\Core\Domain\Theme\Command\EnableThemeCommand; +use PrestaShop\PrestaShop\Core\Domain\Theme\Command\ResetThemeLayoutsCommand; +use PrestaShop\PrestaShop\Core\Domain\Theme\Exception\CannotEnableThemeException; +use PrestaShop\PrestaShop\Core\Domain\Theme\Exception\ThemeConstraintException; +use PrestaShopBundle\ApiPlatform\Metadata\CQRSDelete; +use PrestaShopBundle\ApiPlatform\Metadata\CQRSUpdate; +use Symfony\Component\HttpFoundation\Response; + +#[ApiResource( + operations: [ + new CQRSDelete( + uriTemplate: '/themes/{themeName}', + output: false, + CQRSCommand: DeleteThemeCommand::class, + scopes: ['theme_write'], + ), + new CQRSUpdate( + uriTemplate: '/themes/{themeName}/adapt-to-rtl', + output: false, + allowEmptyBody: true, + CQRSCommand: AdaptThemeToRTLLanguagesCommand::class, + scopes: ['theme_write'], + ), + new CQRSUpdate( + uriTemplate: '/themes/{themeName}/enable', + output: false, + allowEmptyBody: true, + CQRSCommand: EnableThemeCommand::class, + scopes: ['theme_write'], + exceptionToStatus: [ + CannotEnableThemeException::class => Response::HTTP_UNPROCESSABLE_ENTITY, + ThemeConstraintException::class => Response::HTTP_UNPROCESSABLE_ENTITY, + ], + ), + new CQRSUpdate( + uriTemplate: '/themes/{themeName}/reset', + output: false, + allowEmptyBody: true, + CQRSCommand: ResetThemeLayoutsCommand::class, + scopes: ['theme_write'], + ), + ], + normalizationContext: ['skip_null_values' => false], +)] +class Theme +{ + public string $themeName; +} diff --git a/src/ApiPlatform/Resources/Theme/ThemeImport.php b/src/ApiPlatform/Resources/Theme/ThemeImport.php new file mode 100644 index 00000000..33c35bc6 --- /dev/null +++ b/src/ApiPlatform/Resources/Theme/ThemeImport.php @@ -0,0 +1,47 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +declare(strict_types=1); + +namespace PrestaShop\Module\APIResources\ApiPlatform\Resources\Theme; + +use ApiPlatform\Metadata\ApiResource; +use PrestaShop\PrestaShop\Core\Domain\Theme\Command\ImportThemeCommand; +use PrestaShopBundle\ApiPlatform\Metadata\CQRSUpdate; + +#[ApiResource( + operations: [ + new CQRSUpdate( + uriTemplate: '/themes/import', + output: false, + CQRSCommand: ImportThemeCommand::class, + CQRSCommandMapping: [ + '[importSource]' => '[importSource]', + ], + normalizationContext: [ + 'skip_null_values' => false, + ], + ), + ], +)] +class ThemeImport +{ + public string $importSource; +} diff --git a/src/ApiPlatform/Serializer/Theme.php b/src/ApiPlatform/Serializer/Theme.php new file mode 100644 index 00000000..e1936691 --- /dev/null +++ b/src/ApiPlatform/Serializer/Theme.php @@ -0,0 +1,48 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +declare(strict_types=1); + +namespace PrestaShop\Module\APIResources\ApiPlatform\Serializer; + +use PrestaShop\PrestaShop\Core\Domain\Theme\ValueObject\ThemeImportSource; +use PrestaShop\PrestaShop\Core\Domain\Theme\ValueObject\ThemeName; + +class Theme +{ + public static function toThemeImportSource(array $value): ThemeImportSource + { + switch ($value['sourceType']) { + case ThemeImportSource::FROM_ARCHIVE: + return ThemeImportSource::fromArchive($value['source']); + case ThemeImportSource::FROM_WEB: + return ThemeImportSource::fromWeb($value['source']); + case ThemeImportSource::FROM_FTP: + return ThemeImportSource::fromFtp($value['source']); + default: + throw new \Exception(sprintf('Unknown Source Type : %s', $value['sourceType'])); + } + } + + public static function toThemeName(string $value): ThemeName + { + return new ThemeName($value); + } +} diff --git a/tests/Integration/ApiPlatform/ThemeEndpointTest.php b/tests/Integration/ApiPlatform/ThemeEndpointTest.php new file mode 100644 index 00000000..06f457fa --- /dev/null +++ b/tests/Integration/ApiPlatform/ThemeEndpointTest.php @@ -0,0 +1,165 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +declare(strict_types=1); + +namespace PsApiResourcesTest\Integration\ApiPlatform; + +use Db; +use Symfony\Component\HttpFoundation\Response; +use Tests\Resources\DatabaseDump; + +class ThemeEndpointTest extends ApiTestCase +{ + protected static $dirTheme = _PS_ROOT_DIR_ . '/themes/%s'; + + protected static $fileThemeRTL = _PS_ROOT_DIR_ . '/themes/%s/assets/css/theme_rtl.css'; + + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + // Pre-create the API Client with the needed scopes, this way we reduce the number of created API Clients + self::createApiClient(['theme_write']); + + self::cleanThemes(); + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + // Reset DB as it was before this test + DatabaseDump::restoreTables(['shop']); + + self::cleanThemes(); + } + + public static function cleanThemes(): void + { + if (file_exists(sprintf(self::$fileThemeRTL, 'classic'))) { + unlink(sprintf(self::$fileThemeRTL, 'classic')); + } + } + + public static function getProtectedEndpoints(): iterable + { + yield 'update endpoint (Adapt To RTL)' => [ + 'PUT', + '/themes/classic/adapt-to-rtl', + ]; + yield 'update endpoint (Enable)' => [ + 'PUT', + '/themes/classic/enable', + ]; + yield 'update endpoint (Reset)' => [ + 'PUT', + '/themes/classic/reset', + ]; + yield 'delete endpoint' => [ + 'DELETE', + '/themes/classic', + ]; + } + + public function testAdaptToRtl(): void + { + $themeName = 'classic'; + + self::assertEquals(false, $this->isThemeAdaptedToRTL($themeName)); + $this->updateItem('/themes/' . $themeName . '/adapt-to-rtl', [], ['theme_write'], Response::HTTP_NO_CONTENT); + self::assertEquals(true, $this->isThemeAdaptedToRTL($themeName)); + } + + /** + * @depends testAdaptToRtl + */ + public function testReset(): void + { + $themeName = get_env('PS_VERSION') ? 'classic' : 'hummingbird'; + + self::assertEquals($themeName, $this->getCurrentTheme()); + $this->updateItem('/themes/' . $themeName . '/reset', [], ['theme_write']); + self::assertEquals($themeName, $this->getCurrentTheme()); + } + + /** + * @depends testReset + */ + public function testEnable(): void + { + $themeNameCurrent = get_env('PS_VERSION') ? 'classic' : 'hummingbird'; + $themeNameEnable = get_env('PS_VERSION') ? 'hummingbird' : 'classic'; + + self::assertEquals($themeNameCurrent, $this->getCurrentTheme()); + $this->updateItem('/themes/' . $themeNameEnable . '/enable', [], ['theme_write'], Response::HTTP_UNPROCESSABLE_ENTITY); + } + + /** + * @depends testEnable + */ + public function testDelete(): void + { + $themeName = get_env('PS_VERSION') ? 'classic' : 'hummingbird'; + + self::assertEquals(true, $this->hasTheme($themeName)); + $this->deleteItem('/themes/' . $themeName, ['theme_write']); + self::assertEquals(false, $this->hasTheme($themeName)); + } + + /** + * @depends testDelete + */ + public function testImport(): void + { + $themeName = get_env('PS_VERSION') ? 'classic' : 'hummingbird'; + $themeURL = get_env('PS_VERSION') + ? 'https://github.com/PrestaShop/classic-theme/archive/refs/tags/3.1.1.zip' + : 'https://github.com/PrestaShop/hummingbird/releases/download/v1.0.1/hummingbird.zip'; + + self::assertEquals(false, $this->hasTheme($themeName)); + $this->updateItem( + '/themes/import', + [ + 'importSource' => [ + 'sourceType' => 'from_web', + 'source' => $themeURL, + ], + ], + ['theme_write'], + Response::HTTP_NO_CONTENT + ); + self::assertEquals(true, $this->hasTheme($themeName)); + } + + protected function hasTheme(string $themeName): bool + { + return is_dir(sprintf(self::$dirTheme, $themeName)); + } + + protected function isThemeAdaptedToRTL(string $themeName): bool + { + return file_exists(sprintf(self::$fileThemeRTL, $themeName)); + } + + protected function getCurrentTheme(): string + { + return \Db::getInstance()->getValue('SELECT theme_name FROM `' . _DB_PREFIX_ . 'shop` WHERE id_shop="1"'); + } +} diff --git a/tests/Rector/ApiResourceUriTemplateRector.php b/tests/Rector/ApiResourceUriTemplateRector.php index 8a58cbf1..93aaee20 100644 --- a/tests/Rector/ApiResourceUriTemplateRector.php +++ b/tests/Rector/ApiResourceUriTemplateRector.php @@ -100,7 +100,9 @@ final class ApiResourceUriTemplateRector extends AbstractRector * specific elements that are unique (like the cover) */ private const SKIPPED_KEYWORDS = [ + 'adapt-to-rtl', 'batch', + 'import', 'status', 'toggle-status', 'set-status',