From 935c54a61fe0b0bfbc779e13c5818a35f823d6cd Mon Sep 17 00:00:00 2001
From: Kevin Papst <kpapst@gmx.net>
Date: Fri, 21 Jan 2022 00:35:09 +0100
Subject: [PATCH] new export template

---
 src/Export/Base/RendererTrait.php             |  8 +--
 src/Invoice/Renderer/AbstractTwigRenderer.php | 41 +++---------
 src/Twig/LocaleFormatExtensions.php           |  2 +-
 src/Twig/TwigRendererTrait.php                | 66 +++++++++++++++++++
 templates/export/pdf-layout.html.twig         | 37 ++++++-----
 templates/export/renderer/timesheet.pdf.twig  | 18 +++++
 translations/export.de.xlf                    |  4 ++
 translations/export.en.xlf                    | 18 +++--
 8 files changed, 134 insertions(+), 60 deletions(-)
 create mode 100644 src/Twig/TwigRendererTrait.php
 create mode 100644 templates/export/renderer/timesheet.pdf.twig

diff --git a/src/Export/Base/RendererTrait.php b/src/Export/Base/RendererTrait.php
index bf091e2ee..32b6354c8 100644
--- a/src/Export/Base/RendererTrait.php
+++ b/src/Export/Base/RendererTrait.php
@@ -10,14 +10,14 @@
 namespace App\Export\Base;
 
 use App\Activity\ActivityStatisticService;
-use App\Export\ExportItemInterface;
+use App\Invoice\InvoiceItemInterface;
 use App\Project\ProjectStatisticService;
 use App\Repository\Query\TimesheetQuery;
 
 trait RendererTrait
 {
     /**
-     * @param ExportItemInterface[] $exportItems
+     * @param InvoiceItemInterface[] $exportItems
      * @return array
      */
     protected function calculateSummary(array $exportItems)
@@ -130,7 +130,7 @@ trait RendererTrait
     }
 
     /**
-     * @param ExportItemInterface[] $exportItems
+     * @param InvoiceItemInterface[] $exportItems
      * @param TimesheetQuery $query
      * @param ProjectStatisticService $projectStatisticService
      * @return array
@@ -209,7 +209,7 @@ trait RendererTrait
     }
 
     /**
-     * @param ExportItemInterface[] $exportItems
+     * @param InvoiceItemInterface[] $exportItems
      * @param TimesheetQuery $query
      * @param ActivityStatisticService $activityStatisticService
      * @return array
diff --git a/src/Invoice/Renderer/AbstractTwigRenderer.php b/src/Invoice/Renderer/AbstractTwigRenderer.php
index 4ceaf9092..7606b9667 100644
--- a/src/Invoice/Renderer/AbstractTwigRenderer.php
+++ b/src/Invoice/Renderer/AbstractTwigRenderer.php
@@ -12,9 +12,7 @@ namespace App\Invoice\Renderer;
 use App\Entity\InvoiceDocument;
 use App\Invoice\InvoiceModel;
 use App\Invoice\RendererInterface;
-use App\Twig\LocaleFormatExtensions;
-use Symfony\Bridge\Twig\Extension\TranslationExtension;
-use Symfony\Contracts\Translation\LocaleAwareInterface;
+use App\Twig\TwigRendererTrait;
 use Twig\Environment;
 
 /**
@@ -22,6 +20,8 @@ use Twig\Environment;
  */
 abstract class AbstractTwigRenderer implements RendererInterface
 {
+    use TwigRendererTrait;
+
     /**
      * @var Environment
      */
@@ -34,39 +34,16 @@ abstract class AbstractTwigRenderer implements RendererInterface
 
     protected function renderTwigTemplate(InvoiceDocument $document, InvoiceModel $model): string
     {
-        $previousLocale = $this->changeTwigLocale($this->twig, $model->getTemplate()->getLanguage());
-
-        $content = $this->twig->render('@invoice/' . basename($document->getFilename()), [
+        $language = $model->getTemplate()->getLanguage();
+        $formatLocale = $model->getFormatter()->getLocale();
+        $template = '@invoice/' . basename($document->getFilename());
+        $options = [
             // model should not be used in the future, but we can likely not remove it
             'model' => $model,
             // new since 1.16.7 - templates should only use the pre-generated values
             'invoice' => $model->toArray(),
-        ]);
+        ];
 
-        $this->changeTwigLocale($this->twig, $previousLocale);
-
-        return $content;
-    }
-
-    private function changeTwigLocale(Environment $twig, ?string $locale = null): ?string
-    {
-        // for invoices that don't have a language configured (using request locale)
-        if (null === $locale) {
-            return null;
-        }
-
-        /** @var TranslationExtension $extension */
-        $extension = $twig->getExtension(TranslationExtension::class);
-        /** @var LocaleAwareInterface $translator */
-        $translator = $extension->getTranslator();
-        $previousLocale = $translator->getLocale();
-
-        $translator->setLocale($locale);
-
-        /** @var LocaleFormatExtensions $extension */
-        $extension = $twig->getExtension(LocaleFormatExtensions::class);
-        $extension->setLocale($locale);
-
-        return $previousLocale;
+        return $this->renderTwigTemplateWithLanguage($this->twig, $template, $options, $language, $formatLocale);
     }
 }
