From d25bcfe29c4ff5d78b4e94b132477b18b0ecf175 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Wed, 26 Aug 2020 23:41:47 +0200 Subject: [PATCH] use configured language for non-twig invoice templates (#1924) --- src/Controller/InvoiceController.php | 45 ++- src/Invoice/DefaultInvoiceFormatter.php | 31 +- .../Hydrator/InvoiceModelDefaultHydrator.php | 4 +- src/Invoice/InvoiceFormatter.php | 20 +- src/Invoice/ServiceInvoice.php | 45 ++- src/Twig/DateExtensions.php | 134 +------ src/Twig/LocaleExtensions.php | 79 +--- src/Utils/LocaleFormatter.php | 356 ++++++++++++++++++ templates/invoice/index.html.twig | 2 +- tests/Controller/InvoiceControllerTest.php | 2 +- tests/Invoice/Renderer/RendererTestTrait.php | 14 +- tests/Invoice/ServiceInvoiceTest.php | 66 +++- tests/Twig/DateExtensionsTest.php | 2 + tests/Twig/LocaleExtensionsTest.php | 1 + 14 files changed, 531 insertions(+), 270 deletions(-) create mode 100644 src/Utils/LocaleFormatter.php diff --git a/src/Controller/InvoiceController.php b/src/Controller/InvoiceController.php index 67ec1114..e50b0038 100644 --- a/src/Controller/InvoiceController.php +++ b/src/Controller/InvoiceController.php @@ -23,6 +23,7 @@ use App\Repository\InvoiceTemplateRepository; use App\Repository\Query\BaseQuery; use App\Repository\Query\InvoiceQuery; use App\Timesheet\UserDateTimeFactory; +use Exception; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\SubmitButton; @@ -83,7 +84,6 @@ final class InvoiceController extends AbstractController $this->flashWarning('invoice.first_template'); } - $showPreview = false; $model = null; $query = $this->getDefaultQuery(); @@ -92,6 +92,11 @@ final class InvoiceController extends AbstractController $form->submit($request->query->all(), false); if ($this->isGranted('create_invoice') && $form->isValid()) { + // use the current request locale as fallback, if no translation was configured + if (null !== $query->getTemplate() && null === $query->getTemplate()->getLanguage()) { + $query->getTemplate()->setLanguage($request->getLocale()); + } + try { /** @var SubmitButton $createButton */ $createButton = $form->get('create'); @@ -104,7 +109,7 @@ final class InvoiceController extends AbstractController if ($printButton->isClicked()) { return $this->service->renderInvoice($query, $this->dispatcher); } - } catch (\Exception $ex) { + } catch (Exception $ex) { $this->logException($ex); $this->flashError('action.update.error', ['%reason%' => 'check doctor/logs']); } @@ -112,29 +117,23 @@ final class InvoiceController extends AbstractController /** @var SubmitButton $previewButton */ $previewButton = $form->get('preview'); if ($previewButton->isClicked()) { - $showPreview = true; - } - } - - try { - $model = $this->service->createModel($query); - if ($showPreview) { - $entries = $this->service->findInvoiceItems($query); - if (!empty($entries)) { - $model->addEntries($entries); + try { + $model = $this->service->createModel($query); + $entries = $this->service->findInvoiceItems($query); + if (!empty($entries)) { + $model->addEntries($entries); + } + } catch (Exception $ex) { + $this->logException($ex); + $this->flashError($ex->getMessage()); } } - } catch (\Exception $ex) { - $this->logException($ex); - $this->flashError($ex->getMessage()); - $showPreview = false; } return $this->render('invoice/index.html.twig', [ 'query' => $query, 'model' => $model, 'form' => $form->createView(), - 'preview' => $showPreview, ]); } @@ -174,7 +173,7 @@ final class InvoiceController extends AbstractController $file = $this->service->getInvoiceFile($invoice); return $this->file($file->getRealPath(), $file->getBasename()); - } catch (\Exception $ex) { + } catch (Exception $ex) { $this->flashError($ex->getMessage()); } @@ -190,7 +189,7 @@ final class InvoiceController extends AbstractController try { $this->service->changeInvoiceStatus($invoice, $status); $this->flashSuccess('action.update.success'); - } catch (\Exception $ex) { + } catch (Exception $ex) { $this->flashError('action.update.error'); } @@ -206,7 +205,7 @@ final class InvoiceController extends AbstractController try { $this->service->deleteInvoice($invoice); $this->flashSuccess('action.delete.success'); - } catch (\Exception $ex) { + } catch (Exception $ex) { $this->flashError('action.delete.error'); } @@ -330,7 +329,7 @@ final class InvoiceController extends AbstractController $this->flashSuccess('action.update.success'); return $this->redirectToRoute('admin_invoice_document_upload'); - } catch (\Exception $e) { + } catch (Exception $e) { $this->flashError( sprintf('Failed uploading invoice document: %e', $e->getMessage()) ); @@ -375,7 +374,7 @@ final class InvoiceController extends AbstractController try { $this->templateRepository->removeTemplate($template); $this->flashSuccess('action.delete.success'); - } catch (\Exception $ex) { + } catch (Exception $ex) { $this->flashError('action.delete.error', ['%reason%' => $ex->getMessage()]); } @@ -394,7 +393,7 @@ final class InvoiceController extends AbstractController $this->flashSuccess('action.update.success'); return $this->redirectToRoute('admin_invoice_template'); - } catch (\Exception $ex) { + } catch (Exception $ex) { $this->flashError('action.update.error', ['%reason%' => $ex->getMessage()]); } } diff --git a/src/Invoice/DefaultInvoiceFormatter.php b/src/Invoice/DefaultInvoiceFormatter.php index 3a19ebfb..870d1bba 100644 --- a/src/Invoice/DefaultInvoiceFormatter.php +++ b/src/Invoice/DefaultInvoiceFormatter.php @@ -9,24 +9,19 @@ namespace App\Invoice; -use App\Twig\DateExtensions; -use App\Twig\LocaleExtensions; +use App\Configuration\LanguageFormattings; +use App\Utils\LocaleFormatter; final class DefaultInvoiceFormatter implements InvoiceFormatter { /** - * @var DateExtensions + * @var LocaleFormatter */ - private $dateExtension; - /** - * @var LocaleExtensions - */ - private $extension; + private $formatter; - public function __construct(DateExtensions $dateExtension, LocaleExtensions $extensions) + public function __construct(LanguageFormattings $formats, string $locale) { - $this->dateExtension = $dateExtension; - $this->extension = $extensions; + $this->formatter = new LocaleFormatter($formats, $locale); } /** @@ -35,7 +30,7 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter */ public function getFormattedDateTime(\DateTime $date) { - return $this->dateExtension->dateShort($date); + return $this->formatter->dateShort($date); } /** @@ -44,7 +39,7 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter */ public function getFormattedTime(\DateTime $date) { - return $this->dateExtension->time($date); + return $this->formatter->time($date); } /** @@ -53,7 +48,7 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter */ public function getFormattedMonthName(\DateTime $date) { - return $this->dateExtension->monthName($date); + return $this->formatter->monthName($date); } /** @@ -64,7 +59,7 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter */ public function getFormattedMoney($amount, ?string $currency, bool $withCurrency = true) { - return $this->extension->money($amount, $currency, $withCurrency); + return $this->formatter->money($amount, $currency, $withCurrency); } /** @@ -73,7 +68,7 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter */ public function getFormattedDuration($seconds) { - return $this->extension->duration($seconds); + return $this->formatter->duration($seconds); } /** @@ -82,11 +77,11 @@ final class DefaultInvoiceFormatter implements InvoiceFormatter */ public function getFormattedDecimalDuration($seconds) { - return $this->extension->durationDecimal($seconds); + return $this->formatter->durationDecimal($seconds); } public function getCurrencySymbol(string $currency): string { - return $this->extension->currency($currency); + return $this->formatter->currency($currency); } } diff --git a/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php b/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php index 545720b1..62729091 100644 --- a/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php +++ b/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php @@ -22,7 +22,7 @@ class InvoiceModelDefaultHydrator implements InvoiceModelHydrator $subtotal = $model->getCalculator()->getSubtotal(); $formatter = $model->getFormatter(); - $values = [ + return [ 'invoice.due_date' => $formatter->getFormattedDateTime($model->getDueDate()), 'invoice.date' => $formatter->getFormattedDateTime($model->getInvoiceDate()), 'invoice.number' => $model->getInvoiceNumber(), @@ -67,7 +67,5 @@ class InvoiceModelDefaultHydrator implements InvoiceModelHydrator 'query.end_month_number' => $model->getQuery()->getEnd()->format('m'), // since 1.9 'query.end_year' => $model->getQuery()->getEnd()->format('Y'), // since 1.9 ]; - - return $values; } } diff --git a/src/Invoice/InvoiceFormatter.php b/src/Invoice/InvoiceFormatter.php index de2479fc..d5279ada 100644 --- a/src/Invoice/InvoiceFormatter.php +++ b/src/Invoice/InvoiceFormatter.php @@ -9,22 +9,24 @@ namespace App\Invoice; +use DateTime; + /** * @internal this is subject to change */ interface InvoiceFormatter { /** - * @param \DateTime $date + * @param DateTime $date * @return mixed */ - public function getFormattedDateTime(\DateTime $date); + public function getFormattedDateTime(DateTime $date); /** - * @param \DateTime $date + * @param DateTime $date * @return mixed */ - public function getFormattedTime(\DateTime $date); + public function getFormattedTime(DateTime $date); /** * @param int|float $amount @@ -35,10 +37,10 @@ interface InvoiceFormatter public function getFormattedMoney($amount, ?string $currency, bool $withCurrency = true); /** - * @param \DateTime $date + * @param DateTime $date * @return mixed */ - public function getFormattedMonthName(\DateTime $date); + public function getFormattedMonthName(DateTime $date); /** * @param int $seconds @@ -52,5 +54,11 @@ interface InvoiceFormatter */ public function getFormattedDecimalDuration($seconds); + /** + * Returns the currency symbol for the given currency by name. + * + * @param string $currency + * @return string + */ public function getCurrencySymbol(string $currency): string; } diff --git a/src/Invoice/ServiceInvoice.php b/src/Invoice/ServiceInvoice.php index 346eb120..af9e4134 100644 --- a/src/Invoice/ServiceInvoice.php +++ b/src/Invoice/ServiceInvoice.php @@ -9,6 +9,8 @@ namespace App\Invoice; +use App\Configuration\LanguageFormattings; +use App\Constants; use App\Entity\Invoice; use App\Entity\InvoiceDocument; use App\Event\InvoiceCreatedEvent; @@ -57,7 +59,7 @@ final class ServiceInvoice */ private $dateTimeFactory; /** - * @var InvoiceFormatter + * @var LanguageFormattings */ private $formatter; /** @@ -65,7 +67,7 @@ final class ServiceInvoice */ private $invoiceRepository; - public function __construct(InvoiceDocumentRepository $repository, FileHelper $fileHelper, InvoiceRepository $invoiceRepository, UserDateTimeFactory $dateTimeFactory, InvoiceFormatter $formatter) + public function __construct(InvoiceDocumentRepository $repository, FileHelper $fileHelper, InvoiceRepository $invoiceRepository, UserDateTimeFactory $dateTimeFactory, LanguageFormattings $formatter) { $this->documents = $repository; $this->fileHelper = $fileHelper; @@ -417,8 +419,20 @@ final class ServiceInvoice */ public function createModel(InvoiceQuery $query): InvoiceModel { - $model = new InvoiceModel($this->formatter); + $template = $query->getTemplate(); + + if (null === $template) { + throw new \Exception('Cannot create invoice model without template'); + } + + if (null === $template->getLanguage()) { + $template->setLanguage(Constants::DEFAULT_LOCALE); + @trigger_error('Using invoice templates without a language is is deprecated and trigger and will throw an exception with 2.0', E_USER_DEPRECATED); + } + + $model = new InvoiceModel(new DefaultInvoiceFormatter($this->formatter, $template->getLanguage())); $model + ->setTemplate($template) ->setInvoiceDate($this->dateTimeFactory->createDateTime()) ->setQuery($query) ; @@ -431,22 +445,19 @@ final class ServiceInvoice $model->setCustomer($query->getCustomers()[0]); } - if ($query->getTemplate() !== null) { - $generator = $this->getNumberGeneratorByName($query->getTemplate()->getNumberGenerator()); - if (null === $generator) { - throw new \Exception('Unknown number generator: ' . $query->getTemplate()->getNumberGenerator()); - } - - $calculator = $this->getCalculatorByName($query->getTemplate()->getCalculator()); - if (null === $calculator) { - throw new \Exception('Unknown invoice calculator: ' . $query->getTemplate()->getCalculator()); - } - - $model->setTemplate($query->getTemplate()); - $model->setCalculator($calculator); - $model->setNumberGenerator($generator); + $generator = $this->getNumberGeneratorByName($query->getTemplate()->getNumberGenerator()); + if (null === $generator) { + throw new \Exception('Unknown number generator: ' . $query->getTemplate()->getNumberGenerator()); } + $calculator = $this->getCalculatorByName($query->getTemplate()->getCalculator()); + if (null === $calculator) { + throw new \Exception('Unknown invoice calculator: ' . $query->getTemplate()->getCalculator()); + } + + $model->setCalculator($calculator); + $model->setNumberGenerator($generator); + return $model; } } diff --git a/src/Twig/DateExtensions.php b/src/Twig/DateExtensions.php index 35f75ad8..a9434d06 100644 --- a/src/Twig/DateExtensions.php +++ b/src/Twig/DateExtensions.php @@ -12,6 +12,7 @@ namespace App\Twig; use App\Configuration\LanguageFormattings; use App\Constants; use App\Utils\LocaleFormats; +use App\Utils\LocaleFormatter; use DateTime; use Symfony\Component\HttpFoundation\RequestStack; use Twig\Extension\AbstractExtension; @@ -29,29 +30,9 @@ class DateExtensions extends AbstractExtension */ protected $localeFormats = null; /** - * @var string + * @var LocaleFormatter */ - protected $dateFormat = null; - /** - * @var string - */ - protected $dateTimeFormat = null; - /** - * @var string - */ - protected $dateTimeTypeFormat = null; - /** - * @var string - */ - protected $timeFormat = null; - /** - * @var bool - */ - protected $isTwentyFourHour = null; - /** - * @var string - */ - private $locale; + private $formatter; /** * @var LanguageFormattings */ @@ -118,7 +99,7 @@ class DateExtensions extends AbstractExtension */ public function setLocale(string $locale) { - $this->locale = $locale; + $this->formatter = new LocaleFormatter($this->formats, $locale); $this->localeFormats = new LocaleFormats($this->formats, $locale); } @@ -128,19 +109,7 @@ class DateExtensions extends AbstractExtension */ public function dateShort($date) { - if (null === $this->dateFormat) { - $this->dateFormat = $this->localeFormats->getDateFormat(); - } - - if (!$date instanceof DateTime) { - try { - $date = new DateTime($date); - } catch (\Exception $ex) { - return $date; - } - } - - return $date->format($this->dateFormat); + return $this->formatter->dateShort($date); } /** @@ -149,19 +118,7 @@ class DateExtensions extends AbstractExtension */ public function dateTime($date) { - if (null === $this->dateTimeFormat) { - $this->dateTimeFormat = $this->localeFormats->getDateTimeFormat(); - } - - if (!$date instanceof DateTime) { - try { - $date = new DateTime($date); - } catch (\Exception $ex) { - return $date; - } - } - - return $date->format($this->dateTimeFormat); + return $this->formatter->dateTime($date); } /** @@ -170,28 +127,7 @@ class DateExtensions extends AbstractExtension */ public function dateTimeFull($date) { - if (null === $this->dateTimeTypeFormat) { - $this->dateTimeTypeFormat = $this->localeFormats->getDateTimeTypeFormat(); - } - - if (!$date instanceof DateTime) { - try { - $date = new DateTime($date); - } catch (\Exception $ex) { - return $date; - } - } - - $formatter = new \IntlDateFormatter( - $this->locale, - \IntlDateFormatter::MEDIUM, - \IntlDateFormatter::MEDIUM, - date_default_timezone_get(), - \IntlDateFormatter::GREGORIAN, - $this->dateTimeTypeFormat - ); - - return $formatter->format($date); + return $this->formatter->dateTimeFull($date); } /** @@ -202,15 +138,7 @@ class DateExtensions extends AbstractExtension */ public function dateFormat($date, string $format) { - if (!$date instanceof DateTime) { - try { - $date = new DateTime($date); - } catch (\Exception $ex) { - return $date; - } - } - - return $date->format($format); + return $this->formatter->dateFormat($date, $format); } /** @@ -219,47 +147,17 @@ class DateExtensions extends AbstractExtension */ public function time($date) { - if (null === $this->timeFormat) { - $this->timeFormat = $this->localeFormats->getTimeFormat(); - } - - if (!$date instanceof DateTime) { - $date = new DateTime($date); - } - - return $date->format($this->timeFormat); - } - - /** - * @see https://framework.zend.com/manual/1.12/en/zend.date.constants.html#zend.date.constants.selfdefinedformats - * @see http://userguide.icu-project.org/formatparse/datetime - * - * @param DateTime $dateTime - * @param string $format - * @return string - */ - private function formatIntl(\DateTime $dateTime, string $format): string - { - $formatter = new \IntlDateFormatter( - $this->locale, - \IntlDateFormatter::FULL, - \IntlDateFormatter::FULL, - $dateTime->getTimezone()->getName(), - \IntlDateFormatter::GREGORIAN, - $format - ); - - return $formatter->format($dateTime); + return $this->formatter->time($date); } public function monthName(\DateTime $dateTime, bool $withYear = false): string { - return $this->formatIntl($dateTime, ($withYear ? 'LLLL yyyy' : 'LLLL')); + return $this->formatter->monthName($dateTime, $withYear); } public function dayName(\DateTime $dateTime, bool $short = false): string { - return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE')); + return $this->formatter->dayName($dateTime, $short); } /** @@ -269,15 +167,7 @@ class DateExtensions extends AbstractExtension */ public function hour24($twentyFour, $twelveHour) { - if (null === $this->isTwentyFourHour) { - $this->isTwentyFourHour = $this->localeFormats->isTwentyFourHours(); - } - - if (true === $this->isTwentyFourHour) { - return $twentyFour; - } - - return $twelveHour; + return $this->formatter->hour24($twentyFour, $twelveHour); } /** diff --git a/src/Twig/LocaleExtensions.php b/src/Twig/LocaleExtensions.php index f3c19ce7..83354346 100644 --- a/src/Twig/LocaleExtensions.php +++ b/src/Twig/LocaleExtensions.php @@ -13,8 +13,7 @@ use App\Configuration\LanguageFormattings; use App\Constants; use App\Entity\Timesheet; use App\Utils\Duration; -use App\Utils\LocaleFormats; -use App\Utils\LocaleHelper; +use App\Utils\LocaleFormatter; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Intl\Locales; use Twig\Extension\AbstractExtension; @@ -27,17 +26,9 @@ use Twig\TwigFunction; final class LocaleExtensions extends AbstractExtension { /** - * @var LocaleFormats + * @var LocaleFormatter */ - private $localeFormats; - /** - * @var Duration - */ - private $durationFormatter; - /** - * @var LocaleHelper - */ - private $helper; + private $formatter; /** * @var LanguageFormattings */ @@ -52,7 +43,6 @@ final class LocaleExtensions extends AbstractExtension $locale = $requestStack->getMasterRequest()->getLocale(); } - $this->durationFormatter = new Duration(); $this->formats = $formats; $this->setLocale($locale); } @@ -90,66 +80,30 @@ final class LocaleExtensions extends AbstractExtension */ public function setLocale(string $locale) { - $this->helper = new LocaleHelper($locale); - $this->localeFormats = new LocaleFormats($this->formats, $locale); + $this->formatter = new LocaleFormatter($this->formats, $locale); } /** * Transforms seconds into a duration string. * - * @param int|Timesheet $duration + * @param int|Timesheet|null $duration * @param bool $decimal * @return string */ public function duration($duration, $decimal = false) { - if ($decimal) { - return $this->durationDecimal($duration); - } - - $seconds = $this->getSecondsForDuration($duration); - $format = $this->localeFormats->getDurationFormat(); - - return $this->formatDuration($seconds, $format); + return $this->formatter->duration($duration, $decimal); } /** * Transforms seconds into a decimal formatted duration string. * - * @param int|Timesheet $duration + * @param int|Timesheet|null $duration * @return string */ public function durationDecimal($duration) { - $seconds = $this->getSecondsForDuration($duration); - - return $this->helper->durationDecimal($seconds); - } - - private function getSecondsForDuration($duration): int - { - if (null === $duration) { - $duration = 0; - } - - if ($duration instanceof Timesheet) { - if (null === $duration->getEnd()) { - $duration = time() - $duration->getBegin()->getTimestamp(); - } else { - $duration = $duration->getDuration(); - } - } - - return (int) $duration; - } - - private function formatDuration(int $seconds, string $format): string - { - if ($seconds < 0) { - return '?'; - } - - return $this->durationFormatter->format($seconds, $format); + return $this->formatter->durationDecimal($duration); } /** @@ -158,7 +112,7 @@ final class LocaleExtensions extends AbstractExtension */ public function amount($amount) { - return $this->helper->amount($amount); + return $this->formatter->amount($amount); } /** @@ -169,7 +123,7 @@ final class LocaleExtensions extends AbstractExtension */ public function currency($currency) { - return $this->helper->currency($currency); + return $this->formatter->currency($currency); } /** @@ -178,7 +132,7 @@ final class LocaleExtensions extends AbstractExtension */ public function language($language) { - return $this->helper->language($language); + return $this->formatter->language($language); } /** @@ -187,7 +141,7 @@ final class LocaleExtensions extends AbstractExtension */ public function country($country) { - return $this->helper->country($country); + return $this->formatter->country($country); } /** @@ -198,7 +152,7 @@ final class LocaleExtensions extends AbstractExtension */ public function money($amount, ?string $currency = null, bool $withCurrency = true) { - return $this->helper->money($amount, $currency, $withCurrency); + return $this->formatter->money($amount, $currency, $withCurrency); } /** @@ -210,11 +164,6 @@ final class LocaleExtensions extends AbstractExtension */ public function getLocales() { - $locales = []; - foreach ($this->localeFormats->getAvailableLanguages() as $locale) { - $locales[] = ['code' => $locale, 'name' => Locales::getName($locale, $locale)]; - } - - return $locales; + return $this->formatter->getLocales(); } } diff --git a/src/Utils/LocaleFormatter.php b/src/Utils/LocaleFormatter.php new file mode 100644 index 00000000..42a30545 --- /dev/null +++ b/src/Utils/LocaleFormatter.php @@ -0,0 +1,356 @@ +locale = $locale; + $this->durationFormatter = new Duration(); + $this->helper = new LocaleHelper($locale); + $this->localeFormats = new LocaleFormats($formats, $locale); + } + + /** + * Transforms seconds into a duration string. + * + * @param int|Timesheet|null $duration + * @param bool $decimal + * @return string + */ + public function duration($duration, $decimal = false) + { + if ($decimal) { + return $this->durationDecimal($duration); + } + + $seconds = $this->getSecondsForDuration($duration); + $format = $this->localeFormats->getDurationFormat(); + + return $this->formatDuration($seconds, $format); + } + + /** + * Transforms seconds into a decimal formatted duration string. + * + * @param int|Timesheet|null $duration + * @return string + */ + public function durationDecimal($duration) + { + $seconds = $this->getSecondsForDuration($duration); + + return $this->helper->durationDecimal($seconds); + } + + /** + * @param int|Timesheet|null $duration + * @return int + */ + private function getSecondsForDuration($duration): int + { + if (null === $duration) { + return 0; + } + + if ($duration instanceof Timesheet) { + if (null === $duration->getEnd()) { + $duration = time() - $duration->getBegin()->getTimestamp(); + } else { + $duration = $duration->getDuration(); + } + } + + return (int) $duration; + } + + private function formatDuration(int $seconds, string $format): string + { + if ($seconds < 0) { + return '?'; + } + + return $this->durationFormatter->format($seconds, $format); + } + + /** + * @param string|float $amount + * @return bool|false|string + */ + public function amount($amount) + { + return $this->helper->amount($amount); + } + + /** + * Returns the currency symbol. + * + * @param string $currency + * @return string + */ + public function currency($currency) + { + return $this->helper->currency($currency); + } + + /** + * @param string $language + * @return string + */ + public function language($language) + { + return $this->helper->language($language); + } + + /** + * @param string $country + * @return string + */ + public function country($country) + { + return $this->helper->country($country); + } + + /** + * @param float $amount + * @param string|null $currency + * @param bool $withCurrency + * @return string + */ + public function money($amount, ?string $currency = null, bool $withCurrency = true) + { + return $this->helper->money($amount, $currency, $withCurrency); + } + + /** + * Takes the list of codes of the locales (languages) enabled in the + * application and returns an array with the name of each locale written + * in its own language (e.g. English, Français, Español, etc.) + * + * @return array + */ + public function getLocales() + { + $locales = []; + foreach ($this->localeFormats->getAvailableLanguages() as $locale) { + $locales[] = ['code' => $locale, 'name' => Locales::getName($locale, $locale)]; + } + + return $locales; + } + + /** + * @param DateTime|string $date + * @return string + */ + public function dateShort($date) + { + if (null === $this->dateFormat) { + $this->dateFormat = $this->localeFormats->getDateFormat(); + } + + if (!$date instanceof DateTime) { + try { + $date = new DateTime($date); + } catch (Exception $ex) { + return $date; + } + } + + return $date->format($this->dateFormat); + } + + /** + * @param DateTime|string $date + * @return string + */ + public function dateTime($date) + { + if (null === $this->dateTimeFormat) { + $this->dateTimeFormat = $this->localeFormats->getDateTimeFormat(); + } + + if (!$date instanceof DateTime) { + try { + $date = new DateTime($date); + } catch (Exception $ex) { + return $date; + } + } + + return $date->format($this->dateTimeFormat); + } + + /** + * @param DateTime|string $date + * @return bool|false|string + */ + public function dateTimeFull($date) + { + if (null === $this->dateTimeTypeFormat) { + $this->dateTimeTypeFormat = $this->localeFormats->getDateTimeTypeFormat(); + } + + if (!$date instanceof DateTime) { + try { + $date = new DateTime($date); + } catch (Exception $ex) { + return $date; + } + } + + $formatter = new IntlDateFormatter( + $this->locale, + IntlDateFormatter::MEDIUM, + IntlDateFormatter::MEDIUM, + date_default_timezone_get(), + IntlDateFormatter::GREGORIAN, + $this->dateTimeTypeFormat + ); + + return $formatter->format($date); + } + + /** + * @param DateTime|string $date + * @param string $format + * @return false|string + * @throws Exception + */ + public function dateFormat($date, string $format) + { + if (!$date instanceof DateTime) { + try { + $date = new DateTime($date); + } catch (Exception $ex) { + return $date; + } + } + + return $date->format($format); + } + + /** + * @param DateTime|string $date + * @return string + * @throws Exception + */ + public function time($date) + { + if (null === $this->timeFormat) { + $this->timeFormat = $this->localeFormats->getTimeFormat(); + } + + if (!$date instanceof DateTime) { + $date = new DateTime($date); + } + + return $date->format($this->timeFormat); + } + + /** + * @see https://framework.zend.com/manual/1.12/en/zend.date.constants.html#zend.date.constants.selfdefinedformats + * @see http://userguide.icu-project.org/formatparse/datetime + * + * @param DateTime $dateTime + * @param string $format + * @return string + */ + private function formatIntl(\DateTime $dateTime, string $format): string + { + $formatter = new IntlDateFormatter( + $this->locale, + IntlDateFormatter::FULL, + IntlDateFormatter::FULL, + $dateTime->getTimezone()->getName(), + IntlDateFormatter::GREGORIAN, + $format + ); + + return $formatter->format($dateTime); + } + + public function monthName(\DateTime $dateTime, bool $withYear = false): string + { + return $this->formatIntl($dateTime, ($withYear ? 'LLLL yyyy' : 'LLLL')); + } + + public function dayName(\DateTime $dateTime, bool $short = false): string + { + return $this->formatIntl($dateTime, ($short ? 'EE' : 'EEEE')); + } + + /** + * @param mixed $twentyFour + * @param mixed $twelveHour + * @return mixed + */ + public function hour24($twentyFour, $twelveHour) + { + if (null === $this->isTwentyFourHour) { + $this->isTwentyFourHour = $this->localeFormats->isTwentyFourHours(); + } + + if (true === $this->isTwentyFourHour) { + return $twentyFour; + } + + return $twelveHour; + } +} diff --git a/templates/invoice/index.html.twig b/templates/invoice/index.html.twig index 32e9bb04..165d1c34 100644 --- a/templates/invoice/index.html.twig +++ b/templates/invoice/index.html.twig @@ -69,7 +69,7 @@ {{ widgets.callout('danger', 'http_error_403.suggestion'|trans({}, 'exceptions')) }} {% endif %} - {% if preview %} + {% if model is not null %} {% if model.calculator is empty or model.calculator.entries is empty %} {{ widgets.nothing_found() }} {% else %} diff --git a/tests/Controller/InvoiceControllerTest.php b/tests/Controller/InvoiceControllerTest.php index 95c287c8..b43e7f79 100644 --- a/tests/Controller/InvoiceControllerTest.php +++ b/tests/Controller/InvoiceControllerTest.php @@ -72,7 +72,7 @@ class InvoiceControllerTest extends ControllerBaseTest $fixture = new InvoiceFixtures(); $this->importFixture($fixture); - $this->request($client, '/invoice/?preview='); + $this->request($client, '/invoice/?customer=1&template=1&preview='); $this->assertTrue($client->getResponse()->isSuccessful()); $this->assertHasNoEntriesWithFilter($client); diff --git a/tests/Invoice/Renderer/RendererTestTrait.php b/tests/Invoice/Renderer/RendererTestTrait.php index cc628591..c3123886 100644 --- a/tests/Invoice/Renderer/RendererTestTrait.php +++ b/tests/Invoice/Renderer/RendererTestTrait.php @@ -29,10 +29,6 @@ use App\Invoice\InvoiceModel; use App\Invoice\NumberGenerator\DateNumberGenerator; use App\Invoice\Renderer\AbstractRenderer; use App\Repository\Query\InvoiceQuery; -use App\Twig\DateExtensions; -use App\Twig\LocaleExtensions; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; trait RendererTestTrait { @@ -72,7 +68,6 @@ trait RendererTestTrait protected function getFormatter(): InvoiceFormatter { - $requestStack = new RequestStack(); $languages = [ 'en' => [ 'date' => 'Y.m.d', @@ -81,16 +76,9 @@ trait RendererTestTrait ] ]; - $request = new Request(); - $request->setLocale('en'); - $requestStack->push($request); - $formattings = new LanguageFormattings($languages); - $dateExtension = new DateExtensions($requestStack, $formattings); - $extensions = new LocaleExtensions($requestStack, $formattings); - - return new DefaultInvoiceFormatter($dateExtension, $extensions); + return new DefaultInvoiceFormatter($formattings, 'en'); } protected function getInvoiceModel(): InvoiceModel diff --git a/tests/Invoice/ServiceInvoiceTest.php b/tests/Invoice/ServiceInvoiceTest.php index 48739402..320b3ff6 100644 --- a/tests/Invoice/ServiceInvoiceTest.php +++ b/tests/Invoice/ServiceInvoiceTest.php @@ -9,14 +9,17 @@ namespace App\Tests\Invoice; +use App\Configuration\LanguageFormattings; use App\Entity\Invoice; use App\Entity\InvoiceDocument; +use App\Entity\InvoiceTemplate; use App\Invoice\Calculator\DefaultCalculator; use App\Invoice\NumberGenerator\DateNumberGenerator; use App\Invoice\Renderer\TwigRenderer; use App\Invoice\ServiceInvoice; use App\Repository\InvoiceDocumentRepository; use App\Repository\InvoiceRepository; +use App\Repository\Query\InvoiceQuery; use App\Tests\Mocks\Security\UserDateTimeFactoryFactory; use App\Utils\FileHelper; use PHPUnit\Framework\TestCase; @@ -29,11 +32,21 @@ class ServiceInvoiceTest extends TestCase { private function getSut(array $paths): ServiceInvoice { + $languages = [ + 'en' => [ + 'date' => 'Y.m.d', + 'duration' => '%h:%m h', + 'time' => 'H:i', + ] + ]; + + $formattings = new LanguageFormattings($languages); + $repo = new InvoiceDocumentRepository($paths); $invoiceRepo = $this->createMock(InvoiceRepository::class); $userDateTime = (new UserDateTimeFactoryFactory($this))->create(); - return new ServiceInvoice($repo, new FileHelper(realpath(__DIR__ . '/../../var/data/')), $invoiceRepo, $userDateTime, new DebugFormatter()); + return new ServiceInvoice($repo, new FileHelper(realpath(__DIR__ . '/../../var/data/')), $invoiceRepo, $userDateTime, $formattings); } public function testInvalidExceptionOnChangeState() @@ -96,4 +109,55 @@ class ServiceInvoiceTest extends TestCase $this->assertEquals(1, \count($sut->getRenderer())); } + + public function testCreateModelThrowsOnMissingTemplate() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Cannot create invoice model without template'); + + $sut = $this->getSut([]); + $sut->createModel(new InvoiceQuery()); + } + + /** + * @group legacy + */ + public function testCreateModelSetsFallbackLanguage() + { + $template = new InvoiceTemplate(); + $template->setNumberGenerator('date'); + + self::assertNull($template->getLanguage()); + + $query = new InvoiceQuery(); + $query->setTemplate($template); + + $sut = $this->getSut([]); + $sut->addCalculator(new DefaultCalculator()); + $sut->addNumberGenerator(new DateNumberGenerator()); + + $model = $sut->createModel($query); + + self::assertEquals('en', $model->getTemplate()->getLanguage()); + } + + public function testCreateModelUsesTemplateLanguage() + { + $template = new InvoiceTemplate(); + $template->setNumberGenerator('date'); + $template->setLanguage('de'); + + self::assertEquals('de', $template->getLanguage()); + + $query = new InvoiceQuery(); + $query->setTemplate($template); + + $sut = $this->getSut([]); + $sut->addCalculator(new DefaultCalculator()); + $sut->addNumberGenerator(new DateNumberGenerator()); + + $model = $sut->createModel($query); + + self::assertEquals('de', $model->getTemplate()->getLanguage()); + } } diff --git a/tests/Twig/DateExtensionsTest.php b/tests/Twig/DateExtensionsTest.php index 9c1b743f..a2fb2a32 100644 --- a/tests/Twig/DateExtensionsTest.php +++ b/tests/Twig/DateExtensionsTest.php @@ -19,6 +19,8 @@ use Twig\TwigFunction; /** * @covers \App\Twig\DateExtensions + * @covers \App\Utils\LocaleFormats + * @covers \App\Utils\LocaleFormatter */ class DateExtensionsTest extends TestCase { diff --git a/tests/Twig/LocaleExtensionsTest.php b/tests/Twig/LocaleExtensionsTest.php index fb4a2859..37d67fcf 100644 --- a/tests/Twig/LocaleExtensionsTest.php +++ b/tests/Twig/LocaleExtensionsTest.php @@ -21,6 +21,7 @@ use Twig\TwigFunction; /** * @covers \App\Twig\LocaleExtensions + * @covers \App\Utils\LocaleFormatter */ class LocaleExtensionsTest extends TestCase {