From a5d26b61b7a9445a832c7339f311b938973f949c Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:08:25 +0100 Subject: [PATCH 01/15] Upgrade to symplify/easy-coding-standard 13 --- composer.json | 8 ++- easy-coding-standard.yml | 103 --------------------------------------- 2 files changed, 7 insertions(+), 104 deletions(-) delete mode 100644 easy-coding-standard.yml diff --git a/composer.json b/composer.json index 1b356d1..7cca930 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ }, "require-dev": { "bolt/core": "^5.0", - "symplify/easy-coding-standard": "^7.0" + "symplify/easy-coding-standard": "^13.0" }, "autoload": { "psr-4": { @@ -28,5 +28,11 @@ "screenshots": [ "screenshots/redactor.png" ] + }, + "config": { + "allow-plugins": { + "symfony/flex": false, + "drupol/composer-packages": false + } } } diff --git a/easy-coding-standard.yml b/easy-coding-standard.yml deleted file mode 100644 index 1a2ec8e..0000000 --- a/easy-coding-standard.yml +++ /dev/null @@ -1,103 +0,0 @@ -imports: - - { resource: 'vendor/symplify/easy-coding-standard/config/set/clean-code.yaml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/set/common.yaml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/set/php70.yaml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/set/php71.yaml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/set/psr2.yaml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/set/psr12.yaml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/set/symfony.yaml' } - - { resource: 'vendor/symplify/easy-coding-standard/config/set/symfony-risky.yaml' } - -services: - # most of these services are taken from symplify.yml - # see https://github.com/Symplify/Symplify/blob/master/ecs.yml - - # PHP 5.5 - Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer: ~ - - # Control Structures - Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer: ~ - Symplify\CodingStandard\Fixer\ArrayNotation\StandaloneLineInMultilineArrayFixer: ~ - Symplify\CodingStandard\Fixer\ControlStructure\RequireFollowedByAbsolutePathFixer: ~ - - # Spaces - Symplify\CodingStandard\Fixer\Strict\BlankLineAfterStrictTypesFixer: ~ - PhpCsFixer\Fixer\Operator\ConcatSpaceFixer: - spacing: one - - # Comments - Symplify\CodingStandard\Fixer\Commenting\RemoveSuperfluousDocBlockWhitespaceFixer: ~ - - # Naming - PhpCsFixer\Fixer\PhpUnit\PhpUnitMethodCasingFixer: ~ - - # Debug - Symplify\CodingStandard\Sniffs\Debug\DebugFunctionCallSniff: ~ - Symplify\CodingStandard\Sniffs\Debug\CommentedOutCodeSniff: ~ - - # final classes - PhpCsFixer\Fixer\ClassNotation\FinalInternalClassFixer: ~ - - # multibyte - PhpCsFixer\Fixer\Alias\MbStrFunctionsFixer: ~ - - # psr - PhpCsFixer\Fixer\Basic\Psr0Fixer: ~ - PhpCsFixer\Fixer\Basic\Psr4Fixer: ~ - - PhpCsFixer\Fixer\CastNotation\LowercaseCastFixer: ~ - PhpCsFixer\Fixer\CastNotation\ShortScalarCastFixer: ~ - PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer: ~ - PhpCsFixer\Fixer\Import\NoLeadingImportSlashFixer: ~ - PhpCsFixer\Fixer\Import\OrderedImportsFixer: - importsOrder: - - 'class' - - 'const' - - 'function' - PhpCsFixer\Fixer\LanguageConstruct\DeclareEqualNormalizeFixer: - space: 'none' - PhpCsFixer\Fixer\Operator\NewWithBracesFixer: ~ - PhpCsFixer\Fixer\Basic\BracesFixer: - 'allow_single_line_closure': false - 'position_after_functions_and_oop_constructs': 'next' - 'position_after_control_structures': 'same' - 'position_after_anonymous_constructs': 'same' - - PhpCsFixer\Fixer\ClassNotation\NoBlankLinesAfterClassOpeningFixer: ~ - PhpCsFixer\Fixer\ClassNotation\VisibilityRequiredFixer: - elements: - - 'const' - - 'method' - - 'property' - PhpCsFixer\Fixer\Operator\TernaryOperatorSpacesFixer: ~ - PhpCsFixer\Fixer\FunctionNotation\ReturnTypeDeclarationFixer: ~ - PhpCsFixer\Fixer\Whitespace\NoTrailingWhitespaceFixer: ~ - - PhpCsFixer\Fixer\Semicolon\NoSinglelineWhitespaceBeforeSemicolonsFixer: ~ - PhpCsFixer\Fixer\ArrayNotation\NoWhitespaceBeforeCommaInArrayFixer: ~ - PhpCsFixer\Fixer\ArrayNotation\WhitespaceAfterCommaInArrayFixer: ~ - - #remove useless phpdoc - PhpCsFixer\Fixer\FunctionNotation\PhpdocToReturnTypeFixer: ~ - PhpCsFixer\Fixer\Import\FullyQualifiedStrictTypesFixer: ~ - PhpCsFixer\Fixer\Phpdoc\NoSuperfluousPhpdocTagsFixer: ~ - PhpCsFixer\Fixer\Phpdoc\PhpdocLineSpanFixer: - property: single - - #please yoda no - SlevomatCodingStandard\Sniffs\ControlStructures\DisallowYodaComparisonSniff: ~ - -parameters: - cache_directory: var/cache/ecs - skip: - PhpCsFixer\Fixer\ClassNotation\OrderedClassElementsFixer: ~ - PhpCsFixer\Fixer\ControlStructure\YodaStyleFixer: ~ - PhpCsFixer\Fixer\Operator\IncrementStyleFixer: ~ - PhpCsFixer\Fixer\Phpdoc\PhpdocAnnotationWithoutDotFixer: ~ - PhpCsFixer\Fixer\Phpdoc\PhpdocSummaryFixer: ~ - Symplify\CodingStandard\Sniffs\Debug\CommentedOutCodeSniff: ~ #to be removed before beta release - Symplify\CodingStandard\Sniffs\Debug\DebugFunctionCallSniff: ~ #to be removed before beta release - - # Deprecated. Todo: Find replacement - Symplify\CodingStandard\Fixer\ControlStructure\RequireFollowedByAbsolutePathFixer: ~ - Symplify\CodingStandard\Fixer\Property\ArrayPropertyDefaultValueFixer: ~ \ No newline at end of file From 878cf6f97ce783dd4e4f6876cfa9a470b88d01ff Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:09:06 +0100 Subject: [PATCH 02/15] Update ECS config to be in sync with bolt/core, apply fixes --- ecs.php | 127 ++++++++++++++++++++++++++++++++++++++ src/Controller/Images.php | 2 +- src/Controller/Upload.php | 4 +- src/RedactorConfig.php | 2 +- src/TwigExtension.php | 1 - 5 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 ecs.php diff --git a/ecs.php b/ecs.php new file mode 100644 index 0000000..43705ef --- /dev/null +++ b/ecs.php @@ -0,0 +1,127 @@ +withPaths([ + __DIR__ . '/src', + __DIR__ . '/ecs.php', + ]) + ->withCache('var/cache/ecs') + ->withPreparedSets(psr12: true, common: true, cleanCode: true) + ->withSkip([ + OrderedClassElementsFixer::class => null, + YodaStyleFixer::class => null, + IncrementStyleFixer::class => null, + PhpdocAnnotationWithoutDotFixer::class => null, + PhpdocSummaryFixer::class => null, + PhpdocAlignFixer::class => null, + NativeConstantInvocationFixer::class => null, + NativeFunctionInvocationFixer::class => null, + UnaryOperatorSpacesFixer::class => null, + ArrayOpenerAndCloserNewlineFixer::class => null, + ArrayListItemNewlineFixer::class => null, + ]) + ->withRules([ + StandaloneLineInMultilineArrayFixer::class, + BlankLineAfterStrictTypesFixer::class, + RemoveUselessDefaultCommentFixer::class, + PhpUnitMethodCasingFixer::class, + FinalInternalClassFixer::class, + MbStrFunctionsFixer::class, + LowercaseCastFixer::class, + ShortScalarCastFixer::class, + BlankLineAfterOpeningTagFixer::class, + NoLeadingImportSlashFixer::class, + NewWithBracesFixer::class, + NoBlankLinesAfterClassOpeningFixer::class, + TernaryOperatorSpacesFixer::class, + ReturnTypeDeclarationFixer::class, + NoTrailingWhitespaceFixer::class, + NoSinglelineWhitespaceBeforeSemicolonsFixer::class, + NoWhitespaceBeforeCommaInArrayFixer::class, + WhitespaceAfterCommaInArrayFixer::class, + FullyQualifiedStrictTypesFixer::class, + ]) + ->withConfiguredRule(PhpdocToReturnTypeFixer::class, ['union_types' => false]) + ->withConfiguredRule(NoSuperfluousPhpdocTagsFixer::class, ['remove_inheritdoc' => false]) + ->withConfiguredRule( + ConcatSpaceFixer::class, + ['spacing' => 'one'] + ) + ->withConfiguredRule( + OrderedImportsFixer::class, + [ + 'imports_order' => ['class', 'const', 'function'], + ] + ) + ->withConfiguredRule( + DeclareEqualNormalizeFixer::class, + ['space' => 'none'] + ) + ->withConfiguredRule( + BracesFixer::class, + [ + 'allow_single_line_closure' => false, + 'position_after_functions_and_oop_constructs' => 'next', + 'position_after_control_structures' => 'same', + 'position_after_anonymous_constructs' => 'same', + ] + ) + ->withConfiguredRule( + VisibilityRequiredFixer::class, + [ + 'elements' => ['const', 'method', 'property'], + ] + ) + ->withConfiguredRule( + PhpdocLineSpanFixer::class, + ['property' => 'single'] + ) + ->withConfiguredRule( + ClassAttributesSeparationFixer::class, + ['elements' => ['property' => 'none', 'method' => 'one', 'const' => 'none']] + ); diff --git a/src/Controller/Images.php b/src/Controller/Images.php index c186cee..2865460 100644 --- a/src/Controller/Images.php +++ b/src/Controller/Images.php @@ -38,7 +38,7 @@ class Images implements AsyncZoneInterface /** @var ThumbnailHelper */ private $thumbnailHelper; - /** @var redactorConfig */ + /** @var RedactorConfig */ private $redactorConfig; public function __construct(Config $config, CsrfTokenManagerInterface $csrfTokenManager, RequestStack $requestStack, UrlGeneratorInterface $urlGenerator, ThumbnailHelper $thumbnailHelper, RedactorConfig $redactorConfig) diff --git a/src/Controller/Upload.php b/src/Controller/Upload.php index bdbedff..dbfb129 100644 --- a/src/Controller/Upload.php +++ b/src/Controller/Upload.php @@ -13,6 +13,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Sirius\Upload\Handler; use Sirius\Upload\Result\File; +use Symfony\Component\Filesystem\Path; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -20,7 +21,6 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Symfony\Component\Filesystem\Path; /** * @Security("is_granted('upload')") @@ -152,6 +152,6 @@ private function isImage(string $filename): bool { $extension = pathinfo($filename, PATHINFO_EXTENSION); - return in_array($extension, ['gif', 'png', 'jpg', 'jpeg', 'svg', 'avif', 'webp']); + return in_array($extension, ['gif', 'png', 'jpg', 'jpeg', 'svg', 'avif', 'webp']); } } diff --git a/src/RedactorConfig.php b/src/RedactorConfig.php index ab9fdf1..18fce3d 100644 --- a/src/RedactorConfig.php +++ b/src/RedactorConfig.php @@ -106,7 +106,7 @@ public function getDefaults() ]), 'fileUpload' => $this->urlGenerator->generate('bolt_redactor_upload', [ 'location' => 'files', - '_csrf_token' => $this->csrfTokenManager->getToken('bolt_redactor')->getValue() + '_csrf_token' => $this->csrfTokenManager->getToken('bolt_redactor')->getValue(), ]), 'fileManagerJson' => $this->urlGenerator->generate('bolt_redactor_files', [ '_csrf_token' => $this->csrfTokenManager->getToken('bolt_redactor')->getValue(), diff --git a/src/TwigExtension.php b/src/TwigExtension.php index 48f32ee..5115db6 100644 --- a/src/TwigExtension.php +++ b/src/TwigExtension.php @@ -77,7 +77,6 @@ public function redactorIncludes(): string $includes = $this->redactorConfig->getConfig()['includes']; foreach ($includes as $item) { - $item = $this->makePath($item); if (Path::getExtension($item) === 'css') { From d1387e69875df8660c97c3fbcf91d9d1bc916a7b Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:15:40 +0100 Subject: [PATCH 03/15] Add rector, copy bolt/core config and apply changes --- composer.json | 1 + rector.php | 26 ++++++++++++++++++++++++++ src/Controller/Upload.php | 5 +++-- src/RedactorConfig.php | 6 +++--- 4 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 rector.php diff --git a/composer.json b/composer.json index 7cca930..9c6a4ab 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ }, "require-dev": { "bolt/core": "^5.0", + "rector/rector": "2.2.7", "symplify/easy-coding-standard": "^13.0" }, "autoload": { diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..5664b3b --- /dev/null +++ b/rector.php @@ -0,0 +1,26 @@ +withCache('./var/cache/rector', FileCacheStorage::class) + ->withPaths(['./src']) + ->withImportNames() + ->withParallel(timeoutSeconds: 180, jobSize: 10) + ->withPhpSets() + ->withPreparedSets( + typeDeclarations: true, + symfonyCodeQuality: true, + ) + ->withComposerBased( + twig: true, + doctrine: true, + phpunit: true, + symfony: true, + ) + ->withSkip([ + Rector\Symfony\CodeQuality\Rector\Class_\InlineClassRoutePrefixRector::class + ]); diff --git a/src/Controller/Upload.php b/src/Controller/Upload.php index dbfb129..925a609 100644 --- a/src/Controller/Upload.php +++ b/src/Controller/Upload.php @@ -4,6 +4,7 @@ namespace Bolt\Redactor\Controller; +use Throwable; use Bolt\Configuration\Config; use Bolt\Controller\Backend\Async\AsyncZoneInterface; use Bolt\Controller\CsrfTrait; @@ -92,7 +93,7 @@ public function handleUpload(Request $request): JsonResponse 'Upload file' ); - $uploadHandler->setSanitizerCallback(function ($name) { + $uploadHandler->setSanitizerCallback(function (string $name): string { return $this->sanitiseFilename($name); }); @@ -104,7 +105,7 @@ public function handleUpload(Request $request): JsonResponse // later on, should we do a `Request::createFromGlobals();` // @see: https://github.com/bolt/core/issues/2027 $_FILES = []; - } catch (\Throwable $e) { + } catch (Throwable $e) { return new JsonResponse([ 'error' => true, 'message' => 'Ensure the upload does NOT exceed the maximum filesize of ' . $this->textExtension->formatBytes($maxSize) . ', and that the destination folder (on the webserver) is writable.', diff --git a/src/RedactorConfig.php b/src/RedactorConfig.php index 18fce3d..856de97 100644 --- a/src/RedactorConfig.php +++ b/src/RedactorConfig.php @@ -93,7 +93,7 @@ public function getPlugins(): array return $this->plugins; } - public function getDefaults() + public function getDefaults(): array { $defaults = [ 'image' => [ @@ -144,7 +144,7 @@ public function getDefaults() return $defaults; } - public function getDefaultPlugins() + public function getDefaultPlugins(): array { return [ 'alignment' => ['alignment/alignment.min.js'], @@ -175,7 +175,7 @@ public function getDefaultPlugins() private function getLinks(): array { - return $this->cache->get('redactor_insert_links', function (ItemInterface $item) { + return $this->cache->get('redactor_insert_links', function (ItemInterface $item): array { $item->expiresAfter(self::CACHE_DURATION); return $this->getLinksHelper(); From ee1f34d4d981c760820a8c153054810f5b303510 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:17:51 +0100 Subject: [PATCH 04/15] Add phpstan with basic configuration based on bolt/core --- composer.json | 6 +++++- phpstan.dist.neon | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 phpstan.dist.neon diff --git a/composer.json b/composer.json index 9c6a4ab..fff977e 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,9 @@ }, "require-dev": { "bolt/core": "^5.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "2.1.31", + "phpstan/phpstan-deprecation-rules": "2.0.3", "rector/rector": "2.2.7", "symplify/easy-coding-standard": "^13.0" }, @@ -33,7 +36,8 @@ "config": { "allow-plugins": { "symfony/flex": false, - "drupol/composer-packages": false + "drupol/composer-packages": false, + "phpstan/extension-installer": true } } } diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..e5f9e2a --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,8 @@ +parameters: + level: 8 + + paths: + - src + + treatPhpDocTypesAsCertain: false + reportUnmatchedIgnoredErrors: true From 2f4c2606f908be4b84a1bfccce8f5cc5e0a2514d Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:23:30 +0100 Subject: [PATCH 05/15] Bump PHP version to >=8.2, apply rector suggestions --- composer.json | 2 +- src/Controller/Images.php | 36 ++++++++++------------------ src/Controller/Upload.php | 39 +++++++++++-------------------- src/RedactorConfig.php | 49 +++++++-------------------------------- src/TwigExtension.php | 24 ++++++------------- 5 files changed, 42 insertions(+), 108 deletions(-) diff --git a/composer.json b/composer.json index fff977e..6d67094 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": ">=7.2.9" + "php": ">=8.2" }, "require-dev": { "bolt/core": "^5.0", diff --git a/src/Controller/Images.php b/src/Controller/Images.php index 2865460..919636c 100644 --- a/src/Controller/Images.php +++ b/src/Controller/Images.php @@ -17,7 +17,6 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Tightenco\Collect\Support\Collection; @@ -29,25 +28,14 @@ class Images implements AsyncZoneInterface { use CsrfTrait; - /** @var Config */ - private $config; - - /** @var Request */ - private $request; - - /** @var ThumbnailHelper */ - private $thumbnailHelper; - - /** @var RedactorConfig */ - private $redactorConfig; - - public function __construct(Config $config, CsrfTokenManagerInterface $csrfTokenManager, RequestStack $requestStack, UrlGeneratorInterface $urlGenerator, ThumbnailHelper $thumbnailHelper, RedactorConfig $redactorConfig) - { - $this->config = $config; + public function __construct( + private readonly Config $config, + private readonly RequestStack $requestStack, + private readonly ThumbnailHelper $thumbnailHelper, + private readonly RedactorConfig $redactorConfig, + CsrfTokenManagerInterface $csrfTokenManager, + ) { $this->csrfTokenManager = $csrfTokenManager; - $this->request = $requestStack->getCurrentRequest(); - $this->thumbnailHelper = $thumbnailHelper; - $this->redactorConfig = $redactorConfig; } /** @@ -57,14 +45,14 @@ public function getImagesList(Request $request): JsonResponse { try { $this->validateCsrf('bolt_redactor'); - } catch (InvalidCsrfTokenException $e) { + } catch (InvalidCsrfTokenException) { return new JsonResponse([ 'error' => true, 'message' => 'Invalid CSRF token', ], Response::HTTP_FORBIDDEN); } - $locationName = $this->request->query->get('location', 'files'); + $locationName = $this->requestStack->getCurrentRequest()->query->get('location', 'files'); $path = $this->config->getPath($locationName, true); $files = $this->getImageFilesIndex($path); @@ -91,18 +79,18 @@ private function getImageFilesIndex(string $path): Collection /** * @Route("/redactor_files", name="bolt_redactor_files", methods={"GET"}) */ - public function getFilesList(Request $request): JsonResponse + public function getFilesList(): JsonResponse { try { $this->validateCsrf('bolt_redactor'); - } catch (InvalidCsrfTokenException $e) { + } catch (InvalidCsrfTokenException) { return new JsonResponse([ 'error' => true, 'message' => 'Invalid CSRF token', ], Response::HTTP_FORBIDDEN); } - $locationName = $this->request->query->get('location', 'files'); + $locationName = $this->requestStack->getCurrentRequest()->query->get('location', 'files'); $path = $this->config->getPath($locationName, true); $files = $this->getFilesIndex($path); diff --git a/src/Controller/Upload.php b/src/Controller/Upload.php index 925a609..b3afc8b 100644 --- a/src/Controller/Upload.php +++ b/src/Controller/Upload.php @@ -4,7 +4,6 @@ namespace Bolt\Redactor\Controller; -use Throwable; use Bolt\Configuration\Config; use Bolt\Controller\Backend\Async\AsyncZoneInterface; use Bolt\Controller\CsrfTrait; @@ -22,6 +21,7 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Throwable; /** * @Security("is_granted('upload')") @@ -30,25 +30,14 @@ class Upload implements AsyncZoneInterface { use CsrfTrait; - /** @var Config */ - private $config; - - /** @var TextExtension */ - private $textExtension; - - /** @var Request */ - private $request; - - /** @var RedactorConfig */ - private $redactorConfig; - - public function __construct(Config $config, CsrfTokenManagerInterface $csrfTokenManager, TextExtension $textExtension, RequestStack $requestStack, RedactorConfig $redactorConfig) - { - $this->config = $config; + public function __construct( + private readonly Config $config, + private readonly TextExtension $textExtension, + private readonly RequestStack $requestStack, + private readonly RedactorConfig $redactorConfig, + CsrfTokenManagerInterface $csrfTokenManager, + ) { $this->csrfTokenManager = $csrfTokenManager; - $this->textExtension = $textExtension; - $this->request = $requestStack->getCurrentRequest(); - $this->redactorConfig = $redactorConfig; } /** @@ -58,15 +47,15 @@ public function handleUpload(Request $request): JsonResponse { try { $this->validateCsrf('bolt_redactor'); - } catch (InvalidCsrfTokenException $e) { + } catch (InvalidCsrfTokenException) { return new JsonResponse([ 'error' => true, 'message' => 'Invalid CSRF token', ], Response::HTTP_FORBIDDEN); } - $locationName = $this->request->query->get('location', ''); - $path = $this->request->query->get('path', ''); + $locationName = $request->query->get('location', ''); + $path = $request->query->get('path', ''); $target = $this->config->getPath($locationName, true, $path); @@ -93,9 +82,7 @@ public function handleUpload(Request $request): JsonResponse 'Upload file' ); - $uploadHandler->setSanitizerCallback(function (string $name): string { - return $this->sanitiseFilename($name); - }); + $uploadHandler->setSanitizerCallback($this->sanitiseFilename(...)); try { /** @var File $result */ @@ -105,7 +92,7 @@ public function handleUpload(Request $request): JsonResponse // later on, should we do a `Request::createFromGlobals();` // @see: https://github.com/bolt/core/issues/2027 $_FILES = []; - } catch (Throwable $e) { + } catch (Throwable) { return new JsonResponse([ 'error' => true, 'message' => 'Ensure the upload does NOT exceed the maximum filesize of ' . $this->textExtension->formatBytes($maxSize) . ', and that the destination folder (on the webserver) is writable.', diff --git a/src/RedactorConfig.php b/src/RedactorConfig.php index 856de97..a6f23d3 100644 --- a/src/RedactorConfig.php +++ b/src/RedactorConfig.php @@ -18,49 +18,18 @@ class RedactorConfig { private const CACHE_DURATION = 1800; // 30 minutes - /** @var ExtensionRegistry */ - private $registry; - - /** @var UrlGeneratorInterface */ - private $urlGenerator; - - /** @var CsrfTokenManagerInterface */ - private $csrfTokenManager; - - /** @var Config */ - private $boltConfig; - - /** @var Query */ - private $query; - - /** @var array */ - private $config = null; - - /** @var array */ - private $plugins = null; - - /** @var CacheInterface */ - private $cache; - - /** @var Security */ - private $security; + private ?array $config = null; + private ?array $plugins = null; public function __construct( - ExtensionRegistry $registry, - UrlGeneratorInterface $urlGenerator, - CsrfTokenManagerInterface $csrfTokenManager, - Config $boltConfig, - Query $query, - CacheInterface $cache, - Security $security + private readonly ExtensionRegistry $registry, + private readonly UrlGeneratorInterface $urlGenerator, + private readonly CsrfTokenManagerInterface $csrfTokenManager, + private readonly Config $boltConfig, + private readonly Query $query, + private readonly CacheInterface $cache, + private readonly Security $security ) { - $this->registry = $registry; - $this->urlGenerator = $urlGenerator; - $this->csrfTokenManager = $csrfTokenManager; - $this->boltConfig = $boltConfig; - $this->query = $query; - $this->cache = $cache; - $this->security = $security; } public function getConfig(): array diff --git a/src/TwigExtension.php b/src/TwigExtension.php index 5115db6..80cdce9 100644 --- a/src/TwigExtension.php +++ b/src/TwigExtension.php @@ -7,27 +7,17 @@ use Bolt\Common\Json; use Bolt\Configuration\Config; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\Container; use Symfony\Component\Filesystem\Path; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; class TwigExtension extends AbstractExtension { - /** @var RedactorConfig */ - private $redactorConfig; - - /** @var Config */ - private $boltConfig; - - /** @var Container */ - private $container; - - public function __construct(RedactorConfig $redactorConfig, Config $boltConfig, ContainerInterface $container) - { - $this->redactorConfig = $redactorConfig; - $this->boltConfig = $boltConfig; - $this->container = $container; + public function __construct( + private readonly RedactorConfig $redactorConfig, + private readonly Config $boltConfig, + private readonly ContainerInterface $container + ) { } public function getFunctions(): array @@ -37,8 +27,8 @@ public function getFunctions(): array ]; return [ - new TwigFunction('redactor_settings', [$this, 'redactorSettings'], $safe), - new TwigFunction('redactor_includes', [$this, 'redactorIncludes'], $safe), + new TwigFunction('redactor_settings', $this->redactorSettings(...), $safe), + new TwigFunction('redactor_includes', $this->redactorIncludes(...), $safe), ]; } From be6cbe512fbd5621b09c9dd59a92e370bef60973 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:29:46 +0100 Subject: [PATCH 06/15] Directly inject parameters instead of container --- config/services.yaml | 5 +++++ src/TwigExtension.php | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config/services.yaml b/config/services.yaml index 81e8cb7..d8c56b4 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -10,6 +10,11 @@ services: Bolt\Redactor\Controller\Upload: tags: ['controller.service_arguments'] + Bolt\Redactor\TwigExtension: + arguments: + $projectDir: '%kernel.project_dir%' + $publicFolder: '%bolt.public_folder%' + ### Map entities doctrine: orm: diff --git a/src/TwigExtension.php b/src/TwigExtension.php index 80cdce9..edd9ac7 100644 --- a/src/TwigExtension.php +++ b/src/TwigExtension.php @@ -6,7 +6,6 @@ use Bolt\Common\Json; use Bolt\Configuration\Config; -use Psr\Container\ContainerInterface; use Symfony\Component\Filesystem\Path; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -16,7 +15,8 @@ class TwigExtension extends AbstractExtension public function __construct( private readonly RedactorConfig $redactorConfig, private readonly Config $boltConfig, - private readonly ContainerInterface $container + private readonly string $projectDir, + private readonly string $publicFolder, ) { } @@ -84,7 +84,7 @@ public function redactorIncludes(): string private function makePath(string $item): string { $path = $this->boltConfig->getPath($item, false); - $publicFolder = $this->container->getParameter('kernel.project_dir') . '/' . $this->container->getParameter('bolt.public_folder'); + $publicFolder = $this->projectDir . '/' . $this->publicFolder; $path = '/' . Path::makeRelative($path, $publicFolder); From 06b49841be8d203e5d998eb27f8844e0224f2dcc Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:30:47 +0100 Subject: [PATCH 07/15] Require bolt/core 6 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6d67094..a8e40ab 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "php": ">=8.2" }, "require-dev": { - "bolt/core": "^5.0", + "bolt/core": "^6.0", "phpstan/extension-installer": "1.4.3", "phpstan/phpstan": "2.1.31", "phpstan/phpstan-deprecation-rules": "2.0.3", From 759c88c073971f3f4aa53da1aaba85afaa92353a Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:30:09 +0100 Subject: [PATCH 08/15] Switch to attributes --- config/routes.yaml | 2 +- config/services.yaml | 2 +- src/Controller/Images.php | 18 ++++++------------ src/Controller/Upload.php | 12 ++++-------- src/Entity/RedactorField.php | 6 ++---- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/config/routes.yaml b/config/routes.yaml index 866674e..50a9859 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,4 +1,4 @@ redactor_extension_controllers: resource: '../../vendor/bolt/redactor/src/Controller/' prefix: '%bolt.backend_url%/async' - type: annotation \ No newline at end of file + type: attribute \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index d8c56b4..62e143c 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -24,7 +24,7 @@ doctrine: mappings: Redactor: is_bundle: false - type: annotation + type: attribute dir: '%kernel.project_dir%/vendor/bolt/redactor/src/Entity' prefix: 'Bolt\Redactor' alias: Redactor diff --git a/src/Controller/Images.php b/src/Controller/Images.php index 919636c..693c28d 100644 --- a/src/Controller/Images.php +++ b/src/Controller/Images.php @@ -10,20 +10,18 @@ use Bolt\Redactor\RedactorConfig; use Bolt\Twig\TextExtension; use Bolt\Utils\ThumbnailHelper; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Http\Attribute\IsGranted; use Tightenco\Collect\Support\Collection; -/** - * @Security("is_granted('list_files:files')") - */ +#[IsGranted('list_files:files')] class Images implements AsyncZoneInterface { use CsrfTrait; @@ -38,9 +36,7 @@ public function __construct( $this->csrfTokenManager = $csrfTokenManager; } - /** - * @Route("/redactor_images", name="bolt_redactor_images", methods={"GET"}) - */ + #[Route('/redactor_images', name: 'bolt_redactor_images', methods: [Request::METHOD_GET])] public function getImagesList(Request $request): JsonResponse { try { @@ -76,10 +72,8 @@ private function getImageFilesIndex(string $path): Collection return new Collection($files); } - /** - * @Route("/redactor_files", name="bolt_redactor_files", methods={"GET"}) - */ - public function getFilesList(): JsonResponse + #[Route('/redactor_files', name: 'bolt_redactor_files', methods: [Request::METHOD_GET])] + public function getFilesList(Request $request): JsonResponse { try { $this->validateCsrf('bolt_redactor'); diff --git a/src/Controller/Upload.php b/src/Controller/Upload.php index b3afc8b..53b96a6 100644 --- a/src/Controller/Upload.php +++ b/src/Controller/Upload.php @@ -10,7 +10,6 @@ use Bolt\Redactor\RedactorConfig; use Bolt\Twig\TextExtension; use Cocur\Slugify\Slugify; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Sirius\Upload\Handler; use Sirius\Upload\Result\File; use Symfony\Component\Filesystem\Path; @@ -18,14 +17,13 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Http\Attribute\IsGranted; use Throwable; -/** - * @Security("is_granted('upload')") - */ +#[IsGranted('upload')] class Upload implements AsyncZoneInterface { use CsrfTrait; @@ -40,9 +38,7 @@ public function __construct( $this->csrfTokenManager = $csrfTokenManager; } - /** - * @Route("/redactor_upload", name="bolt_redactor_upload", methods={"POST"}) - */ + #[Route('/redactor_upload', name: 'bolt_redactor_upload', methods: [Request::METHOD_POST])] public function handleUpload(Request $request): JsonResponse { try { diff --git a/src/Entity/RedactorField.php b/src/Entity/RedactorField.php index 95df8ed..a913744 100644 --- a/src/Entity/RedactorField.php +++ b/src/Entity/RedactorField.php @@ -10,9 +10,7 @@ use Doctrine\ORM\Mapping as ORM; use Twig\Markup; -/** - * @ORM\Entity - */ +#[ORM\Entity] class RedactorField extends Field implements Excerptable, FieldInterface { public const TYPE = 'redactor'; @@ -20,7 +18,7 @@ class RedactorField extends Field implements Excerptable, FieldInterface /** * Override getTwigValue to render field as html */ - public function getTwigValue() + public function getTwigValue(): Markup { $value = parent::getTwigValue(); From f63891e99cf6da227bb4e0b250a5d9fffff63b53 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:31:16 +0100 Subject: [PATCH 09/15] Changes due to Symfony 6 upgrade (rector) --- src/RedactorConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RedactorConfig.php b/src/RedactorConfig.php index a6f23d3..42114ba 100644 --- a/src/RedactorConfig.php +++ b/src/RedactorConfig.php @@ -8,8 +8,8 @@ use Bolt\Entity\Content; use Bolt\Extension\ExtensionRegistry; use Bolt\Storage\Query; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\ItemInterface; From 82b799d4810a25048d7951bec4d62da567506f75 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:45:54 +0100 Subject: [PATCH 10/15] Switch from request stack to injected request --- src/Controller/Images.php | 6 ++---- src/Controller/Upload.php | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Controller/Images.php b/src/Controller/Images.php index 693c28d..81467ff 100644 --- a/src/Controller/Images.php +++ b/src/Controller/Images.php @@ -13,7 +13,6 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; @@ -28,7 +27,6 @@ class Images implements AsyncZoneInterface public function __construct( private readonly Config $config, - private readonly RequestStack $requestStack, private readonly ThumbnailHelper $thumbnailHelper, private readonly RedactorConfig $redactorConfig, CsrfTokenManagerInterface $csrfTokenManager, @@ -48,7 +46,7 @@ public function getImagesList(Request $request): JsonResponse ], Response::HTTP_FORBIDDEN); } - $locationName = $this->requestStack->getCurrentRequest()->query->get('location', 'files'); + $locationName = $request->query->get('location', 'files'); $path = $this->config->getPath($locationName, true); $files = $this->getImageFilesIndex($path); @@ -84,7 +82,7 @@ public function getFilesList(Request $request): JsonResponse ], Response::HTTP_FORBIDDEN); } - $locationName = $this->requestStack->getCurrentRequest()->query->get('location', 'files'); + $locationName = $request->query->get('location', 'files'); $path = $this->config->getPath($locationName, true); $files = $this->getFilesIndex($path); diff --git a/src/Controller/Upload.php b/src/Controller/Upload.php index 53b96a6..4a92325 100644 --- a/src/Controller/Upload.php +++ b/src/Controller/Upload.php @@ -15,7 +15,6 @@ use Symfony\Component\Filesystem\Path; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; @@ -31,7 +30,6 @@ class Upload implements AsyncZoneInterface public function __construct( private readonly Config $config, private readonly TextExtension $textExtension, - private readonly RequestStack $requestStack, private readonly RedactorConfig $redactorConfig, CsrfTokenManagerInterface $csrfTokenManager, ) { From 6b71e961328972d6a755cc3a4a5ad64417c10666 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:58:09 +0100 Subject: [PATCH 11/15] Replaced `tightenco/collect` with `illuminate/collections` --- src/Controller/Images.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/Images.php b/src/Controller/Images.php index 81467ff..8368105 100644 --- a/src/Controller/Images.php +++ b/src/Controller/Images.php @@ -10,6 +10,7 @@ use Bolt\Redactor\RedactorConfig; use Bolt\Twig\TextExtension; use Bolt\Utils\ThumbnailHelper; +use Illuminate\Support\Collection; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -18,7 +19,6 @@ use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Attribute\IsGranted; -use Tightenco\Collect\Support\Collection; #[IsGranted('list_files:files')] class Images implements AsyncZoneInterface From f6b40ca85245b03d2ff3cc4970b0635cc4d6d474 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 21:58:56 +0100 Subject: [PATCH 12/15] Fix PHPStan issues --- src/Controller/Images.php | 23 +++++++++++++++++-- src/Controller/Upload.php | 4 ++-- src/Extension.php | 2 ++ src/RedactorConfig.php | 42 ++++++++++++++++++++++++++++++---- src/RedactorInjectorWidget.php | 6 ++++- 5 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/Controller/Images.php b/src/Controller/Images.php index 8368105..aa49c23 100644 --- a/src/Controller/Images.php +++ b/src/Controller/Images.php @@ -46,7 +46,7 @@ public function getImagesList(Request $request): JsonResponse ], Response::HTTP_FORBIDDEN); } - $locationName = $request->query->get('location', 'files'); + $locationName = $request->query->getString('location', 'files'); $path = $this->config->getPath($locationName, true); $files = $this->getImageFilesIndex($path); @@ -54,6 +54,12 @@ public function getImagesList(Request $request): JsonResponse return new JsonResponse($files); } + /** + * @return Collection + */ private function getImageFilesIndex(string $path): Collection { $glob = '*.{' . implode(',', self::getImageTypes()) . '}'; @@ -82,7 +88,7 @@ public function getFilesList(Request $request): JsonResponse ], Response::HTTP_FORBIDDEN); } - $locationName = $request->query->get('location', 'files'); + $locationName = $request->query->getString('location', 'files'); $path = $this->config->getPath($locationName, true); $files = $this->getFilesIndex($path); @@ -90,6 +96,13 @@ public function getFilesList(Request $request): JsonResponse return new JsonResponse($files); } + /** + * @return Collection + */ private function getFilesIndex(string $path): Collection { $glob = '*.{' . implode(',', self::getFileTypes()) . '}'; @@ -121,11 +134,17 @@ private function findFiles(string $path, ?string $glob = null): Finder return $finder; } + /** + * @return string[] + */ private static function getImageTypes(): array { return ['gif', 'png', 'jpg', 'jpeg', 'svg', 'avif', 'webp']; } + /** + * @return string[] + */ private static function getFileTypes(): array { return ['doc', 'docx', 'txt', 'pdf', 'xls', 'xlsx', 'zip', 'tgz', 'gz']; diff --git a/src/Controller/Upload.php b/src/Controller/Upload.php index 4a92325..a83a39d 100644 --- a/src/Controller/Upload.php +++ b/src/Controller/Upload.php @@ -48,8 +48,8 @@ public function handleUpload(Request $request): JsonResponse ], Response::HTTP_FORBIDDEN); } - $locationName = $request->query->get('location', ''); - $path = $request->query->get('path', ''); + $locationName = $request->query->getString('location'); + $path = $request->query->getString('path'); $target = $this->config->getPath($locationName, true, $path); diff --git a/src/Extension.php b/src/Extension.php index 75fca67..df30407 100644 --- a/src/Extension.php +++ b/src/Extension.php @@ -22,7 +22,9 @@ public function initialize(): void public function install(): void { + /** @var string $projectDir */ $projectDir = $this->getContainer()->getParameter('kernel.project_dir'); + /** @var string $public */ $public = $this->getContainer()->getParameter('bolt.public_folder'); $source = dirname(__DIR__) . '/assets/'; diff --git a/src/RedactorConfig.php b/src/RedactorConfig.php index 42114ba..0ffb542 100644 --- a/src/RedactorConfig.php +++ b/src/RedactorConfig.php @@ -8,6 +8,8 @@ use Bolt\Entity\Content; use Bolt\Extension\ExtensionRegistry; use Bolt\Storage\Query; +use Pagerfanta\PagerfantaInterface; +use RuntimeException; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -18,7 +20,10 @@ class RedactorConfig { private const CACHE_DURATION = 1800; // 30 minutes + /** @var array|string>> */ private ?array $config = null; + + /** @var array */ private ?array $plugins = null; public function __construct( @@ -32,26 +37,32 @@ public function __construct( ) { } + /** + * @phpstan-ignore missingType.iterableValue (complex type) + */ public function getConfig(): array { if ($this->config) { return $this->config; } - $extension = $this->registry->getExtension(Extension::class); + $extension = $this->getExtension(); $this->config = array_replace_recursive($this->getDefaults(), $extension->getConfig()['default'], $this->getLinks()); return $this->config; } + /** + * @phpstan-ignore missingType.iterableValue (complex type) + */ public function getPlugins(): array { if ($this->plugins) { return $this->plugins; } - $extension = $this->registry->getExtension(Extension::class); + $extension = $this->getExtension(); $this->plugins = $this->getDefaultPlugins(); @@ -62,6 +73,9 @@ public function getPlugins(): array return $this->plugins; } + /** + * @return array|string>> + */ public function getDefaults(): array { $defaults = [ @@ -113,6 +127,9 @@ public function getDefaults(): array return $defaults; } + /** + * @return array + */ public function getDefaultPlugins(): array { return [ @@ -142,6 +159,9 @@ public function getDefaultPlugins(): array ]; } + /** + * @phpstan-ignore missingType.iterableValue (complex type) + */ private function getLinks(): array { return $this->cache->get('redactor_insert_links', function (ItemInterface $item): array { @@ -151,6 +171,9 @@ private function getLinks(): array }); } + /** + * @phpstan-ignore missingType.iterableValue (complex type) + */ private function getLinksHelper(): array { $amount = 100; @@ -161,7 +184,11 @@ private function getLinksHelper(): array ]; $contentTypes = $this->boltConfig->get('contenttypes')->where('viewless', false)->keys()->implode(','); - $records = $this->query->getContentForTwig($contentTypes, $params)->setMaxPerPage($amount); + /** @var Content[]|PagerfantaInterface $records */ + $records = $this->query->getContentForTwig($contentTypes, $params) ?? []; + if ($records instanceof PagerfantaInterface) { + $records->setMaxPerPage($amount); + } $links = [ '___' => [ @@ -170,7 +197,6 @@ private function getLinksHelper(): array ], ]; - /** @var Content $record */ foreach ($records as $record) { $extras = $record->getExtras(); @@ -186,4 +212,12 @@ private function getLinksHelper(): array 'definedlinks' => array_values($links), ]; } + + private function getExtension(): Extension + { + /** @var Extension|null $extension */ + $extension = $this->registry->getExtension(Extension::class); + + return $extension ?? throw new RuntimeException('Redactor extension not registered'); + } } diff --git a/src/RedactorInjectorWidget.php b/src/RedactorInjectorWidget.php index 98d44a4..1ac0317 100644 --- a/src/RedactorInjectorWidget.php +++ b/src/RedactorInjectorWidget.php @@ -8,6 +8,7 @@ use Bolt\Widget\Injector\RequestZone; use Bolt\Widget\Injector\Target; use Bolt\Widget\TwigAwareInterface; +use Symfony\Component\HttpFoundation\Request; class RedactorInjectorWidget extends BaseWidget implements TwigAwareInterface { @@ -21,12 +22,15 @@ public function __construct() { } + /** + * @phpstan-ignore missingType.iterableValue (not used here) + */ public function run(array $params = []): ?string { $request = $this->getExtension()->getRequest(); // Only produce output when editing or creating a Record, with GET method. if (! in_array($request->get('_route'), ['bolt_content_edit', 'bolt_content_new', 'bolt_content_duplicate'], true) || - ($this->getExtension()->getRequest()->getMethod() !== 'GET')) { + ($this->getExtension()->getRequest()->getMethod() !== Request::METHOD_GET)) { return null; } From 6164196316776b6a76b514536d039400a73056cb Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 22:00:07 +0100 Subject: [PATCH 13/15] Add code analysis github workflow --- .github/workflows/code_analysis.yaml | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/code_analysis.yaml diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml new file mode 100644 index 0000000..1ef8cb9 --- /dev/null +++ b/.github/workflows/code_analysis.yaml @@ -0,0 +1,66 @@ +name: Code Analysis + +on: + pull_request: null + push: + branches: + - main + +jobs: + rector_analysis: + name: Rector analysis + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + extensions: json, mbstring, pdo, curl, pdo_sqlite + coverage: none + tools: symfony-cli + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - run: composer install --no-progress --ansi + + - run: vendor/bin/rector process -n --no-progress-bar --ansi + + code_analysis: + strategy: + fail-fast: false + matrix: + php-version: ['8.2', '8.3', '8.4'] + actions: + - + name: Coding Standard + # tip: add "--ansi" to commands in CI to make them full of colors + run: vendor/bin/ecs check src --ansi + + - + name: PHPStan + run: vendor/bin/phpstan analyse --ansi + + - + name: Check composer.json and composer.lock + run: composer validate --strict --ansi + + name: ${{ matrix.actions.name }} - PHP ${{ matrix.php-version }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + # see https://github.com/shivammathur/setup-php + - uses: shivammathur/setup-php@v2 + with: + # test the lowest version, to make sure checks pass on it + php-version: ${{ matrix.php-version }} + extensions: json, mbstring, pdo, curl, pdo_sqlite + coverage: none + tools: symfony-cli + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - run: composer install --no-progress --ansi + + - run: ${{ matrix.actions.run }} From f6065b0fa083e2518b0e9e37f4cef4130af1297f Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Wed, 12 Nov 2025 22:37:00 +0100 Subject: [PATCH 14/15] Add branch alias for main branch --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index a8e40ab..31fd17b 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,9 @@ "minimum-stability": "dev", "prefer-stable": true, "extra": { + "branch-alias": { + "dev-main": "3.0.x-dev" + }, "entrypoint": "Bolt\\Redactor\\Extension", "screenshots": [ "screenshots/redactor.png" From 914e6db7eb49e8880cae2a96817ada5acd6d3b70 Mon Sep 17 00:00:00 2001 From: Bob van de Vijver Date: Thu, 13 Nov 2025 13:38:48 +0100 Subject: [PATCH 15/15] Fixate composer version --- .github/workflows/code_analysis.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_analysis.yaml b/.github/workflows/code_analysis.yaml index 1ef8cb9..4d72940 100644 --- a/.github/workflows/code_analysis.yaml +++ b/.github/workflows/code_analysis.yaml @@ -18,7 +18,7 @@ jobs: php-version: 8.4 extensions: json, mbstring, pdo, curl, pdo_sqlite coverage: none - tools: symfony-cli + tools: symfony-cli, composer:v2.8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -57,7 +57,7 @@ jobs: php-version: ${{ matrix.php-version }} extensions: json, mbstring, pdo, curl, pdo_sqlite coverage: none - tools: symfony-cli + tools: symfony-cli, composer:v2.8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}