Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -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
Expand All @@ -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
21 changes: 11 additions & 10 deletions Classes/Controller/OperationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -98,7 +99,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');
Expand Down Expand Up @@ -140,12 +141,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 */
Expand Down Expand Up @@ -209,10 +210,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);

Expand Down Expand Up @@ -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 . ')');
Expand All @@ -276,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;
}
Expand Down
43 changes: 18 additions & 25 deletions Classes/Domain/Repository/OperationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
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 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;
Expand Down Expand Up @@ -100,22 +102,23 @@ 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<string,int> $years
* @param array<mixed> $types
* @param array $years
* @param array $types
* @param string $operationUids
* @return array<mixed>
* @return array
*/
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('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, '. 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('FROM_UNIXTIME(o.begin, \'%Y\') IN(' . $this->convertYearsToString($years) . ')');
->where(SqlUtility::getSelectYearFromUnixTime($connection, 'o.begin') . SqlUtility::getWhereYearInString($connection, $years));
if ($operationUids != '') {
$result = $result->andWhere('o.uid IN (' . $operationUids . ')');
}
Expand Down Expand Up @@ -239,17 +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);
}


/**
* Counts all available operations grouped by year
Expand All @@ -261,14 +254,14 @@ protected function convertYearsToString(array $years): string
*/
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, ' . SqlUtility::getSelectYearFromUnixTime($connection, 'o.begin') . ' as year')
->from('tx_operations_domain_model_operation', 'o')
->where('FROM_UNIXTIME(begin, \'%Y\') IN(' . $this->convertYearsToString($years) . ')');
->where(SqlUtility::getSelectYearFromUnixTime($connection, 'o.begin') . SqlUtility::getWhereYearInString($connection, $years));
if ($operationUids != '') {
$statement = $statement->andWhere('o.uid IN (' . $operationUids . ')');
}
Expand Down
65 changes: 65 additions & 0 deletions Classes/Utility/SqlUtility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

namespace Kanow\Operations\Utility;

use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use TYPO3\CMS\Core\Database\Connection;

class SqlUtility
{

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(' . $column . '))';
} elseif ($isSqlite) {
return 'STRFTIME(\'%Y\', DATETIME(' . $column . ', \'unixepoch\'))';
} else {
return 'FROM_UNIXTIME(' . $column . ', \'%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));
}

}
65 changes: 65 additions & 0 deletions Tests/Functional/Controller/OperationControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Kanow\Operations\Tests\Functional\Controller;

use PHPUnit\Framework\Attributes\Test;
use Kanow\Operations\Controller\OperationController;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use TYPO3\TestingFramework\Core\SystemEnvironmentBuilder;

final class OperationControllerTest extends FunctionalTestCase
{
protected OperationController $subject;

protected array $testExtensionsToLoad = [
'typo3conf/ext/operations',
];
protected function setUp(): void
{
parent::setUp();
$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest())
->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);
}
}
Loading