From 680832844301da75e006032bd4654c94ea059d80 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 06:30:04 +0200 Subject: [PATCH 01/18] [TASK] cleanup service configurate * use PHP-Attribute for command registration * exclude Domain/Model as Service --- Classes/Command/CollectGarbageCommand.php | 2 ++ Classes/Command/JobCreatorCommand.php | 2 ++ Classes/Command/JobKillerCommand.php | 2 ++ Classes/Command/RunnerCommand.php | 2 ++ Classes/Command/StatusReportCommand.php | 2 ++ Configuration/Services.yaml | 27 +---------------------- 6 files changed, 11 insertions(+), 26 deletions(-) diff --git a/Classes/Command/CollectGarbageCommand.php b/Classes/Command/CollectGarbageCommand.php index 0449802..cf4d134 100644 --- a/Classes/Command/CollectGarbageCommand.php +++ b/Classes/Command/CollectGarbageCommand.php @@ -13,10 +13,12 @@ */ use B13\ContentSync\Domain\Repository\JobRepository; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +#[AsCommand(name: 'content-sync:collect-garbage')] final class CollectGarbageCommand extends Command { public function __construct( diff --git a/Classes/Command/JobCreatorCommand.php b/Classes/Command/JobCreatorCommand.php index 44e3355..abf5ee7 100644 --- a/Classes/Command/JobCreatorCommand.php +++ b/Classes/Command/JobCreatorCommand.php @@ -16,11 +16,13 @@ use B13\ContentSync\Domain\Model\Job; use B13\ContentSync\Domain\Repository\JobRepository; use B13\ContentSync\Domain\Validation\ConfigurationValidator; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; +#[AsCommand(name: 'content-sync:job:create')] final class JobCreatorCommand extends Command { public function __construct( diff --git a/Classes/Command/JobKillerCommand.php b/Classes/Command/JobKillerCommand.php index 7669b5b..5a1b598 100644 --- a/Classes/Command/JobKillerCommand.php +++ b/Classes/Command/JobKillerCommand.php @@ -13,10 +13,12 @@ */ use B13\ContentSync\Domain\Repository\JobRepository; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +#[AsCommand(name: 'content-sync:job:kill')] final class JobKillerCommand extends Command { public function __construct( diff --git a/Classes/Command/RunnerCommand.php b/Classes/Command/RunnerCommand.php index ada1674..21aaf59 100644 --- a/Classes/Command/RunnerCommand.php +++ b/Classes/Command/RunnerCommand.php @@ -16,10 +16,12 @@ use B13\ContentSync\Domain\Service\ProcessRunner; use B13\ContentSync\Domain\Validation\ConfigurationValidator; use B13\ContentSync\Exception; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +#[AsCommand(name: 'content-sync:runner')] class RunnerCommand extends Command { public function __construct( diff --git a/Classes/Command/StatusReportCommand.php b/Classes/Command/StatusReportCommand.php index 0fe1561..62b5fd3 100644 --- a/Classes/Command/StatusReportCommand.php +++ b/Classes/Command/StatusReportCommand.php @@ -14,11 +14,13 @@ use B13\ContentSync\Domain\Factory\StatusReportFactory; use B13\ContentSync\Domain\Model\Job; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; +#[AsCommand(name: 'content-sync:status-report')] class StatusReportCommand extends Command { public function __construct( diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index dd59c9b..8575ec6 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -6,29 +6,4 @@ services: B13\ContentSync\: resource: '../Classes/*' - - B13\ContentSync\Command\StatusReportCommand: - tags: - - name: 'console.command' - command: 'content-sync:status-report' - schedulable: true - B13\ContentSync\Command\RunnerCommand: - tags: - - name: 'console.command' - command: 'content-sync:runner' - schedulable: true - B13\ContentSync\Command\JobCreatorCommand: - tags: - - name: 'console.command' - command: 'content-sync:job:create' - schedulable: true - B13\ContentSync\Command\JobKillerCommand: - tags: - - name: 'console.command' - command: 'content-sync:job:kill' - schedulable: true - B13\ContentSync\Command\CollectGarbageCommand: - tags: - - name: 'console.command' - command: 'content-sync:collect-garbage' - schedulable: true + exclude: '../Classes/Domain/Model/*' From c6353086fd9f1acd1ef497f6a16cd10daaf6f01f Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 06:45:31 +0200 Subject: [PATCH 02/18] [BUGFIX] Controller Actions returns ResponseInterface --- Classes/Backend/Controller/Ajax/JobController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Classes/Backend/Controller/Ajax/JobController.php b/Classes/Backend/Controller/Ajax/JobController.php index cad9897..ca38a5f 100644 --- a/Classes/Backend/Controller/Ajax/JobController.php +++ b/Classes/Backend/Controller/Ajax/JobController.php @@ -17,6 +17,7 @@ use B13\ContentSync\Domain\Repository\JobRepository; use B13\ContentSync\Domain\Validation\ConfigurationValidator; use B13\ContentSync\Exception; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Attribute\AsController; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; @@ -38,7 +39,7 @@ public function __construct( private JobRepository $jobRepository, ) {} - public function create(ServerRequestInterface $request): Response + public function create(ServerRequestInterface $request): ResponseInterface { if (!$this->checkAccess()) { return (new Response())->withStatus(403); @@ -77,7 +78,7 @@ public function create(ServerRequestInterface $request): Response return new JsonResponse($return); } - public function kill(ServerRequestInterface $request): Response + public function kill(ServerRequestInterface $request): ResponseInterface { if (!$this->checkAccess()) { return (new Response())->withStatus(403); From f268faeedd3c255acdb26dae30eedebb4e840233 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 06:48:34 +0200 Subject: [PATCH 03/18] [BUGFIX] partialRootPaths array has one dimension --- Classes/Backend/Controller/Ajax/JobController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/Backend/Controller/Ajax/JobController.php b/Classes/Backend/Controller/Ajax/JobController.php index ca38a5f..c76136e 100644 --- a/Classes/Backend/Controller/Ajax/JobController.php +++ b/Classes/Backend/Controller/Ajax/JobController.php @@ -52,7 +52,7 @@ public function create(ServerRequestInterface $request): ResponseInterface $this->jobRepository->add($job); $viewFactoryData = new ViewFactoryData( templateRootPaths: ['EXT:content_sync/Resources/Private/Templates/'], - partialRootPaths: [['EXT:content_sync/Resources/Private/Partials/']], + partialRootPaths: ['EXT:content_sync/Resources/Private/Partials/'], request: $request, ); $view = $this->viewFactory->create($viewFactoryData); @@ -86,7 +86,7 @@ public function kill(ServerRequestInterface $request): ResponseInterface $job = $this->jobRepository->findOneLast(); $viewFactoryData = new ViewFactoryData( templateRootPaths: ['EXT:content_sync/Resources/Private/Templates/'], - partialRootPaths: [['EXT:content_sync/Resources/Private/Partials/']], + partialRootPaths: ['EXT:content_sync/Resources/Private/Partials/'], request: $request, ); $view = $this->viewFactory->create($viewFactoryData); From d7e6e7b0141f9ba9ec6013d15139ca6dbdf5e181 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 06:51:34 +0200 Subject: [PATCH 04/18] [TASK] use logging for commands --- Classes/Domain/Service/ProcessRunner.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Classes/Domain/Service/ProcessRunner.php b/Classes/Domain/Service/ProcessRunner.php index ddf927c..54fb5e2 100644 --- a/Classes/Domain/Service/ProcessRunner.php +++ b/Classes/Domain/Service/ProcessRunner.php @@ -14,12 +14,14 @@ use B13\ContentSync\Domain\Model\Configuration; use B13\ContentSync\Exception; +use Psr\Log\LoggerInterface; use Symfony\Component\Process\Process; final readonly class ProcessRunner { public function __construct( - private DatabaseParameterBuilder $databaseParameterBuilder + private DatabaseParameterBuilder $databaseParameterBuilder, + private LoggerInterface $logger ) {} public function localToRemote(Configuration $configuration): void @@ -70,6 +72,7 @@ public function remoteToLocal(Configuration $configuration): void private function exec(string $cmd): void { + $this->logger->debug($cmd); $process = Process::fromShellCommandline($cmd); $process->run(); if (!$process->isSuccessful()) { From 2a179531c07a41978c2e02649c84c232bed3d86b Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 07:47:49 +0200 Subject: [PATCH 05/18] [FEATURE] provide BeforeProcessRunnerExecutesCommandsEvent --- Classes/Domain/Service/ProcessRunner.php | 19 ++++++++++---- ...foreProcessRunnerExecutesCommandsEvent.php | 25 +++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php diff --git a/Classes/Domain/Service/ProcessRunner.php b/Classes/Domain/Service/ProcessRunner.php index 54fb5e2..9609229 100644 --- a/Classes/Domain/Service/ProcessRunner.php +++ b/Classes/Domain/Service/ProcessRunner.php @@ -13,7 +13,9 @@ */ use B13\ContentSync\Domain\Model\Configuration; +use B13\ContentSync\Event\BeforeProcessRunnerExecutesCommandsEvent; use B13\ContentSync\Exception; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Process\Process; @@ -21,7 +23,8 @@ { public function __construct( private DatabaseParameterBuilder $databaseParameterBuilder, - private LoggerInterface $logger + private LoggerInterface $logger, + private EventDispatcherInterface $eventDispatcher ) {} public function localToRemote(Configuration $configuration): void @@ -42,8 +45,11 @@ public function localToRemote(Configuration $configuration): void $file = rtrim($file, '/'); $commands[] = 'rsync -a --delete --omit-dir-times --no-owner --no-group ' . $localNode->getBasePath() . $file . '/ ' . $remoteNode->getConnection() . ':' . $remoteNode->getBasePath() . $file; } + $beforeProcessRunnnerExecuteCommands = new BeforeProcessRunnerExecutesCommandsEvent($configuration, $commands); + $this->eventDispatcher->dispatch($beforeProcessRunnnerExecuteCommands); + $commands = $beforeProcessRunnnerExecuteCommands->commands; foreach ($commands as $command) { - $this->exec($command); + $this->exec($command, $beforeProcessRunnnerExecuteCommands->timeoutPerProcess); } } @@ -65,15 +71,18 @@ public function remoteToLocal(Configuration $configuration): void $file = rtrim($file, '/'); $commands[] = 'rsync -a --delete --omit-dir-times --no-owner --no-group ' . $remoteNode->getConnection() . ':' . $remoteNode->getBasePath() . $file . '/ ' . $localNode->getBasePath() . $file; } + $beforeProcessRunnnerExecuteCommands = new BeforeProcessRunnerExecutesCommandsEvent($configuration, $commands); + $this->eventDispatcher->dispatch($beforeProcessRunnnerExecuteCommands); + $commands = $beforeProcessRunnnerExecuteCommands->commands; foreach ($commands as $command) { - $this->exec($command); + $this->exec($command, $beforeProcessRunnnerExecuteCommands->timeoutPerProcess); } } - private function exec(string $cmd): void + private function exec(string $cmd, int $timeout): void { $this->logger->debug($cmd); - $process = Process::fromShellCommandline($cmd); + $process = Process::fromShellCommandline(command: $cmd, timeout: $timeout); $process->run(); if (!$process->isSuccessful()) { throw new Exception('cannot exec command ' . $cmd . ' with error ' . $process->getErrorOutput(), 1600757440); diff --git a/Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php b/Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php new file mode 100644 index 0000000..60fc841 --- /dev/null +++ b/Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php @@ -0,0 +1,25 @@ + Date: Wed, 3 Jun 2026 07:48:22 +0200 Subject: [PATCH 06/18] [BUGFIX] start and endTime of Job --- Classes/Domain/Model/Job.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Classes/Domain/Model/Job.php b/Classes/Domain/Model/Job.php index 9d74e40..31f5b03 100644 --- a/Classes/Domain/Model/Job.php +++ b/Classes/Domain/Model/Job.php @@ -27,8 +27,8 @@ class Job protected int $status = self::STATUS_WAITING; protected Configuration $configuration; - protected \DateTime $startTime; - protected \DateTime $endTime; + protected ?\DateTime $startTime = null; + protected ?\DateTime $endTime = null; protected \DateTime $createdTime; protected string $error = ''; protected int $uid; @@ -36,8 +36,6 @@ class Job public function __construct() { $this->createdTime = new \DateTime(); - $this->startTime = new \DateTime(); - $this->endTime = new \DateTime(); } public function fail(string $error): void @@ -91,8 +89,8 @@ public function toDatabaseRow(): array 'status' => $this->status, 'json_configuration' => $json, 'created_time' => $this->createdTime->format('U'), - 'start_time' => $this->startTime->format('U'), - 'end_time' => $this->endTime->format('U'), + 'start_time' => $this->startTime ? $this->startTime->format('U') : 0, + 'end_time' => $this->endTime ? $this->endTime->format('U'): 0, 'error' => $this->error, ]; } From 70247b6d54bc1dd8064033416a9bd18dda1a8d94 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 07:55:32 +0200 Subject: [PATCH 07/18] [TASK] do not allow createing a job if one is running or waiting --- Classes/Command/JobCreatorCommand.php | 8 +++++++- Classes/Domain/Repository/JobRepository.php | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Classes/Command/JobCreatorCommand.php b/Classes/Command/JobCreatorCommand.php index abf5ee7..e13a945 100644 --- a/Classes/Command/JobCreatorCommand.php +++ b/Classes/Command/JobCreatorCommand.php @@ -16,6 +16,7 @@ use B13\ContentSync\Domain\Model\Job; use B13\ContentSync\Domain\Repository\JobRepository; use B13\ContentSync\Domain\Validation\ConfigurationValidator; +use B13\ContentSync\Exception; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -40,7 +41,12 @@ public function execute(InputInterface $input, OutputInterface $output): int $this->validator->assertValid($configuration); $job = new Job(); $job->setConfiguration($configuration); - $this->jobRepository->add($job); + try { + $this->jobRepository->add($job); + } catch (Exception $e) { + $output->writeln($e->getMessage()); + return Command::FAILURE; + } return 0; } } diff --git a/Classes/Domain/Repository/JobRepository.php b/Classes/Domain/Repository/JobRepository.php index 5ee4943..386a463 100644 --- a/Classes/Domain/Repository/JobRepository.php +++ b/Classes/Domain/Repository/JobRepository.php @@ -13,6 +13,7 @@ */ use B13\ContentSync\Domain\Model\Job; +use B13\ContentSync\Exception; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; @@ -26,6 +27,14 @@ public function __construct( public function add(Job $job): void { + $waitingJob = $this->findOneWaiting(); + if ($waitingJob !== null) { + throw new Exception('there is already a waiting job', 1780465898); + } + $runningJob = $this->findOneRunning(); + if ($runningJob !== null) { + throw new Exception('there is already a running job', 1780465899); + } $this->connectionPool->getConnectionForTable(self::TABLE)->insert(self::TABLE, $job->toDatabaseRow()); } From 4c3ca6447da80e634f2638737e1705ae8bbd2898 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 07:58:09 +0200 Subject: [PATCH 08/18] [TASK] use constants for commands return --- Classes/Command/CollectGarbageCommand.php | 2 +- Classes/Command/JobCreatorCommand.php | 2 +- Classes/Command/JobKillerCommand.php | 4 ++-- Classes/Command/RunnerCommand.php | 8 ++++---- Classes/Command/StatusReportCommand.php | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Classes/Command/CollectGarbageCommand.php b/Classes/Command/CollectGarbageCommand.php index cf4d134..fadcdd2 100644 --- a/Classes/Command/CollectGarbageCommand.php +++ b/Classes/Command/CollectGarbageCommand.php @@ -35,6 +35,6 @@ public function execute(InputInterface $input, OutputInterface $output): int $job->fail('job too old'); $this->jobRepository->updateJob($job); } - return 0; + return Command::SUCCESS; } } diff --git a/Classes/Command/JobCreatorCommand.php b/Classes/Command/JobCreatorCommand.php index e13a945..8a7a696 100644 --- a/Classes/Command/JobCreatorCommand.php +++ b/Classes/Command/JobCreatorCommand.php @@ -47,6 +47,6 @@ public function execute(InputInterface $input, OutputInterface $output): int $output->writeln($e->getMessage()); return Command::FAILURE; } - return 0; + return Command::SUCCESS; } } diff --git a/Classes/Command/JobKillerCommand.php b/Classes/Command/JobKillerCommand.php index 5a1b598..3a2b5e1 100644 --- a/Classes/Command/JobKillerCommand.php +++ b/Classes/Command/JobKillerCommand.php @@ -32,10 +32,10 @@ public function execute(InputInterface $input, OutputInterface $output): int { $job = $this->jobRepository->findOneLast(); if ($job === null) { - return 0; + return Command::SUCCESS; } $job->kill(); $this->jobRepository->updateJob($job); - return 0; + return Command::SUCCESS; } } diff --git a/Classes/Command/RunnerCommand.php b/Classes/Command/RunnerCommand.php index 21aaf59..f52d1b3 100644 --- a/Classes/Command/RunnerCommand.php +++ b/Classes/Command/RunnerCommand.php @@ -37,11 +37,11 @@ public function execute(InputInterface $input, OutputInterface $output): int { $runningJob = $this->jobRepository->findOneRunning(); if ($runningJob !== null) { - return 0; + return Command::SUCCESS; } $job = $this->jobRepository->findOneWaiting(); if ($job === null) { - return 0; + return Command::SUCCESS; } $job->start(); $this->jobRepository->updateJob($job); @@ -57,11 +57,11 @@ public function execute(InputInterface $input, OutputInterface $output): int // @extensionScannerIgnoreLine $job->finish(); $this->jobRepository->updateJob($job); - return 0; + return Command::SUCCESS; } catch (Exception $e) { $job->fail($e->getCode() . ' - ' . $e->getMessage()); $this->jobRepository->updateJob($job); - return 1; + return Command::FAILURE; } } } diff --git a/Classes/Command/StatusReportCommand.php b/Classes/Command/StatusReportCommand.php index 62b5fd3..4a212a0 100644 --- a/Classes/Command/StatusReportCommand.php +++ b/Classes/Command/StatusReportCommand.php @@ -89,6 +89,6 @@ public function execute(InputInterface $input, OutputInterface $output): int } else { $output->writeln(LocalizationUtility::translate($llPrefix . 'no-jobs')); } - return 0; + return Command::SUCCESS; } } From 6d187b43961bf7f8f8b064da3c5483e763e3b700 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 08:01:41 +0200 Subject: [PATCH 09/18] [TASK] github CI --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d080c7d..3bad4db 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -10,14 +10,14 @@ jobs: tests: name: All tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: matrix: php: [ '8.2'] TYPO3: [ '13', '14' ] steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v6 - name: Install testing system run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s composerUpdate From 55d13acc8ce96120c04c65e3353ab11b400cfe3a Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 08:16:07 +0200 Subject: [PATCH 10/18] [TASK] CI for PHP 8.5 * change docker image * remove unused code in runTests.sh --- .github/workflows/test.yaml | 2 +- Build/Scripts/runTests.sh | 55 +----- Build/testing-docker/docker-compose.yml | 237 +----------------------- 3 files changed, 7 insertions(+), 287 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 3bad4db..a4fc4e8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - php: [ '8.2'] + php: [ '8.2', '8.5'] TYPO3: [ '13', '14' ] steps: - name: Checkout repository diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index 4c8ab13..632ead8 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -49,43 +49,20 @@ No arguments: Run all unit tests with PHP 8.0 Options: -s <...> Specifies which test suite to run - - acceptance: backend acceptance tests - cgl: cgl test and fix all php files - composerUpdate: "composer update", handy if host has no PHP - composerValidate: "composer validate" - - functional: functional tests - lint: PHP linting - phpstan: phpstan analyze - - unit (default): PHP unit tests -t <13|14> Only with -s composerUpdate|acceptance|functional TYPO3 core major version the extension is embedded in for testing. - -d - Only with -s functional - Specifies on which DBMS tests are performed - - mariadb (default): use mariadb - - postgres: use postgres - - sqlite: use sqlite - -p <8.2|8.3|8.4|8.5> Specifies the PHP minor version to be used - 8.2 (default): use PHP 8.2 - -e "" - Only with -s acceptance|functional|unit - Additional options to send to phpunit (unit & functional tests) or codeception (acceptance - tests). For phpunit, options starting with "--" must be added after options starting with "-". - Example -e "-v --filter canRetrieveValueWithGP" to enable verbose output AND filter tests - named "canRetrieveValueWithGP" - - -x - Only with -s functional|unit|acceptance - Send information to host instance for test or system under test break points. This is especially - useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port - can be selected with -y - -y Send xdebug information to a different port than default 9003 if an IDE like PhpStorm is not listening on default port. @@ -207,7 +184,7 @@ if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then fi # Move "7.4" to "php74", the latter is the docker container name -DOCKER_PHP_IMAGE=$(echo "php${PHP_VERSION}" | sed -e 's/\.//') +DOCKER_PHP_IMAGE="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest" # Set $1 to first mass argument, this is the optional test file or test directory to execute shift $((OPTIND - 1)) @@ -222,12 +199,6 @@ fi # Suite execution case ${TEST_SUITE} in - acceptance) - setUpDockerComposeDotEnv - docker compose run acceptance_backend_mariadb10 - SUITE_EXIT_CODE=$? - docker compose down - ;; cgl) # Active dry-run for cgl needs not "-n" but specific options if [ -n "${CGLCHECK_DRY_RUN}" ]; then @@ -250,30 +221,6 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? docker compose down ;; - functional) - setUpDockerComposeDotEnv - case ${DBMS} in - mariadb) - docker compose run functional_mariadb10 - SUITE_EXIT_CODE=$? - ;; - postgres) - docker compose run functional_postgres10 - SUITE_EXIT_CODE=$? - ;; - sqlite) - mkdir -p ${CORE_ROOT}/.Build/Web/typo3temp/var/tests/functional-sqlite-dbs/ - docker compose run functional_sqlite - SUITE_EXIT_CODE=$? - ;; - *) - echo "Invalid -d option argument ${DBMS}" >&2 - echo >&2 - echo "${HELP}" >&2 - exit 1 - esac - docker compose down - ;; lint) setUpDockerComposeDotEnv docker compose run lint diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml index 254aa2d..b3b0c26 100644 --- a/Build/testing-docker/docker-compose.yml +++ b/Build/testing-docker/docker-compose.yml @@ -1,97 +1,7 @@ services: - chrome: - # Image for Mac M1 - # image: seleniarm/standalone-chromium:4.1.2-20220227 - image: selenium/standalone-chrome:4.0.0-20211102 - tmpfs: - - /dev/shm:rw,nosuid,nodev,noexec,relatime - - mariadb10: - # not using mariadb:10 for the time being, because 10.5.7 (currently latest) is broken - image: mariadb:10.5.6 - environment: - MYSQL_ROOT_PASSWORD: funcp - tmpfs: - - /var/lib/mysql/:rw,noexec,nosuid - - postgres10: - image: postgres:10-alpine - environment: - POSTGRES_PASSWORD: funcp - POSTGRES_USER: ${HOST_USER} - tmpfs: - - /var/lib/postgresql/data:rw,noexec,nosuid - - web: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - stop_grace_period: 1s - volumes: - - ${CORE_ROOT}:${CORE_ROOT} - - /etc/passwd:/etc/passwd:ro - - /etc/group:/etc/group:ro - environment: - TYPO3_PATH_ROOT: ${CORE_ROOT}/.Build/Web/typo3temp/var/tests/acceptance - TYPO3_PATH_APP: ${CORE_ROOT}/.Build/Web/typo3temp/var/tests/acceptance - command: > - /bin/sh -c " - if [ ${PHP_XDEBUG_ON} -eq 0 ]; then - XDEBUG_MODE=\"off\" \ - php -S web:8000 -t ${CORE_ROOT}/.Build/Web - else - DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` - XDEBUG_MODE=\"debug,develop\" \ - XDEBUG_TRIGGER=\"foo\" \ - XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" \ - php -S web:8000 -t ${CORE_ROOT}/.Build/Web - fi - " - - acceptance_backend_mariadb10: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - links: - - mariadb10 - - chrome - - web - environment: - typo3DatabaseName: func_test - typo3DatabaseUsername: root - typo3DatabasePassword: funcp - typo3DatabaseHost: mariadb10 - volumes: - - ${CORE_ROOT}:${CORE_ROOT} - - ${HOST_HOME}:${HOST_HOME} - - /etc/passwd:/etc/passwd:ro - - /etc/group:/etc/group:ro - working_dir: ${CORE_ROOT}/.Build - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - echo Waiting for database start...; - while ! nc -z mariadb10 3306; do - sleep 1; - done; - echo Database is up; - php -v | grep '^PHP'; - mkdir -p Web/typo3temp/var/tests/ - COMMAND=\"vendor/codeception/codeception/codecept run Backend -d -c Web/typo3conf/ext/form_custom_templates/Tests/codeception.yml ${TEST_FILE}\" - if [ ${PHP_XDEBUG_ON} -eq 0 ]; then - XDEBUG_MODE=\"off\" \ - $${COMMAND}; - else - DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` - XDEBUG_MODE=\"debug,develop\" \ - XDEBUG_TRIGGER=\"foo\" \ - XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" \ - $${COMMAND}; - fi - " cgl: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + image: ${DOCKER_PHP_IMAGE} user: "${HOST_UID}" volumes: - ${CORE_ROOT}:${CORE_ROOT} @@ -109,7 +19,7 @@ services: " composer_update: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + image: ${DOCKER_PHP_IMAGE} user: "${HOST_UID}" volumes: - ${CORE_ROOT}:${CORE_ROOT} @@ -127,7 +37,7 @@ services: " composer_validate: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + image: ${DOCKER_PHP_IMAGE} user: "${HOST_UID}" volumes: - ${CORE_ROOT}:${CORE_ROOT} @@ -144,118 +54,8 @@ services: composer validate; " - functional_mariadb10: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - links: - - mariadb10 - volumes: - - ${CORE_ROOT}:${CORE_ROOT} - - ${HOST_HOME}:${HOST_HOME} - - /etc/passwd:/etc/passwd:ro - - /etc/group:/etc/group:ro - environment: - typo3DatabaseName: func_test - typo3DatabaseUsername: root - typo3DatabasePassword: funcp - typo3DatabaseHost: mariadb10 - working_dir: ${CORE_ROOT}/.Build - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - echo Waiting for database start...; - while ! nc -z mariadb10 3306; do - sleep 1; - done; - echo Database is up; - php -v | grep '^PHP'; - if [ ${PHP_XDEBUG_ON} -eq 0 ]; then - XDEBUG_MODE=\"off\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; - else - DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` - XDEBUG_MODE=\"debug,develop\" \ - XDEBUG_TRIGGER=\"foo\" \ - XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; - fi - " - - functional_postgres10: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - links: - - postgres10 - volumes: - - ${CORE_ROOT}:${CORE_ROOT} - - ${HOST_HOME}:${HOST_HOME} - - /etc/passwd:/etc/passwd:ro - - /etc/group:/etc/group:ro - environment: - typo3DatabaseDriver: pdo_pgsql - typo3DatabaseName: bamboo - typo3DatabaseUsername: ${HOST_USER} - typo3DatabaseHost: postgres10 - typo3DatabasePassword: funcp - working_dir: ${CORE_ROOT}/.Build - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - echo Waiting for database start...; - while ! nc -z postgres10 5432; do - sleep 1; - done; - echo Database is up; - php -v | grep '^PHP'; - if [ ${PHP_XDEBUG_ON} -eq 0 ]; then - XDEBUG_MODE=\"off\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-postgres ${TEST_FILE}; - else - DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` - XDEBUG_MODE=\"debug,develop\" \ - XDEBUG_TRIGGER=\"foo\" \ - XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-postgres ${TEST_FILE}; - fi - " - - functional_sqlite: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${CORE_ROOT}:${CORE_ROOT} - - ${HOST_HOME}:${HOST_HOME} - - /etc/passwd:/etc/passwd:ro - - /etc/group:/etc/group:ro - tmpfs: - - ${CORE_ROOT}/.Build/Web/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid,uid=${HOST_UID} - environment: - typo3DatabaseDriver: pdo_sqlite - working_dir: ${CORE_ROOT}/.Build - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - if [ ${PHP_XDEBUG_ON} -eq 0 ]; then - XDEBUG_MODE=\"off\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-sqlite ${TEST_FILE}; - else - DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` - XDEBUG_MODE=\"debug,develop\" \ - XDEBUG_TRIGGER=\"foo\" \ - XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/FunctionalTests.xml ${EXTRA_TEST_OPTIONS} --exclude-group not-sqlite ${TEST_FILE}; - fi - " - lint: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + image: ${DOCKER_PHP_IMAGE} user: "${HOST_UID}" volumes: - ${CORE_ROOT}:${CORE_ROOT} @@ -272,7 +72,7 @@ services: " phpstan: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest + image: ${DOCKER_PHP_IMAGE} user: "${HOST_UID}" volumes: - ${CORE_ROOT}:${CORE_ROOT} @@ -288,30 +88,3 @@ services: php -v | grep '^PHP'; php -dxdebug.mode=off .Build/bin/phpstan analyze -c Build/phpstan.neon --no-progress --no-interaction " - - unit: - image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest - user: "${HOST_UID}" - volumes: - - ${CORE_ROOT}:${CORE_ROOT} - - ${HOST_HOME}:${HOST_HOME} - - /etc/passwd:/etc/passwd:ro - - /etc/group:/etc/group:ro - working_dir: ${CORE_ROOT}/.Build - command: > - /bin/sh -c " - if [ ${SCRIPT_VERBOSE} -eq 1 ]; then - set -x - fi - php -v | grep '^PHP'; - if [ ${PHP_XDEBUG_ON} -eq 0 ]; then - XDEBUG_MODE=\"off\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; - else - DOCKER_HOST=`route -n | awk '/^0.0.0.0/ { print $$2 }'` - XDEBUG_MODE=\"debug,develop\" \ - XDEBUG_TRIGGER=\"foo\" \ - XDEBUG_CONFIG=\"client_port=${PHP_XDEBUG_PORT} client_host=$${DOCKER_HOST}\" \ - bin/phpunit -c Web/typo3conf/ext/form_custom_templates/Build/UnitTests.xml ${EXTRA_TEST_OPTIONS} ${TEST_FILE}; - fi - " From 32b11ba03af1ba684f37a28aa099df7a490b85bd Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 08:25:53 +0200 Subject: [PATCH 11/18] [BUGFIX] nullable type declaration addition: CI should fail on cgl fail --- .gitignore | 1 + Build/php-cs-fixer.php | 9 +++++++-- Build/testing-docker/docker-compose.yml | 2 +- Classes/Command/CollectGarbageCommand.php | 2 +- Classes/Command/JobCreatorCommand.php | 2 +- Classes/Command/JobKillerCommand.php | 2 +- Classes/Command/RunnerCommand.php | 2 +- Classes/Command/StatusReportCommand.php | 2 +- Classes/Domain/Model/Job.php | 2 +- .../Event/BeforeProcessRunnerExecutesCommandsEvent.php | 2 +- 10 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index c142ac8..a6b8c54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ public composer.lock vendor +var .php_cs.cache .php-cs-fixer.cache /.Build diff --git a/Build/php-cs-fixer.php b/Build/php-cs-fixer.php index eef762f..d9a9315 100644 --- a/Build/php-cs-fixer.php +++ b/Build/php-cs-fixer.php @@ -1,6 +1,11 @@ getFinder()->in(['Classes', 'Configuration']); -$config->getFinder()->exclude(['var', 'public']); +$config->getFinder()->exclude(['var', 'public', 'Build'])->in(__DIR__ . '/..'); +$config->addRules([ + 'nullable_type_declaration' => [ + 'syntax' => 'question_mark', + ], + 'nullable_type_declaration_for_default_null_value' => true, +]); return $config; diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml index b3b0c26..3ece711 100644 --- a/Build/testing-docker/docker-compose.yml +++ b/Build/testing-docker/docker-compose.yml @@ -15,7 +15,7 @@ services: set -x fi php -v | grep '^PHP'; - ${CORE_ROOT}/.Build/bin/php-cs-fixer fix --config Build/php-cs-fixer.php + ${CORE_ROOT}/.Build/bin/php-cs-fixer fix --config Build/php-cs-fixer.php --dry-run --stop-on-violation --using-cache=no " composer_update: diff --git a/Classes/Command/CollectGarbageCommand.php b/Classes/Command/CollectGarbageCommand.php index fadcdd2..3821236 100644 --- a/Classes/Command/CollectGarbageCommand.php +++ b/Classes/Command/CollectGarbageCommand.php @@ -23,7 +23,7 @@ final class CollectGarbageCommand extends Command { public function __construct( private readonly JobRepository $jobRepository, - string $name = null + ?string $name = null ) { parent::__construct($name); } diff --git a/Classes/Command/JobCreatorCommand.php b/Classes/Command/JobCreatorCommand.php index 8a7a696..58c824f 100644 --- a/Classes/Command/JobCreatorCommand.php +++ b/Classes/Command/JobCreatorCommand.php @@ -30,7 +30,7 @@ public function __construct( private readonly ExtensionConfiguration $extensionConfiguration, private readonly ConfigurationValidator $validator, private readonly JobRepository $jobRepository, - string $name = null + ?string $name = null ) { parent::__construct($name); } diff --git a/Classes/Command/JobKillerCommand.php b/Classes/Command/JobKillerCommand.php index 3a2b5e1..e60e403 100644 --- a/Classes/Command/JobKillerCommand.php +++ b/Classes/Command/JobKillerCommand.php @@ -23,7 +23,7 @@ final class JobKillerCommand extends Command { public function __construct( private readonly JobRepository $jobRepository, - string $name = null + ?string $name = null ) { parent::__construct($name); } diff --git a/Classes/Command/RunnerCommand.php b/Classes/Command/RunnerCommand.php index f52d1b3..2d2294d 100644 --- a/Classes/Command/RunnerCommand.php +++ b/Classes/Command/RunnerCommand.php @@ -28,7 +28,7 @@ public function __construct( private readonly ProcessRunner $processRunner, private readonly JobRepository $jobRepository, private readonly ConfigurationValidator $validator, - string $name = null + ?string $name = null ) { parent::__construct($name); } diff --git a/Classes/Command/StatusReportCommand.php b/Classes/Command/StatusReportCommand.php index 4a212a0..16fa405 100644 --- a/Classes/Command/StatusReportCommand.php +++ b/Classes/Command/StatusReportCommand.php @@ -25,7 +25,7 @@ class StatusReportCommand extends Command { public function __construct( private readonly StatusReportFactory $statusReportFactory, - string $name = null + ?string $name = null ) { parent::__construct($name); } diff --git a/Classes/Domain/Model/Job.php b/Classes/Domain/Model/Job.php index 31f5b03..e468aaf 100644 --- a/Classes/Domain/Model/Job.php +++ b/Classes/Domain/Model/Job.php @@ -90,7 +90,7 @@ public function toDatabaseRow(): array 'json_configuration' => $json, 'created_time' => $this->createdTime->format('U'), 'start_time' => $this->startTime ? $this->startTime->format('U') : 0, - 'end_time' => $this->endTime ? $this->endTime->format('U'): 0, + 'end_time' => $this->endTime ? $this->endTime->format('U') : 0, 'error' => $this->error, ]; } diff --git a/Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php b/Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php index 60fc841..740351c 100644 --- a/Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php +++ b/Classes/Event/BeforeProcessRunnerExecutesCommandsEvent.php @@ -18,7 +18,7 @@ final class BeforeProcessRunnerExecutesCommandsEvent { public int $timeoutPerProcess = 60; - public function __construct( + public function __construct( public readonly Configuration $configuration, public array $commands ) {} From 49452f344c160c46cb4e7d1acbfcf674064fcbbb Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 08:34:59 +0200 Subject: [PATCH 12/18] [TASK] declare strict types --- Build/php-cs-fixer.php | 1 + Configuration/Backend/AjaxRoutes.php | 2 ++ Configuration/Icons.php | 2 ++ Configuration/JavaScriptModules.php | 2 ++ ext_emconf.php | 2 ++ ext_localconf.php | 2 ++ 6 files changed, 11 insertions(+) diff --git a/Build/php-cs-fixer.php b/Build/php-cs-fixer.php index d9a9315..319c169 100644 --- a/Build/php-cs-fixer.php +++ b/Build/php-cs-fixer.php @@ -7,5 +7,6 @@ 'syntax' => 'question_mark', ], 'nullable_type_declaration_for_default_null_value' => true, + 'declare_strict_types' => true, ]); return $config; diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php index fc0c318..50c19b2 100644 --- a/Configuration/Backend/AjaxRoutes.php +++ b/Configuration/Backend/AjaxRoutes.php @@ -1,5 +1,7 @@ [ 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php index 5a3ef58..975d8ea 100644 --- a/Configuration/JavaScriptModules.php +++ b/Configuration/JavaScriptModules.php @@ -1,5 +1,7 @@ [ '@b13/content-sync/' => 'EXT:content_sync/Resources/Public/JavaScript/', diff --git a/ext_emconf.php b/ext_emconf.php index df8138c..fec09ed 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -1,5 +1,7 @@ 'Content Sync', 'description' => 'Sync Database Tables and Files between two TYPO3 Installations', diff --git a/ext_localconf.php b/ext_localconf.php index 871e1f7..7ba1a66 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,3 +1,5 @@ Date: Wed, 3 Jun 2026 08:44:36 +0200 Subject: [PATCH 13/18] [BUGFIX] install TYPO3 v13 in CI --- .github/workflows/test.yaml | 2 +- Build/Scripts/runTests.sh | 6 +++--- Build/testing-docker/docker-compose.yml | 9 +++++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a4fc4e8..3c23da3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v6 - name: Install testing system - run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s composerUpdate + run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -t ${{ matrix.TYPO3 }} -s composerInstall - name: Composer validate run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s composerValidate diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index 632ead8..42944d0 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -116,7 +116,7 @@ PHP_XDEBUG_PORT=9003 EXTRA_TEST_OPTIONS="" SCRIPT_VERBOSE=0 CGLCHECK_DRY_RUN="" -TYPO3="13" +TYPO3="14" # Option parsing # Reset in case getopts has been used previously in the shell @@ -209,9 +209,9 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? docker compose down ;; - composerUpdate) + composerInstall) setUpDockerComposeDotEnv - docker compose run composer_update + docker compose run composer_install SUITE_EXIT_CODE=$? docker compose down ;; diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml index 3ece711..3c8775c 100644 --- a/Build/testing-docker/docker-compose.yml +++ b/Build/testing-docker/docker-compose.yml @@ -18,7 +18,7 @@ services: ${CORE_ROOT}/.Build/bin/php-cs-fixer fix --config Build/php-cs-fixer.php --dry-run --stop-on-violation --using-cache=no " - composer_update: + composer_install: image: ${DOCKER_PHP_IMAGE} user: "${HOST_UID}" volumes: @@ -33,7 +33,12 @@ services: set -x fi php -v | grep '^PHP'; - COMPOSER_HOME=${CORE_ROOT}/.Build/.composer composer update --no-progress --no-interaction; + if [ ${TYPO3} -eq 13 ]; then + COMPOSER_HOME=${CORE_ROOT}/.Build/.composer composer require typo3/cms-core:^13.4 --dev -W --no-progress --no-interaction --no-plugins; + else + COMPOSER_HOME=${CORE_ROOT}/.Build/.composer composer install --no-progress --no-interaction; + fi + " composer_validate: From 092d3eff9ea2a445d1699c2ee8e51fe3a0ff6079 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 08:51:55 +0200 Subject: [PATCH 14/18] [TASK] exclude Event and Exception from services --- Configuration/Services.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index 8575ec6..ec19adc 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -6,4 +6,7 @@ services: B13\ContentSync\: resource: '../Classes/*' - exclude: '../Classes/Domain/Model/*' + exclude: + - '../Classes/Domain/Model/*' + - '../Classes/Event/*' + - '../Classes/Exception.php' From 228ce04caf3bceae66c661021da66625b96dccc8 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 08:55:59 +0200 Subject: [PATCH 15/18] [TASK] update composer and ext_emconf files --- composer.json | 8 ++++++-- ext_emconf.php | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 821cea1..5385442 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "b13/content-sync", "type": "typo3-cms-extension", - "description": "Sync Database Tables and Files between two TYPO3 Installations", + "description": "Content Sync - Sync Database Tables and Files between two TYPO3 Installations", "homepage": "https://b13.com", "license": [ "GPL-2.0-or-later" @@ -16,7 +16,11 @@ }, "extra": { "typo3/cms": { - "extension-key": "content_sync" + "extension-key": "content_sync", + "version": "4.0.0", + "Package": { + "providesPackages": {} + } } }, "replace": { diff --git a/ext_emconf.php b/ext_emconf.php index fec09ed..a5c5b48 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -13,9 +13,9 @@ 'uploadfolder' => false, 'createDirs' => '', 'clearCacheOnLoad' => true, - 'version' => '1.0.2', + 'version' => '3.0.0', 'constraints' => [ - 'depends' => ['typo3' => '10.4.0-10.4.99'], + 'depends' => ['typo3' => '13.4.0-14.99.99'], 'conflicts' => [], 'suggests' => [], ], From 70daf7d82d434419f81f512ebd8f59e640cfa483 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 09:20:38 +0200 Subject: [PATCH 16/18] [TASK] typo3 is our console bin --- Classes/Domain/Validation/ConfigurationValidator.php | 4 ++-- ext_conf_template.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Classes/Domain/Validation/ConfigurationValidator.php b/Classes/Domain/Validation/ConfigurationValidator.php index f7fbe74..b8ebb40 100644 --- a/Classes/Domain/Validation/ConfigurationValidator.php +++ b/Classes/Domain/Validation/ConfigurationValidator.php @@ -37,7 +37,7 @@ public function assertRemoteIsValid(Node $remoteNode): void $process = Process::fromShellCommandline('ssh -o BatchMode=yes -o ConnectTimeout=5 ' . $remoteNode->getConnection() . ' ls ' . $remoteNode->getBin()); $process->run(); if (!$process->isSuccessful()) { - throw new Exception('typo3cms bin not found at remote node', 1600765841); + throw new Exception('typo3 bin not found at remote node', 1600765841); } // remote basePath exists $process = Process::fromShellCommandline('ssh -o BatchMode=yes -o ConnectTimeout=5 ' . $remoteNode->getConnection() . ' ls -d ' . $remoteNode->getBasePath()); @@ -69,7 +69,7 @@ public function assertValid(Configuration $configuration): void $process = Process::fromShellCommandline('ls ' . $localNode->getBin()); $process->run(); if (!$process->isSuccessful()) { - throw new Exception('typo3cms bin not found at local node', 1600765843); + throw new Exception('typo3 bin not found at local node', 1600765843); } // remote basePath exists $process = Process::fromShellCommandline('ls -d ' . $localNode->getBasePath()); diff --git a/ext_conf_template.txt b/ext_conf_template.txt index 0273055..904dfc5 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -11,7 +11,7 @@ sourceNode { local = 1 # cat=sourceNode; type=string; label=Connection for Remote Node (@) connection = - # cat=sourceNode; type=string; label=Full Path to TYPO3 console bin (e.g. /var/www/html/bin/typo3cms) + # cat=sourceNode; type=string; label=Full Path to TYPO3 console bin (e.g. /var/www/html/bin/typo3) bin = # cat=sourceNode; type=string; label=Base Path for sync basePath = @@ -21,8 +21,8 @@ targetNode { local = 0 # cat=targetNode; type=string; label=Connection for Remote Node (@) connection = - # cat=targetNode; type=string; label=Full Path to TYPO3 console bin (e.g. /var/www/html/bin/typo3cms) + # cat=targetNode; type=string; label=Full Path to TYPO3 console bin (e.g. /var/www/html/bin/typo3) bin = # cat=targetNode; type=string; label=Base Path for sync basePath = -} \ No newline at end of file +} From 8b0d2810eed590ec1a352614ab90a9c12dd0ccce Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 09:21:20 +0200 Subject: [PATCH 17/18] [TASK] phpstan config --- Build/phpstan-baseline.neon | 2 -- Build/phpstan.neon | 3 --- 2 files changed, 5 deletions(-) delete mode 100644 Build/phpstan-baseline.neon diff --git a/Build/phpstan-baseline.neon b/Build/phpstan-baseline.neon deleted file mode 100644 index 364905f..0000000 --- a/Build/phpstan-baseline.neon +++ /dev/null @@ -1,2 +0,0 @@ -parameters: - ignoreErrors: diff --git a/Build/phpstan.neon b/Build/phpstan.neon index ead1b3e..bb5c2d3 100644 --- a/Build/phpstan.neon +++ b/Build/phpstan.neon @@ -1,6 +1,3 @@ -includes: - - ../.Build/vendor/phpstan/phpstan/conf/bleedingEdge.neon - - ./phpstan-baseline.neon parameters: level: 5 From 4d5294a25751faff198c1b6831282786e25cfd72 Mon Sep 17 00:00:00 2001 From: Achim Fritz Date: Wed, 3 Jun 2026 10:14:24 +0200 Subject: [PATCH 18/18] [FEATURE] provide a job reload in toolbar drop down --- .../Backend/Controller/Ajax/JobController.php | 18 ++++++++++++++++++ Configuration/Backend/AjaxRoutes.php | 4 ++++ Resources/Private/Language/locallang.xlf | 5 ++++- .../Private/Partials/ToolbarItems/Job.html | 3 ++- .../Private/Partials/ToolbarItems/NewJob.html | 2 +- .../Private/Templates/Ajax/Job/Create.html | 4 ++-- Resources/Private/Templates/Ajax/Job/Kill.html | 4 ++-- .../Private/Templates/Ajax/Job/Reload.html | 6 ++++++ .../ToolbarItems/JobStatusToolbarItem.html | 2 +- .../JobStatusToolbarItemDropDown.html | 7 +++++-- Resources/Public/JavaScript/content-sync.js | 16 ++++++++++++++++ 11 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 Resources/Private/Templates/Ajax/Job/Reload.html diff --git a/Classes/Backend/Controller/Ajax/JobController.php b/Classes/Backend/Controller/Ajax/JobController.php index c76136e..cc0b06b 100644 --- a/Classes/Backend/Controller/Ajax/JobController.php +++ b/Classes/Backend/Controller/Ajax/JobController.php @@ -12,6 +12,7 @@ * of the License, or any later version. */ +use B13\ContentSync\Domain\Factory\StatusReportFactory; use B13\ContentSync\Domain\Model\Configuration; use B13\ContentSync\Domain\Model\Job; use B13\ContentSync\Domain\Repository\JobRepository; @@ -37,6 +38,7 @@ public function __construct( private ExtensionConfiguration $extensionConfiguration, private ConfigurationValidator $validator, private JobRepository $jobRepository, + private StatusReportFactory $statusReportFactory, ) {} public function create(ServerRequestInterface $request): ResponseInterface @@ -78,6 +80,22 @@ public function create(ServerRequestInterface $request): ResponseInterface return new JsonResponse($return); } + public function reload(ServerRequestInterface $request): ResponseInterface + { + $statusReport = $this->statusReportFactory->build(); + $viewFactoryData = new ViewFactoryData( + templateRootPaths: ['EXT:content_sync/Resources/Private/Templates/'], + partialRootPaths: ['EXT:content_sync/Resources/Private/Partials/'], + request: $request, + ); + $view = $this->viewFactory->create($viewFactoryData); + $view->assign('statusReport', $statusReport); + $return = [ + 'content' => $view->render('Ajax/Job/Reload'), + ]; + return new JsonResponse($return); + } + public function kill(ServerRequestInterface $request): ResponseInterface { if (!$this->checkAccess()) { diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php index 50c19b2..dffae23 100644 --- a/Configuration/Backend/AjaxRoutes.php +++ b/Configuration/Backend/AjaxRoutes.php @@ -14,6 +14,10 @@ 'path' => '/ContentSync/job/kill', 'target' => B13\ContentSync\Backend\Controller\Ajax\JobController::class . '::kill', ], + 'content-sync_reload' => [ + 'path' => '/ContentSync/job/reload', + 'target' => B13\ContentSync\Backend\Controller\Ajax\JobController::class . '::reload', + ], 'content-sync_collect-garbage' => [ 'path' => '/ContentSync/collectGarbage', 'target' => B13\ContentSync\Backend\Controller\Ajax\JobController::class . '::collectGarbage', diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index 94b03dc..08b9549 100644 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -27,6 +27,9 @@ Kill Job + + Reload + Created at @@ -71,4 +74,4 @@ - \ No newline at end of file + diff --git a/Resources/Private/Partials/ToolbarItems/Job.html b/Resources/Private/Partials/ToolbarItems/Job.html index 5b2a92d..70e07c9 100644 --- a/Resources/Private/Partials/ToolbarItems/Job.html +++ b/Resources/Private/Partials/ToolbarItems/Job.html @@ -1,4 +1,4 @@ - + : @@ -37,6 +37,7 @@ {job.error} +

diff --git a/Resources/Private/Partials/ToolbarItems/NewJob.html b/Resources/Private/Partials/ToolbarItems/NewJob.html index c3dcd75..f0dd24e 100644 --- a/Resources/Private/Partials/ToolbarItems/NewJob.html +++ b/Resources/Private/Partials/ToolbarItems/NewJob.html @@ -1,4 +1,4 @@ - +