diff --git a/src/Twig/LocaleFormatExtensions.php b/src/Twig/LocaleFormatExtensions.php
index c0c8d6dcf..ed709b5b3 100644
--- a/src/Twig/LocaleFormatExtensions.php
+++ b/src/Twig/LocaleFormatExtensions.php
@@ -137,7 +137,7 @@ final class LocaleFormatExtensions extends AbstractExtension
         return $this->formatter;
     }
 
-    private function getLocale()
+    public function getLocale(): string
     {
         if (null === $this->locale) {
             $this->locale = \Locale::getDefault();
diff --git a/src/Twig/TwigRendererTrait.php b/src/Twig/TwigRendererTrait.php
new file mode 100644
index 000000000..66a175dcc
--- /dev/null
+++ b/src/Twig/TwigRendererTrait.php
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of the Kimai time-tracking app.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace App\Twig;
+
+use Symfony\Bridge\Twig\Extension\TranslationExtension;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+use Twig\Environment;
+
+/**
+ * @internal
+ */
+trait TwigRendererTrait
+{
+    protected function renderTwigTemplateWithLanguage(Environment $twig, string $template, array $options = [], ?string $language = null, ?string $formatLocale = null): string
+    {
+        $previousTranslation = null;
+        $previousFormatLocale = null;
+
+        if ($language === null) {
+            $previousTranslation = $this->switchTranslationLocale($twig, $language);
+        }
+        if ($formatLocale === null) {
+            $previousFormatLocale = $this->switchFormatLocale($twig, $formatLocale);
+        }
+
+        $content = $twig->render($template, $options);
+
+        if ($previousTranslation !== null) {
+            $this->switchTranslationLocale($twig, $previousTranslation);
+        }
+        if ($previousFormatLocale !== null) {
+            $this->switchFormatLocale($twig, $previousFormatLocale);
+        }
+
+        return $content;
+    }
+
+    protected function switchTranslationLocale(Environment $twig, string $language): string
+    {
+        /** @var TranslationExtension $extension */
+        $extension = $twig->getExtension(TranslationExtension::class);
+        /** @var LocaleAwareInterface $translator */
+        $translator = $extension->getTranslator();
+        $previous = $translator->getLocale();
+        $translator->setLocale($language);
+
+        return $previous;
+    }
+
+    protected function switchFormatLocale(Environment $twig, string $language): string
+    {
+        /** @var LocaleFormatExtensions $extension */
+        $extension = $twig->getExtension(LocaleFormatExtensions::class);
+        $previous = $extension->getLocale();
+        $extension->setLocale($language);
+
+        return $previous;
+    }
+}
diff --git a/templates/export/pdf-layout.html.twig b/templates/export/pdf-layout.html.twig
index dabe4010d..1aa1cb5d5 100644
--- a/templates/export/pdf-layout.html.twig
+++ b/templates/export/pdf-layout.html.twig
@@ -3,16 +3,18 @@
 {% set showRateColumn = showRateColumn ?? is_granted('view_rate_other_timesheet') %}
 {% set showRateBudget = showRateBudget ?? false %}
 {% set showTimeBudget = showTimeBudget ?? false %}
+{% set showCustomerSummary = showCustomerSummary ?? true %}
 {% set showTotalSummary = showTotalSummary ?? true %}
+{% set showDateTimeShort = showDateTimeShort ?? false %}
 {% set now = create_date('now', app.user) %}
 {% set decimal = decimal ?? false %}
 {# this is only triggered, if a user exports from his personal timesheet screen #}
-{% if query.user is not null %}
+{% if query is defined and query.user is not null %}
     {% set showUserColumn = false %}
     {# if exporting via the admin screen, users without view_rate_own_timesheet might still see their own rates #}
     {% set showRateColumn = is_granted('view_rate_own_timesheet') %}
 {% endif %}
-<html lang="{{ app.request.locale }}">
+<html{% if app.request is defined and app.request is not null %} lang="{{ app.request.locale }}"{% endif %}>
 <head>
 {% block styles %}
     <style>
@@ -82,7 +84,7 @@
 <tr>
     <td align="left">
     {{ 'export.page_of'|trans({'%page%': '{PAGENO}', '%pages%': '{nb}'}) }}
-    {% if not showUserColumn and query.user %}
+    {% if not showUserColumn and query is defined and query.user %}
         &ndash;
         {{ 'label.user'|trans }}: {{ query.user.displayName }}
     {% endif %}
@@ -102,11 +104,13 @@
 mpdf-->
 {% endblock %}
 {% block summary %}
-    <h2 style="margin-bottom: 0; padding-bottom: 0">{{ 'export.document_title'|trans }}</h2>
+    <h2 style="margin-bottom: 4px; padding-bottom: 0">{% block title %}{{ 'export.document_title'|trans }}{% endblock %}</h2>
+    {% if query is defined %}
     <p>
         {{ 'export.period'|trans }}:
         {{ query.begin|date_short }} - {{ query.end|date_short }}
     </p>
+    {% endif %}
     <h3>{{ 'export.summary'|trans }}</h3>
     <table class="items">
         <thead>
@@ -206,7 +210,7 @@ mpdf-->
             {% set customerInternalRate = customerInternalRate + summary.rate_internal %}
             {% set customerCount = customerCount + 1 %}
         {% endfor %}
-        {% if customer is not same as(null) %}
+        {% if showCustomerSummary and customer is not same as(null) %}
         <tr class="summary">
             <td colspan="2"></td>
             {% if showTimeBudget %}
@@ -225,7 +229,7 @@ mpdf-->
         </tr>
         {% endif %}
         {% if showTotalSummary and not multiCurrency %}
-        <tr class="summary-total">
+        <tr class="{% if not showCustomerSummary %}summary{% else %}summary-total{% endif %}">
             <td class="totals" colspan="2">
                 {{ 'sum.total'|trans }}
             </td>
@@ -285,21 +289,22 @@ mpdf-->
             {% endif %}
             <tr class="{{ cycle(['odd', 'even'], loop.index0) }}">
                 <td class="text-nowrap">
-                    {{ entry.begin|date_time }}
+                    {% block date_begin %}{% if not showDateTimeShort %}{{ entry.begin|date_time }}{% else %}{{ entry.begin|date_short }}{% endif %}{% endblock %}
                     {% if entry.end %}
-                        <br>
-                        {{ entry.end|date_time }}
+                        {% block date_end %}{% if not showDateTimeShort %}<br>{{ entry.end|date_time }}{% endif %}{% endblock %}
                     {% endif %}
                 </td>
                 {% if showUserColumn %}
                     <td>{{ entry.user.displayName }}</td>
                 {% endif %}
                 <td>
-                    {{ entry.project.customer.name }} - {{ entry.project.name }}{% if entry.activity is not null %} - {{ entry.activity.name }}{% endif %}
-                    {% if entry.description is not empty %}
-                        <br>
-                        <i>{{ entry.description|desc2html }}</i>
-                    {% endif %}
+                    {% block description %}
+                        {{ entry.project.customer.name }} - {{ entry.project.name }}{% if entry.activity is not null %} - {{ entry.activity.name }}{% endif %}
+                        {% if entry.description is not empty %}
+                            <br>
+                            <i>{{ entry.description|desc2html }}</i>
+                        {% endif %}
+                    {% endblock %}
                 </td>
                 <td class="duration">{{ entry.duration|duration(decimal) }}</td>
                 {% if showRateColumn %}
@@ -318,9 +323,9 @@ mpdf-->
         {% endfor %}
         <tr class="summary">
             {% if showUserColumn %}
-                <td colspan="3"></td>
+                <td colspan="3">{{ 'sum.total'|trans }}</td>
             {% else %}
-                <td colspan="2"></td>
+                <td colspan="2">{{ 'sum.total'|trans }}</td>
             {% endif %}
             <td class="totals duration">{{ duration|duration(decimal) }}</td>
             {% if showRateColumn %}
diff --git a/templates/export/renderer/timesheet.pdf.twig b/templates/export/renderer/timesheet.pdf.twig
new file mode 100644
index 000000000..5727b4ddc
--- /dev/null
+++ b/templates/export/renderer/timesheet.pdf.twig
@@ -0,0 +1,18 @@
+{% extends 'export/pdf-layout.html.twig' %}
+{% set showRateColumn = false %}
+{% set showRateBudget = false %}
+{% set showTimeBudget = false %}
+{% set showUserColumn = true %}
+{% set showCustomerSummary = false %}
+{% set decimal = true %}
+{% set showDateTimeShort = true %}
+{% block title %}{{ 'timesheet.pdf.twig'|trans({}, 'export') }}{% endblock %}
+{% block description %}
+    {% if entry.description is not empty %}
+        {{ entry.description|desc2html }}
+    {% elseif entry.activity is not null %}
+        {{ entry.activity.name }}
+    {% endif %}
+    <br>
+    <small>{{ entry.project.customer.name }}, {{ entry.project.name }}</small>
+{% endblock %}
\ No newline at end of file
diff --git a/translations/export.de.xlf b/translations/export.de.xlf
index 22a85f092..b43e2c251 100644
--- a/translations/export.de.xlf
+++ b/translations/export.de.xlf
@@ -14,6 +14,10 @@
         <source>default-internal.pdf.twig</source>
         <target>Inkl. interner Kosten</target>
       </trans-unit>
+      <trans-unit id="bTIKMV." resname="timesheet.pdf.twig">
+        <source>Timesheet</source>
+        <target>Stundennachweis</target>
+      </trans-unit>
     </body>
   </file>
 </xliff>
diff --git a/translations/export.en.xlf b/translations/export.en.xlf
index 09699b086..41f998ac6 100644
--- a/translations/export.en.xlf
+++ b/translations/export.en.xlf
@@ -2,18 +2,22 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
   <file source-language="en" target-language="en" datatype="plaintext" original="export.en.xlf">
     <body>
-      <trans-unit id="oRRnhwf" resname="default.pdf.twig">
-        <source>default.pdf.twig</source>
-        <target>Standard</target>
+      <trans-unit id="IbERy.5" resname="default.pdf.twig">
+        <source>Default</source>
+        <target>Default</target>
       </trans-unit>
-      <trans-unit id="YjWg9hr" resname="default-budget.pdf.twig">
-        <source>default-budget.pdf.twig</source>
+      <trans-unit id="Xvh5B5U" resname="default-budget.pdf.twig">
+        <source>With remaining budget</source>
         <target>With remaining budget</target>
       </trans-unit>
-      <trans-unit id="AuhL8Sb" resname="default-internal.pdf.twig">
-        <source>default-internal.pdf.twig</source>
+      <trans-unit id="rok3rBO" resname="default-internal.pdf.twig">
+        <source>With internal rates</source>
         <target>With internal rates</target>
       </trans-unit>
+      <trans-unit id="bTIKMV." resname="timesheet.pdf.twig">
+        <source>Timesheet</source>
+        <target>Timesheet</target>
+      </trans-unit>
     </body>
   </file>
 </xliff>