From cee61d2fd738e5900daa529bb1058e8fbb5868d2 Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Tue, 2 Sep 2025 15:48:10 +0200 Subject: [PATCH 01/12] [TASK] Wrapping strings with "" in csv fixture --- Tests/Functional/Fixtures/TxOperations.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Functional/Fixtures/TxOperations.csv b/Tests/Functional/Fixtures/TxOperations.csv index 3cb324c..5e44dda 100644 --- a/Tests/Functional/Fixtures/TxOperations.csv +++ b/Tests/Functional/Fixtures/TxOperations.csv @@ -1,7 +1,7 @@ "tx_operations_domain_model_operation",,,,,, ,"uid","pid","title","onlyEld","begin","end","location","teaser","report","longitude","latitude" -,1,1,"Einsatztest",0,1660774000,1660814089,Thale,Wohnhaus brennt,,, -,2,1,"Einsatztest nur ELD",1,1657663800,1657749600,Neinstedt,,Wohnhaus brennt,, +,1,1,"Einsatztest",0,1660774000,1660814089,"Thale","Wohnhaus brennt",,, +,2,1,"Einsatztest nur ELD",1,1657663800,1657749600,"Neinstedt",,"Wohnhaus brennt",, ,3,1,"Einsatztest 3",0,1609459200,1609543000,,,,, "tx_operations_domain_model_type",,,, ,"uid","pid","title","color" From 652c770312e02ec6db88ab19fb96f855811d97f5 Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Tue, 2 Sep 2025 15:52:35 +0200 Subject: [PATCH 02/12] [TASK] Add functional test for counting by year and type Refactor query in repository. Using aggregated columns to avoid error with selected columns not in `GROUP BY` clause. Related: #32 --- .../Domain/Repository/OperationRepository.php | 8 +-- .../Repository/OperationRepositoryTest.php | 55 +++++++++++++++++++ Tests/Functional/Fixtures/TxOperations.csv | 2 - 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/Classes/Domain/Repository/OperationRepository.php b/Classes/Domain/Repository/OperationRepository.php index 04c9096..6815ab9 100644 --- a/Classes/Domain/Repository/OperationRepository.php +++ b/Classes/Domain/Repository/OperationRepository.php @@ -100,10 +100,10 @@ public function countDemandedForStatistics(OperationDemand $demand, array $setti * Counts all available operations grouped by a year and type * Optionally use operation uid list, which created before with category constraints * - * @param array $years - * @param array $types + * @param array $years + * @param array $types * @param string $operationUids - * @return array + * @return array */ public function countGroupedByYearAndType(array $years, array $types, string $operationUids = ''): array { @@ -111,7 +111,7 @@ public function countGroupedByYearAndType(array $years, array $types, string $op $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('tx_operations_domain_model_operation'); $result = $queryBuilder - ->addSelectLiteral('ot.color as color, ot.title as title, ot.uid as type_uid, COUNT(*) as count, FROM_UNIXTIME(o.begin, \'%Y\') as year') + ->addSelectLiteral('MIN(ot.color) as color, MIN(ot.title) as title, ot.uid as type_uid, COUNT(*) as count, FROM_UNIXTIME(o.begin, \'%Y\') as year') ->from('tx_operations_domain_model_type', 'ot') ->innerJoin('ot', 'tx_operations_operation_type_mm', 'type_mm', 'type_mm.uid_foreign = ot.uid') ->innerJoin('type_mm', 'tx_operations_domain_model_operation', 'o', 'type_mm.uid_local = o.uid') diff --git a/Tests/Functional/Domain/Repository/OperationRepositoryTest.php b/Tests/Functional/Domain/Repository/OperationRepositoryTest.php index 552b521..4be036e 100644 --- a/Tests/Functional/Domain/Repository/OperationRepositoryTest.php +++ b/Tests/Functional/Domain/Repository/OperationRepositoryTest.php @@ -6,6 +6,7 @@ use Kanow\Operations\Domain\Model\OperationDemand; use Kanow\Operations\Domain\Repository\OperationRepository; +use Kanow\Operations\Domain\Repository\TypeRepository; use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; @@ -18,6 +19,11 @@ class OperationRepositoryTest extends FunctionalTestCase */ private OperationRepository $subject; + /** + * @var TypeRepository $typeRepository + */ + private TypeRepository $typeRepository; + /** * @var array $testExtensionsToLoad */ @@ -31,6 +37,7 @@ public function setUp(): void $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest()) ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); $this->subject = $this->getContainer()->get(OperationRepository::class); + $this->typeRepository = $this->getContainer()->get(TypeRepository::class); $this->importCSVDataSet(__DIR__ . '/../../Fixtures/TxOperations.csv'); } @@ -119,6 +126,54 @@ public function countOperationsByYear(): void self::assertEquals($expectedResult, $result); } + #[Test] + public function countOperationsByYearAndType(): void + { + $years = [ + '2020' => 2020, + '2021' => 2021, + '2022' => 2022, + ]; + $types = [ + 0 => $this->typeRepository->findByUid(1), + 1 => $this->typeRepository->findByUid(2), + 2 => $this->typeRepository->findByUid(3), + ]; + $operationUids = '1,2,3'; + $expectedResult = [ + 1 => [ + 'title' => 'Type 1', + 'color' => '#000000', + 'years' => [ + '2022' => 2, + '2021' => 0, + '2020' => 0, + ], + ], + 2 => [ + 'title' => 'Type 2', + 'color' => '#cccccc', + 'years' => [ + '2022' => 0, + '2021' => 0, + '2020' => 0, + ], + ], + 3 => [ + 'title' => 'Type 3', + 'color' => '#aaaaaa', + 'years' => [ + '2022' => 0, + '2021' => 1, + '2020' => 0, + ], + ], + + ]; + $result = $this->subject->countGroupedByYearAndType($years, $types, $operationUids); + self::assertEquals($expectedResult, $result); + } + public function tearDown(): void { parent::tearDown(); diff --git a/Tests/Functional/Fixtures/TxOperations.csv b/Tests/Functional/Fixtures/TxOperations.csv index 5e44dda..2fc56d7 100644 --- a/Tests/Functional/Fixtures/TxOperations.csv +++ b/Tests/Functional/Fixtures/TxOperations.csv @@ -12,6 +12,4 @@ ,"uid_local","uid_foreign" ,1,1 ,2,1 -,3,1 -,3,2 ,3,3 From 60ed44afd9116c0ad24702ce408476c934e851ce Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 11:57:52 +0200 Subject: [PATCH 03/12] [TASK] count operations by year and type runs on other databases Related: #23 --- .../Domain/Repository/OperationRepository.php | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/Classes/Domain/Repository/OperationRepository.php b/Classes/Domain/Repository/OperationRepository.php index 6815ab9..714a312 100644 --- a/Classes/Domain/Repository/OperationRepository.php +++ b/Classes/Domain/Repository/OperationRepository.php @@ -3,10 +3,13 @@ namespace Kanow\Operations\Domain\Repository; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; use Kanow\Operations\Domain\Model\Category; use Kanow\Operations\Domain\Model\Operation; use Kanow\Operations\Domain\Model\OperationDemand; use Kanow\Operations\Domain\Model\Type; +use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -107,15 +110,16 @@ public function countDemandedForStatistics(OperationDemand $demand, array $setti */ public function countGroupedByYearAndType(array $years, array $types, string $operationUids = ''): array { - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('tx_operations_domain_model_operation'); + $connection = GeneralUtility::makeInstance(ConnectionPool::class); + $queryBuilder = $connection->getQueryBuilderForTable('tx_operations_domain_model_operation'); + $connection = $connection->getConnectionForTable('tx_operations_domain_model_operation'); + $result = $queryBuilder - ->addSelectLiteral('MIN(ot.color) as color, MIN(ot.title) as title, ot.uid as type_uid, COUNT(*) as count, FROM_UNIXTIME(o.begin, \'%Y\') as year') + ->addSelectLiteral('MIN(ot.color) as color, MIN(ot.title) as title, ot.uid as type_uid, COUNT(*) as count, '. $this->getSelectYearFromUnixTime($connection, $years) . ' as year') ->from('tx_operations_domain_model_type', 'ot') ->innerJoin('ot', 'tx_operations_operation_type_mm', 'type_mm', 'type_mm.uid_foreign = ot.uid') ->innerJoin('type_mm', 'tx_operations_domain_model_operation', 'o', 'type_mm.uid_local = o.uid') - ->where('FROM_UNIXTIME(o.begin, \'%Y\') IN(' . $this->convertYearsToString($years) . ')'); + ->where($this->getSelectYearFromUnixTime($connection, $years) . $this->getWhereYearInString($connection, $years)); if ($operationUids != '') { $result = $result->andWhere('o.uid IN (' . $operationUids . ')'); } @@ -251,6 +255,46 @@ protected function convertYearsToString(array $years): string return implode(',', $years); } + /* + * convert years array to comma separated list + * and wrapped with '' to get proper result in sqlite databases + * + * @param array $years + * @return string + */ + protected function convertYearsToStringForSqlite(array $years): string + { + // Every year must be set between '' to get a proper list for sqlite + return implode(',', array_map(function(string $year) { + return "'$year'"; + }, $years)); + } + + protected function getSelectYearFromUnixTime(Connection $connection, array $years): string + { + $isPostgres = $connection->getDatabasePlatform() instanceof PostgreSQLPlatform; + $isSqlite = $connection->getDatabasePlatform() instanceof SQLitePlatform; + if($isPostgres) { + return 'EXTRACT(YEAR FROM TO_TIMESTAMP(o.begin))'; + } elseif ($isSqlite) { + return 'STRFTIME(\'%Y\', DATETIME(o.begin, \'unixepoch\'))'; + } else { + return 'FROM_UNIXTIME(o.begin, \'%Y\')'; + } + } + protected function getWhereYearInString(Connection $connection, array $years): string + { + $isPostgres = $connection->getDatabasePlatform() instanceof PostgreSQLPlatform; + $isSqlite = $connection->getDatabasePlatform() instanceof SQLitePlatform; + if($isPostgres) { + return 'IN(' . $this->convertYearsToString($years) . ')'; + } elseif ($isSqlite) { + return 'IN(' . $this->convertYearsToStringForSqlite($years) . ')'; + } else { + return 'IN(' . $this->convertYearsToString($years) . ')'; + } + } + /** * Counts all available operations grouped by year * Optionally use operation uid list, which created before with category constraints From 8e69e391e2f870062ea3c96f93d9a23b1b6fc204 Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 12:12:04 +0200 Subject: [PATCH 04/12] [TASK] Refactor sql to support sqlite and postgres db systems Related: #23 --- Classes/Domain/Repository/OperationRepository.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Classes/Domain/Repository/OperationRepository.php b/Classes/Domain/Repository/OperationRepository.php index 714a312..3680310 100644 --- a/Classes/Domain/Repository/OperationRepository.php +++ b/Classes/Domain/Repository/OperationRepository.php @@ -305,14 +305,14 @@ protected function getWhereYearInString(Connection $connection, array $years): s */ public function countGroupedByYear(array $years, string $operationUids = ''): array { - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('tx_operations_domain_model_operation'); + $connection = GeneralUtility::makeInstance(ConnectionPool::class); + $queryBuilder = $connection->getQueryBuilderForTable('tx_operations_domain_model_operation'); + $connection = $connection->getConnectionForTable('tx_operations_domain_model_operation'); $statement = $queryBuilder - ->addSelectLiteral('COUNT(*) as count, FROM_UNIXTIME(begin, \'%Y\') as year') + ->addSelectLiteral('COUNT(*) as count, ' . $this->getSelectYearFromUnixTime($connection, $years) . ' as year') ->from('tx_operations_domain_model_operation', 'o') - ->where('FROM_UNIXTIME(begin, \'%Y\') IN(' . $this->convertYearsToString($years) . ')'); + ->where($this->getSelectYearFromUnixTime($connection, $years) . $this->getWhereYearInString($connection, $years)); if ($operationUids != '') { $statement = $statement->andWhere('o.uid IN (' . $operationUids . ')'); } From 504c7d3c6350b115d040988ea5bbea7724ec109d Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 12:41:27 +0200 Subject: [PATCH 05/12] [TASK] Add new sql utility to prepare usage in controller too Related: #23 --- .../Domain/Repository/OperationRepository.php | 61 ++--------------- Classes/Utility/SqlUtility.php | 65 +++++++++++++++++++ 2 files changed, 70 insertions(+), 56 deletions(-) create mode 100644 Classes/Utility/SqlUtility.php diff --git a/Classes/Domain/Repository/OperationRepository.php b/Classes/Domain/Repository/OperationRepository.php index 3680310..74044ea 100644 --- a/Classes/Domain/Repository/OperationRepository.php +++ b/Classes/Domain/Repository/OperationRepository.php @@ -9,9 +9,8 @@ use Kanow\Operations\Domain\Model\Operation; use Kanow\Operations\Domain\Model\OperationDemand; use Kanow\Operations\Domain\Model\Type; -use TYPO3\CMS\Core\Database\Connection; +use Kanow\Operations\Utility\SqlUtility; use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException; use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface; @@ -115,11 +114,11 @@ public function countGroupedByYearAndType(array $years, array $types, string $op $connection = $connection->getConnectionForTable('tx_operations_domain_model_operation'); $result = $queryBuilder - ->addSelectLiteral('MIN(ot.color) as color, MIN(ot.title) as title, ot.uid as type_uid, COUNT(*) as count, '. $this->getSelectYearFromUnixTime($connection, $years) . ' as year') + ->addSelectLiteral('MIN(ot.color) as color, MIN(ot.title) as title, ot.uid as type_uid, COUNT(*) as count, '. SqlUtility::getSelectYearFromUnixTime($connection) . ' as year') ->from('tx_operations_domain_model_type', 'ot') ->innerJoin('ot', 'tx_operations_operation_type_mm', 'type_mm', 'type_mm.uid_foreign = ot.uid') ->innerJoin('type_mm', 'tx_operations_domain_model_operation', 'o', 'type_mm.uid_local = o.uid') - ->where($this->getSelectYearFromUnixTime($connection, $years) . $this->getWhereYearInString($connection, $years)); + ->where(SqlUtility::getSelectYearFromUnixTime($connection) . SqlUtility::getWhereYearInString($connection, $years)); if ($operationUids != '') { $result = $result->andWhere('o.uid IN (' . $operationUids . ')'); } @@ -243,57 +242,7 @@ protected function sortResultByTypeUid(array $data): array return $data; } - /* - * convert years array to comma separated list - * which can be check in sql - * - * @param array $years - * @return string - */ - protected function convertYearsToString(array $years): string - { - return implode(',', $years); - } - /* - * convert years array to comma separated list - * and wrapped with '' to get proper result in sqlite databases - * - * @param array $years - * @return string - */ - protected function convertYearsToStringForSqlite(array $years): string - { - // Every year must be set between '' to get a proper list for sqlite - return implode(',', array_map(function(string $year) { - return "'$year'"; - }, $years)); - } - - protected function getSelectYearFromUnixTime(Connection $connection, array $years): string - { - $isPostgres = $connection->getDatabasePlatform() instanceof PostgreSQLPlatform; - $isSqlite = $connection->getDatabasePlatform() instanceof SQLitePlatform; - if($isPostgres) { - return 'EXTRACT(YEAR FROM TO_TIMESTAMP(o.begin))'; - } elseif ($isSqlite) { - return 'STRFTIME(\'%Y\', DATETIME(o.begin, \'unixepoch\'))'; - } else { - return 'FROM_UNIXTIME(o.begin, \'%Y\')'; - } - } - protected function getWhereYearInString(Connection $connection, array $years): string - { - $isPostgres = $connection->getDatabasePlatform() instanceof PostgreSQLPlatform; - $isSqlite = $connection->getDatabasePlatform() instanceof SQLitePlatform; - if($isPostgres) { - return 'IN(' . $this->convertYearsToString($years) . ')'; - } elseif ($isSqlite) { - return 'IN(' . $this->convertYearsToStringForSqlite($years) . ')'; - } else { - return 'IN(' . $this->convertYearsToString($years) . ')'; - } - } /** * Counts all available operations grouped by year @@ -310,9 +259,9 @@ public function countGroupedByYear(array $years, string $operationUids = ''): ar $connection = $connection->getConnectionForTable('tx_operations_domain_model_operation'); $statement = $queryBuilder - ->addSelectLiteral('COUNT(*) as count, ' . $this->getSelectYearFromUnixTime($connection, $years) . ' as year') + ->addSelectLiteral('COUNT(*) as count, ' . SqlUtility::getSelectYearFromUnixTime($connection) . ' as year') ->from('tx_operations_domain_model_operation', 'o') - ->where($this->getSelectYearFromUnixTime($connection, $years) . $this->getWhereYearInString($connection, $years)); + ->where(SqlUtility::getSelectYearFromUnixTime($connection) . SqlUtility::getWhereYearInString($connection, $years)); if ($operationUids != '') { $statement = $statement->andWhere('o.uid IN (' . $operationUids . ')'); } diff --git a/Classes/Utility/SqlUtility.php b/Classes/Utility/SqlUtility.php new file mode 100644 index 0000000..83e767c --- /dev/null +++ b/Classes/Utility/SqlUtility.php @@ -0,0 +1,65 @@ +getDatabasePlatform() instanceof PostgreSQLPlatform; + $isSqlite = $connection->getDatabasePlatform() instanceof SQLitePlatform; + if($isPostgres) { + return 'EXTRACT(YEAR FROM TO_TIMESTAMP(o.begin))'; + } elseif ($isSqlite) { + return 'STRFTIME(\'%Y\', DATETIME(o.begin, \'unixepoch\'))'; + } else { + return 'FROM_UNIXTIME(o.begin, \'%Y\')'; + } + } + + public static function getWhereYearInString(Connection $connection, array $years): string + { + $isPostgres = $connection->getDatabasePlatform() instanceof PostgreSQLPlatform; + $isSqlite = $connection->getDatabasePlatform() instanceof SQLitePlatform; + if($isPostgres) { + return 'IN(' . self::convertYearsToString($years) . ')'; + } elseif ($isSqlite) { + return 'IN(' . self::convertYearsToStringForSqlite($years) . ')'; + } else { + return 'IN(' . self::convertYearsToString($years) . ')'; + } + } + + /* + * convert years array to comma separated list + * which can be check in sql + * + * @param array $years + * @return string + */ + private static function convertYearsToString(array $years): string + { + return implode(',', $years); + } + + /* + * convert years array to comma separated list + * and wrapped with '' to get proper result in sqlite databases + * + * @param array $years + * @return string + */ + private static function convertYearsToStringForSqlite(array $years): string + { + // Every year must be set between '' to get a proper list for sqlite + return implode(',', array_map(function(string $year) { + return "'$year'"; + }, $years)); + } + +} From 0e12d1175e4c452c375b9b2020bdd4cff52f4f36 Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 12:48:45 +0200 Subject: [PATCH 06/12] [TASK] Make phpstan happy with nullable types --- Classes/Controller/OperationController.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Classes/Controller/OperationController.php b/Classes/Controller/OperationController.php index 00754f1..3e42197 100644 --- a/Classes/Controller/OperationController.php +++ b/Classes/Controller/OperationController.php @@ -98,7 +98,7 @@ public function __construct( * @return ResponseInterface * @throws InvalidQueryException */ - public function listAction(OperationDemand $demand = null, int $currentPage = 1): ResponseInterface + public function listAction(?OperationDemand $demand = null, int $currentPage = 1): ResponseInterface { if ($this->request->hasArgument('demand')) { $forwardResponse = new ForwardResponse('search'); @@ -140,12 +140,12 @@ public function listAction(OperationDemand $demand = null, int $currentPage = 1) /** * action search * - * @param OperationDemand $demand + * @param OperationDemand|null $demand * @param int $currentPage * @throws InvalidQueryException * @throws NoSuchArgumentException */ - public function searchAction(OperationDemand $demand = null, int $currentPage = 1): ResponseInterface + public function searchAction(?OperationDemand $demand = null, int $currentPage = 1): ResponseInterface { $demand = $this->createDemandObjectFromSettings($demand); /** @var OperationDemand $demand */ @@ -209,10 +209,10 @@ public function showAction(Operation $operation): ResponseInterface /** * action for statistics * - * @param OperationDemand $demand + * @param OperationDemand|null $demand * @throws InvalidQueryException */ - public function statisticsAction(OperationDemand $demand = null): ResponseInterface + public function statisticsAction(?OperationDemand $demand = null): ResponseInterface { $demand = $this->createDemandObjectFromSettings($demand); From 2252ca187222134753a052c157a1fe9f62a02d6e Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 12:49:35 +0200 Subject: [PATCH 07/12] [TASK] Remove test of convertYearsToSring Related: #23 --- .../Domain/Repository/OperationRepositoryTest.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Tests/Unit/Domain/Repository/OperationRepositoryTest.php b/Tests/Unit/Domain/Repository/OperationRepositoryTest.php index ddbe401..febd215 100644 --- a/Tests/Unit/Domain/Repository/OperationRepositoryTest.php +++ b/Tests/Unit/Domain/Repository/OperationRepositoryTest.php @@ -69,21 +69,6 @@ public function isRepository(): void self::assertInstanceOf(Repository::class, $this->subject); } - /** - * @test - */ - public function convertYearsToStringReturnsString(): void - { - $yearsAsString = '2020,2021,2022'; - $yearsAsArray = ['2020', '2021', '2022']; - - $reflector = new \ReflectionClass(OperationRepository::class); - $method = $reflector->getMethod('convertYearsToString'); - $method->setAccessible(true); - $result = $method->invokeArgs($this->subject, [$yearsAsArray]); - self::assertSame($result, $yearsAsString); - } - /** * @test */ From efb1f048c093c48672cb837ea87365b5eb98fcfe Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 12:53:39 +0200 Subject: [PATCH 08/12] [TASK] Controller use SqlUtility to get year from unix time Parameter for column added to make this possible. Related: #23 --- Classes/Controller/OperationController.php | 9 +++++---- Classes/Domain/Repository/OperationRepository.php | 8 ++++---- Classes/Utility/SqlUtility.php | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Classes/Controller/OperationController.php b/Classes/Controller/OperationController.php index 3e42197..3bab748 100644 --- a/Classes/Controller/OperationController.php +++ b/Classes/Controller/OperationController.php @@ -38,6 +38,7 @@ use Kanow\Operations\Domain\Repository\OperationRepository; use Kanow\Operations\Domain\Repository\TypeRepository; use Kanow\Operations\Service\CategoryService; +use Kanow\Operations\Utility\SqlUtility; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerAwareInterface; @@ -261,11 +262,11 @@ protected function generateYears(string $operationUids = ''): array { $years = []; $lastYears = $this->settings['lastYears']; - /** @var QueryBuilder $queryBuilder */ - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('tx_operations_domain_model_operation'); + $connection = GeneralUtility::makeInstance(ConnectionPool::class); + $queryBuilder = $connection->getQueryBuilderForTable('tx_operations_domain_model_operation'); + $connection = $connection->getConnectionForTable('tx_operations_domain_model_operation'); $rows = $queryBuilder - ->addSelectLiteral('FROM_UNIXTIME(begin, \'%Y\') AS year') + ->addSelectLiteral(SqlUtility::getSelectYearFromUnixTime($connection, 'begin') . ' AS year') ->from('tx_operations_domain_model_operation'); if ($operationUids != '') { $rows = $rows->andWhere('uid IN (' . $operationUids . ')'); diff --git a/Classes/Domain/Repository/OperationRepository.php b/Classes/Domain/Repository/OperationRepository.php index 74044ea..bdcc808 100644 --- a/Classes/Domain/Repository/OperationRepository.php +++ b/Classes/Domain/Repository/OperationRepository.php @@ -114,11 +114,11 @@ public function countGroupedByYearAndType(array $years, array $types, string $op $connection = $connection->getConnectionForTable('tx_operations_domain_model_operation'); $result = $queryBuilder - ->addSelectLiteral('MIN(ot.color) as color, MIN(ot.title) as title, ot.uid as type_uid, COUNT(*) as count, '. SqlUtility::getSelectYearFromUnixTime($connection) . ' as year') + ->addSelectLiteral('MIN(ot.color) as color, MIN(ot.title) as title, ot.uid as type_uid, COUNT(*) as count, '. SqlUtility::getSelectYearFromUnixTime($connection, 'o.begin') . ' as year') ->from('tx_operations_domain_model_type', 'ot') ->innerJoin('ot', 'tx_operations_operation_type_mm', 'type_mm', 'type_mm.uid_foreign = ot.uid') ->innerJoin('type_mm', 'tx_operations_domain_model_operation', 'o', 'type_mm.uid_local = o.uid') - ->where(SqlUtility::getSelectYearFromUnixTime($connection) . SqlUtility::getWhereYearInString($connection, $years)); + ->where(SqlUtility::getSelectYearFromUnixTime($connection, 'o.begin') . SqlUtility::getWhereYearInString($connection, $years)); if ($operationUids != '') { $result = $result->andWhere('o.uid IN (' . $operationUids . ')'); } @@ -259,9 +259,9 @@ public function countGroupedByYear(array $years, string $operationUids = ''): ar $connection = $connection->getConnectionForTable('tx_operations_domain_model_operation'); $statement = $queryBuilder - ->addSelectLiteral('COUNT(*) as count, ' . SqlUtility::getSelectYearFromUnixTime($connection) . ' as year') + ->addSelectLiteral('COUNT(*) as count, ' . SqlUtility::getSelectYearFromUnixTime($connection, 'o.begin') . ' as year') ->from('tx_operations_domain_model_operation', 'o') - ->where(SqlUtility::getSelectYearFromUnixTime($connection) . SqlUtility::getWhereYearInString($connection, $years)); + ->where(SqlUtility::getSelectYearFromUnixTime($connection, 'o.begin') . SqlUtility::getWhereYearInString($connection, $years)); if ($operationUids != '') { $statement = $statement->andWhere('o.uid IN (' . $operationUids . ')'); } diff --git a/Classes/Utility/SqlUtility.php b/Classes/Utility/SqlUtility.php index 83e767c..3d4dd53 100644 --- a/Classes/Utility/SqlUtility.php +++ b/Classes/Utility/SqlUtility.php @@ -9,16 +9,16 @@ class SqlUtility { - public static function getSelectYearFromUnixTime(Connection $connection): string + public static function getSelectYearFromUnixTime(Connection $connection, string $column): string { $isPostgres = $connection->getDatabasePlatform() instanceof PostgreSQLPlatform; $isSqlite = $connection->getDatabasePlatform() instanceof SQLitePlatform; if($isPostgres) { - return 'EXTRACT(YEAR FROM TO_TIMESTAMP(o.begin))'; + return 'EXTRACT(YEAR FROM TO_TIMESTAMP(' . $column . '))'; } elseif ($isSqlite) { - return 'STRFTIME(\'%Y\', DATETIME(o.begin, \'unixepoch\'))'; + return 'STRFTIME(\'%Y\', DATETIME(' . $column . ', \'unixepoch\'))'; } else { - return 'FROM_UNIXTIME(o.begin, \'%Y\')'; + return 'FROM_UNIXTIME(' . $column . ', \'%Y\')'; } } From dfcecd5a533d3848174d36311b045ede83d4dd0b Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 14:54:09 +0200 Subject: [PATCH 09/12] [TASK] Remove check for TYPO3 11 in unit test --- Tests/Unit/Controller/OperationControllerTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Tests/Unit/Controller/OperationControllerTest.php b/Tests/Unit/Controller/OperationControllerTest.php index 498c4de..c8ad413 100644 --- a/Tests/Unit/Controller/OperationControllerTest.php +++ b/Tests/Unit/Controller/OperationControllerTest.php @@ -92,9 +92,7 @@ protected function setUp(): void // We need to create an accessible mock in order to be able to set the protected `view`. $methodsToMock = ['htmlResponse', 'redirect', 'redirectToUri']; - if ((new Typo3Version())->getMajorVersion() <= 11) { - $methodsToMock[] = 'forward'; - } + $this->subject = $this->getAccessibleMock(OperationController::class, $methodsToMock, [ $this->operationRepositoryMock, $this->typeRepositoryMock, From c0d08daea39facf08bc244a1017025b6770d8aff Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 14:55:15 +0200 Subject: [PATCH 10/12] [TASK] Add functional test for operation controller Related: #23 --- .../Controller/OperationControllerTest.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Tests/Functional/Controller/OperationControllerTest.php diff --git a/Tests/Functional/Controller/OperationControllerTest.php b/Tests/Functional/Controller/OperationControllerTest.php new file mode 100644 index 0000000..ab17f8c --- /dev/null +++ b/Tests/Functional/Controller/OperationControllerTest.php @@ -0,0 +1,65 @@ +withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); + $this->subject = $this->getContainer()->get(OperationController::class); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/TxOperations.csv'); + } + + #[Test] + public function generateYearsReturnsYears(): void + { + $expectedResult = [ + 2022 => '2022', + 2021 => '2021', + ]; + $reflection = new \ReflectionClass($this->subject); + $settingsProperty = $reflection->getProperty('settings'); + $settingsProperty->setValue($this->subject, ['lastYears' => 5]); + $method = $reflection->getMethod('generateYears'); + + $result = $method->invoke($this->subject, '1,2,3'); + self::assertSame($result,$expectedResult); + + } + #[Test] + public function generateYearsRespectSettingsForLastYears(): void + { + $expectedResult = [ + 2022 => '2022', + ]; + $reflection = new \ReflectionClass($this->subject); + $settingsProperty = $reflection->getProperty('settings'); + $settingsProperty->setValue($this->subject, ['lastYears' => 1]); + $method = $reflection->getMethod('generateYears'); + + $result = $method->invoke($this->subject, '1,2,3'); + self::assertSame($result,$expectedResult); + } + + public function tearDown(): void + { + parent::tearDown(); + unset($this->subject); + } +} From 524ec048a2b9092153140bf07f7aacb90a7497e8 Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 15:00:09 +0200 Subject: [PATCH 11/12] [TASK] Extend github workflow due to new circumstances Check new db systems and ad php8.4 checks Related: #23 --- .github/workflows/ci.yml | 9 +++++---- composer.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d84b68d..bdd43cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Install Composer dependencies - run: Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -s composerUpdateMax + run: Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -t 13.4 -s composerUpdateMax - name: Run code quality checks run: | Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -s composer ci:${{ matrix.command }} @@ -45,15 +45,16 @@ jobs: - "php:cs-fixer" - "php:stan" php-version: - - "8.3" + - "8.4" testsuite: name: "Testsuite" runs-on: ubuntu-latest strategy: matrix: - php-version: [ '8.2', '8.3' ] + php-version: [ '8.2', '8.3', '8.4' ] minMax: [ 'composerUpdateMin', 'composerUpdateMax' ] coreVersion: [ '12.4', '13.4' ] + db: ['mariadb', 'mysql', 'sqlite', 'postgres'] steps: - name: Checkout uses: actions/checkout@v2 @@ -65,4 +66,4 @@ jobs: run: Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -s unit - name: Functional tests with mariadb - run: Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -d mariadb -s functional + run: Build/Scripts/runTests.sh -p ${{ matrix.php-version }} -d ${{matrix.db}} -s functional diff --git a/composer.json b/composer.json index 1ecac17..6115e12 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "typo3/cms-fluid-styled-content": "^12.4.1 || ^13.1", "typo3/cms-install": "^12.4.1 || ^13.1", "typo3/coding-standards": "^0.6.1 || ^0.8.0", - "typo3/testing-framework": "^8.2.7", + "typo3/testing-framework": "^8.2.7 || ^9", "webmozart/assert": "^1.11.0" }, "minimum-stability": "dev", From 7ab293e395558318bfce57824c9dc40aec51db3d Mon Sep 17 00:00:00 2001 From: Karsten Nowak Date: Wed, 3 Sep 2025 15:57:48 +0200 Subject: [PATCH 12/12] [BUGFIX] Be sure to get years as string and no float Related: #23 --- Classes/Controller/OperationController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Controller/OperationController.php b/Classes/Controller/OperationController.php index 3bab748..30117d8 100644 --- a/Classes/Controller/OperationController.php +++ b/Classes/Controller/OperationController.php @@ -277,7 +277,7 @@ protected function generateYears(string $operationUids = ''): array ->executeQuery() ->fetchAllAssociative(); foreach ($rows as $year) { - $years[$year['year']] = $year['year']; + $years[$year['year']] = (string) intval($year['year']); } return $years; }