Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
@@ -11,47 +9,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Export\Annotation;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Enum;
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
|
||||
/**
|
||||
* Annotation class for @Expose().
|
||||
*
|
||||
* @Annotation
|
||||
* @Target({"CLASS", "PROPERTY", "METHOD"})
|
||||
*/
|
||||
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)]
|
||||
final class Expose
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required
|
||||
*/
|
||||
public $label;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
/**
|
||||
* @Enum({"string", "datetime", "date", "time", "integer", "float", "duration", "boolean", "array"})
|
||||
*/
|
||||
public $type = 'string';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $exp = null;
|
||||
public string $type = 'string';
|
||||
|
||||
public function __construct(array $data)
|
||||
public function __construct(public ?string $name = null, public ?string $label = null, string $type = 'string', public ?string $exp = null)
|
||||
{
|
||||
if (isset($data['value'])) {
|
||||
$this->name = $data['value'];
|
||||
unset($data['value']);
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (!property_exists(self::class, $key)) {
|
||||
throw new \InvalidArgumentException(sprintf('Unknown property "%s" on annotation "%s".', $key, self::class));
|
||||
}
|
||||
$this->{$key} = $value;
|
||||
if (!\in_array($type, ['string', 'datetime', 'date', 'time', 'integer', 'float', 'duration', 'boolean', 'array'])) {
|
||||
throw new \InvalidArgumentException(sprintf('Unknown type "%s" on annotation "%s".', $type, self::class));
|
||||
}
|
||||
$this->type = $type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
@@ -11,22 +9,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Export\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS"})
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
final class Order
|
||||
{
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
public $order = [];
|
||||
|
||||
public function __construct(array $data)
|
||||
public function __construct(public array $order = [])
|
||||
{
|
||||
if (isset($data['value'])) {
|
||||
$this->order = $data['value'];
|
||||
unset($data['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Export\Base;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
@@ -17,7 +18,6 @@ use App\Event\ProjectMetaDisplayEvent;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Event\UserPreferenceDisplayEvent;
|
||||
use App\Export\ExportFilename;
|
||||
use App\Export\ExportItemInterface;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Twig\LocaleFormatExtensions;
|
||||
@@ -56,28 +56,12 @@ abstract class AbstractSpreadsheetRenderer
|
||||
*/
|
||||
public const RATE_FORMAT_NO_CURRENCY = '#,##0.00;-#,##0.00';
|
||||
|
||||
protected $durationFormat = self::DURATION_FORMAT;
|
||||
protected $durationBase = 86400;
|
||||
protected string $durationFormat = self::DURATION_FORMAT;
|
||||
protected int $durationBase = 86400;
|
||||
/**
|
||||
* @var LocaleFormatExtensions
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected $dateExtension;
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
/**
|
||||
* @var Security
|
||||
*/
|
||||
protected $voter;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $columns = [
|
||||
protected array $columns = [
|
||||
'date' => [],
|
||||
'begin' => [],
|
||||
'end' => [],
|
||||
@@ -111,12 +95,12 @@ abstract class AbstractSpreadsheetRenderer
|
||||
'order_number' => [],
|
||||
];
|
||||
|
||||
public function __construct(TranslatorInterface $translator, LocaleFormatExtensions $dateExtension, EventDispatcherInterface $dispatcher, Security $security)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->dateExtension = $dateExtension;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->voter = $security;
|
||||
public function __construct(
|
||||
protected TranslatorInterface $translator,
|
||||
protected LocaleFormatExtensions $dateExtension,
|
||||
protected EventDispatcherInterface $dispatcher,
|
||||
protected Security $voter
|
||||
) {
|
||||
}
|
||||
|
||||
protected function isRenderRate(TimesheetQuery $query): bool
|
||||
@@ -133,7 +117,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
return $this->voter->isGranted('view_rate_other_timesheet');
|
||||
}
|
||||
|
||||
protected function setFormattedDateTime(Worksheet $sheet, $column, $row, ?DateTime $date)
|
||||
protected function setFormattedDateTime(Worksheet $sheet, $column, $row, ?DateTime $date): void
|
||||
{
|
||||
if (null === $date) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, '');
|
||||
@@ -154,7 +138,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::DATETIME_FORMAT);
|
||||
}
|
||||
|
||||
protected function setFormattedTime(Worksheet $sheet, $column, $row, ?DateTime $date)
|
||||
protected function setFormattedTime(Worksheet $sheet, $column, $row, ?DateTime $date): void
|
||||
{
|
||||
if (null === $date) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, '');
|
||||
@@ -174,7 +158,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(self::TIME_FORMAT);
|
||||
}
|
||||
|
||||
protected function setFormattedDate(Worksheet $sheet, $column, $row, ?DateTime $date)
|
||||
protected function setFormattedDate(Worksheet $sheet, $column, $row, ?DateTime $date): void
|
||||
{
|
||||
if (null === $date) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, '');
|
||||
@@ -195,14 +179,14 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
|
||||
}
|
||||
|
||||
protected function setDurationTotal(Worksheet $sheet, $column, $row, $startCoordinate, $endCoordinate)
|
||||
protected function setDurationTotal(Worksheet $sheet, $column, $row, $startCoordinate, $endCoordinate): void
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, sprintf('=SUBTOTAL(9,%s:%s)', $startCoordinate, $endCoordinate));
|
||||
$style = $sheet->getStyleByColumnAndRow($column, $row);
|
||||
$style->getNumberFormat()->setFormatCode($this->durationFormat);
|
||||
}
|
||||
|
||||
protected function setDuration(Worksheet $sheet, $column, $row, $duration)
|
||||
protected function setDuration(Worksheet $sheet, $column, $row, $duration): void
|
||||
{
|
||||
if (null === $duration) {
|
||||
$duration = 0;
|
||||
@@ -211,19 +195,19 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode($this->durationFormat);
|
||||
}
|
||||
|
||||
protected function setRateTotal(Worksheet $sheet, $column, $row, $startCoordinate, $endCoordinate)
|
||||
protected function setRateTotal(Worksheet $sheet, $column, $row, $startCoordinate, $endCoordinate): void
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, sprintf('=SUBTOTAL(9,%s:%s)', $startCoordinate, $endCoordinate));
|
||||
}
|
||||
|
||||
protected function setRateStyle(Worksheet $sheet, $column, $row, $rate, $currency)
|
||||
protected function setRateStyle(Worksheet $sheet, $column, $row, $rate, $currency): void
|
||||
{
|
||||
$sheet->getStyleByColumnAndRow($column, $row)->getNumberFormat()->setFormatCode(
|
||||
sprintf(self::RATE_FORMAT_LEFT, $currency)
|
||||
);
|
||||
}
|
||||
|
||||
protected function setRate(Worksheet $sheet, $column, $row, $rate, $currency)
|
||||
protected function setRate(Worksheet $sheet, $column, $row, $rate, $currency): void
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $rate);
|
||||
$this->setRateStyle($sheet, $column, $row, $rate, $currency);
|
||||
@@ -241,7 +225,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param ExportableItem[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @param array $columns
|
||||
* @return array
|
||||
@@ -256,31 +240,31 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$showRates = $this->isRenderRate($query);
|
||||
|
||||
if (isset($columns['date']) && !isset($columns['date']['render'])) {
|
||||
$columns['date']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['date']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$this->setFormattedDate($sheet, $column, $row, $entity->getBegin());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['begin']) && !isset($columns['begin']['render'])) {
|
||||
$columns['begin']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['begin']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$this->setFormattedTime($sheet, $column, $row, $entity->getBegin());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['end']) && !isset($columns['end']['render'])) {
|
||||
$columns['end']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['end']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$this->setFormattedTime($sheet, $column, $row, $entity->getEnd());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['duration']) && !isset($columns['duration']['render'])) {
|
||||
$columns['duration']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['duration']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$this->setDuration($sheet, $column, $row, $entity->getDuration());
|
||||
};
|
||||
}
|
||||
|
||||
if ($showRates && isset($columns['rate']) && !isset($columns['rate']['render'])) {
|
||||
$columns['rate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['rate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$currency = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$currency = $entity->getProject()->getCustomer()->getCurrency();
|
||||
@@ -290,21 +274,17 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
if ($showRates && isset($columns['rate_internal']) && !isset($columns['rate_internal']['render'])) {
|
||||
$columns['rate_internal']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['rate_internal']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$currency = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$currency = $entity->getProject()->getCustomer()->getCurrency();
|
||||
}
|
||||
$rate = $entity->getRate();
|
||||
if (method_exists($entity, 'getInternalRate')) {
|
||||
$rate = $entity->getInternalRate();
|
||||
}
|
||||
$this->setRate($sheet, $column, $row, $rate, $currency);
|
||||
$this->setRate($sheet, $column, $row, $entity->getInternalRate(), $currency);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['user']) && !isset($columns['user']['render'])) {
|
||||
$columns['user']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['user']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$user = '';
|
||||
if (null !== $entity->getUser()) {
|
||||
$user = $entity->getUser()->getDisplayName();
|
||||
@@ -315,17 +295,17 @@ abstract class AbstractSpreadsheetRenderer
|
||||
|
||||
if (isset($columns['username'])) {
|
||||
if (!isset($columns['username']['render'])) {
|
||||
$columns['username']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['username']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$username = '';
|
||||
if (null !== $entity->getUser()) {
|
||||
$username = $entity->getUser()->getUsername();
|
||||
$username = $entity->getUser()->getUserIdentifier();
|
||||
}
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $username);
|
||||
};
|
||||
}
|
||||
if (!isset($columns['username']['header'])) {
|
||||
$columns['username']['header'] = function (Worksheet $sheet, $row, $column) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('label.name'));
|
||||
$columns['username']['header'] = function (Worksheet $sheet, $row, $column): int {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('name'));
|
||||
|
||||
return 1;
|
||||
};
|
||||
@@ -333,7 +313,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
if (isset($columns['customer']) && !isset($columns['customer']['render'])) {
|
||||
$columns['customer']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['customer']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$customer = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$customer = $entity->getProject()->getCustomer()->getName();
|
||||
@@ -343,7 +323,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
if (isset($columns['project']) && !isset($columns['project']['render'])) {
|
||||
$columns['project']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['project']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$project = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$project = $entity->getProject()->getName();
|
||||
@@ -353,7 +333,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
if (isset($columns['activity']) && !isset($columns['activity']['render'])) {
|
||||
$columns['activity']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['activity']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$activity = '';
|
||||
if (null !== $entity->getActivity()) {
|
||||
$activity = $entity->getActivity()->getName();
|
||||
@@ -371,7 +351,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
// It needs to be executed once, so we use this as a flag on when to skip it.
|
||||
$isColumnFormatted = false;
|
||||
|
||||
$columns['description']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use (&$isColumnFormatted, $maxWidth, $wrapText, $sanitizeText) {
|
||||
$columns['description']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) use (&$isColumnFormatted, $maxWidth, $wrapText, $sanitizeText) {
|
||||
$cell = $sheet->getCellByColumnAndRow($column, $row);
|
||||
$desc = $entity->getDescription();
|
||||
|
||||
@@ -397,27 +377,27 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
if (isset($columns['exported']) && !isset($columns['exported']['render'])) {
|
||||
$columns['exported']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['exported']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$exported = $entity->isExported() ? 'yes' : 'no';
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans($exported));
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['billable']) && !isset($columns['billable']['render'])) {
|
||||
$columns['billable']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$exported = (method_exists($entity, 'isBillable') && !$entity->isBillable()) ? 'no' : 'yes';
|
||||
$columns['billable']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$exported = $entity->isBillable() ? 'yes' : 'no';
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans($exported));
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['tags']) && !isset($columns['tags']['render'])) {
|
||||
$columns['tags']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['tags']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, implode(',', $entity->getTagsAsArray()));
|
||||
};
|
||||
}
|
||||
|
||||
if ($showRates && isset($columns['hourlyRate']) && !isset($columns['hourlyRate']['render'])) {
|
||||
$columns['hourlyRate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['hourlyRate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$currency = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$currency = $entity->getProject()->getCustomer()->getCurrency();
|
||||
@@ -427,7 +407,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
if ($showRates && isset($columns['fixedRate']) && !isset($columns['fixedRate']['render'])) {
|
||||
$columns['fixedRate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['fixedRate']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$currency = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$currency = $entity->getProject()->getCustomer()->getCurrency();
|
||||
@@ -440,14 +420,14 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
||||
|
||||
$columns['timesheet-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($timesheetMetaFields) {
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($timesheetMetaFields): int {
|
||||
foreach ($timesheetMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return \count($timesheetMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($timesheetMetaFields) {
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) use ($timesheetMetaFields): int {
|
||||
foreach ($timesheetMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
$metaField = $entity->getMetaField($metaField->getName());
|
||||
@@ -468,14 +448,14 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($customerQuery, CustomerMetaDisplayEvent::EXPORT));
|
||||
|
||||
$columns['customer-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($customerMetaFields) {
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($customerMetaFields): int {
|
||||
foreach ($customerMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return \count($customerMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($customerMetaFields) {
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) use ($customerMetaFields): int {
|
||||
foreach ($customerMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
@@ -495,14 +475,14 @@ abstract class AbstractSpreadsheetRenderer
|
||||
if (isset($columns['project-meta'])) {
|
||||
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::EXPORT));
|
||||
$columns['project-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($projectMetaFields) {
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($projectMetaFields): int {
|
||||
foreach ($projectMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return \count($projectMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($projectMetaFields) {
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) use ($projectMetaFields): int {
|
||||
foreach ($projectMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
@@ -522,14 +502,14 @@ abstract class AbstractSpreadsheetRenderer
|
||||
if (isset($columns['activity-meta'])) {
|
||||
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::EXPORT));
|
||||
$columns['activity-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($activityMetaFields) {
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($activityMetaFields): int {
|
||||
foreach ($activityMetaFields as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return \count($activityMetaFields);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($activityMetaFields) {
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) use ($activityMetaFields): int {
|
||||
foreach ($activityMetaFields as $metaField) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getActivity()) {
|
||||
@@ -551,14 +531,14 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$this->dispatcher->dispatch($event);
|
||||
$userPreferences = $event->getPreferences();
|
||||
$columns['user-meta'] = [
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($userPreferences) {
|
||||
'header' => function (Worksheet $sheet, $row, $column) use ($userPreferences): int {
|
||||
foreach ($userPreferences as $metaField) {
|
||||
$sheet->setCellValueByColumnAndRow($column++, $row, $this->translator->trans($metaField->getLabel()));
|
||||
}
|
||||
|
||||
return \count($userPreferences);
|
||||
},
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use ($userPreferences) {
|
||||
'render' => function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) use ($userPreferences): int {
|
||||
foreach ($userPreferences as $preference) {
|
||||
$metaFieldValue = '';
|
||||
if (null !== $entity->getUser()) {
|
||||
@@ -576,28 +556,28 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
if (isset($columns['type']) && !isset($columns['type']['render'])) {
|
||||
$columns['type']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['type']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $entity->getType());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['category']) && !isset($columns['category']['render'])) {
|
||||
$columns['category']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['category']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $entity->getCategory());
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($columns['customer_number'])) {
|
||||
if (!isset($columns['customer_number']['header'])) {
|
||||
$columns['customer_number']['header'] = function (Worksheet $sheet, $row, $column) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('label.number'));
|
||||
$columns['customer_number']['header'] = function (Worksheet $sheet, $row, $column): int {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('number'));
|
||||
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
if (!isset($columns['customer_number']['render'])) {
|
||||
$columns['customer_number']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['customer_number']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$customerId = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$customerId = $entity->getProject()->getCustomer()->getNumber();
|
||||
@@ -609,15 +589,15 @@ abstract class AbstractSpreadsheetRenderer
|
||||
|
||||
if (isset($columns['customer_vat']) && !isset($columns['customer_vat']['render'])) {
|
||||
if (!isset($columns['customer_vat']['header'])) {
|
||||
$columns['customer_vat']['header'] = function (Worksheet $sheet, $row, $column) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('label.vat_id'));
|
||||
$columns['customer_vat']['header'] = function (Worksheet $sheet, $row, $column): int {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('vat_id'));
|
||||
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
if (!isset($columns['customer_vat']['render'])) {
|
||||
$columns['customer_vat']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['customer_vat']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$customerVat = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$customerVat = $entity->getProject()->getCustomer()->getVatId();
|
||||
@@ -629,15 +609,15 @@ abstract class AbstractSpreadsheetRenderer
|
||||
|
||||
if (isset($columns['order_number']) && !isset($columns['order_number']['render'])) {
|
||||
if (!isset($columns['order_number']['header'])) {
|
||||
$columns['order_number']['header'] = function (Worksheet $sheet, $row, $column) {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('label.orderNumber'));
|
||||
$columns['order_number']['header'] = function (Worksheet $sheet, $row, $column): int {
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, $this->translator->trans('orderNumber'));
|
||||
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
if (!isset($columns['order_number']['render'])) {
|
||||
$columns['order_number']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) {
|
||||
$columns['order_number']['render'] = function (Worksheet $sheet, int $row, int $column, ExportableItem $entity) {
|
||||
$orderNumber = '';
|
||||
if (null !== $entity->getProject()) {
|
||||
$orderNumber = $entity->getProject()->getOrderNumber();
|
||||
@@ -660,7 +640,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param ExportableItem[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @return Spreadsheet
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
@@ -686,7 +666,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
$amount = $settings['header']($sheet, $recordsHeaderRow, $recordsHeaderColumn);
|
||||
$recordsHeaderColumn += $amount;
|
||||
} else {
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans('label.' . $label));
|
||||
$sheet->setCellValueByColumnAndRow($recordsHeaderColumn++, $recordsHeaderRow, $this->translator->trans($label));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,7 +737,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param ExportableItem[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \PhpOffice\PhpSpreadsheet\Exception
|
||||
@@ -782,7 +762,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
* @param string $filename
|
||||
* @return BinaryFileResponse
|
||||
*/
|
||||
protected function getFileResponse($file, $filename): Response
|
||||
protected function getFileResponse($file, $filename): BinaryFileResponse
|
||||
{
|
||||
$response = new BinaryFileResponse($file);
|
||||
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename);
|
||||
|
||||
@@ -54,12 +54,12 @@ class CsvRenderer extends AbstractSpreadsheetRenderer
|
||||
return 'csv';
|
||||
}
|
||||
|
||||
protected function setDuration(Worksheet $sheet, $column, $row, $duration)
|
||||
protected function setDuration(Worksheet $sheet, $column, $row, $duration): void
|
||||
{
|
||||
$sheet->setCellValueByColumnAndRow($column, $row, sprintf('=%s', $duration));
|
||||
}
|
||||
|
||||
protected function setRateStyle(Worksheet $sheet, $column, $row, $rate, $currency)
|
||||
protected function setRateStyle(Worksheet $sheet, $column, $row, $rate, $currency): void
|
||||
{
|
||||
if ($rate === 0.00) {
|
||||
return;
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
|
||||
namespace App\Export\Base;
|
||||
|
||||
/**
|
||||
* @deprecated remove me in 2.0
|
||||
*/
|
||||
interface DispositionInlineInterface
|
||||
{
|
||||
public function setDispositionInline(bool $useInlineDisposition): void;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?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\Base;
|
||||
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
trait DispositionInlineTrait
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT;
|
||||
|
||||
public function getDisposition(): string
|
||||
{
|
||||
return $this->disposition;
|
||||
}
|
||||
|
||||
public function setDispositionInline(bool $useInlineDisposition): void
|
||||
{
|
||||
if ($useInlineDisposition) {
|
||||
$this->disposition = ResponseHeaderBag::DISPOSITION_INLINE;
|
||||
} else {
|
||||
$this->disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Export\Base;
|
||||
|
||||
use App\Activity\ActivityStatisticService;
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Event\ActivityMetaDisplayEvent;
|
||||
use App\Event\CustomerMetaDisplayEvent;
|
||||
@@ -17,7 +18,6 @@ use App\Event\MetaDisplayEventInterface;
|
||||
use App\Event\ProjectMetaDisplayEvent;
|
||||
use App\Event\TimesheetMetaDisplayEvent;
|
||||
use App\Event\UserPreferenceDisplayEvent;
|
||||
use App\Export\ExportItemInterface;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
@@ -29,13 +29,6 @@ class HtmlRenderer
|
||||
{
|
||||
use RendererTrait;
|
||||
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
protected $twig;
|
||||
protected $dispatcher;
|
||||
private $projectStatisticService;
|
||||
private $activityStatisticService;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -45,12 +38,12 @@ class HtmlRenderer
|
||||
*/
|
||||
private $template = 'default.html.twig';
|
||||
|
||||
public function __construct(Environment $twig, EventDispatcherInterface $dispatcher, ProjectStatisticService $projectStatisticService, ActivityStatisticService $activityStatisticService)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->projectStatisticService = $projectStatisticService;
|
||||
$this->activityStatisticService = $activityStatisticService;
|
||||
public function __construct(
|
||||
protected Environment $twig,
|
||||
protected EventDispatcherInterface $dispatcher,
|
||||
private ProjectStatisticService $projectStatisticService,
|
||||
private ActivityStatisticService $activityStatisticService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +70,7 @@ class HtmlRenderer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExportItemInterface[] $timesheets
|
||||
* @param ExportableItem[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
@@ -106,8 +99,6 @@ class HtmlRenderer
|
||||
'summaries' => $summary,
|
||||
'budgets' => $this->calculateProjectBudget($timesheets, $query, $this->projectStatisticService),
|
||||
'activity_budgets' => $this->calculateActivityBudget($timesheets, $query, $this->activityStatisticService),
|
||||
// @deprecated since 1.3, will be removed with 2.0
|
||||
'metaColumns' => $timesheetMetaFields,
|
||||
'timesheetMetaFields' => $timesheetMetaFields,
|
||||
'customerMetaFields' => $customerMetaFields,
|
||||
'projectMetaFields' => $projectMetaFields,
|
||||
|
||||
@@ -9,51 +9,27 @@
|
||||
|
||||
namespace App\Export\Base;
|
||||
|
||||
use App\Export\ExportContext;
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Export\ExportFilename;
|
||||
use App\Export\ExportItemInterface;
|
||||
use App\Pdf\HtmlToPdfConverter;
|
||||
use App\Pdf\PdfContext;
|
||||
use App\Pdf\PdfRendererTrait;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Utils\FileHelper;
|
||||
use App\Utils\HtmlToPdfConverter;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Twig\Environment;
|
||||
|
||||
class PDFRenderer implements DispositionInlineInterface
|
||||
{
|
||||
use RendererTrait;
|
||||
use DispositionInlineTrait;
|
||||
use PDFRendererTrait;
|
||||
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
private $twig;
|
||||
/**
|
||||
* @var HtmlToPdfConverter
|
||||
*/
|
||||
private $converter;
|
||||
/**
|
||||
* @var ProjectStatisticService
|
||||
*/
|
||||
private $projectStatisticService;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $id = 'pdf';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $template = 'default.pdf.twig';
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $pdfOptions = [];
|
||||
private string $id = 'pdf';
|
||||
private string $template = 'default.pdf.twig';
|
||||
private array $pdfOptions = [];
|
||||
|
||||
public function __construct(Environment $twig, HtmlToPdfConverter $converter, ProjectStatisticService $projectStatisticService)
|
||||
public function __construct(private Environment $twig, private HtmlToPdfConverter $converter, private ProjectStatisticService $projectStatisticService)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->converter = $converter;
|
||||
$this->projectStatisticService = $projectStatisticService;
|
||||
}
|
||||
|
||||
protected function getTemplate(): string
|
||||
@@ -86,7 +62,7 @@ class PDFRenderer implements DispositionInlineInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ExportItemInterface[] $timesheets
|
||||
* @param ExportableItem[] $timesheets
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
* @throws \Twig\Error\LoaderError
|
||||
@@ -96,15 +72,13 @@ class PDFRenderer implements DispositionInlineInterface
|
||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||
{
|
||||
$filename = new ExportFilename($query);
|
||||
$context = new ExportContext();
|
||||
$context = new PdfContext();
|
||||
$context->setOption('filename', $filename->getFilename());
|
||||
|
||||
$summary = $this->calculateSummary($timesheets);
|
||||
$content = $this->twig->render($this->getTemplate(), array_merge([
|
||||
'entries' => $timesheets,
|
||||
'query' => $query,
|
||||
// @deprecated since 1.13
|
||||
'now' => new \DateTime('now', new \DateTimeZone(date_default_timezone_get())),
|
||||
'summaries' => $summary,
|
||||
'budgets' => $this->calculateProjectBudget($timesheets, $query, $this->projectStatisticService),
|
||||
'decimal' => false,
|
||||
@@ -115,22 +89,7 @@ class PDFRenderer implements DispositionInlineInterface
|
||||
|
||||
$content = $this->converter->convertToPdf($content, $pdfOptions);
|
||||
|
||||
$response = new Response($content);
|
||||
|
||||
$filename = $context->getOption('filename');
|
||||
if (empty($filename)) {
|
||||
$filename = new ExportFilename($query);
|
||||
$filename = $filename->getFilename();
|
||||
}
|
||||
|
||||
$filename = FileHelper::convertToAsciiFilename($filename);
|
||||
|
||||
$disposition = $response->headers->makeDisposition($this->getDisposition(), $filename . '.pdf');
|
||||
|
||||
$response->headers->set('Content-Type', 'application/pdf');
|
||||
$response->headers->set('Content-Disposition', $disposition);
|
||||
|
||||
return $response;
|
||||
return $this->createPdfResponse($content, $context);
|
||||
}
|
||||
|
||||
public function setTemplate(string $filename): PDFRenderer
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
namespace App\Export\Base;
|
||||
|
||||
use App\Activity\ActivityStatisticService;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Model\TimesheetCountedStatistic;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
@@ -18,10 +18,12 @@ use App\Repository\Query\TimesheetQuery;
|
||||
trait RendererTrait
|
||||
{
|
||||
/**
|
||||
* @param InvoiceItemInterface[] $exportItems
|
||||
* FIXME use statistic events to calculate budgets and do NOT iterate all results!
|
||||
*
|
||||
* @param ExportableItem[] $exportItems
|
||||
* @return array
|
||||
*/
|
||||
protected function calculateSummary(array $exportItems)
|
||||
protected function calculateSummary(array $exportItems): array
|
||||
{
|
||||
$summary = [];
|
||||
|
||||
@@ -133,10 +135,7 @@ trait RendererTrait
|
||||
}
|
||||
|
||||
$rate = $exportItem->getRate();
|
||||
$internalRate = $rate;
|
||||
if (method_exists($exportItem, 'getInternalRate')) {
|
||||
$internalRate = $exportItem->getInternalRate();
|
||||
}
|
||||
$internalRate = $exportItem->getInternalRate();
|
||||
|
||||
// rate
|
||||
$summary[$id]['rate'] += $rate;
|
||||
@@ -169,12 +168,12 @@ trait RendererTrait
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceItemInterface[] $exportItems
|
||||
* @param ExportableItem[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @param ProjectStatisticService $projectStatisticService
|
||||
* @return array
|
||||
*/
|
||||
protected function calculateProjectBudget(array $exportItems, TimesheetQuery $query, ProjectStatisticService $projectStatisticService)
|
||||
protected function calculateProjectBudget(array $exportItems, TimesheetQuery $query, ProjectStatisticService $projectStatisticService): array
|
||||
{
|
||||
$summary = [];
|
||||
$projects = [];
|
||||
@@ -254,12 +253,12 @@ trait RendererTrait
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceItemInterface[] $exportItems
|
||||
* @param ExportableItem[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @param ActivityStatisticService $activityStatisticService
|
||||
* @return array
|
||||
*/
|
||||
protected function calculateActivityBudget(array $exportItems, TimesheetQuery $query, ActivityStatisticService $activityStatisticService)
|
||||
protected function calculateActivityBudget(array $exportItems, TimesheetQuery $query, ActivityStatisticService $activityStatisticService): array
|
||||
{
|
||||
$summary = [];
|
||||
$activities = [];
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options = [];
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string|array $value
|
||||
* @return void
|
||||
*/
|
||||
public function setOption(string $key, $value): void
|
||||
{
|
||||
$this->options[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return array|string|null
|
||||
*/
|
||||
public function getOption(string $key)
|
||||
{
|
||||
if (\array_key_exists($key, $this->options)) {
|
||||
return $this->options[$key];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -17,22 +17,10 @@ use App\Utils\FileHelper;
|
||||
|
||||
final class ExportFilename
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $filename;
|
||||
/**
|
||||
* @var Customer|null
|
||||
*/
|
||||
private $customer;
|
||||
/**
|
||||
* @var Project|null
|
||||
*/
|
||||
private $project;
|
||||
/**
|
||||
* @var User|null
|
||||
*/
|
||||
private $user;
|
||||
private ?string $filename = null;
|
||||
private ?Customer $customer = null;
|
||||
private ?Project $project = null;
|
||||
private ?User $user = null;
|
||||
|
||||
public function __construct(TimesheetQuery $query)
|
||||
{
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?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;
|
||||
|
||||
use App\Entity\MetaTableTypeInterface;
|
||||
use App\Invoice\InvoiceItemInterface;
|
||||
|
||||
/**
|
||||
* Will be merged with InvoiceItemInterface in 2.0
|
||||
*/
|
||||
interface ExportItemInterface extends InvoiceItemInterface
|
||||
{
|
||||
/**
|
||||
* Whether this item was already exported.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isExported(): bool;
|
||||
|
||||
// will be activated with 2.0
|
||||
// public function isBillable(): bool;
|
||||
|
||||
/**
|
||||
* Returns the named meta field or null.
|
||||
*
|
||||
* @param string $name
|
||||
* @return MetaTableTypeInterface|null
|
||||
*/
|
||||
public function getMetaField(string $name): ?MetaTableTypeInterface;
|
||||
|
||||
/**
|
||||
* Returns all assigned tag names.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTagsAsArray(): array;
|
||||
}
|
||||
@@ -9,13 +9,14 @@
|
||||
|
||||
namespace App\Export;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
interface ExportRendererInterface
|
||||
{
|
||||
/**
|
||||
* @param ExportItemInterface[] $exportItems
|
||||
* @param ExportableItem[] $exportItems
|
||||
* @param TimesheetQuery $query
|
||||
* @return Response
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Export;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Repository\Query\ExportQuery;
|
||||
|
||||
interface ExportRepositoryInterface
|
||||
@@ -17,20 +18,20 @@ interface ExportRepositoryInterface
|
||||
* This method will receive ALL exported items, loaded from all repositories.
|
||||
* Be careful to only handle the ones, which belong to your repository.
|
||||
*
|
||||
* @param ExportItemInterface[] $items
|
||||
* @param ExportableItem[] $items
|
||||
* @return void
|
||||
*/
|
||||
public function setExported(array $items): void;
|
||||
|
||||
/**
|
||||
* @param ExportQuery $query
|
||||
* @return ExportItemInterface[]
|
||||
* @return ExportableItem[]
|
||||
*/
|
||||
public function getExportItemsForQuery(ExportQuery $query): iterable;
|
||||
|
||||
/**
|
||||
* Returns the type of this repository.
|
||||
* Must match the value returned by your entities via ExportItemInterface::getType().
|
||||
* Must match the value returned by your entities via ExportableItem::getType().
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?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\Renderer;
|
||||
|
||||
use App\Export\Base\AbstractSpreadsheetRenderer as BaseAbstractSpreadsheetRenderer;
|
||||
|
||||
/**
|
||||
* @deprecated since 1.6, will be removed with 2.0
|
||||
*/
|
||||
abstract class AbstractSpreadsheetRenderer extends BaseAbstractSpreadsheetRenderer
|
||||
{
|
||||
}
|
||||
@@ -16,17 +16,12 @@ use Twig\Environment;
|
||||
|
||||
final class HtmlRendererFactory
|
||||
{
|
||||
private $twig;
|
||||
private $dispatcher;
|
||||
private $projectStatisticService;
|
||||
private $activityStatisticService;
|
||||
|
||||
public function __construct(Environment $twig, EventDispatcherInterface $dispatcher, ProjectStatisticService $projectStatisticService, ActivityStatisticService $activityStatisticService)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->projectStatisticService = $projectStatisticService;
|
||||
$this->activityStatisticService = $activityStatisticService;
|
||||
public function __construct(
|
||||
private Environment $twig,
|
||||
private EventDispatcherInterface $dispatcher,
|
||||
private ProjectStatisticService $projectStatisticService,
|
||||
private ActivityStatisticService $activityStatisticService
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(string $id, string $template): HtmlRenderer
|
||||
|
||||
@@ -9,30 +9,17 @@
|
||||
|
||||
namespace App\Export\Renderer;
|
||||
|
||||
use App\Pdf\HtmlToPdfConverter;
|
||||
use App\Project\ProjectStatisticService;
|
||||
use App\Utils\HtmlToPdfConverter;
|
||||
use Twig\Environment;
|
||||
|
||||
final class PdfRendererFactory
|
||||
{
|
||||
/**
|
||||
* @var Environment
|
||||
*/
|
||||
private $twig;
|
||||
/**
|
||||
* @var HtmlToPdfConverter
|
||||
*/
|
||||
private $converter;
|
||||
/**
|
||||
* @var ProjectStatisticService
|
||||
*/
|
||||
private $projectStatisticService;
|
||||
|
||||
public function __construct(Environment $twig, HtmlToPdfConverter $converter, ProjectStatisticService $projectStatisticService)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->converter = $converter;
|
||||
$this->projectStatisticService = $projectStatisticService;
|
||||
public function __construct(
|
||||
private Environment $twig,
|
||||
private HtmlToPdfConverter $converter,
|
||||
private ProjectStatisticService $projectStatisticService
|
||||
) {
|
||||
}
|
||||
|
||||
public function create(string $id, string $template): PDFRenderer
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?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\Renderer;
|
||||
|
||||
use App\Export\Base\RendererTrait as BaseRendererTrait;
|
||||
|
||||
/**
|
||||
* @deprecated since 1.6, will be removed with 2.0
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
trait RendererTrait
|
||||
{
|
||||
use BaseRendererTrait;
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Export;
|
||||
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Event\ExportItemsQueryEvent;
|
||||
use App\Repository\Query\ExportQuery;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
@@ -27,14 +28,9 @@ final class ServiceExport
|
||||
* @var ExportRepositoryInterface[]
|
||||
*/
|
||||
private $repositories = [];
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher)
|
||||
public function __construct(private EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function addRenderer(ExportRendererInterface $renderer): void
|
||||
@@ -92,19 +88,17 @@ final class ServiceExport
|
||||
|
||||
/**
|
||||
* @param ExportQuery $query
|
||||
* @return ExportItemInterface[]
|
||||
* @return ExportableItem[]
|
||||
* @throws TooManyItemsExportException
|
||||
*/
|
||||
public function getExportItems(ExportQuery $query)
|
||||
public function getExportItems(ExportQuery $query): array
|
||||
{
|
||||
$items = [];
|
||||
|
||||
$event = new ExportItemsQueryEvent($query);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
$max = $event->getExportQuery()->getMaxResults();
|
||||
$max = $this->getMaximumResults($query);
|
||||
|
||||
foreach ($this->repositories as $repository) {
|
||||
$items = array_merge($items, $repository->getExportItemsForQuery($event->getExportQuery()));
|
||||
$items = array_merge($items, $repository->getExportItemsForQuery($query));
|
||||
if ($max !== null && \count($items) > $max) {
|
||||
throw new TooManyItemsExportException(
|
||||
sprintf('Limit reached! Expected max. %s items but got %s', $max, \count($items))
|
||||
@@ -121,4 +115,12 @@ final class ServiceExport
|
||||
$repository->setExported($items);
|
||||
}
|
||||
}
|
||||
|
||||
public function getMaximumResults(ExportQuery $query): ?int
|
||||
{
|
||||
$event = new ExportItemsQueryEvent($query);
|
||||
$this->eventDispatcher->dispatch($event);
|
||||
|
||||
return $event->getExportQuery()->getMaxResults();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,8 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
final class AnnotatedObjectExporter
|
||||
{
|
||||
private $spreadsheetExporter;
|
||||
private $annotationExtractor;
|
||||
|
||||
public function __construct(SpreadsheetExporter $spreadsheetExporter, AnnotationExtractor $annotationExtractor)
|
||||
public function __construct(private SpreadsheetExporter $spreadsheetExporter, private AnnotationExtractor $annotationExtractor)
|
||||
{
|
||||
$this->spreadsheetExporter = $spreadsheetExporter;
|
||||
$this->annotationExtractor = $annotationExtractor;
|
||||
}
|
||||
|
||||
public function export(string $class, array $entries): Spreadsheet
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace App\Export\Spreadsheet\CellFormatter;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class ArrayFormatter implements CellFormatterInterface
|
||||
final class ArrayFormatter implements CellFormatterInterface
|
||||
{
|
||||
public function setFormattedValue(Worksheet $sheet, int $column, int $row, $value): void
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace App\Export\Spreadsheet\CellFormatter;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class BooleanFormatter implements CellFormatterInterface
|
||||
final class BooleanFormatter implements CellFormatterInterface
|
||||
{
|
||||
public function setFormattedValue(Worksheet $sheet, int $column, int $row, $value): void
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class DateFormatter implements CellFormatterInterface
|
||||
final class DateFormatter implements CellFormatterInterface
|
||||
{
|
||||
public function setFormattedValue(Worksheet $sheet, int $column, int $row, $value): void
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace App\Export\Spreadsheet\CellFormatter;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class DateTimeFormatter implements CellFormatterInterface
|
||||
final class DateTimeFormatter implements CellFormatterInterface
|
||||
{
|
||||
public const DATETIME_FORMAT = 'yyyy-mm-dd hh:mm';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace App\Export\Spreadsheet\CellFormatter;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class DurationFormatter implements CellFormatterInterface
|
||||
final class DurationFormatter implements CellFormatterInterface
|
||||
{
|
||||
public const DURATION_FORMAT = '[hh]:mm';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace App\Export\Spreadsheet\CellFormatter;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class TimeFormatter implements CellFormatterInterface
|
||||
final class TimeFormatter implements CellFormatterInterface
|
||||
{
|
||||
public const TIME_FORMAT = 'hh:mm';
|
||||
|
||||
|
||||
@@ -11,14 +11,10 @@ namespace App\Export\Spreadsheet;
|
||||
|
||||
final class ColumnDefinition
|
||||
{
|
||||
private $label;
|
||||
private $type;
|
||||
private $accessor;
|
||||
|
||||
public function __construct(string $label, string $type, callable $accessor)
|
||||
public function __construct(private string $label, private string $type, callable $accessor)
|
||||
{
|
||||
$this->label = $label;
|
||||
$this->type = $type;
|
||||
$this->accessor = $accessor;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,8 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
final class EntityWithMetaFieldsExporter
|
||||
{
|
||||
private $exporter;
|
||||
private $annotationExtractor;
|
||||
private $metaFieldExtractor;
|
||||
|
||||
public function __construct(SpreadsheetExporter $exporter, AnnotationExtractor $annotationExtractor, MetaFieldExtractor $metaFieldExtractor)
|
||||
public function __construct(private SpreadsheetExporter $exporter, private AnnotationExtractor $annotationExtractor, private MetaFieldExtractor $metaFieldExtractor)
|
||||
{
|
||||
$this->exporter = $exporter;
|
||||
$this->annotationExtractor = $annotationExtractor;
|
||||
$this->metaFieldExtractor = $metaFieldExtractor;
|
||||
}
|
||||
|
||||
public function export(string $class, array $entries, MetaDisplayEventInterface $event): Spreadsheet
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace App\Export\Spreadsheet\Extractor;
|
||||
use App\Export\Annotation\Expose;
|
||||
use App\Export\Annotation\Order;
|
||||
use App\Export\Spreadsheet\ColumnDefinition;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
|
||||
/**
|
||||
@@ -20,18 +19,10 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
*/
|
||||
final class AnnotationExtractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExpressionLanguage
|
||||
*/
|
||||
private $expressionLanguage;
|
||||
/**
|
||||
* @var Reader
|
||||
*/
|
||||
private $annotationReader;
|
||||
private ExpressionLanguage $expressionLanguage;
|
||||
|
||||
public function __construct(Reader $annotationReader)
|
||||
public function __construct()
|
||||
{
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->expressionLanguage = new ExpressionLanguage();
|
||||
}
|
||||
|
||||
@@ -54,88 +45,98 @@ final class AnnotationExtractor implements ExtractorInterface
|
||||
|
||||
$columns = [];
|
||||
|
||||
if (null !== ($definitions = $this->annotationReader->getClassAnnotations($reflectionClass))) {
|
||||
foreach ($definitions as $definition) {
|
||||
if ($definition instanceof Order) {
|
||||
foreach ($definition->order as $columnName) {
|
||||
$columns[$columnName] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($definitions as $definition) {
|
||||
if ($definition instanceof Expose) {
|
||||
if (null === $definition->name) {
|
||||
throw new ExtractorException(sprintf('@Expose needs a name attribute on class level hierarchy, check %s::class', $value));
|
||||
}
|
||||
if (null === $definition->exp) {
|
||||
throw new ExtractorException(sprintf('@Expose needs an expression attribute on class level hierarchy, check %s::class', $value));
|
||||
}
|
||||
|
||||
$parsed = $this->expressionLanguage->parse($definition->exp, ['object']);
|
||||
|
||||
$columns[$definition->name] = new ColumnDefinition(
|
||||
$definition->label,
|
||||
$definition->type,
|
||||
function ($obj) use ($parsed) {
|
||||
return $parsed->getNodes()->evaluate([], ['object' => $obj]);
|
||||
}
|
||||
);
|
||||
}
|
||||
$order = $reflectionClass->getAttributes(Order::class);
|
||||
foreach ($order as $definition) {
|
||||
foreach ($definition->getArguments()[0] as $columnName) {
|
||||
$columns[$columnName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$definitions = $reflectionClass->getAttributes(Expose::class);
|
||||
foreach ($definitions as $definition) {
|
||||
$arguments = $definition->getArguments();
|
||||
if (!\array_key_exists('name', $arguments) || $arguments['name'] === null) {
|
||||
throw new ExtractorException(sprintf('@Expose needs the "name" attribute on class level hierarchy, check %s::class', $value));
|
||||
}
|
||||
if (!\array_key_exists('exp', $arguments) || $arguments['exp'] === null) {
|
||||
throw new ExtractorException(sprintf('@Expose needs the "exp" attribute on class level hierarchy, check %s::class', $value));
|
||||
}
|
||||
if (!\array_key_exists('label', $arguments) || $arguments['label'] === null) {
|
||||
throw new ExtractorException(sprintf('@Expose needs the "label" attribute on class level hierarchy, check %s::class', $value));
|
||||
}
|
||||
|
||||
$parsed = $this->expressionLanguage->parse($arguments['exp'], ['object']);
|
||||
|
||||
$columns[$arguments['name']] = new ColumnDefinition(
|
||||
$arguments['label'],
|
||||
$arguments['type'] ?? 'string',
|
||||
function ($obj) use ($parsed) {
|
||||
return $parsed->getNodes()->evaluate([], ['object' => $obj]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($reflectionClass->getProperties() as $property) {
|
||||
if (null !== ($definitions = $this->annotationReader->getPropertyAnnotations($property))) {
|
||||
foreach ($definitions as $definition) {
|
||||
if ($definition instanceof Expose) {
|
||||
if (null !== $definition->exp) {
|
||||
throw new ExtractorException(sprintf('@Expose only supports the expression attribute on class level hierarchy, check %s::$%s', $value, $property->getName()));
|
||||
$definitions = $property->getAttributes(Expose::class);
|
||||
foreach ($definitions as $definition) {
|
||||
$arguments = $definition->getArguments();
|
||||
if (\array_key_exists('exp', $arguments) && $arguments['exp'] !== null) {
|
||||
throw new ExtractorException(sprintf('@Expose only supports the "exp" attribute on class level hierarchy, check %s::$%s', $value, $property->getName()));
|
||||
}
|
||||
if (!\array_key_exists('label', $arguments) || $arguments['label'] === null) {
|
||||
throw new ExtractorException(sprintf('@Expose needs the "label" attribute on property level hierarchy, check %s::$%s', $value, $property->getName()));
|
||||
}
|
||||
|
||||
$name = $property->getName();
|
||||
if (\array_key_exists('name', $arguments) && $arguments['name'] !== null) {
|
||||
$name = $arguments['name'];
|
||||
}
|
||||
|
||||
$columns[$name] = new ColumnDefinition(
|
||||
$arguments['label'],
|
||||
$arguments['type'] ?? 'string',
|
||||
function ($obj) use ($property) {
|
||||
if (!$property->isPublic()) {
|
||||
$property->setAccessible(true);
|
||||
}
|
||||
|
||||
$name = empty($definition->name) ? $property->getName() : $definition->name;
|
||||
|
||||
$columns[$name] = new ColumnDefinition(
|
||||
$definition->label,
|
||||
$definition->type,
|
||||
function ($obj) use ($property) {
|
||||
if (!$property->isPublic()) {
|
||||
$property->setAccessible(true);
|
||||
}
|
||||
|
||||
return $property->getValue($obj);
|
||||
}
|
||||
);
|
||||
return $property->getValue($obj);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($reflectionClass->getMethods() as $method) {
|
||||
if (null !== ($definitions = $this->annotationReader->getMethodAnnotations($method))) {
|
||||
foreach ($definitions as $definition) {
|
||||
if ($definition instanceof Expose) {
|
||||
if (null !== $definition->exp) {
|
||||
throw new ExtractorException(sprintf('@Expose only supports the expression attribute on class level hierarchy, check %s::%s()', $value, $method->getName()));
|
||||
}
|
||||
$name = empty($definition->name) ? $method->getName() : $definition->name;
|
||||
|
||||
if (\count($method->getParameters()) > 0) {
|
||||
throw new ExtractorException(sprintf('@Expose does not support method %s::%s(...), it has required parameters.', $value, $method->getName()));
|
||||
}
|
||||
|
||||
$columns[$name] = new ColumnDefinition(
|
||||
$definition->label,
|
||||
$definition->type,
|
||||
function ($obj) use ($method) {
|
||||
if (!$method->isPublic()) {
|
||||
$method->setAccessible(true);
|
||||
}
|
||||
|
||||
return $method->invoke($obj);
|
||||
}
|
||||
);
|
||||
}
|
||||
$definitions = $method->getAttributes(Expose::class);
|
||||
foreach ($definitions as $definition) {
|
||||
if (\count($method->getParameters()) > 0) {
|
||||
throw new ExtractorException(sprintf('@Expose does not support method %s::%s(...) as it has required parameters.', $value, $method->getName()));
|
||||
}
|
||||
|
||||
$arguments = $definition->getArguments();
|
||||
if (\array_key_exists('exp', $arguments) && $arguments['exp'] !== null) {
|
||||
throw new ExtractorException(sprintf('@Expose only supports the "exp" attribute on method level hierarchy, check %s::%s()', $value, $method->getName()));
|
||||
}
|
||||
if (!\array_key_exists('label', $arguments) || $arguments['label'] === null) {
|
||||
throw new ExtractorException(sprintf('@Expose needs the "label" attribute on method level hierarchy, check %s::%s()', $value, $method->getName()));
|
||||
}
|
||||
|
||||
$name = $method->getName();
|
||||
if (\array_key_exists('name', $arguments) && $arguments['name'] !== null) {
|
||||
$name = $arguments['name'];
|
||||
}
|
||||
|
||||
$columns[$name] = new ColumnDefinition(
|
||||
$arguments['label'],
|
||||
$arguments['type'] ?? 'string',
|
||||
function ($obj) use ($method) {
|
||||
if (!$method->isPublic()) {
|
||||
$method->setAccessible(true);
|
||||
}
|
||||
|
||||
return $method->invoke($obj);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
namespace App\Export\Spreadsheet\Extractor;
|
||||
|
||||
class ExtractorException extends \Exception
|
||||
final class ExtractorException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
@@ -19,14 +19,8 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
*/
|
||||
final class MetaFieldExtractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher)
|
||||
public function __construct(private EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,14 +19,8 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
*/
|
||||
final class UserPreferenceExtractor implements ExtractorInterface
|
||||
{
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
private $eventDispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher)
|
||||
public function __construct(private EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,19 +24,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
*/
|
||||
class SpreadsheetExporter
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
/**
|
||||
* @var CellFormatterInterface[]
|
||||
*/
|
||||
private $formatter = [];
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
public function __construct(private TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
|
||||
$this->registerCellFormatter('datetime', new DateTimeFormatter());
|
||||
$this->registerCellFormatter('date', new DateFormatter());
|
||||
$this->registerCellFormatter('time', new TimeFormatter());
|
||||
@@ -45,7 +39,7 @@ class SpreadsheetExporter
|
||||
$this->registerCellFormatter('array', new ArrayFormatter());
|
||||
}
|
||||
|
||||
public function registerCellFormatter(string $type, CellFormatterInterface $formatter)
|
||||
public function registerCellFormatter(string $type, CellFormatterInterface $formatter): void
|
||||
{
|
||||
$this->formatter[$type] = $formatter;
|
||||
}
|
||||
|
||||
@@ -17,15 +17,8 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
final class UserExporter
|
||||
{
|
||||
private $exporter;
|
||||
private $annotationExtractor;
|
||||
private $userPreferenceExtractor;
|
||||
|
||||
public function __construct(SpreadsheetExporter $exporter, AnnotationExtractor $annotationExtractor, UserPreferenceExtractor $userPreferenceExtractor)
|
||||
public function __construct(private SpreadsheetExporter $exporter, private AnnotationExtractor $annotationExtractor, private UserPreferenceExtractor $userPreferenceExtractor)
|
||||
{
|
||||
$this->exporter = $exporter;
|
||||
$this->annotationExtractor = $annotationExtractor;
|
||||
$this->userPreferenceExtractor = $userPreferenceExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,24 +14,16 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
class BinaryFileResponseWriter implements WriterInterface
|
||||
final class BinaryFileResponseWriter implements WriterInterface
|
||||
{
|
||||
/**
|
||||
* @var WriterInterface
|
||||
*/
|
||||
private $writer;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $prefix;
|
||||
private string $prefix;
|
||||
|
||||
/**
|
||||
* @param WriterInterface $writer
|
||||
* @param string $prefix is only urlencoded but not validated and can break the response if you pass in invalid character
|
||||
*/
|
||||
public function __construct(WriterInterface $writer, string $prefix)
|
||||
public function __construct(private WriterInterface $writer, string $prefix)
|
||||
{
|
||||
$this->writer = $writer;
|
||||
$this->prefix = urlencode($prefix);
|
||||
}
|
||||
|
||||
@@ -45,9 +37,6 @@ class BinaryFileResponseWriter implements WriterInterface
|
||||
return $this->writer->getContentType();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(Spreadsheet $spreadsheet, array $options = []): \SplFileInfo
|
||||
{
|
||||
return $this->writer->save($spreadsheet, $options);
|
||||
|
||||
@@ -13,16 +13,13 @@ use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
|
||||
class XlsxWriter implements WriterInterface
|
||||
final class XlsxWriter implements WriterInterface
|
||||
{
|
||||
public function getFileExtension(): string
|
||||
{
|
||||
return 'xlsx';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContentType(): string
|
||||
{
|
||||
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
|
||||
@@ -15,14 +15,8 @@ use App\Repository\TimesheetRepository;
|
||||
|
||||
final class TimesheetExportRepository implements ExportRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @var TimesheetRepository
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
public function __construct(TimesheetRepository $repository)
|
||||
public function __construct(private TimesheetRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user