Release 2.37 (#5546)
This commit is contained in:
@@ -20,19 +20,16 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
abstract class AbstractRoleCommand extends Command
|
||||
{
|
||||
public function __construct(private UserService $userService)
|
||||
public function __construct(private readonly UserService $userService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDefinition([
|
||||
new InputArgument('username', InputArgument::REQUIRED, 'The username'),
|
||||
new InputArgument('role', InputArgument::OPTIONAL, 'The role'),
|
||||
new InputOption('super', null, InputOption::VALUE_NONE, 'Instead specifying role, use this to quickly add the super administrator role'),
|
||||
]);
|
||||
$this->addArgument('username', InputArgument::REQUIRED, 'The username');
|
||||
$this->addArgument('role', InputArgument::OPTIONAL, 'The role');
|
||||
$this->addOption('super', null, InputOption::VALUE_NONE, 'Instead specifying role, use this to quickly add the super administrator role');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
|
||||
@@ -26,7 +26,7 @@ abstract class AbstractUserCommand extends Command
|
||||
|
||||
$passwordQuestion = new Question('Please enter the password: ');
|
||||
$passwordQuestion->setHidden(true);
|
||||
$passwordQuestion->setHiddenFallback(false);
|
||||
$passwordQuestion->setHiddenFallback(true);
|
||||
$passwordQuestion->setValidator(function (?string $value) {
|
||||
$password = trim($value);
|
||||
if (empty($password)) {
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'kimai:user:activate')]
|
||||
#[AsCommand(name: 'kimai:user:activate', description: 'Activate a user')]
|
||||
final class ActivateUserCommand extends Command
|
||||
{
|
||||
public function __construct(private UserService $userService)
|
||||
@@ -28,10 +28,7 @@ final class ActivateUserCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Activate a user')
|
||||
->setDefinition([
|
||||
new InputArgument('username', InputArgument::REQUIRED, 'The username'),
|
||||
])
|
||||
->addArgument('username', InputArgument::REQUIRED, 'The username')
|
||||
->setHelp(
|
||||
<<<'EOT'
|
||||
The <info>kimai:user:activate</info> command activates a user (so they will be able to log in):
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'kimai:user:password')]
|
||||
#[AsCommand(name: 'kimai:user:password', description: 'Change the password of a user')]
|
||||
final class ChangePasswordCommand extends AbstractUserCommand
|
||||
{
|
||||
public function __construct(private UserService $userService)
|
||||
@@ -29,11 +29,8 @@ final class ChangePasswordCommand extends AbstractUserCommand
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Change the password of a user.')
|
||||
->setDefinition([
|
||||
new InputArgument('username', InputArgument::REQUIRED, 'The username'),
|
||||
new InputArgument('password', InputArgument::OPTIONAL, 'The password'),
|
||||
])
|
||||
->addArgument('username', InputArgument::REQUIRED, 'The username')
|
||||
->addArgument('password', InputArgument::OPTIONAL, 'The password')
|
||||
->setHelp(
|
||||
<<<'EOT'
|
||||
The <info>kimai:user:password</info> command changes the password of a user:
|
||||
|
||||
@@ -20,7 +20,7 @@ use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'kimai:user:create')]
|
||||
#[AsCommand(name: 'kimai:user:create', description: 'Create a new user')]
|
||||
final class CreateUserCommand extends AbstractUserCommand
|
||||
{
|
||||
public function __construct(private UserService $userService)
|
||||
@@ -33,7 +33,6 @@ final class CreateUserCommand extends AbstractUserCommand
|
||||
$roles = implode(',', [User::DEFAULT_ROLE, User::ROLE_ADMIN]);
|
||||
|
||||
$this
|
||||
->setDescription('Create a new user')
|
||||
->setHelp('This command allows you to create a new user.')
|
||||
->addArgument('username', InputArgument::REQUIRED, 'A name for the new user (must be unique)')
|
||||
->addArgument('email', InputArgument::REQUIRED, 'Email address of the new user (must be unique)')
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'kimai:user:deactivate')]
|
||||
#[AsCommand(name: 'kimai:user:deactivate', description: 'Deactivate a user')]
|
||||
final class DeactivateUserCommand extends Command
|
||||
{
|
||||
public function __construct(private UserService $userService)
|
||||
@@ -28,10 +28,7 @@ final class DeactivateUserCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Deactivate a user')
|
||||
->setDefinition([
|
||||
new InputArgument('username', InputArgument::REQUIRED, 'The username'),
|
||||
])
|
||||
->addArgument('username', InputArgument::REQUIRED, 'The username')
|
||||
->setHelp(
|
||||
<<<'EOT'
|
||||
The <info>kimai:user:deactivate</info> command deactivates a user (will not be able to log in)
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\User\UserService;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'kimai:user:demote')]
|
||||
#[AsCommand(name: 'kimai:user:demote', description: 'Demote a user by removing a role')]
|
||||
final class DemoteUserCommand extends AbstractRoleCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
@@ -22,7 +22,6 @@ final class DemoteUserCommand extends AbstractRoleCommand
|
||||
parent::configure();
|
||||
|
||||
$this
|
||||
->setDescription('Demote a user by removing a role')
|
||||
->setHelp(
|
||||
<<<'EOT'
|
||||
The <info>kimai:user:demote</info> command demotes a user by removing a role
|
||||
|
||||
@@ -31,7 +31,7 @@ use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
#[AsCommand(name: 'kimai:export:create')]
|
||||
#[AsCommand(name: 'kimai:export:create', description: 'Create exports')]
|
||||
final class ExportCreateCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
@@ -49,7 +49,6 @@ final class ExportCreateCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Create exports')
|
||||
->setHelp('Create exports by several different filters and sent them via email.')
|
||||
->addOption('username', null, InputOption::VALUE_REQUIRED, 'The user to be used for generating the export (e.g. used for permissions and decimal setting)')
|
||||
->addOption('start', null, InputOption::VALUE_OPTIONAL, 'Start date (format: 2020-01-01, default: start of the month)', null)
|
||||
|
||||
@@ -34,7 +34,7 @@ use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[AsCommand(name: 'kimai:invoice:create')]
|
||||
#[AsCommand(name: 'kimai:invoice:create', description: 'Create invoices')]
|
||||
final class InvoiceCreateCommand extends Command
|
||||
{
|
||||
private ?string $previewDirectory = null;
|
||||
@@ -54,7 +54,6 @@ final class InvoiceCreateCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Create invoices')
|
||||
->setHelp('This command allows to create invoices by several different filters.')
|
||||
->addOption('user', null, InputOption::VALUE_REQUIRED, 'The user to be used for generating the invoices')
|
||||
->addOption('start', null, InputOption::VALUE_OPTIONAL, 'Start date (format: 2020-01-01, default: start of the month)', null)
|
||||
|
||||
@@ -16,7 +16,6 @@ use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
@@ -31,7 +30,6 @@ final class MailTestCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addArgument('to', InputArgument::REQUIRED, 'The email address to send the email to');
|
||||
$this->addOption('from', null, InputOption::VALUE_OPTIONAL, 'Deprecated: uses the MAILER_FROM env variable.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
@@ -41,10 +39,6 @@ final class MailTestCommand extends Command
|
||||
throw new \InvalidArgumentException('Need a non-empty "to" address');
|
||||
}
|
||||
|
||||
if ($input->getOption('from') !== null) {
|
||||
throw new \InvalidArgumentException('The "from" option is deprecated and will be ignored');
|
||||
}
|
||||
|
||||
$message = new Email();
|
||||
$message->to($to);
|
||||
$message->subject('Test email - ' . Constants::SOFTWARE);
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\User\UserService;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'kimai:user:promote')]
|
||||
#[AsCommand(name: 'kimai:user:promote', description: 'Promotes a user by adding a role')]
|
||||
final class PromoteUserCommand extends AbstractRoleCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
@@ -22,7 +22,6 @@ final class PromoteUserCommand extends AbstractRoleCommand
|
||||
parent::configure();
|
||||
|
||||
$this
|
||||
->setDescription('Promotes a user by adding a role')
|
||||
->setHelp(
|
||||
<<<'EOT'
|
||||
The <info>kimai:user:promote</info> command promotes a user by adding a role
|
||||
|
||||
@@ -19,19 +19,14 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[AsCommand(name: 'kimai:timesheet:stop-all')]
|
||||
#[AsCommand(name: 'kimai:timesheet:stop-all', description: 'Stop all running timesheets immediately')]
|
||||
final class TimesheetStopAllCommand extends Command
|
||||
{
|
||||
public function __construct(private TimesheetService $timesheetService)
|
||||
public function __construct(private readonly TimesheetService $timesheetService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Stop all running timesheets immediately');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$amount = $this->timesheetService->stopAll();
|
||||
|
||||
@@ -25,7 +25,7 @@ use Symfony\Component\HttpClient\HttpClient;
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[AsCommand(name: 'kimai:translations')]
|
||||
#[AsCommand(name: 'kimai:translations', description: 'Automated translation adjustments')]
|
||||
final class TranslationCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
@@ -40,7 +40,6 @@ final class TranslationCommand extends Command
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Translation adjustments')
|
||||
->addOption('resname', null, InputOption::VALUE_NONE, 'Fix the resname vs. id attribute')
|
||||
->addOption('duplicates', null, InputOption::VALUE_NONE, 'Find duplicate translation keys')
|
||||
->addOption('delete-resname', null, InputOption::VALUE_REQUIRED, 'Deletes the translation by resname')
|
||||
|
||||
@@ -17,17 +17,14 @@ use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(name: 'kimai:version')]
|
||||
#[AsCommand(name: 'kimai:version', description: 'Show version information')]
|
||||
final class VersionCommand extends Command
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('Receive version information')
|
||||
->setHelp('This command allows you to fetch various version information about Kimai.')
|
||||
->addOption('short', null, InputOption::VALUE_NONE, 'Display the version only')
|
||||
->addOption('number', null, InputOption::VALUE_NONE, 'Display the version identifier only only')
|
||||
;
|
||||
$this->setHelp('This command allows you to fetch various version information about Kimai.');
|
||||
$this->addOption('short', null, InputOption::VALUE_NONE, 'Display the version only');
|
||||
$this->addOption('number', null, InputOption::VALUE_NONE, 'Display the version identifier only only');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
|
||||
@@ -458,6 +458,11 @@ final class SystemConfiguration
|
||||
return (bool) $this->find('timesheet.rules.break_time_active');
|
||||
}
|
||||
|
||||
public function getExportTimeout(): int
|
||||
{
|
||||
return (int) $this->find('export.timeout');
|
||||
}
|
||||
|
||||
// ========== Company configurations ==========
|
||||
|
||||
public function getFinancialYearStart(): ?string
|
||||
|
||||
@@ -17,11 +17,11 @@ final class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.36.1';
|
||||
public const VERSION = '2.37.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 23601;
|
||||
public const VERSION_ID = 23700;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Configuration\SamlConfigurationInterface;
|
||||
use App\Saml\SamlAuthFactory;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
@@ -76,7 +77,7 @@ final class SamlController extends AbstractController
|
||||
throw new \RuntimeException('SAML login failed');
|
||||
}
|
||||
|
||||
return $this->redirect($url);
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
|
||||
#[Route(path: '/metadata', name: 'saml_metadata')]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\ExportableItem;
|
||||
use App\Entity\ExportTemplate;
|
||||
use App\Export\Base\DispositionInlineInterface;
|
||||
@@ -114,7 +115,7 @@ final class ExportController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route(path: '/data', name: 'export_data', methods: ['POST'])]
|
||||
public function export(Request $request): Response
|
||||
public function export(Request $request, SystemConfiguration $systemConfiguration): Response
|
||||
{
|
||||
$query = $this->getDefaultQuery();
|
||||
|
||||
@@ -132,6 +133,9 @@ final class ExportController extends AbstractController
|
||||
throw $this->createNotFoundException('Unknown export renderer');
|
||||
}
|
||||
|
||||
$oldMaxExecTime = \ini_get('max_execution_time');
|
||||
ini_set('max_execution_time', $systemConfiguration->getExportTimeout());
|
||||
|
||||
// display file inline if supported and `markAsExported` is not set
|
||||
if ($renderer instanceof DispositionInlineInterface && !$query->isMarkAsExported()) {
|
||||
$renderer->setDispositionInline(true);
|
||||
@@ -144,6 +148,8 @@ final class ExportController extends AbstractController
|
||||
$this->export->setExported($entries);
|
||||
}
|
||||
|
||||
ini_set('max_execution_time', $oldMaxExecTime);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,11 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
abstract class TimesheetAbstractController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
protected TimesheetRepository $repository,
|
||||
protected EventDispatcherInterface $dispatcher,
|
||||
protected TimesheetService $service,
|
||||
protected SystemConfiguration $configuration,
|
||||
protected TagRepository $tagRepository
|
||||
protected readonly TimesheetRepository $repository,
|
||||
protected readonly EventDispatcherInterface $dispatcher,
|
||||
protected readonly TimesheetService $service,
|
||||
protected readonly SystemConfiguration $configuration,
|
||||
protected readonly TagRepository $tagRepository
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -263,7 +263,14 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
|
||||
$entries = $this->repository->getTimesheetResult($query);
|
||||
|
||||
return $exporter->render($entries->getResults(), $query);
|
||||
$oldMaxExecTime = \ini_get('max_execution_time');
|
||||
ini_set('max_execution_time', $this->configuration->getExportTimeout());
|
||||
|
||||
$response = $exporter->render($entries->getResults(), $query);
|
||||
|
||||
ini_set('max_execution_time', $oldMaxExecTime);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function multiUpdate(Request $request): Response
|
||||
|
||||
@@ -396,6 +396,10 @@ final class Configuration implements ConfigurationInterface
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->end()
|
||||
->integerNode('timeout')
|
||||
->defaultValue(60)
|
||||
->end()
|
||||
|
||||
->end()
|
||||
;
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class ExportTemplate
|
||||
#[Assert\NotNull]
|
||||
private array $columns = [];
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
* @var array<string, int|string|null|bool>
|
||||
*/
|
||||
#[ORM\Column(name: 'options', type: Types::JSON, nullable: false)]
|
||||
#[Assert\NotNull]
|
||||
@@ -106,8 +106,22 @@ class ExportTemplate
|
||||
$this->columns = $columns ?? [];
|
||||
}
|
||||
|
||||
public function getOption(string $key, int|string|bool $default): int|string|bool
|
||||
{
|
||||
if (\array_key_exists($key, $this->options)) {
|
||||
return $this->options[$key] ?? $default;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
public function setOption(string $key, int|string|null|bool $value): void
|
||||
{
|
||||
$this->options[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
* @return array<string, int|string|null|bool>
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
@@ -115,13 +129,30 @@ class ExportTemplate
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
* @param array<string, int|string|null|bool> $options
|
||||
*/
|
||||
public function setOptions(?array $options): void
|
||||
{
|
||||
$this->options = $options ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Only used for CSV export
|
||||
*/
|
||||
public function setSeparator(string $separator): void
|
||||
{
|
||||
if (!\in_array($separator, [',', ';'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid separator, comma and semicolon are allowed.');
|
||||
}
|
||||
|
||||
$this->setOption('separator', $separator);
|
||||
}
|
||||
|
||||
public function getSeparator(): string
|
||||
{
|
||||
return (string) $this->getOption('separator', ',');
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->title ?? 'New';
|
||||
|
||||
@@ -914,6 +914,11 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for setUserIdentifier()
|
||||
*
|
||||
* The visible username is setAlias())
|
||||
*/
|
||||
public function setUsername(string $username): void
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Entity\Invoice;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* This event can be used, to dynamically add meta fields to invoices
|
||||
* This event can be used, to dynamically add meta-fields to invoices
|
||||
*/
|
||||
final class InvoiceMetaDefinitionEvent extends Event
|
||||
{
|
||||
|
||||
@@ -87,7 +87,12 @@ final class CsvRenderer implements RendererInterface, TimesheetExportInterface
|
||||
$options = new Options();
|
||||
$options->SHOULD_ADD_BOM = false;
|
||||
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer($options), $this->translator, $this->locale);
|
||||
$opts = $this->spreadsheetRenderer->getTemplate()->getOptions();
|
||||
if (\array_key_exists('separator', $opts) && $opts['separator'] === ';') {
|
||||
$options->FIELD_DELIMITER = ';';
|
||||
}
|
||||
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer($options), $this->translator, $this->locale ?? $this->spreadsheetRenderer->getTemplate()->getLocale());
|
||||
$spreadsheet->open($filename);
|
||||
|
||||
$this->spreadsheetRenderer->registerFormatter('date', new DateStringFormatter());
|
||||
|
||||
@@ -65,13 +65,14 @@ final class SpreadsheetRenderer
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
private function getTemplate(): TemplateInterface
|
||||
public function getTemplate(): TemplateInterface
|
||||
{
|
||||
if ($this->template === null) {
|
||||
$template = new Template('default', 'default');
|
||||
$template->setColumns($this->getDefaultColumns());
|
||||
$template->setLocale('en');
|
||||
|
||||
return $template;
|
||||
$this->template = $template;
|
||||
}
|
||||
|
||||
return $this->template;
|
||||
@@ -263,6 +264,8 @@ final class SpreadsheetRenderer
|
||||
$columns[] = (new Column('alias', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getUser()?->getDisplayName())->withColumnWidth(ColumnWidth::MEDIUM);
|
||||
} elseif ($column === 'user.name') {
|
||||
$columns[] = (new Column('username', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getUser()?->getUserIdentifier())->withColumnWidth(ColumnWidth::MEDIUM);
|
||||
} elseif ($column === 'user.email') {
|
||||
$columns[] = (new Column('email', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getUser()?->getEmail())->withColumnWidth(ColumnWidth::MEDIUM);
|
||||
} elseif ($column === 'user.account_number') {
|
||||
$columns[] = (new Column('account_number', $this->getFormatter('default')))->withExtractor(fn (ExportableItem $exportableItem) => $exportableItem->getUser()?->getAccountNumber());
|
||||
} elseif ($column === 'customer.name') {
|
||||
@@ -336,6 +339,7 @@ final class SpreadsheetRenderer
|
||||
'fixed_rate',
|
||||
'user.alias',
|
||||
'user.name',
|
||||
'user.email',
|
||||
'user.account_number',
|
||||
'customer.name',
|
||||
'project.name',
|
||||
|
||||
@@ -81,7 +81,7 @@ final class XlsxRenderer implements RendererInterface, TimesheetExportInterface
|
||||
throw new \Exception('Could not open temporary file');
|
||||
}
|
||||
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer(), $this->translator, $this->locale);
|
||||
$spreadsheet = new SpoutSpreadsheet(new Writer(), $this->translator, $this->locale ?? $this->spreadsheetRenderer->getTemplate()->getLocale());
|
||||
$spreadsheet->open($filename);
|
||||
|
||||
$this->spreadsheetRenderer->writeSpreadsheet($spreadsheet, $exportItems, $query);
|
||||
|
||||
@@ -85,6 +85,7 @@ final class ServiceExport
|
||||
$tpl = new Template((string) $template->getId(), $template->getTitle()); // @phpstan-ignore argument.type
|
||||
$tpl->setColumns($template->getColumns());
|
||||
$tpl->setLocale($template->getLanguage());
|
||||
$tpl->setOptions($template->getOptions());
|
||||
|
||||
switch ($template->getRenderer()) {
|
||||
case 'csv':
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Form\Type\ExportColumnsType;
|
||||
use App\Form\Type\ExportRendererType;
|
||||
use App\Form\Type\LanguageType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -26,6 +27,7 @@ class ExportTemplateSpreadsheetForm extends AbstractType
|
||||
$builder->add('renderer', ExportRendererType::class, ['label' => 'type']);
|
||||
$builder->add('language', LanguageType::class, ['required' => false]);
|
||||
$builder->add('columns', ExportColumnsType::class, ['required' => true]);
|
||||
$builder->add('separator', ChoiceType::class, ['choices' => ['Comma (,)' => ',', 'Semicolon (;)' => ';'], 'required' => true]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
|
||||
@@ -61,7 +61,7 @@ final class QuickEntryTimesheetType extends AbstractType
|
||||
}
|
||||
|
||||
if ($data instanceof Timesheet && !$this->security->isGranted('edit', $data)) {
|
||||
$event->getForm()->remove('duration');
|
||||
// do not call $event->getForm()->remove() this would change the field order
|
||||
$event->getForm()->add('duration', DurationType::class, array_merge(['disabled' => true], $durationOptions));
|
||||
|
||||
$mainForm = $event->getForm()->getParent()?->getParent();
|
||||
@@ -79,7 +79,7 @@ final class QuickEntryTimesheetType extends AbstractType
|
||||
continue;
|
||||
}
|
||||
$type = \get_class($child->getConfig()->getType()->getInnerType());
|
||||
$mainForm->remove($key);
|
||||
// do not call $mainForm->remove() this would change the field order
|
||||
$mainForm->add($key, $type, array_merge($child->getConfig()->getOptions(), ['disabled' => true]));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user