added export context (#2216)
This commit is contained in:
@@ -8,6 +8,10 @@ you can upgrade your Kimai installation to the latest stable release.
|
|||||||
Check below if there are more version specific steps required, which need to be executed after the normal update process.
|
Check below if there are more version specific steps required, which need to be executed after the normal update process.
|
||||||
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
Perform EACH version specific task between your version and the new one, otherwise you risk data inconsistency or a broken installation.
|
||||||
|
|
||||||
|
## [1.13](https://github.com/kevinpapst/kimai2/releases/tag/1.13)
|
||||||
|
|
||||||
|
- Deprecated `now` variable in export templates: create it yourself with `{% set now = create_date('now', app.user) %}`
|
||||||
|
|
||||||
## [1.12](https://github.com/kevinpapst/kimai2/releases/tag/1.12)
|
## [1.12](https://github.com/kevinpapst/kimai2/releases/tag/1.12)
|
||||||
|
|
||||||
- Export templates can now include items from plugins (eg. Expenses).
|
- Export templates can now include items from plugins (eg. Expenses).
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
|
|
||||||
namespace App\Export\Base;
|
namespace App\Export\Base;
|
||||||
|
|
||||||
|
use App\Export\ExportContext;
|
||||||
use App\Export\ExportItemInterface;
|
use App\Export\ExportItemInterface;
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Timesheet\UserDateTimeFactory;
|
|
||||||
use App\Utils\HtmlToPdfConverter;
|
use App\Utils\HtmlToPdfConverter;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||||
@@ -26,10 +26,6 @@ class PDFRenderer
|
|||||||
* @var Environment
|
* @var Environment
|
||||||
*/
|
*/
|
||||||
private $twig;
|
private $twig;
|
||||||
/**
|
|
||||||
* @var UserDateTimeFactory
|
|
||||||
*/
|
|
||||||
private $dateTime;
|
|
||||||
/**
|
/**
|
||||||
* @var HtmlToPdfConverter
|
* @var HtmlToPdfConverter
|
||||||
*/
|
*/
|
||||||
@@ -46,11 +42,14 @@ class PDFRenderer
|
|||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
private $template = 'default.pdf.twig';
|
private $template = 'default.pdf.twig';
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $pdfOptions = [];
|
||||||
|
|
||||||
public function __construct(Environment $twig, UserDateTimeFactory $dateTime, HtmlToPdfConverter $converter, ProjectRepository $projectRepository)
|
public function __construct(Environment $twig, HtmlToPdfConverter $converter, ProjectRepository $projectRepository)
|
||||||
{
|
{
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
$this->dateTime = $dateTime;
|
|
||||||
$this->converter = $converter;
|
$this->converter = $converter;
|
||||||
$this->projectRepository = $projectRepository;
|
$this->projectRepository = $projectRepository;
|
||||||
}
|
}
|
||||||
@@ -72,6 +71,18 @@ class PDFRenderer
|
|||||||
return ['decimal' => $decimal];
|
return ['decimal' => $decimal];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPdfOptions(): array
|
||||||
|
{
|
||||||
|
return $this->pdfOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPdfOption(string $key, string $value): PDFRenderer
|
||||||
|
{
|
||||||
|
$this->pdfOptions[$key] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ExportItemInterface[] $timesheets
|
* @param ExportItemInterface[] $timesheets
|
||||||
* @param TimesheetQuery $query
|
* @param TimesheetQuery $query
|
||||||
@@ -82,21 +93,33 @@ class PDFRenderer
|
|||||||
*/
|
*/
|
||||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||||
{
|
{
|
||||||
|
$context = new ExportContext();
|
||||||
|
$context->setOption('filename', 'kimai-export.pdf');
|
||||||
|
|
||||||
$summary = $this->calculateSummary($timesheets);
|
$summary = $this->calculateSummary($timesheets);
|
||||||
$content = $this->twig->render($this->getTemplate(), array_merge([
|
$content = $this->twig->render($this->getTemplate(), array_merge([
|
||||||
'entries' => $timesheets,
|
'entries' => $timesheets,
|
||||||
'query' => $query,
|
'query' => $query,
|
||||||
'now' => $this->dateTime->createDateTime(),
|
// @deprecated since 1.13
|
||||||
|
'now' => new \DateTime('now', new \DateTimeZone(date_default_timezone_get())),
|
||||||
'summaries' => $summary,
|
'summaries' => $summary,
|
||||||
'budgets' => $this->calculateProjectBudget($timesheets, $query, $this->projectRepository),
|
'budgets' => $this->calculateProjectBudget($timesheets, $query, $this->projectRepository),
|
||||||
'decimal' => false,
|
'decimal' => false,
|
||||||
|
'pdfContext' => $context
|
||||||
], $this->getOptions($query)));
|
], $this->getOptions($query)));
|
||||||
|
|
||||||
$content = $this->converter->convertToPdf($content);
|
$pdfOptions = array_merge($context->getOptions(), $this->getPdfOptions());
|
||||||
|
|
||||||
|
$content = $this->converter->convertToPdf($content, $pdfOptions);
|
||||||
|
|
||||||
$response = new Response($content);
|
$response = new Response($content);
|
||||||
|
|
||||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'kimai-export.pdf');
|
$filename = $context->getOption('filename');
|
||||||
|
if (empty($filename)) {
|
||||||
|
$filename = 'kimai-export.pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename);
|
||||||
|
|
||||||
$response->headers->set('Content-Type', 'application/pdf');
|
$response->headers->set('Content-Type', 'application/pdf');
|
||||||
$response->headers->set('Content-Disposition', $disposition);
|
$response->headers->set('Content-Disposition', $disposition);
|
||||||
|
|||||||
37
src/Export/ExportContext.php
Normal file
37
src/Export/ExportContext.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?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\Export;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple class that is available in twig renderer context, which can be used to define global renderer options.
|
||||||
|
*/
|
||||||
|
final class ExportContext
|
||||||
|
{
|
||||||
|
private $options = [];
|
||||||
|
|
||||||
|
public function setOption(string $key, string $value): void
|
||||||
|
{
|
||||||
|
$this->options[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOptions(): array
|
||||||
|
{
|
||||||
|
return $this->options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOption(string $key): ?string
|
||||||
|
{
|
||||||
|
if (\array_key_exists($key, $this->options)) {
|
||||||
|
return $this->options[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
namespace App\Export\Renderer;
|
namespace App\Export\Renderer;
|
||||||
|
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
use App\Timesheet\UserDateTimeFactory;
|
|
||||||
use App\Utils\HtmlToPdfConverter;
|
use App\Utils\HtmlToPdfConverter;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
|
|
||||||
@@ -20,10 +19,6 @@ final class PdfRendererFactory
|
|||||||
* @var Environment
|
* @var Environment
|
||||||
*/
|
*/
|
||||||
private $twig;
|
private $twig;
|
||||||
/**
|
|
||||||
* @var UserDateTimeFactory
|
|
||||||
*/
|
|
||||||
private $dateTime;
|
|
||||||
/**
|
/**
|
||||||
* @var HtmlToPdfConverter
|
* @var HtmlToPdfConverter
|
||||||
*/
|
*/
|
||||||
@@ -33,17 +28,16 @@ final class PdfRendererFactory
|
|||||||
*/
|
*/
|
||||||
private $projectRepository;
|
private $projectRepository;
|
||||||
|
|
||||||
public function __construct(Environment $twig, UserDateTimeFactory $dateTime, HtmlToPdfConverter $converter, ProjectRepository $projectRepository)
|
public function __construct(Environment $twig, HtmlToPdfConverter $converter, ProjectRepository $projectRepository)
|
||||||
{
|
{
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
$this->dateTime = $dateTime;
|
|
||||||
$this->converter = $converter;
|
$this->converter = $converter;
|
||||||
$this->projectRepository = $projectRepository;
|
$this->projectRepository = $projectRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(string $id, string $template): PDFRenderer
|
public function create(string $id, string $template): PDFRenderer
|
||||||
{
|
{
|
||||||
$renderer = new PDFRenderer($this->twig, $this->dateTime, $this->converter, $this->projectRepository);
|
$renderer = new PDFRenderer($this->twig, $this->converter, $this->projectRepository);
|
||||||
$renderer->setId($id);
|
$renderer->setId($id);
|
||||||
$renderer->setTemplate($template);
|
$renderer->setTemplate($template);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace App\Twig;
|
|||||||
|
|
||||||
use App\Configuration\LanguageFormattings;
|
use App\Configuration\LanguageFormattings;
|
||||||
use App\Constants;
|
use App\Constants;
|
||||||
|
use App\Entity\User;
|
||||||
use App\Utils\LocaleFormats;
|
use App\Utils\LocaleFormats;
|
||||||
use App\Utils\LocaleFormatter;
|
use App\Utils\LocaleFormatter;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
@@ -89,6 +90,7 @@ class DateExtensions extends AbstractExtension
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
new TwigFunction('get_format_duration', [$this, 'getDurationFormat']),
|
new TwigFunction('get_format_duration', [$this, 'getDurationFormat']),
|
||||||
|
new TwigFunction('create_date', [$this, 'createDate']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +132,13 @@ class DateExtensions extends AbstractExtension
|
|||||||
return $this->formatter->dateTimeFull($date);
|
return $this->formatter->dateTimeFull($date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createDate(string $date, ?User $user = null): \DateTime
|
||||||
|
{
|
||||||
|
$timezone = $user !== null ? $user->getTimezone() : date_default_timezone_get();
|
||||||
|
|
||||||
|
return new DateTime($date, new \DateTimeZone($timezone));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DateTime|string $date
|
* @param DateTime|string $date
|
||||||
* @param string $format
|
* @param string $format
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
namespace App\Utils;
|
namespace App\Utils;
|
||||||
|
|
||||||
use App\Constants;
|
use App\Constants;
|
||||||
|
use Mpdf\Config\ConfigVariables;
|
||||||
|
use Mpdf\Config\FontVariables;
|
||||||
use Mpdf\Mpdf;
|
use Mpdf\Mpdf;
|
||||||
use Mpdf\Output\Destination;
|
use Mpdf\Output\Destination;
|
||||||
|
|
||||||
@@ -25,6 +27,32 @@ class MPdfConverter implements HtmlToPdfConverter
|
|||||||
$this->cacheDirectory = $cacheDirectory;
|
$this->cacheDirectory = $cacheDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function sanitizeOptions(array $options): array
|
||||||
|
{
|
||||||
|
$configs = new ConfigVariables();
|
||||||
|
$fonts = new FontVariables();
|
||||||
|
$allowed = [
|
||||||
|
'mode', 'format', 'default_font_size', 'default_font', 'margin_left', 'margin_right', 'margin_top',
|
||||||
|
'margin_bottom', 'margin_header', 'margin_footer', 'orientation'
|
||||||
|
];
|
||||||
|
|
||||||
|
$filtered = array_filter($options, function ($key) use ($allowed, $configs, $fonts) {
|
||||||
|
if (!\in_array($key, $allowed)) {
|
||||||
|
if (!\array_key_exists($key, $configs->getDefaults())) {
|
||||||
|
return \array_key_exists($key, $fonts->getDefaults());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
if (\array_key_exists('tempDir', $filtered)) {
|
||||||
|
unset($filtered['tempDir']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filtered;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $html
|
* @param string $html
|
||||||
* @param array $options
|
* @param array $options
|
||||||
@@ -33,7 +61,11 @@ class MPdfConverter implements HtmlToPdfConverter
|
|||||||
*/
|
*/
|
||||||
public function convertToPdf(string $html, array $options = [])
|
public function convertToPdf(string $html, array $options = [])
|
||||||
{
|
{
|
||||||
$options = array_merge($options, ['tempDir' => $this->cacheDirectory]);
|
$options = array_merge(
|
||||||
|
$this->sanitizeOptions($options),
|
||||||
|
['tempDir' => $this->cacheDirectory, 'exposeVersion' => false]
|
||||||
|
);
|
||||||
|
|
||||||
$mpdf = new Mpdf($options);
|
$mpdf = new Mpdf($options);
|
||||||
$mpdf->creator = Constants::SOFTWARE;
|
$mpdf->creator = Constants::SOFTWARE;
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,11 @@
|
|||||||
<br>
|
<br>
|
||||||
<small>{{ widgets.label_customer(entry.project.customer) }}</small>
|
<small>{{ widgets.label_customer(entry.project.customer) }}</small>
|
||||||
</td>
|
</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}">{{ widgets.label_activity(entry.activity) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}">
|
||||||
|
{% if entry.activity is not null %}
|
||||||
|
{{ widgets.label_activity(entry.activity) }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}">
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'description') }}">
|
||||||
{{ entry.description|desc2html }}
|
{{ entry.description|desc2html }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
{% set showRateColumn = showRateColumn ?? is_granted('view_rate_other_timesheet') %}
|
{% set showRateColumn = showRateColumn ?? is_granted('view_rate_other_timesheet') %}
|
||||||
{% set showRateBudget = showRateBudget ?? false %}
|
{% set showRateBudget = showRateBudget ?? false %}
|
||||||
{% set showTimeBudget = showTimeBudget ?? false %}
|
{% set showTimeBudget = showTimeBudget ?? false %}
|
||||||
|
{% set showTotalSummary = showTotalSummary ?? true %}
|
||||||
|
{% set now = create_date('now', app.user) %}
|
||||||
{% set decimal = decimal ?? false %}
|
{% set decimal = decimal ?? false %}
|
||||||
{# this is only triggered, if a user exports from his personal timesheet screen #}
|
{# this is only triggered, if a user exports from his personal timesheet screen #}
|
||||||
{% if query.user is not null %}
|
{% if query.user is not null %}
|
||||||
@@ -12,7 +14,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<html lang="{{ app.request.locale }}">
|
<html lang="{{ app.request.locale }}">
|
||||||
<head>
|
<head>
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
@@ -70,7 +72,7 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block pdf_footer %}
|
{% block pdf_footer %}
|
||||||
@@ -132,29 +134,39 @@ mpdf-->
|
|||||||
{% set customerInternalRate = 0 %}
|
{% set customerInternalRate = 0 %}
|
||||||
{% set customerCurrency = null %}
|
{% set customerCurrency = null %}
|
||||||
{% set customerCount = 0 %}
|
{% set customerCount = 0 %}
|
||||||
|
{% set multiCurrency = false %}
|
||||||
|
{% set totalDuration = 0 %}
|
||||||
|
{% set totalInternalRate = 0 %}
|
||||||
|
{% set totalRate = 0 %}
|
||||||
{% for id, summary in summaries %}
|
{% for id, summary in summaries %}
|
||||||
|
{% set totalDuration = totalDuration + summary.duration %}
|
||||||
|
{% set totalInternalRate = totalInternalRate + summary.rate_internal %}
|
||||||
|
{% set totalRate = totalRate + summary.rate %}
|
||||||
|
{% if customerCurrency is not null and customerCurrency is not same as(summary.currency) %}
|
||||||
|
{% set multiCurrency = true %}
|
||||||
|
{% endif %}
|
||||||
{% if customer is same as(null) %}
|
{% if customer is same as(null) %}
|
||||||
{% set customer = summary.customer %}
|
{% set customer = summary.customer %}
|
||||||
{% set customerCurrency = summary.currency %}
|
{% set customerCurrency = summary.currency %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if customer is not same as(summary.customer) %}
|
{% if customer is not same as(summary.customer) %}
|
||||||
<tr class="summary">
|
<tr class="summary">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
</td>
|
</td>
|
||||||
{% if showTimeBudget %}
|
{% if showTimeBudget %}
|
||||||
<td></td>
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
{% if showRateBudget %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
<td class="totals duration">{{ customerDuration|duration(decimal) }}</td>
|
||||||
|
{% if showRateColumn %}
|
||||||
|
{% if showInternalRate %}
|
||||||
|
<td class="totals cost">{{ customerInternalRate|money(customerCurrency) }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if showRateBudget %}
|
<td class="totals cost">{{ customerRate|money(customerCurrency) }}</td>
|
||||||
<td></td>
|
{% endif %}
|
||||||
{% endif %}
|
</tr>
|
||||||
<td class="totals duration">{{ customerDuration|duration(decimal) }}</td>
|
|
||||||
{% if showRateColumn %}
|
|
||||||
{% if showInternalRate %}
|
|
||||||
<td class="totals cost">{{ customerInternalRate|money(customerCurrency) }}</td>
|
|
||||||
{% endif %}
|
|
||||||
<td class="totals cost">{{ customerRate|money(customerCurrency) }}</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
{% set customerCurrency = summary.currency %}
|
{% set customerCurrency = summary.currency %}
|
||||||
{% set customer = summary.customer %}
|
{% set customer = summary.customer %}
|
||||||
{% set customerDuration = 0 %}
|
{% set customerDuration = 0 %}
|
||||||
@@ -210,6 +222,26 @@ mpdf-->
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if showTotalSummary and not multiCurrency %}
|
||||||
|
<tr class="summary-total">
|
||||||
|
<td class="totals" colspan="2">
|
||||||
|
{{ 'sum.total'|trans }}
|
||||||
|
</td>
|
||||||
|
{% if showTimeBudget %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
{% if showRateBudget %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
<td class="totals duration">{{ totalDuration|duration(decimal) }}</td>
|
||||||
|
{% if showRateColumn %}
|
||||||
|
{% if showInternalRate %}
|
||||||
|
<td class="totals cost">{{ totalInternalRate|money(customerCurrency) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
<td class="totals cost">{{ totalRate|money(customerCurrency) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -92,14 +92,22 @@ class ExportControllerTest extends ControllerBaseTest
|
|||||||
$this->assertDataTableRowCount($client, 'datatable_export', 23);
|
$this->assertDataTableRowCount($client, 'datatable_export', 23);
|
||||||
|
|
||||||
// assert export type buttons are available
|
// assert export type buttons are available
|
||||||
$expected = ['csv', 'default.html.twig', 'default-budget.pdf.twig', 'default-internal.pdf.twig', 'default.pdf.twig', 'xlsx'];
|
$expected = [
|
||||||
|
'csv' => 'csv',
|
||||||
|
'default.html.twig' => 'default.html.twig',
|
||||||
|
'default-budget.pdf.twig' => 'default-budget.pdf.twig',
|
||||||
|
'default-internal.pdf.twig' => 'default-internal.pdf.twig',
|
||||||
|
'default.pdf.twig' => 'default.pdf.twig',
|
||||||
|
'xlsx' => 'xlsx'
|
||||||
|
];
|
||||||
$node = $client->getCrawler()->filter('#export-buttons .startExportBtn');
|
$node = $client->getCrawler()->filter('#export-buttons .startExportBtn');
|
||||||
$this->assertEquals(\count($expected), $node->count());
|
$this->assertGreaterThanOrEqual(\count($expected), $node->count());
|
||||||
/** @var \DOMElement $button */
|
/** @var \DOMElement $button */
|
||||||
foreach ($node->getIterator() as $button) {
|
foreach ($node->getIterator() as $button) {
|
||||||
$type = $button->getAttribute('data-type');
|
$type = $button->getAttribute('data-type');
|
||||||
$this->assertContains($type, $expected);
|
unset($expected[$type]);
|
||||||
}
|
}
|
||||||
|
$this->assertEmpty($expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIndexActionWithEntriesForTeamleadDoesNotShowUserWithoutTeam()
|
public function testIndexActionWithEntriesForTeamleadDoesNotShowUserWithoutTeam()
|
||||||
@@ -144,14 +152,22 @@ class ExportControllerTest extends ControllerBaseTest
|
|||||||
$this->assertDataTableRowCount($client, 'datatable_export', 3);
|
$this->assertDataTableRowCount($client, 'datatable_export', 3);
|
||||||
|
|
||||||
// assert export type buttons are available
|
// assert export type buttons are available
|
||||||
$expected = ['csv', 'default.html.twig', 'default-budget.pdf.twig', 'default-internal.pdf.twig', 'default.pdf.twig', 'xlsx'];
|
$expected = [
|
||||||
|
'csv' => 'csv',
|
||||||
|
'default.html.twig' => 'default.html.twig',
|
||||||
|
'default-budget.pdf.twig' => 'default-budget.pdf.twig',
|
||||||
|
'default-internal.pdf.twig' => 'default-internal.pdf.twig',
|
||||||
|
'default.pdf.twig' => 'default.pdf.twig',
|
||||||
|
'xlsx' => 'xlsx'
|
||||||
|
];
|
||||||
$node = $client->getCrawler()->filter('#export-buttons .startExportBtn');
|
$node = $client->getCrawler()->filter('#export-buttons .startExportBtn');
|
||||||
$this->assertEquals(\count($expected), $node->count());
|
$this->assertGreaterThanOrEqual(\count($expected), $node->count());
|
||||||
/** @var \DOMElement $button */
|
/** @var \DOMElement $button */
|
||||||
foreach ($node->getIterator() as $button) {
|
foreach ($node->getIterator() as $button) {
|
||||||
$type = $button->getAttribute('data-type');
|
$type = $button->getAttribute('data-type');
|
||||||
$this->assertContains($type, $expected);
|
unset($expected[$type]);
|
||||||
}
|
}
|
||||||
|
$this->assertEmpty($expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testExportActionWithMissingRenderer()
|
public function testExportActionWithMissingRenderer()
|
||||||
|
|||||||
37
tests/Export/ExportContextTest.php
Normal file
37
tests/Export/ExportContextTest.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?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\Tests\Export;
|
||||||
|
|
||||||
|
use App\Export\ExportContext;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Export\ExportContext
|
||||||
|
*/
|
||||||
|
class ExportContextTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEmptyObject()
|
||||||
|
{
|
||||||
|
$sut = new ExportContext();
|
||||||
|
|
||||||
|
self::assertIsArray($sut->getOptions());
|
||||||
|
self::assertEmpty($sut->getOptions());
|
||||||
|
self::assertNull($sut->getOption('unknown'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetterAndGetter()
|
||||||
|
{
|
||||||
|
$sut = new ExportContext();
|
||||||
|
|
||||||
|
self::assertNull($sut->getOption('unknown'));
|
||||||
|
$sut->setOption('unknown', 'foo');
|
||||||
|
self::assertEquals('foo', $sut->getOption('unknown'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,6 @@ namespace App\Tests\Export\Renderer;
|
|||||||
use App\Export\Renderer\PDFRenderer;
|
use App\Export\Renderer\PDFRenderer;
|
||||||
use App\Export\Renderer\PdfRendererFactory;
|
use App\Export\Renderer\PdfRendererFactory;
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
use App\Timesheet\UserDateTimeFactory;
|
|
||||||
use App\Utils\HtmlToPdfConverter;
|
use App\Utils\HtmlToPdfConverter;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Twig\Environment;
|
use Twig\Environment;
|
||||||
@@ -26,7 +25,6 @@ class PdfRendererFactoryTest extends TestCase
|
|||||||
{
|
{
|
||||||
$sut = new PdfRendererFactory(
|
$sut = new PdfRendererFactory(
|
||||||
$this->createMock(Environment::class),
|
$this->createMock(Environment::class),
|
||||||
$this->createMock(UserDateTimeFactory::class),
|
|
||||||
$this->createMock(HtmlToPdfConverter::class),
|
$this->createMock(HtmlToPdfConverter::class),
|
||||||
$this->createMock(ProjectRepository::class)
|
$this->createMock(ProjectRepository::class)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ namespace App\Tests\Export\Renderer;
|
|||||||
|
|
||||||
use App\Export\Renderer\PDFRenderer;
|
use App\Export\Renderer\PDFRenderer;
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
use App\Tests\Mocks\Security\UserDateTimeFactoryFactory;
|
|
||||||
use App\Utils\HtmlToPdfConverter;
|
use App\Utils\HtmlToPdfConverter;
|
||||||
use App\Utils\MPdfConverter;
|
use App\Utils\MPdfConverter;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -25,16 +24,10 @@ use Twig\Environment;
|
|||||||
*/
|
*/
|
||||||
class PdfRendererTest extends AbstractRendererTest
|
class PdfRendererTest extends AbstractRendererTest
|
||||||
{
|
{
|
||||||
protected function getDateTimeFactory()
|
|
||||||
{
|
|
||||||
return (new UserDateTimeFactoryFactory($this))->create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testConfiguration()
|
public function testConfiguration()
|
||||||
{
|
{
|
||||||
$sut = new PDFRenderer(
|
$sut = new PDFRenderer(
|
||||||
$this->createMock(Environment::class),
|
$this->createMock(Environment::class),
|
||||||
$this->getDateTimeFactory(),
|
|
||||||
$this->createMock(HtmlToPdfConverter::class),
|
$this->createMock(HtmlToPdfConverter::class),
|
||||||
$this->createMock(ProjectRepository::class)
|
$this->createMock(ProjectRepository::class)
|
||||||
);
|
);
|
||||||
@@ -42,6 +35,11 @@ class PdfRendererTest extends AbstractRendererTest
|
|||||||
$this->assertEquals('pdf', $sut->getId());
|
$this->assertEquals('pdf', $sut->getId());
|
||||||
$this->assertEquals('pdf', $sut->getTitle());
|
$this->assertEquals('pdf', $sut->getTitle());
|
||||||
$this->assertEquals('pdf', $sut->getIcon());
|
$this->assertEquals('pdf', $sut->getIcon());
|
||||||
|
$this->assertEquals([], $sut->getPdfOptions());
|
||||||
|
|
||||||
|
$sut->setPdfOption('foo', 'bar');
|
||||||
|
$sut->setPdfOption('bar1', 'foo1');
|
||||||
|
$this->assertEquals(['foo' => 'bar', 'bar1' => 'foo1'], $sut->getPdfOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRender()
|
public function testRender()
|
||||||
@@ -56,7 +54,7 @@ class PdfRendererTest extends AbstractRendererTest
|
|||||||
$request->setLocale('en');
|
$request->setLocale('en');
|
||||||
$stack->push($request);
|
$stack->push($request);
|
||||||
|
|
||||||
$sut = new PDFRenderer($twig, $this->getDateTimeFactory(), $converter, $this->createMock(ProjectRepository::class));
|
$sut = new PDFRenderer($twig, $converter, $this->createMock(ProjectRepository::class));
|
||||||
|
|
||||||
$response = $this->render($sut);
|
$response = $this->render($sut);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ namespace App\Tests\Export\Timesheet;
|
|||||||
|
|
||||||
use App\Export\Timesheet\PDFRenderer;
|
use App\Export\Timesheet\PDFRenderer;
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
use App\Tests\Mocks\Security\UserDateTimeFactoryFactory;
|
|
||||||
use App\Utils\HtmlToPdfConverter;
|
use App\Utils\HtmlToPdfConverter;
|
||||||
use App\Utils\MPdfConverter;
|
use App\Utils\MPdfConverter;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -25,16 +24,10 @@ use Twig\Environment;
|
|||||||
*/
|
*/
|
||||||
class PdfRendererTest extends AbstractRendererTest
|
class PdfRendererTest extends AbstractRendererTest
|
||||||
{
|
{
|
||||||
protected function getDateTimeFactory()
|
|
||||||
{
|
|
||||||
return (new UserDateTimeFactoryFactory($this))->create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testConfiguration()
|
public function testConfiguration()
|
||||||
{
|
{
|
||||||
$sut = new PDFRenderer(
|
$sut = new PDFRenderer(
|
||||||
$this->createMock(Environment::class),
|
$this->createMock(Environment::class),
|
||||||
$this->getDateTimeFactory(),
|
|
||||||
$this->createMock(HtmlToPdfConverter::class),
|
$this->createMock(HtmlToPdfConverter::class),
|
||||||
$this->createMock(ProjectRepository::class)
|
$this->createMock(ProjectRepository::class)
|
||||||
);
|
);
|
||||||
@@ -54,7 +47,7 @@ class PdfRendererTest extends AbstractRendererTest
|
|||||||
$request->setLocale('en');
|
$request->setLocale('en');
|
||||||
$stack->push($request);
|
$stack->push($request);
|
||||||
|
|
||||||
$sut = new PDFRenderer($twig, $this->getDateTimeFactory(), $converter, $this->createMock(ProjectRepository::class));
|
$sut = new PDFRenderer($twig, $converter, $this->createMock(ProjectRepository::class));
|
||||||
|
|
||||||
$response = $this->render($sut);
|
$response = $this->render($sut);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
namespace App\Tests\Twig;
|
namespace App\Tests\Twig;
|
||||||
|
|
||||||
use App\Configuration\LanguageFormattings;
|
use App\Configuration\LanguageFormattings;
|
||||||
|
use App\Entity\User;
|
||||||
use App\Twig\DateExtensions;
|
use App\Twig\DateExtensions;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@@ -54,7 +55,7 @@ class DateExtensionsTest extends TestCase
|
|||||||
|
|
||||||
public function testGetFunctions()
|
public function testGetFunctions()
|
||||||
{
|
{
|
||||||
$functions = ['get_format_duration'];
|
$functions = ['get_format_duration', 'create_date'];
|
||||||
$sut = $this->getSut('de', []);
|
$sut = $this->getSut('de', []);
|
||||||
$twigFunctions = $sut->getFunctions();
|
$twigFunctions = $sut->getFunctions();
|
||||||
$this->assertCount(\count($functions), $twigFunctions);
|
$this->assertCount(\count($functions), $twigFunctions);
|
||||||
@@ -217,4 +218,21 @@ class DateExtensionsTest extends TestCase
|
|||||||
/* @phpstan-ignore-next-line */
|
/* @phpstan-ignore-next-line */
|
||||||
$this->assertEquals(189.45, $sut->dateTimeFull(189.45));
|
$this->assertEquals(189.45, $sut->dateTimeFull(189.45));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCreateDate()
|
||||||
|
{
|
||||||
|
$user = new User();
|
||||||
|
$user->setTimezone('Europe/Berlin');
|
||||||
|
$sut = $this->getSut('en', []);
|
||||||
|
$date = $sut->createDate('now', $user);
|
||||||
|
$this->assertEquals('Europe/Berlin', $date->getTimezone()->getName());
|
||||||
|
|
||||||
|
$user->setTimezone('Asia/Dubai');
|
||||||
|
$date = $sut->createDate('2019-08-27 16:30:45', $user);
|
||||||
|
$this->assertEquals('2019-08-27T16:30:45+0400', $date->format(DATE_ISO8601));
|
||||||
|
$this->assertEquals('Asia/Dubai', $date->getTimezone()->getName());
|
||||||
|
|
||||||
|
$date = $sut->createDate('2019-08-27 16:30:45', null);
|
||||||
|
$this->assertEquals(date_default_timezone_get(), $date->getTimezone()->getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,10 @@
|
|||||||
<source>rates.title</source>
|
<source>rates.title</source>
|
||||||
<target>Gebühren</target>
|
<target>Gebühren</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sum.total">
|
||||||
|
<source>sum.total</source>
|
||||||
|
<target>Gesamt</target>
|
||||||
|
</trans-unit>
|
||||||
<!--
|
<!--
|
||||||
Login / Security
|
Login / Security
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -80,7 +80,10 @@
|
|||||||
<source>rates.title</source>
|
<source>rates.title</source>
|
||||||
<target>Fees</target>
|
<target>Fees</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sum.total">
|
||||||
|
<source>sum.total</source>
|
||||||
|
<target>Total</target>
|
||||||
|
</trans-unit>
|
||||||
<!--
|
<!--
|
||||||
Login / Security
|
Login / Security
|
||||||
-->
|
-->
|
||||||
|
|||||||
Reference in New Issue
Block a user