From 94a6b99c57d13e2ffbe8fbf855ff27b34f4806d0 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Thu, 24 Nov 2022 13:12:18 +0100 Subject: [PATCH] fix php 8.1 deprecations (#3648) * show session maxlifetime in doctor * rephrase compatibility in release notes * bump version --- .github/release-drafter.yml | 9 ++++-- src/Constants.php | 4 +-- src/Controller/DoctorController.php | 1 + src/Entity/InvoiceDocument.php | 14 +++++++-- .../RedirectToLocaleSubscriber.php | 3 +- .../Renderer/AbstractSpreadsheetRenderer.php | 5 +++- src/Invoice/ServiceInvoice.php | 18 ++++++++++- src/Model/TimesheetCountedStatistic.php | 1 + src/Utils/LocaleHelper.php | 30 ++++++++++++++----- .../Constraints/DateTimeFormatValidator.php | 14 +++++++-- tests/Entity/InvoiceDocumentTest.php | 23 ++++++++++++++ tests/Invoice/Renderer/DocxRendererTest.php | 8 ++--- tests/Invoice/Renderer/JsonRendererTest.php | 7 ++--- tests/Invoice/Renderer/OdsRendererTest.php | 6 ++-- tests/Invoice/Renderer/PdfRendererTest.php | 6 ++-- tests/Invoice/Renderer/TextRendererTest.php | 8 ++--- tests/Invoice/Renderer/TwigRendererTest.php | 6 ++-- tests/Invoice/Renderer/XlsxRendererTest.php | 6 ++-- tests/Invoice/Renderer/XmlRendererTest.php | 8 ++--- .../Constraints/DurationValidatorTest.php | 5 +++- 20 files changed, 125 insertions(+), 57 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index e1c64e77..db5e48a3 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -34,10 +34,13 @@ version-resolver: default: patch template: | [Upgrade Kimai](https://www.kimai.org/documentation/updates.html) - [Install Kimai](https://www.kimai.org/documentation/installation.html) - [Docker](https://tobybatch.github.io/kimai2/) - + **PHP Version compatibility:** - - PHP 7.3 is [end-of-life](https://www.php.net/supported-versions.php): please update now - - PHP 7.4 is almost EOL, PHP 8 and PHP 8.1 are supported + - PHP 7.3 is [end-of-life](https://www.php.net/supported-versions.php) + - PHP 7.4 is [end-of-life](https://www.php.net/supported-versions.php) in a few days! + - PHP 8.0 and PHP 8.1 are supported + + **Next release will be PHP 8 ONLY, as [announced one year ago](https://www.kimai.org/blog/2021/sunsetting-php-7/).** $CHANGES diff --git a/src/Constants.php b/src/Constants.php index 64e33451..d82a74ac 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -17,11 +17,11 @@ class Constants /** * The current release version */ - public const VERSION = '1.29.0'; + public const VERSION = '1.29.1'; /** * The current release: major * 10000 + minor * 100 + patch */ - public const VERSION_ID = 12900; + public const VERSION_ID = 12901; /** * The current release status, either "stable" or "dev" */ diff --git a/src/Controller/DoctorController.php b/src/Controller/DoctorController.php index a2f7f74a..b2995cb0 100644 --- a/src/Controller/DoctorController.php +++ b/src/Controller/DoctorController.php @@ -268,6 +268,7 @@ class DoctorController extends AbstractController 'post_max_size', 'sys_temp_dir', 'date.timezone', + 'session.gc_maxlifetime', ]; $settings = []; diff --git a/src/Entity/InvoiceDocument.php b/src/Entity/InvoiceDocument.php index 72e20203..549b30f9 100644 --- a/src/Entity/InvoiceDocument.php +++ b/src/Entity/InvoiceDocument.php @@ -35,7 +35,12 @@ final class InvoiceDocument public function getFilename(): string { - return $this->file->getRealPath(); + $path = $this->file->getRealPath(); + if ($path === false) { + throw new \Exception('Invoice template got deleted from filesystem'); + } + + return $path; } public function getFileExtension(): string @@ -45,6 +50,11 @@ final class InvoiceDocument public function getLastChange(): int { - return $this->file->getMTime(); + $modified = $this->file->getMTime(); + if ($modified === false) { + throw new \Exception('Invoice template got deleted from filesystem'); + } + + return $modified; } } diff --git a/src/EventSubscriber/RedirectToLocaleSubscriber.php b/src/EventSubscriber/RedirectToLocaleSubscriber.php index 8a6f0bf5..f01e3957 100644 --- a/src/EventSubscriber/RedirectToLocaleSubscriber.php +++ b/src/EventSubscriber/RedirectToLocaleSubscriber.php @@ -53,7 +53,8 @@ class RedirectToLocaleSubscriber implements EventSubscriberInterface // Ignore requests from referrers with the same HTTP host in order to prevent // changing language for users who possibly already selected it for this application. - if (0 === stripos($request->headers->get('referer'), $request->getSchemeAndHttpHost())) { + $referer = $request->headers->get('referer'); + if ($referer !== null && 0 === stripos($referer, $request->getSchemeAndHttpHost())) { return; } diff --git a/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php b/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php index f24fac82..0da8cc5e 100644 --- a/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php +++ b/src/Invoice/Renderer/AbstractSpreadsheetRenderer.php @@ -68,6 +68,9 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer foreach ($row->getCellIterator() as $cell) { $value = $cell->getValue(); $replacer = null; + if ($value === null) { + continue; + } $firstReplacerPos = stripos($value, '${'); if ($firstReplacerPos === false) { continue; @@ -134,7 +137,7 @@ abstract class AbstractSpreadsheetRenderer extends AbstractRenderer $cellCounter = 0; foreach ($row->getCellIterator() as $cell) { $value = $cell->getValue(); - if (stripos($value, '${entry.') !== false) { + if ($value !== null && stripos($value, '${entry.') !== false) { $startRow = $row->getRowIndex(); $worksheet->insertNewRowBefore($startRow + 1, $invoiceItemCount - 1); break 2; diff --git a/src/Invoice/ServiceInvoice.php b/src/Invoice/ServiceInvoice.php index 668dec18..275724ef 100644 --- a/src/Invoice/ServiceInvoice.php +++ b/src/Invoice/ServiceInvoice.php @@ -11,6 +11,7 @@ namespace App\Invoice; use App\Configuration\LanguageFormattings; use App\Constants; +use App\Entity\Customer; use App\Entity\Invoice; use App\Entity\InvoiceDocument; use App\Event\InvoiceCreatedEvent; @@ -580,7 +581,22 @@ final class ServiceInvoice } uasort($customerEntries, function ($a, $b) { - return strcmp($a['customer']->getName(), $b['customer']->getName()); + $customerA = $a['customer'] ?? null; + $customerB = $b['customer'] ?? null; + $nameA = ($customerA instanceof Customer) ? $customerA->getName() : null; + $nameB = ($customerB instanceof Customer) ? $customerB->getName() : null; + + if ($nameA === null && $nameB === null) { + $result = 0; + } elseif ($nameA === null && $nameB !== null) { + $result = 1; + } elseif ($nameA !== null && $nameB === null) { + $result = -1; + } else { + $result = strcmp($nameA, $nameB); + } + + return $result; }); foreach ($customerEntries as $id => $settings) { diff --git a/src/Model/TimesheetCountedStatistic.php b/src/Model/TimesheetCountedStatistic.php index 7f99a633..d16307d7 100644 --- a/src/Model/TimesheetCountedStatistic.php +++ b/src/Model/TimesheetCountedStatistic.php @@ -296,6 +296,7 @@ class TimesheetCountedStatistic implements \JsonSerializable $this->recordRateExported = $recordRate; } + #[\ReturnTypeWillChange] public function jsonSerialize() { return [ diff --git a/src/Utils/LocaleHelper.php b/src/Utils/LocaleHelper.php index 1316e96f..fb9e5cc6 100644 --- a/src/Utils/LocaleHelper.php +++ b/src/Utils/LocaleHelper.php @@ -45,33 +45,45 @@ final class LocaleHelper /** * Transforms seconds into a decimal formatted duration string. * - * @param int $seconds + * @param int|null $seconds * @return string */ - public function durationDecimal(int $seconds) + public function durationDecimal(?int $seconds): string { + if ($seconds === null) { + $seconds = 0; + } + $value = round($seconds / 3600, 2); - return $this->getDurationFormatter()->format((float) $value); + return $this->getDurationFormatter()->format($value); } /** * Only used in twig filter |amount and invoice templates * - * @param string|float $amount + * @param string|float|null $amount * @return bool|false|string */ public function amount($amount) { + if ($amount === null) { + $amount = 0.00; + } + return $this->getNumberFormatter()->format($amount); } /** - * @param string $currency + * @param string|null $currency * @return string */ - public function currency($currency) + public function currency(?string $currency) { + if ($currency === null) { + return ''; + } + try { return Currencies::getSymbol(strtoupper($currency), $this->locale); } catch (\Exception $ex) { @@ -109,7 +121,7 @@ final class LocaleHelper } /** - * @param int|float $amount + * @param int|float|null $amount * @param string|null $currency * @param bool $withCurrency * @return string @@ -120,6 +132,10 @@ final class LocaleHelper $withCurrency = false; } + if ($amount === null) { + $amount = 0; + } + if (false === $withCurrency) { return $this->getMoneyFormatter($withCurrency)->format($amount, NumberFormatter::TYPE_DEFAULT); } diff --git a/src/Validator/Constraints/DateTimeFormatValidator.php b/src/Validator/Constraints/DateTimeFormatValidator.php index 7e179713..2edca46b 100644 --- a/src/Validator/Constraints/DateTimeFormatValidator.php +++ b/src/Validator/Constraints/DateTimeFormatValidator.php @@ -25,12 +25,20 @@ class DateTimeFormatValidator extends ConstraintValidator throw new UnexpectedTypeException($constraint, __NAMESPACE__ . '\DateTimeFormat'); } + if ($value === null) { + return; + } + $valid = true; - try { - $test = new \DateTime($value); - } catch (\Exception $ex) { + if (!\is_string($value)) { $valid = false; + } else { + try { + $test = new \DateTime($value); + } catch (\Exception $ex) { + $valid = false; + } } if (false === $valid) { diff --git a/tests/Entity/InvoiceDocumentTest.php b/tests/Entity/InvoiceDocumentTest.php index b0f86ed1..dbf534af 100644 --- a/tests/Entity/InvoiceDocumentTest.php +++ b/tests/Entity/InvoiceDocumentTest.php @@ -28,4 +28,27 @@ class InvoiceDocumentTest extends TestCase self::assertEquals('default.html.twig', $sut->getName()); self::assertIsInt($sut->getLastChange()); } + + public function testThrowsOnDeletedFile() + { + $catchedException = false; + + $dir = realpath(__DIR__ . '/../../templates/invoice/renderer'); + $file = tempnam($dir, 'invoice-renderer'); + + if ($file !== false) { + touch($file); + $sut = new InvoiceDocument(new \SplFileInfo($file)); + unlink($file); + + try { + // names are cached by SplFileInfo, so we need to trigger a function that does access the file + $sut->getLastChange(); + } catch (\Exception $exception) { + $catchedException = true; + } + } + + self::assertTrue($catchedException, 'Invoice document did not throw exception'); + } } diff --git a/tests/Invoice/Renderer/DocxRendererTest.php b/tests/Invoice/Renderer/DocxRendererTest.php index ef73181f..7a492f94 100644 --- a/tests/Invoice/Renderer/DocxRendererTest.php +++ b/tests/Invoice/Renderer/DocxRendererTest.php @@ -27,13 +27,11 @@ class DocxRendererTest extends TestCase $sut = $this->getAbstractRenderer(DocxRenderer::class); $this->assertFalse($sut->supports($this->getInvoiceDocument('default.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.html.twig'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.pdf.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertTrue($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); } public function testRender() diff --git a/tests/Invoice/Renderer/JsonRendererTest.php b/tests/Invoice/Renderer/JsonRendererTest.php index c274f7f8..09cd0d80 100644 --- a/tests/Invoice/Renderer/JsonRendererTest.php +++ b/tests/Invoice/Renderer/JsonRendererTest.php @@ -31,13 +31,10 @@ class JsonRendererTest extends KernelTestCase $sut = new JsonRenderer($env); $this->assertFalse($sut->supports($this->getInvoiceDocument('default.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); $this->assertFalse($sut->supports($this->getInvoiceDocument('text.txt.twig'))); $this->assertTrue($sut->supports($this->getInvoiceDocument('javascript.json.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('xml.xml.twig'))); diff --git a/tests/Invoice/Renderer/OdsRendererTest.php b/tests/Invoice/Renderer/OdsRendererTest.php index df090ace..89b88bac 100644 --- a/tests/Invoice/Renderer/OdsRendererTest.php +++ b/tests/Invoice/Renderer/OdsRendererTest.php @@ -29,12 +29,10 @@ class OdsRendererTest extends TestCase $sut = $this->getAbstractRenderer(OdsRenderer::class); $this->assertFalse($sut->supports($this->getInvoiceDocument('default.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.html.twig'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.pdf.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); $this->assertTrue($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); } diff --git a/tests/Invoice/Renderer/PdfRendererTest.php b/tests/Invoice/Renderer/PdfRendererTest.php index 5d831065..812d726a 100644 --- a/tests/Invoice/Renderer/PdfRendererTest.php +++ b/tests/Invoice/Renderer/PdfRendererTest.php @@ -48,11 +48,9 @@ class PdfRendererTest extends KernelTestCase $this->assertTrue($sut->supports($this->getInvoiceDocument('default.pdf.twig', true))); $this->assertTrue($sut->supports($this->getInvoiceDocument('freelancer.pdf.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); } public function testRender() diff --git a/tests/Invoice/Renderer/TextRendererTest.php b/tests/Invoice/Renderer/TextRendererTest.php index 477d1889..71872d2e 100644 --- a/tests/Invoice/Renderer/TextRendererTest.php +++ b/tests/Invoice/Renderer/TextRendererTest.php @@ -31,13 +31,11 @@ class TextRendererTest extends KernelTestCase $sut = new TextRenderer($env); $this->assertFalse($sut->supports($this->getInvoiceDocument('default.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.html.twig'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.pdf.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); $this->assertTrue($sut->supports($this->getInvoiceDocument('text.txt.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('javascript.json.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('xml.xml.twig'))); diff --git a/tests/Invoice/Renderer/TwigRendererTest.php b/tests/Invoice/Renderer/TwigRendererTest.php index e2086c10..70e55266 100644 --- a/tests/Invoice/Renderer/TwigRendererTest.php +++ b/tests/Invoice/Renderer/TwigRendererTest.php @@ -33,11 +33,9 @@ class TwigRendererTest extends KernelTestCase $this->assertTrue($sut->supports($this->getInvoiceDocument('default.html.twig'))); $this->assertTrue($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.pdf.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); } public function testRender() diff --git a/tests/Invoice/Renderer/XlsxRendererTest.php b/tests/Invoice/Renderer/XlsxRendererTest.php index 078168c5..4a4bd440 100644 --- a/tests/Invoice/Renderer/XlsxRendererTest.php +++ b/tests/Invoice/Renderer/XlsxRendererTest.php @@ -30,13 +30,11 @@ class XlsxRendererTest extends TestCase $sut = $this->getAbstractRenderer(XlsxRenderer::class); $this->assertFalse($sut->supports($this->getInvoiceDocument('default.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.html.twig'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.pdf.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); $this->assertTrue($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); } public function getTestModel() diff --git a/tests/Invoice/Renderer/XmlRendererTest.php b/tests/Invoice/Renderer/XmlRendererTest.php index 6f3eb61c..fada8672 100644 --- a/tests/Invoice/Renderer/XmlRendererTest.php +++ b/tests/Invoice/Renderer/XmlRendererTest.php @@ -31,14 +31,12 @@ class XmlRendererTest extends KernelTestCase $sut = new XmlRenderer($env); $this->assertFalse($sut->supports($this->getInvoiceDocument('default.html.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.html.twig'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('freelancer.pdf.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('timesheet.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('javascript.json.twig'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('foo.html.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('company.docx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('export.csv'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx'))); - $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods'))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('spreadsheet.xlsx', true))); + $this->assertFalse($sut->supports($this->getInvoiceDocument('open-spreadsheet.ods', true))); $this->assertFalse($sut->supports($this->getInvoiceDocument('text.txt.twig'))); $this->assertFalse($sut->supports($this->getInvoiceDocument('javascript.json.twig'))); $this->assertTrue($sut->supports($this->getInvoiceDocument('xml.xml.twig'))); diff --git a/tests/Validator/Constraints/DurationValidatorTest.php b/tests/Validator/Constraints/DurationValidatorTest.php index 1b247641..afc03f5a 100644 --- a/tests/Validator/Constraints/DurationValidatorTest.php +++ b/tests/Validator/Constraints/DurationValidatorTest.php @@ -69,7 +69,10 @@ class DurationValidatorTest extends ConstraintValidatorTestCase { $constraint = new Duration(); $this->validator->validate($input, $constraint); - $this->validator->validate(strtoupper($input), $constraint); + if ($input !== null) { + $input = strtoupper($input); + } + $this->validator->validate($input, $constraint); $this->assertNoViolation(); }