Next major version 2 with PHP 8.1, Symfony 6, Tabler UI, 2FA ... (#2902)
This commit is contained in:
@@ -10,30 +10,7 @@
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\ActivityEditForm;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ActivityApiEditForm extends ActivityEditForm
|
||||
final class ActivityApiEditForm extends ActivityEditForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->remove('metaFields');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,7 @@
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\ActivityRateForm;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ActivityRateApiForm extends ActivityRateForm
|
||||
final class ActivityRateApiForm extends ActivityRateForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,30 +10,7 @@
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\CustomerEditForm;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class CustomerApiEditForm extends CustomerEditForm
|
||||
final class CustomerApiEditForm extends CustomerEditForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->remove('metaFields');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,7 @@
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\CustomerRateForm;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class CustomerRateApiForm extends CustomerRateForm
|
||||
final class CustomerRateApiForm extends CustomerRateForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,16 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Type;
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\API\BaseApiController;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class APIDateTimeType extends AbstractType
|
||||
final class DateTimeApiType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'documentation' => [
|
||||
@@ -28,16 +25,12 @@ class APIDateTimeType extends AbstractType
|
||||
'example' => (new \DateTime())->format(BaseApiController::DATE_FORMAT_PHP),
|
||||
],
|
||||
'widget' => 'single_text',
|
||||
'html5' => true,
|
||||
'model_timezone' => date_default_timezone_get(),
|
||||
'view_timezone' => date_default_timezone_get(),
|
||||
'html5' => true, // for the correct format
|
||||
'with_seconds' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return DateTimeType::class;
|
||||
}
|
||||
@@ -10,36 +10,7 @@
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\ProjectEditForm;
|
||||
use App\Form\Type\APITrueFalseType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectApiEditForm extends ProjectEditForm
|
||||
final class ProjectApiEditForm extends ProjectEditForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->remove('metaFields');
|
||||
$builder->remove('globalActivities');
|
||||
$builder->add('globalActivities', APITrueFalseType::class, [
|
||||
'label' => 'label.globalActivities',
|
||||
'empty_data' => '1',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,7 @@
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\ProjectRateForm;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectRateApiForm extends ProjectRateForm
|
||||
final class ProjectRateApiForm extends ProjectRateForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,7 @@
|
||||
namespace App\Form\API;
|
||||
|
||||
use App\Form\TagEditForm;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TagApiEditForm extends TagEditForm
|
||||
final class TagApiEditForm extends TagEditForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,25 +11,12 @@ namespace App\Form\API;
|
||||
|
||||
use App\Form\TeamEditForm;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TeamApiEditForm extends TeamEditForm
|
||||
final class TeamApiEditForm extends TeamEditForm
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
$builder->remove('users');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,25 +19,27 @@ use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TimesheetApiEditForm extends TimesheetEditForm
|
||||
final class TimesheetApiEditForm extends TimesheetEditForm
|
||||
{
|
||||
protected function addBillable(FormBuilderInterface $builder, array $options)
|
||||
protected function addBillable(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if (!$options['include_billable']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$builder->add('billable', BillableType::class, []);
|
||||
$builder->add('billable', BillableType::class);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
$data = $event->getData();
|
||||
if (\array_key_exists('billable', $data)) {
|
||||
$data['billableMode'] = Timesheet::BILLABLE_AUTOMATIC;
|
||||
$event->getForm()->add('billableMode', TimesheetBillableType::class, []);
|
||||
if ($data['billable'] === true) {
|
||||
$billable = $data['billable'] === null ? false : (bool) $data['billable'];
|
||||
if ($billable === true) {
|
||||
$data['billableMode'] = Timesheet::BILLABLE_YES;
|
||||
} elseif ($data['billable'] === false) {
|
||||
} elseif ($billable === false) {
|
||||
$data['billableMode'] = Timesheet::BILLABLE_NO;
|
||||
}
|
||||
}
|
||||
@@ -46,14 +48,13 @@ class TimesheetApiEditForm extends TimesheetEditForm
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->remove('metaFields');
|
||||
if ($builder->has('metaFields')) {
|
||||
$builder->remove('metaFields');
|
||||
}
|
||||
|
||||
if ($builder->has('duration')) {
|
||||
$builder->remove('duration');
|
||||
@@ -63,6 +64,11 @@ class TimesheetApiEditForm extends TimesheetEditForm
|
||||
$builder->get('user')->setRequired(false);
|
||||
}
|
||||
|
||||
// TODO this is only a quick fix, see bugs reports
|
||||
if ($builder->has('duration')) {
|
||||
$builder->remove('duration');
|
||||
}
|
||||
|
||||
if ($builder->has('tags')) {
|
||||
$builder->remove('tags');
|
||||
// @deprecated for BC reasons here, arrays will be supported in 2.0
|
||||
@@ -72,7 +78,22 @@ class TimesheetApiEditForm extends TimesheetEditForm
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
protected function addBegin(FormBuilderInterface $builder, array $dateTimeOptions, array $options = []): void
|
||||
{
|
||||
$builder->add('begin', DateTimeApiType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'begin',
|
||||
]));
|
||||
}
|
||||
|
||||
protected function addEnd(FormBuilderInterface $builder, array $dateTimeOptions, array $options = []): void
|
||||
{
|
||||
$builder->add('end', DateTimeApiType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'end',
|
||||
'required' => false,
|
||||
]));
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
||||
@@ -15,12 +15,9 @@ use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserApiCreateForm extends UserCreateType
|
||||
final class UserApiCreateForm extends UserCreateType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
@@ -30,7 +27,7 @@ class UserApiCreateForm extends UserCreateType
|
||||
|
||||
$builder->add('plainPassword', PasswordType::class, [
|
||||
'required' => true,
|
||||
'label' => 'label.password',
|
||||
'label' => 'password',
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Plain text password',
|
||||
@@ -43,7 +40,7 @@ class UserApiCreateForm extends UserCreateType
|
||||
|
||||
$builder->add('plainApiToken', PasswordType::class, [
|
||||
'required' => false,
|
||||
'label' => 'label.api_token',
|
||||
'label' => 'api_token',
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Plain API token',
|
||||
@@ -52,7 +49,7 @@ class UserApiCreateForm extends UserCreateType
|
||||
|
||||
if ($options['include_roles']) {
|
||||
$builder->add('roles', UserRoleType::class, [
|
||||
'label' => 'label.roles',
|
||||
'label' => 'roles',
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
@@ -60,14 +57,13 @@ class UserApiCreateForm extends UserCreateType
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
'include_roles' => true,
|
||||
'include_add_more' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,15 @@ use App\Form\UserEditType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class UserApiEditForm extends UserEditType
|
||||
final class UserApiEditForm extends UserEditType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
if ($options['include_roles']) {
|
||||
$builder->add('roles', UserRoleType::class, [
|
||||
'label' => 'label.roles',
|
||||
'label' => 'roles',
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
@@ -33,7 +30,7 @@ class UserApiEditForm extends UserEditType
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ abstract class AbstractRateForm extends AbstractType
|
||||
'type' => 'number',
|
||||
'description' => 'The rate (eg. 10.5)',
|
||||
],
|
||||
'label' => 'label.rate',
|
||||
'label' => 'rate',
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
],
|
||||
@@ -42,13 +42,13 @@ abstract class AbstractRateForm extends AbstractType
|
||||
'type' => 'number',
|
||||
'description' => 'The internal rate (eg. 10.0 or 10)',
|
||||
],
|
||||
'label' => 'label.rate_internal',
|
||||
'label' => 'internalRate',
|
||||
'currency' => $currency,
|
||||
'required' => false,
|
||||
'help' => 'help.rate_internal',
|
||||
'help' => 'help.internalRate',
|
||||
])
|
||||
->add('isFixed', YesNoType::class, [
|
||||
'label' => 'label.fixedRate',
|
||||
'label' => 'fixedRate',
|
||||
'help' => 'help.fixedRate',
|
||||
'documentation' => [
|
||||
'type' => 'boolean',
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace App\Form;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Form\Type\InvoiceLabelType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\ProjectFormTypeQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
@@ -24,78 +26,79 @@ class ActivityEditForm extends AbstractType
|
||||
{
|
||||
use EntityFormTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$project = null;
|
||||
$customer = null;
|
||||
$new = true;
|
||||
$isNew = true;
|
||||
$isGlobal = false;
|
||||
$options['currency'] = null;
|
||||
|
||||
if (isset($options['data'])) {
|
||||
/** @var Activity $entry */
|
||||
$entry = $options['data'];
|
||||
$isGlobal = $entry->isGlobal();
|
||||
|
||||
if (null !== $entry->getProject()) {
|
||||
if (!$isGlobal) {
|
||||
$project = $entry->getProject();
|
||||
$customer = $project->getCustomer();
|
||||
$options['currency'] = $customer->getCurrency();
|
||||
} else {
|
||||
$isGlobal = null === $entry->getProject();
|
||||
}
|
||||
|
||||
$new = $entry->getId() === null;
|
||||
$isNew = $entry->getId() === null;
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
'label' => 'name',
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
],
|
||||
])
|
||||
->add('comment', TextareaType::class, [
|
||||
'label' => 'label.description',
|
||||
'required' => false,
|
||||
])
|
||||
->add('invoiceText', TextareaType::class, [
|
||||
'label' => 'label.invoiceText',
|
||||
'label' => 'description',
|
||||
'required' => false,
|
||||
])
|
||||
->add('invoiceText', InvoiceLabelType::class)
|
||||
;
|
||||
|
||||
if ($new || !$isGlobal) {
|
||||
if ($isNew || !$isGlobal) {
|
||||
$builder
|
||||
->add('project', ProjectType::class, [
|
||||
'required' => false,
|
||||
'help' => 'help.globalActivity',
|
||||
'query_builder' => function (ProjectRepository $repo) use ($builder, $project, $customer) {
|
||||
$query = new ProjectFormTypeQuery($project, $customer);
|
||||
$query->setUser($builder->getOption('user'));
|
||||
$query->setIgnoreDate(true);
|
||||
$query->setWithCustomer(true);
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if ($isNew) {
|
||||
$builder
|
||||
->add('teams', TeamType::class, [
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'by_reference' => false,
|
||||
'help' => 'help.teams',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addCommonFields($builder, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Activity::class,
|
||||
'csrf_protection' => true,
|
||||
'csrf_field_name' => '_token',
|
||||
'csrf_token_id' => 'admin_activity_edit',
|
||||
// @deprecated not supported since 1.15, which removed the customer select completely
|
||||
'customer' => false,
|
||||
'currency' => Customer::DEFAULT_CURRENCY,
|
||||
'include_budget' => false,
|
||||
'include_time' => false,
|
||||
|
||||
@@ -15,10 +15,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ActivityRateForm extends AbstractRateForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$currency = null;
|
||||
|
||||
@@ -34,10 +31,7 @@ class ActivityRateForm extends AbstractRateForm
|
||||
$this->addFields($builder, $currency);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ActivityRate::class,
|
||||
|
||||
@@ -15,12 +15,9 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ActivityTeamPermissionForm extends AbstractType
|
||||
final class ActivityTeamPermissionForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('teams', TeamType::class, [
|
||||
@@ -31,10 +28,7 @@ class ActivityTeamPermissionForm extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Activity::class,
|
||||
|
||||
@@ -14,7 +14,7 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class CalendarForm extends AbstractType
|
||||
final class CalendarForm extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
|
||||
@@ -15,24 +15,16 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class CustomerCommentForm extends AbstractType
|
||||
final class CustomerCommentForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('message', TextareaType::class, [
|
||||
'label' => false,
|
||||
])
|
||||
;
|
||||
$builder->add('message', TextareaType::class, [
|
||||
'label' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => CustomerComment::class,
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Form\Type\InvoiceTemplateType;
|
||||
use App\Form\Type\MailType;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Form\Type\TimezoneType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CountryType;
|
||||
@@ -26,86 +28,108 @@ class CustomerEditForm extends AbstractType
|
||||
{
|
||||
use EntityFormTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$isNew = false;
|
||||
|
||||
if (isset($options['data'])) {
|
||||
/** @var Customer $customer */
|
||||
$customer = $options['data'];
|
||||
$isNew = $customer->getId() === null;
|
||||
$options['currency'] = $customer->getCurrency();
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
'label' => 'name',
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
],
|
||||
])
|
||||
->add('number', TextType::class, [
|
||||
'label' => 'label.number',
|
||||
'label' => 'number',
|
||||
'required' => false,
|
||||
])
|
||||
->add('comment', TextareaType::class, [
|
||||
'label' => 'label.description',
|
||||
'label' => 'description',
|
||||
'required' => false,
|
||||
])
|
||||
->add('company', TextType::class, [
|
||||
'label' => 'label.company',
|
||||
'label' => 'company',
|
||||
'required' => false,
|
||||
])
|
||||
->add('vatId', TextType::class, [
|
||||
'label' => 'label.vat_id',
|
||||
'label' => 'vat_id',
|
||||
'required' => false,
|
||||
])
|
||||
->add('contact', TextType::class, [
|
||||
'label' => 'label.contact',
|
||||
'label' => 'contact',
|
||||
'required' => false,
|
||||
])
|
||||
->add('address', TextareaType::class, [
|
||||
'label' => 'label.address',
|
||||
'label' => 'address',
|
||||
'required' => false,
|
||||
])
|
||||
->add('country', CountryType::class, [
|
||||
'label' => 'label.country',
|
||||
'label' => 'country',
|
||||
])
|
||||
->add('currency', CurrencyType::class, [
|
||||
'label' => 'label.currency',
|
||||
'label' => 'currency',
|
||||
])
|
||||
->add('phone', TelType::class, [
|
||||
'label' => 'label.phone',
|
||||
'label' => 'phone',
|
||||
'required' => false,
|
||||
'block_prefix' => 'phone',
|
||||
])
|
||||
->add('fax', TelType::class, [
|
||||
'label' => 'label.fax',
|
||||
'label' => 'fax',
|
||||
'required' => false,
|
||||
'attr' => ['icon' => 'fax'],
|
||||
'block_prefix' => 'phone',
|
||||
])
|
||||
->add('mobile', TelType::class, [
|
||||
'label' => 'label.mobile',
|
||||
'label' => 'mobile',
|
||||
'required' => false,
|
||||
'attr' => ['icon' => 'mobile'],
|
||||
'block_prefix' => 'phone',
|
||||
])
|
||||
->add('email', MailType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
->add('homepage', UrlType::class, [
|
||||
'label' => 'label.homepage',
|
||||
'label' => 'homepage',
|
||||
'required' => false,
|
||||
'block_prefix' => 'homepage',
|
||||
])
|
||||
->add('timezone', TimezoneType::class, [
|
||||
'label' => 'label.timezone',
|
||||
]);
|
||||
'label' => 'timezone',
|
||||
])
|
||||
->add('invoiceText', TextareaType::class, [
|
||||
'label' => 'invoiceText',
|
||||
'help' => 'help.invoiceText',
|
||||
'required' => false,
|
||||
])
|
||||
->add('invoiceTemplate', InvoiceTemplateType::class, [
|
||||
'help' => 'help.invoiceTemplate_customer',
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
|
||||
if ($isNew) {
|
||||
$builder
|
||||
->add('teams', TeamType::class, [
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'by_reference' => false,
|
||||
'help' => 'help.teams',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addCommonFields($builder, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Customer::class,
|
||||
|
||||
@@ -15,10 +15,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class CustomerRateForm extends AbstractRateForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$currency = null;
|
||||
|
||||
@@ -34,10 +31,7 @@ class CustomerRateForm extends AbstractRateForm
|
||||
$this->addFields($builder, $currency);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => CustomerRate::class,
|
||||
|
||||
@@ -15,26 +15,18 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class CustomerTeamPermissionForm extends AbstractType
|
||||
final class CustomerTeamPermissionForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('teams', TeamType::class, [
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'by_reference' => false,
|
||||
])
|
||||
;
|
||||
$builder->add('teams', TeamType::class, [
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'by_reference' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Customer::class,
|
||||
|
||||
@@ -14,32 +14,16 @@ use App\Validator\Constraints\Duration as DurationConstraint;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
class DurationStringToSecondsTransformer implements DataTransformerInterface
|
||||
final class DurationStringToSecondsTransformer implements DataTransformerInterface
|
||||
{
|
||||
/**
|
||||
* @var Duration
|
||||
*/
|
||||
protected $formatter;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $pattern;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->formatter = new Duration();
|
||||
$constraint = new DurationConstraint();
|
||||
$this->pattern = $constraint->pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $intToFormat
|
||||
* @return string|null
|
||||
*/
|
||||
public function transform($intToFormat)
|
||||
public function transform(mixed $intToFormat): ?string
|
||||
{
|
||||
try {
|
||||
return $this->formatter->format($intToFormat);
|
||||
return (new Duration())->format($intToFormat);
|
||||
} catch (\Exception | \TypeError $e) {
|
||||
throw new TransformationFailedException($e->getMessage());
|
||||
}
|
||||
@@ -49,7 +33,7 @@ class DurationStringToSecondsTransformer implements DataTransformerInterface
|
||||
* @param string|null $formatToInt
|
||||
* @return int|null
|
||||
*/
|
||||
public function reverseTransform($formatToInt)
|
||||
public function reverseTransform(mixed $formatToInt): ?int
|
||||
{
|
||||
if (null === $formatToInt) {
|
||||
return null;
|
||||
@@ -60,12 +44,12 @@ class DurationStringToSecondsTransformer implements DataTransformerInterface
|
||||
}
|
||||
|
||||
// we need this one here, because the data transformer is executed BEFORE the constraint is called
|
||||
if (!preg_match($this->pattern, $formatToInt)) {
|
||||
if (!preg_match((new DurationConstraint())->pattern, $formatToInt)) {
|
||||
throw new TransformationFailedException('Invalid duration format given');
|
||||
}
|
||||
|
||||
try {
|
||||
$seconds = $this->formatter->parseDurationString($formatToInt);
|
||||
$seconds = (new Duration())->parseDurationString($formatToInt);
|
||||
|
||||
// DateTime throws if a duration with too many seconds is passed and an amount of so
|
||||
// many seconds is likely not required in a time-tracking application ;-)
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Utils\SearchTerm;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
class SearchTermTransformer implements DataTransformerInterface
|
||||
final class SearchTermTransformer implements DataTransformerInterface
|
||||
{
|
||||
/**
|
||||
* Transforms a SearchTerm object to a string.
|
||||
@@ -21,7 +21,7 @@ class SearchTermTransformer implements DataTransformerInterface
|
||||
* @param SearchTerm|null $searchTerm
|
||||
* @return string
|
||||
*/
|
||||
public function transform($searchTerm)
|
||||
public function transform(mixed $searchTerm): mixed
|
||||
{
|
||||
if (empty($searchTerm) || !($searchTerm instanceof SearchTerm)) {
|
||||
return '';
|
||||
@@ -37,7 +37,7 @@ class SearchTermTransformer implements DataTransformerInterface
|
||||
* @return SearchTerm|null
|
||||
* @throws TransformationFailedException if object (issue) is not found
|
||||
*/
|
||||
public function reverseTransform($searchTerm)
|
||||
public function reverseTransform(mixed $searchTerm): mixed
|
||||
{
|
||||
if (empty($searchTerm)) {
|
||||
return null;
|
||||
|
||||
58
src/Form/DataTransformer/StringToArrayTransformer.php
Normal file
58
src/Form/DataTransformer/StringToArrayTransformer.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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\Form\DataTransformer;
|
||||
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
/**
|
||||
* @implements DataTransformerInterface<array, string>
|
||||
*/
|
||||
final class StringToArrayTransformer implements DataTransformerInterface
|
||||
{
|
||||
/**
|
||||
* @param non-empty-string $separator
|
||||
*/
|
||||
public function __construct(private readonly string $separator = ',')
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an array of strings to a string.
|
||||
*
|
||||
* @param array<string> $value
|
||||
* @return string
|
||||
*/
|
||||
public function transform(mixed $value): mixed
|
||||
{
|
||||
if (empty($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return implode($this->separator, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a string to an array of tags.
|
||||
*
|
||||
* @param string|null $value
|
||||
* @return array<string>
|
||||
* @throws TransformationFailedException
|
||||
*/
|
||||
public function reverseTransform(mixed $value): mixed
|
||||
{
|
||||
// check for empty list
|
||||
if ('' === $value || null === $value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_filter(array_unique(array_map('trim', explode($this->separator, $value))));
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,10 @@ use App\Repository\TagRepository;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
|
||||
class TagArrayToStringTransformer implements DataTransformerInterface
|
||||
final class TagArrayToStringTransformer implements DataTransformerInterface
|
||||
{
|
||||
/**
|
||||
* @var TagRepository
|
||||
*/
|
||||
private $tagRepository;
|
||||
|
||||
public function __construct(TagRepository $tagRepository)
|
||||
public function __construct(private TagRepository $tagRepository)
|
||||
{
|
||||
$this->tagRepository = $tagRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,7 +26,7 @@ class TagArrayToStringTransformer implements DataTransformerInterface
|
||||
* @param Tag[]|null $tags
|
||||
* @return string
|
||||
*/
|
||||
public function transform($tags)
|
||||
public function transform(mixed $tags): mixed
|
||||
{
|
||||
if (empty($tags)) {
|
||||
return '';
|
||||
@@ -50,7 +44,7 @@ class TagArrayToStringTransformer implements DataTransformerInterface
|
||||
* @return Tag[]
|
||||
* @throws TransformationFailedException
|
||||
*/
|
||||
public function reverseTransform($stringOfTags)
|
||||
public function reverseTransform(mixed $stringOfTags): mixed
|
||||
{
|
||||
// check for empty tag list
|
||||
if ('' === $stringOfTags || null === $stringOfTags) {
|
||||
|
||||
@@ -14,7 +14,6 @@ use App\Form\Type\BudgetType;
|
||||
use App\Form\Type\DurationType;
|
||||
use App\Form\Type\MetaFieldsCollectionType;
|
||||
use App\Form\Type\YesNoType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
@@ -33,7 +32,7 @@ trait EntityFormTrait
|
||||
if ($showMoney) {
|
||||
$builder->add('budget', MoneyType::class, [
|
||||
'empty_data' => '0.00',
|
||||
'label' => 'label.budget',
|
||||
'label' => 'budget',
|
||||
'required' => false,
|
||||
'currency' => $options['currency'],
|
||||
]);
|
||||
@@ -42,7 +41,7 @@ trait EntityFormTrait
|
||||
if ($showTime) {
|
||||
$builder->add('timeBudget', DurationType::class, [
|
||||
'empty_data' => 0,
|
||||
'label' => 'label.timeBudget',
|
||||
'label' => 'timeBudget',
|
||||
'icon' => 'clock',
|
||||
'required' => false,
|
||||
]);
|
||||
@@ -56,22 +55,10 @@ trait EntityFormTrait
|
||||
|
||||
$builder
|
||||
->add('visible', YesNoType::class, [
|
||||
'label' => 'label.visible',
|
||||
'label' => 'visible',
|
||||
'help' => 'help.visible',
|
||||
])
|
||||
->add('billable', BillableType::class)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.15
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
public function addCreateMore(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('create_more', CheckboxType::class, [
|
||||
'label' => 'label.create_more',
|
||||
'required' => false,
|
||||
'mapped' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
82
src/Form/Extension/ApiFormExtension.php
Normal file
82
src/Form/Extension/ApiFormExtension.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?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\Form\Extension;
|
||||
|
||||
use App\Form\API\ActivityApiEditForm;
|
||||
use App\Form\API\ActivityRateApiForm;
|
||||
use App\Form\API\CustomerApiEditForm;
|
||||
use App\Form\API\CustomerRateApiForm;
|
||||
use App\Form\API\ProjectApiEditForm;
|
||||
use App\Form\API\ProjectRateApiForm;
|
||||
use App\Form\API\TagApiEditForm;
|
||||
use App\Form\API\TeamApiEditForm;
|
||||
use App\Form\API\TimesheetApiEditForm;
|
||||
use App\Form\API\UserApiCreateForm;
|
||||
use App\Form\API\UserApiEditForm;
|
||||
use App\Form\Type\BillableType;
|
||||
use App\Form\Type\YesNoType;
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
final class ApiFormExtension extends AbstractTypeExtension
|
||||
{
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [
|
||||
ActivityApiEditForm::class,
|
||||
ActivityRateApiForm::class,
|
||||
CustomerApiEditForm::class,
|
||||
CustomerRateApiForm::class,
|
||||
ProjectApiEditForm::class,
|
||||
ProjectRateApiForm::class,
|
||||
TagApiEditForm::class,
|
||||
TeamApiEditForm::class,
|
||||
TimesheetApiEditForm::class,
|
||||
UserApiCreateForm::class,
|
||||
UserApiEditForm::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if ($builder->has('metaFields')) {
|
||||
$builder->remove('metaFields');
|
||||
}
|
||||
|
||||
$replaces = [];
|
||||
|
||||
foreach ($builder as $child) {
|
||||
if (\in_array($child->getType()->getInnerType()->getParent(), [CheckboxType::class, BillableType::class, YesNoType::class])) {
|
||||
$replaces[] = [
|
||||
'name' => $child->getName(),
|
||||
'label' => $child->getFormConfig()->getOption('label'),
|
||||
'required' => $child->getFormConfig()->getRequired(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($replaces as $field) {
|
||||
$builder->remove($field['name']);
|
||||
$builder->add($field['name'], CheckboxType::class, [
|
||||
'label' => $field['label'],
|
||||
'required' => $field['required'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Adds a linked help text with the link to the given documentation
|
||||
*/
|
||||
class DocumentationLinkExtension extends AbstractTypeExtension
|
||||
final class DocumentationLinkExtension extends AbstractTypeExtension
|
||||
{
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
@@ -30,7 +30,7 @@ class DocumentationLinkExtension extends AbstractTypeExtension
|
||||
* @param FormInterface $form
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['docu_chapter'] = $options['docu_chapter'] ?? null;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class DocumentationLinkExtension extends AbstractTypeExtension
|
||||
/**
|
||||
* @param OptionsResolver $resolver
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['docu_chapter']);
|
||||
$resolver->setAllowedTypes('docu_chapter', 'string');
|
||||
|
||||
@@ -21,11 +21,6 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
final class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
{
|
||||
/**
|
||||
* @deprecated since 1.7 will be removed with 2.0
|
||||
*/
|
||||
public const TYPE_SELECTPICKER = 'selectpicker';
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [EntityType::class, ChoiceType::class];
|
||||
@@ -36,7 +31,7 @@ final class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
* @param FormInterface $form
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
if (isset($options['selectpicker']) && false === $options['selectpicker']) {
|
||||
return;
|
||||
@@ -59,16 +54,13 @@ final class EnhancedChoiceTypeExtension extends AbstractTypeExtension
|
||||
}
|
||||
|
||||
if (false === $options['search']) {
|
||||
$extendedOptions['data-minimum-results-for-search'] = 'Infinity';
|
||||
$extendedOptions['data-disable-search'] = 1;
|
||||
}
|
||||
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], $extendedOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OptionsResolver $resolver
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['selectpicker']);
|
||||
$resolver->setAllowedTypes('selectpicker', 'boolean');
|
||||
|
||||
@@ -18,7 +18,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Allows to register custom icons at form fields
|
||||
*/
|
||||
class IconExtension extends AbstractTypeExtension
|
||||
final class IconExtension extends AbstractTypeExtension
|
||||
{
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
@@ -30,7 +30,7 @@ class IconExtension extends AbstractTypeExtension
|
||||
* @param FormInterface $form
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['icon'] = $options['icon'] ?? null;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class IconExtension extends AbstractTypeExtension
|
||||
/**
|
||||
* @param OptionsResolver $resolver
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['icon']);
|
||||
$resolver->setAllowedTypes('icon', 'string');
|
||||
|
||||
@@ -19,19 +19,10 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
/**
|
||||
* Support Remote-API calls for Entity select-boxes.
|
||||
*/
|
||||
class SelectWithApiDataExtension extends AbstractTypeExtension
|
||||
final class SelectWithApiDataExtension extends AbstractTypeExtension
|
||||
{
|
||||
/**
|
||||
* @var UrlGeneratorInterface
|
||||
*/
|
||||
private $router;
|
||||
|
||||
/**
|
||||
* @param UrlGeneratorInterface $router
|
||||
*/
|
||||
public function __construct(UrlGeneratorInterface $router)
|
||||
public function __construct(private UrlGeneratorInterface $router)
|
||||
{
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
@@ -44,7 +35,7 @@ class SelectWithApiDataExtension extends AbstractTypeExtension
|
||||
* @param FormInterface $form
|
||||
* @param array $options
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
if (!isset($options['api_data'])) {
|
||||
return;
|
||||
@@ -56,6 +47,12 @@ class SelectWithApiDataExtension extends AbstractTypeExtension
|
||||
throw new \InvalidArgumentException('Option "api_data" must be an array for form "' . $form->getName() . '"');
|
||||
}
|
||||
|
||||
if (isset($apiData['create'])) {
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'data-create' => $this->router->generate($apiData['create']),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!isset($apiData['select'])) {
|
||||
return;
|
||||
}
|
||||
@@ -93,12 +90,15 @@ class SelectWithApiDataExtension extends AbstractTypeExtension
|
||||
'data-empty-url' => $this->router->generate($apiData['route'], $apiData['empty_route_params']),
|
||||
]);
|
||||
}
|
||||
|
||||
if (isset($apiData['reload'])) {
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'data-reload' => $this->router->generate($apiData['reload']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OptionsResolver $resolver
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['api_data']);
|
||||
$resolver->setAllowedTypes('api_data', 'array');
|
||||
|
||||
@@ -12,30 +12,25 @@ namespace App\Form\Extension;
|
||||
use App\Form\Helper\ToolbarHelper;
|
||||
use App\Form\Toolbar\ExportToolbarForm;
|
||||
use App\Form\Toolbar\InvoiceToolbarForm;
|
||||
use App\Form\Toolbar\InvoiceToolbarSimpleForm;
|
||||
use App\Form\Toolbar\TimesheetExportToolbarForm;
|
||||
use App\Form\Toolbar\TimesheetToolbarForm;
|
||||
use App\Form\Toolbar\UserToolbarForm;
|
||||
use App\Reporting\MonthlyUserListForm;
|
||||
use App\Reporting\WeeklyUserListForm;
|
||||
use App\Reporting\YearlyUserListForm;
|
||||
use App\Reporting\MonthlyUserList\MonthlyUserListForm;
|
||||
use App\Reporting\WeeklyUserList\WeeklyUserListForm;
|
||||
use App\Reporting\YearlyUserList\YearlyUserListForm;
|
||||
use Symfony\Component\Form\AbstractTypeExtension;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
final class ToolbarFormExtension extends AbstractTypeExtension
|
||||
{
|
||||
private $toolbarHelper;
|
||||
|
||||
public function __construct(ToolbarHelper $toolbarHelper)
|
||||
public function __construct(private ToolbarHelper $toolbarHelper)
|
||||
{
|
||||
$this->toolbarHelper = $toolbarHelper;
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
{
|
||||
return [
|
||||
InvoiceToolbarForm::class,
|
||||
InvoiceToolbarSimpleForm::class,
|
||||
ExportToolbarForm::class,
|
||||
TimesheetToolbarForm::class,
|
||||
TimesheetExportToolbarForm::class,
|
||||
@@ -46,7 +41,7 @@ final class ToolbarFormExtension extends AbstractTypeExtension
|
||||
];
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->toolbarHelper->cleanupForm($builder);
|
||||
}
|
||||
|
||||
@@ -17,14 +17,8 @@ use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class UserExtension extends AbstractTypeExtension
|
||||
{
|
||||
/**
|
||||
* @var Security
|
||||
*/
|
||||
private $security;
|
||||
|
||||
public function __construct(Security $security)
|
||||
public function __construct(private Security $security)
|
||||
{
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
public static function getExtendedTypes(): iterable
|
||||
@@ -32,7 +26,7 @@ final class UserExtension extends AbstractTypeExtension
|
||||
return [FormType::class];
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['user']);
|
||||
// null needs to be allowed, as there is no user for anonymous forms (like "forgot password" and "registration")
|
||||
|
||||
@@ -14,9 +14,7 @@ use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Form\Type\ActivityType;
|
||||
use App\Form\Type\CustomerType;
|
||||
use App\Form\Type\DescriptionType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\TagsType;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\ProjectFormTypeQuery;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
@@ -30,12 +28,12 @@ use Symfony\Component\Form\FormEvents;
|
||||
*/
|
||||
trait FormTrait
|
||||
{
|
||||
protected function addCustomer(FormBuilderInterface $builder, ?Customer $customer = null)
|
||||
protected function addCustomer(FormBuilderInterface $builder, ?Customer $customer = null): void
|
||||
{
|
||||
$builder->add('customer', CustomerType::class, [
|
||||
'query_builder_for_user' => true,
|
||||
'customers' => $customer,
|
||||
'data' => $customer ? $customer : '',
|
||||
'data' => $customer,
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
'mapped' => false,
|
||||
@@ -43,7 +41,7 @@ trait FormTrait
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addProject(FormBuilderInterface $builder, bool $isNew, ?Project $project = null, ?Customer $customer = null, array $options = [])
|
||||
protected function addProject(FormBuilderInterface $builder, bool $isNew, ?Project $project = null, ?Customer $customer = null, array $options = []): void
|
||||
{
|
||||
$options = array_merge([
|
||||
'placeholder' => '',
|
||||
@@ -61,26 +59,39 @@ trait FormTrait
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($builder, $project, $customer, $isNew, $options) {
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $event->getData();
|
||||
$customer = isset($data['customer']) && !empty($data['customer']) ? $data['customer'] : null;
|
||||
$project = isset($data['project']) && !empty($data['project']) ? $data['project'] : $project;
|
||||
$customer = \array_key_exists('customer', $data) && $data['customer'] !== '' ? $data['customer'] : null;
|
||||
$project = \array_key_exists('project', $data) && $data['project'] !== '' ? $data['project'] : $project;
|
||||
|
||||
$event->getForm()->add('project', ProjectType::class, array_merge($options, [
|
||||
'group_by' => null,
|
||||
'query_builder' => function (ProjectRepository $repo) use ($builder, $project, $customer, $isNew) {
|
||||
// is there a better wa to prevent starting a record with a hidden project ?
|
||||
if ($isNew && !empty($project) && (\is_int($project) || \is_string($project))) {
|
||||
// is there a better way to prevent starting a record with a hidden project ?
|
||||
$project = \is_string($project) ? (int) $project : $project;
|
||||
$customer = \is_string($customer) ? (int) $customer : $customer;
|
||||
if ($isNew && \is_int($project)) {
|
||||
/** @var Project $project */
|
||||
$project = $repo->find($project);
|
||||
if (null !== $project) {
|
||||
if (!$project->getCustomer()->isVisible()) {
|
||||
$customer = null;
|
||||
$project = null;
|
||||
} elseif (!$project->isVisible()) {
|
||||
$project = null;
|
||||
}
|
||||
if ($project === null) {
|
||||
throw new \Exception('Unknown project');
|
||||
}
|
||||
if (!$project->getCustomer()->isVisible()) {
|
||||
$customer = null;
|
||||
$project = null;
|
||||
} elseif (!$project->isVisible()) {
|
||||
$project = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($project !== null && !\is_int($project) && !($project instanceof Project)) {
|
||||
throw new \InvalidArgumentException('Project type needs a project object or an ID');
|
||||
}
|
||||
|
||||
if ($customer !== null && !\is_int($customer) && !($customer instanceof Customer)) {
|
||||
throw new \InvalidArgumentException('Project type needs a customer object or an ID');
|
||||
}
|
||||
|
||||
$query = new ProjectFormTypeQuery($project, $customer);
|
||||
$query->setUser($builder->getOption('user'));
|
||||
$query->setWithCustomer(true);
|
||||
@@ -92,7 +103,7 @@ trait FormTrait
|
||||
);
|
||||
}
|
||||
|
||||
protected function addActivity(FormBuilderInterface $builder, ?Activity $activity = null, ?Project $project = null, array $options = [])
|
||||
protected function addActivity(FormBuilderInterface $builder, ?Activity $activity = null, ?Project $project = null, array $options = []): void
|
||||
{
|
||||
$options = array_merge(['placeholder' => '', 'query_builder_for_user' => true], $options);
|
||||
|
||||
@@ -105,8 +116,10 @@ trait FormTrait
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($options) {
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $event->getData();
|
||||
if (!isset($data['project']) || empty($data['project'])) {
|
||||
|
||||
if (!\array_key_exists('project', $data) || $data['project'] === '' || $data['project'] === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,31 +129,4 @@ trait FormTrait
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.13
|
||||
*/
|
||||
protected function addDescription(FormBuilderInterface $builder)
|
||||
{
|
||||
@trigger_error('FormTrait::addDescription() is deprecated and will be removed with 2.0, use DescriptionType instead', E_USER_DEPRECATED);
|
||||
|
||||
$builder->add('description', DescriptionType::class, [
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.14
|
||||
*/
|
||||
protected function addTags(FormBuilderInterface $builder)
|
||||
{
|
||||
@trigger_error('FormTrait::addTags() is deprecated and will be removed with 2.0, use TagsType instead', E_USER_DEPRECATED);
|
||||
|
||||
$builder->add('tags', TagsType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ final class ActivityHelper
|
||||
public const PATTERN_SPACER = '{spacer}';
|
||||
public const SPACER = ' - ';
|
||||
|
||||
private $configuration;
|
||||
private $pattern;
|
||||
private ?string $pattern = null;
|
||||
|
||||
public function __construct(SystemConfiguration $configuration)
|
||||
public function __construct(private SystemConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function getChoicePattern(): string
|
||||
|
||||
@@ -21,12 +21,10 @@ final class CustomerHelper
|
||||
public const PATTERN_SPACER = '{spacer}';
|
||||
public const SPACER = ' - ';
|
||||
|
||||
private $configuration;
|
||||
private $pattern;
|
||||
private ?string $pattern = null;
|
||||
|
||||
public function __construct(SystemConfiguration $configuration)
|
||||
public function __construct(private SystemConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function getChoicePattern(): string
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
|
||||
namespace App\Form\Helper;
|
||||
|
||||
use App\Configuration\LocaleService;
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Project;
|
||||
use App\Utils\LocaleSettings;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class ProjectHelper
|
||||
{
|
||||
@@ -25,15 +26,13 @@ final class ProjectHelper
|
||||
public const PATTERN_SPACER = '{spacer}';
|
||||
public const SPACER = ' - ';
|
||||
|
||||
private $configuration;
|
||||
private $localeSettings;
|
||||
private $dateFormat;
|
||||
private $pattern;
|
||||
private ?\IntlDateFormatter $dateFormatter = null;
|
||||
private ?string $pattern = null;
|
||||
private bool $showStart = false;
|
||||
private bool $showEnd = false;
|
||||
|
||||
public function __construct(SystemConfiguration $configuration, LocaleSettings $localeSettings)
|
||||
public function __construct(private SystemConfiguration $configuration, private LocaleService $localeService, private TranslatorInterface $translator)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->localeSettings = $localeSettings;
|
||||
}
|
||||
|
||||
public function getChoicePattern(): string
|
||||
@@ -54,27 +53,40 @@ final class ProjectHelper
|
||||
|
||||
public function getChoiceLabel(Project $project): string
|
||||
{
|
||||
if ($this->dateFormat === null) {
|
||||
$this->dateFormat = $this->localeSettings->getDateFormat();
|
||||
}
|
||||
|
||||
$start = '?';
|
||||
if ($project->getStart() !== null) {
|
||||
$start = $project->getStart()->format($this->dateFormat);
|
||||
}
|
||||
|
||||
$end = '?';
|
||||
if ($project->getEnd() !== null) {
|
||||
$end = $project->getEnd()->format($this->dateFormat);
|
||||
}
|
||||
|
||||
$name = $this->getChoicePattern();
|
||||
$name = str_replace(self::PATTERN_NAME, $project->getName(), $name);
|
||||
$name = str_replace(self::PATTERN_COMMENT, $project->getComment() ?? '', $name);
|
||||
$name = str_replace(self::PATTERN_CUSTOMER, $project->getCustomer()->getName() ?? '', $name);
|
||||
$name = str_replace(self::PATTERN_ORDERNUMBER, $project->getOrderNumber() ?? '', $name);
|
||||
$name = str_replace(self::PATTERN_START, $start, $name);
|
||||
$name = str_replace(self::PATTERN_END, $end, $name);
|
||||
|
||||
if ($this->dateFormatter === null) {
|
||||
$this->showStart = stripos($name, self::PATTERN_START) !== false;
|
||||
$this->showEnd = stripos($name, self::PATTERN_END) !== false;
|
||||
$this->dateFormatter = new \IntlDateFormatter(
|
||||
\Locale::getDefault(),
|
||||
\IntlDateFormatter::MEDIUM,
|
||||
\IntlDateFormatter::MEDIUM,
|
||||
date_default_timezone_get(),
|
||||
\IntlDateFormatter::GREGORIAN,
|
||||
$this->localeService->getDateFormat(\Locale::getDefault())
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->showStart) {
|
||||
$start = '';
|
||||
if ($project->getStart() !== null) {
|
||||
$start = $this->translator->trans('project_start') . ': ' . $this->dateFormatter->format($project->getStart()) . ' ';
|
||||
}
|
||||
$name = str_replace(self::PATTERN_START, $start, $name);
|
||||
}
|
||||
|
||||
if ($this->showEnd) {
|
||||
$end = '';
|
||||
if ($project->getEnd() !== null) {
|
||||
$end = ' ' . $this->translator->trans('project_end') . ': ' . $this->dateFormatter->format($project->getEnd());
|
||||
}
|
||||
$name = str_replace(self::PATTERN_END, $end, $name);
|
||||
}
|
||||
|
||||
$name = ltrim($name, self::SPACER);
|
||||
$name = rtrim($name, self::SPACER);
|
||||
|
||||
@@ -15,18 +15,16 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
final class ToolbarHelper
|
||||
{
|
||||
private $userService;
|
||||
private $teamService;
|
||||
private $teamNames = ['team', 'teams', 'searchTeams'];
|
||||
private $userNames = ['user', 'users'];
|
||||
/** @var array<string> */
|
||||
private array $teamNames = ['team', 'teams', 'searchTeams'];
|
||||
/** @var array<string> */
|
||||
private array $userNames = ['user', 'users'];
|
||||
|
||||
public function __construct(UserService $userService, TeamService $teamService)
|
||||
public function __construct(private UserService $userService, private TeamService $teamService)
|
||||
{
|
||||
$this->userService = $userService;
|
||||
$this->teamService = $teamService;
|
||||
}
|
||||
|
||||
public function cleanupForm(FormBuilderInterface $builder)
|
||||
public function cleanupForm(FormBuilderInterface $builder): void
|
||||
{
|
||||
$deleteUser = false;
|
||||
foreach ($this->userNames as $name) {
|
||||
|
||||
@@ -19,19 +19,13 @@ use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Constraints\File;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
class InvoiceDocumentUploadForm extends AbstractType
|
||||
final class InvoiceDocumentUploadForm extends AbstractType
|
||||
{
|
||||
private $repository;
|
||||
|
||||
public function __construct(InvoiceDocumentRepository $repository)
|
||||
public function __construct(private InvoiceDocumentRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$mimetypes = [
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
@@ -41,7 +35,7 @@ class InvoiceDocumentUploadForm extends AbstractType
|
||||
|
||||
$builder
|
||||
->add('document', FileType::class, [
|
||||
'label' => 'label.invoice_renderer',
|
||||
'label' => 'invoice_renderer',
|
||||
'translation_domain' => 'invoice-renderer',
|
||||
'help' => 'help.upload',
|
||||
'mapped' => false,
|
||||
@@ -80,10 +74,7 @@ class InvoiceDocumentUploadForm extends AbstractType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => true,
|
||||
|
||||
@@ -18,12 +18,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class InvoiceEditForm extends AbstractType
|
||||
final class InvoiceEditForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$dateTimeOptions = [
|
||||
'model_timezone' => $options['timezone'],
|
||||
@@ -32,7 +29,7 @@ class InvoiceEditForm extends AbstractType
|
||||
|
||||
$builder
|
||||
->add('comment', TextareaType::class, [
|
||||
'label' => 'label.description',
|
||||
'label' => 'description',
|
||||
'required' => false,
|
||||
])
|
||||
->add('status', ChoiceType::class, [
|
||||
@@ -42,7 +39,7 @@ class InvoiceEditForm extends AbstractType
|
||||
'status.paid' => Invoice::STATUS_PAID,
|
||||
'status.canceled' => Invoice::STATUS_CANCELED,
|
||||
],
|
||||
'label' => 'label.status',
|
||||
'label' => 'status',
|
||||
'required' => true,
|
||||
])
|
||||
->add('paymentDate', DatePickerType::class, array_merge($dateTimeOptions, [
|
||||
@@ -53,10 +50,7 @@ class InvoiceEditForm extends AbstractType
|
||||
$builder->add('metaFields', MetaFieldsCollectionType::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Invoice::class,
|
||||
|
||||
@@ -14,7 +14,6 @@ use App\Form\Type\InvoiceCalculatorType;
|
||||
use App\Form\Type\InvoiceNumberGeneratorType;
|
||||
use App\Form\Type\InvoiceRendererType;
|
||||
use App\Form\Type\LanguageType;
|
||||
use App\Form\Type\YesNoType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
@@ -26,66 +25,55 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Defines the form used to manipulate invoice templates.
|
||||
*/
|
||||
class InvoiceTemplateForm extends AbstractType
|
||||
final class InvoiceTemplateForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
'label' => 'name',
|
||||
])
|
||||
->add('title', TextType::class, [
|
||||
'label' => 'label.title',
|
||||
'label' => 'title',
|
||||
])
|
||||
->add('company', TextType::class, [
|
||||
'label' => 'label.company',
|
||||
'label' => 'company',
|
||||
])
|
||||
->add('vatId', TextType::class, [
|
||||
'label' => 'label.vat_id',
|
||||
'label' => 'vat_id',
|
||||
'required' => false,
|
||||
])
|
||||
->add('address', TextareaType::class, [
|
||||
'label' => 'label.address',
|
||||
'label' => 'address',
|
||||
'required' => false,
|
||||
])
|
||||
->add('contact', TextareaType::class, [
|
||||
'label' => 'label.contact',
|
||||
'label' => 'contact',
|
||||
'required' => false,
|
||||
])
|
||||
->add('paymentTerms', TextareaType::class, [
|
||||
'label' => 'label.payment_terms',
|
||||
'label' => 'payment_terms',
|
||||
'required' => false,
|
||||
])
|
||||
->add('paymentDetails', TextareaType::class, [
|
||||
'label' => 'label.invoice_bank_account',
|
||||
'label' => 'invoice_bank_account',
|
||||
'required' => false,
|
||||
])
|
||||
->add('dueDays', IntegerType::class, [
|
||||
'label' => 'label.due_days',
|
||||
'label' => 'due_days',
|
||||
])
|
||||
->add('vat', NumberType::class, [
|
||||
'label' => 'label.tax_rate',
|
||||
'label' => 'tax_rate',
|
||||
'scale' => 3,
|
||||
])
|
||||
->add('renderer', InvoiceRendererType::class)
|
||||
->add('calculator', InvoiceCalculatorType::class)
|
||||
->add('numberGenerator', InvoiceNumberGeneratorType::class)
|
||||
->add('language', LanguageType::class, [
|
||||
'required' => true,
|
||||
])
|
||||
->add('decimalDuration', YesNoType::class, [
|
||||
'label' => 'label.decimalDuration'
|
||||
])
|
||||
->add('language', LanguageType::class)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => InvoiceTemplate::class,
|
||||
@@ -97,6 +85,7 @@ class InvoiceTemplateForm extends AbstractType
|
||||
'data-msg-success' => 'action.update.success',
|
||||
'data-msg-error' => 'action.update.error',
|
||||
],
|
||||
'docu_chapter' => 'invoices.html',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,73 +9,44 @@
|
||||
|
||||
namespace App\Form\Model;
|
||||
|
||||
use App\Form\Type\YesNoType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
final class Configuration
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $label;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $translationDomain = 'messages';
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $value;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options = [];
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $enabled = true;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $required = true;
|
||||
private ?string $label = null;
|
||||
private string $translationDomain = 'messages';
|
||||
private string|int|null|bool|array $value = null;
|
||||
private ?string $type = null;
|
||||
private array $options = [];
|
||||
private bool $enabled = true;
|
||||
private bool $required = true;
|
||||
/**
|
||||
* @var Constraint[]
|
||||
*/
|
||||
private $constraints = [];
|
||||
private array $constraints = [];
|
||||
|
||||
public function getName(): ?string
|
||||
public function __construct(private string $name)
|
||||
{
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): Configuration
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
public function getValue(): string|int|null|bool|object
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return Configuration
|
||||
*/
|
||||
public function setValue($value): Configuration
|
||||
public function setValue(string|int|null|bool|object $value): Configuration
|
||||
{
|
||||
if ($this->type === CheckboxType::class || $this->type === YesNoType::class) {
|
||||
$value = (bool) $value;
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -13,13 +13,11 @@ use DateTime;
|
||||
|
||||
final class DateRange
|
||||
{
|
||||
private $resetTimes;
|
||||
private $begin;
|
||||
private $end;
|
||||
private ?DateTime $begin = null;
|
||||
private ?DateTime $end = null;
|
||||
|
||||
public function __construct(bool $resetTimes = true)
|
||||
public function __construct(private bool $resetTimes = true)
|
||||
{
|
||||
$this->resetTimes = $resetTimes;
|
||||
}
|
||||
|
||||
public function getBegin(): ?DateTime
|
||||
|
||||
@@ -12,22 +12,21 @@ namespace App\Form\Model;
|
||||
use App\Entity\Team;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Validator\Constraints as Constraints;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* @App\Validator\Constraints\TimesheetMultiUser
|
||||
*/
|
||||
#[Constraints\TimesheetMultiUser]
|
||||
final class MultiUserTimesheet extends Timesheet
|
||||
{
|
||||
/**
|
||||
* @var Collection<User>
|
||||
*/
|
||||
private $users;
|
||||
private Collection $users;
|
||||
/**
|
||||
* @var Collection<Team>
|
||||
*/
|
||||
private $teams;
|
||||
private Collection $teams;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -44,20 +43,16 @@ final class MultiUserTimesheet extends Timesheet
|
||||
return $this->users;
|
||||
}
|
||||
|
||||
public function addUser(User $user)
|
||||
public function addUser(User $user): void
|
||||
{
|
||||
$this->users->add($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeUser(User $user)
|
||||
public function removeUser(User $user): void
|
||||
{
|
||||
if ($this->users->contains($user)) {
|
||||
$this->users->remove($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,19 +63,15 @@ final class MultiUserTimesheet extends Timesheet
|
||||
return $this->teams;
|
||||
}
|
||||
|
||||
public function addTeam(Team $team)
|
||||
public function addTeam(Team $team): void
|
||||
{
|
||||
$this->teams->add($team);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTeam(Team $team)
|
||||
public function removeTeam(Team $team): void
|
||||
{
|
||||
if ($this->teams->contains($team)) {
|
||||
$this->teams->remove($team);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,47 +11,15 @@ namespace App\Form\Model;
|
||||
|
||||
final class SystemConfiguration
|
||||
{
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_ROUNDING = 'rounding';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_LOCKDOWN = 'lockdown_period';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_TIMESHEET = 'timesheet';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_FORM_INVOICE = 'invoice';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_FORM_CUSTOMER = 'customer';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_FORM_USER = 'user';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_THEME = 'theme';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_AUTHENTICATION = 'authentication';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_CALENDAR = 'calendar';
|
||||
/** @deprecated since 1.16.10 */
|
||||
public const SECTION_BRANDING = 'branding';
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $section;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $translation;
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $translationDomain = 'system-configuration';
|
||||
private ?string $translation = null;
|
||||
private string $translationDomain = 'system-configuration';
|
||||
/**
|
||||
* @var Configuration[]
|
||||
*/
|
||||
private $configuration = [];
|
||||
private array $configuration = [];
|
||||
|
||||
public function __construct(?string $section = null)
|
||||
public function __construct(private ?string $section = null)
|
||||
{
|
||||
$this->section = $section;
|
||||
}
|
||||
|
||||
public function getSection(): ?string
|
||||
@@ -59,18 +27,6 @@ final class SystemConfiguration
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 1.16.10
|
||||
* @param string|null $section
|
||||
* @return $this
|
||||
*/
|
||||
public function setSection(?string $section): SystemConfiguration
|
||||
{
|
||||
$this->section = $section;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTranslation(string $translation): SystemConfiguration
|
||||
{
|
||||
$this->translation = $translation;
|
||||
|
||||
36
src/Form/Model/TotpActivation.php
Normal file
36
src/Form/Model/TotpActivation.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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\Form\Model;
|
||||
|
||||
use App\Entity\User;
|
||||
|
||||
final class TotpActivation
|
||||
{
|
||||
private ?string $code = null;
|
||||
|
||||
public function __construct(private User $user)
|
||||
{
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setCode(?string $code): void
|
||||
{
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
}
|
||||
@@ -13,17 +13,14 @@ use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class MultiUpdateTable extends AbstractType
|
||||
final class MultiUpdateTable extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
/** @var EntityRepository $repository */
|
||||
$repository = $options['repository'];
|
||||
@@ -36,7 +33,7 @@ class MultiUpdateTable extends AbstractType
|
||||
|
||||
$builder->get('entities')->addModelTransformer(
|
||||
new CallbackTransformer(
|
||||
function ($ids) {
|
||||
function ($ids): string {
|
||||
return implode(',', $ids);
|
||||
},
|
||||
function ($ids) use ($repository) {
|
||||
@@ -49,18 +46,22 @@ class MultiUpdateTable extends AbstractType
|
||||
)
|
||||
);
|
||||
|
||||
$builder->add('action', ChoiceType::class, [
|
||||
'mapped' => false,
|
||||
'required' => false,
|
||||
'choices' => $dto->getActions(),
|
||||
'search' => false,
|
||||
]);
|
||||
$i = 0;
|
||||
foreach ($dto->getActions() as $key => $value) {
|
||||
if (empty($key) || empty($value)) {
|
||||
continue;
|
||||
}
|
||||
$builder->add('action_' . $i++, ButtonType::class, [
|
||||
'label' => $key,
|
||||
'attr' => [
|
||||
'data-href' => $value,
|
||||
'class' => 'multi_update_table_action' . (stripos($key, 'delete') !== false ? ' btn-danger' : ''),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['repository']);
|
||||
$resolver->setAllowedTypes('repository', EntityRepository::class);
|
||||
|
||||
@@ -9,34 +9,33 @@
|
||||
|
||||
namespace App\Form\MultiUpdate;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
class MultiUpdateTableDTO
|
||||
{
|
||||
/**
|
||||
* @var object[]
|
||||
* @var iterable<object>|Collection<object>
|
||||
*/
|
||||
private $entities = [];
|
||||
private iterable|Collection $entities = [];
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $actions = ['' => ''];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $action = null;
|
||||
private array $actions = ['' => ''];
|
||||
private ?string $action = null;
|
||||
|
||||
/**
|
||||
* @return object[]
|
||||
*/
|
||||
public function getEntities(): iterable
|
||||
public function getEntities(): iterable|Collection
|
||||
{
|
||||
return $this->entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object[] $entities
|
||||
* @param iterable<object>|Collection<object> $entities
|
||||
* @return MultiUpdateTableDTO
|
||||
*/
|
||||
public function setEntities(iterable $entities): MultiUpdateTableDTO
|
||||
public function setEntities(iterable|Collection $entities): MultiUpdateTableDTO
|
||||
{
|
||||
$this->entities = $entities;
|
||||
|
||||
@@ -65,7 +64,7 @@ class MultiUpdateTableDTO
|
||||
|
||||
public function addDelete(string $url): MultiUpdateTableDTO
|
||||
{
|
||||
$this->actions['action.delete'] = $url;
|
||||
$this->actions['delete'] = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -30,27 +30,13 @@ use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TimesheetMultiUpdate extends AbstractType
|
||||
final class TimesheetMultiUpdate extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var TimesheetRepository
|
||||
*/
|
||||
private $timesheet;
|
||||
/**
|
||||
* @var CustomerRepository
|
||||
*/
|
||||
private $customers;
|
||||
|
||||
public function __construct(TimesheetRepository $timesheet, CustomerRepository $customer)
|
||||
public function __construct(private TimesheetRepository $timesheet, private CustomerRepository $customers)
|
||||
{
|
||||
$this->timesheet = $timesheet;
|
||||
$this->customers = $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$activity = null;
|
||||
$project = null;
|
||||
@@ -65,7 +51,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
|
||||
$activity = $entry->getActivity();
|
||||
$project = $entry->getProject();
|
||||
$customer = null === $project ? null : $project->getCustomer();
|
||||
$customer = $project?->getCustomer();
|
||||
|
||||
if (null === $project && null !== $activity) {
|
||||
$project = $activity->getProject();
|
||||
@@ -80,7 +66,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
->add('customer', CustomerType::class, [
|
||||
'query_builder_for_user' => true,
|
||||
'customers' => $customer,
|
||||
'data' => $customer ? $customer : '',
|
||||
'data' => $customer,
|
||||
'required' => false,
|
||||
'placeholder' => '',
|
||||
'mapped' => false,
|
||||
@@ -156,8 +142,8 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
'required' => true,
|
||||
'expanded' => true,
|
||||
'choices' => [
|
||||
'label.replaceTags' => true,
|
||||
'label.appendTags' => false,
|
||||
'replaceTags' => true,
|
||||
'appendTags' => false,
|
||||
]
|
||||
]);
|
||||
|
||||
@@ -173,7 +159,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
|
||||
if ($options['include_exported']) {
|
||||
$builder->add('exported', ChoiceType::class, [
|
||||
'label' => 'label.mark_as_exported',
|
||||
'label' => 'mark_as_exported',
|
||||
'required' => false,
|
||||
'choices' => [
|
||||
'entryState.exported' => true,
|
||||
@@ -184,7 +170,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
|
||||
if ($options['include_billable']) {
|
||||
$builder->add('billable', ChoiceType::class, [
|
||||
'label' => 'label.billable',
|
||||
'label' => 'billable',
|
||||
'choices' => [
|
||||
'' => null,
|
||||
'yes' => true,
|
||||
@@ -196,7 +182,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
if ($options['include_rate']) {
|
||||
$builder
|
||||
->add('recalculateRates', YesNoType::class, [
|
||||
'label' => 'label.recalculate_rates',
|
||||
'label' => 'recalculate_rates',
|
||||
])
|
||||
->add('fixedRate', FixedRateType::class, [
|
||||
'currency' => $currency,
|
||||
@@ -221,7 +207,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
'choices' => $choices,
|
||||
'multiple' => true,
|
||||
'expanded' => true,
|
||||
'label' => 'label.batch_meta_fields',
|
||||
'label' => 'batch_meta_fields',
|
||||
'help' => 'help.batch_meta_fields',
|
||||
]);
|
||||
}
|
||||
@@ -232,7 +218,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
|
||||
$builder->get('entities')->addModelTransformer(
|
||||
new CallbackTransformer(
|
||||
function ($timesheets) {
|
||||
function ($timesheets): string {
|
||||
$ids = [];
|
||||
/** @var \App\Entity\Timesheet $timesheet */
|
||||
foreach ($timesheets as $timesheet) {
|
||||
@@ -252,10 +238,7 @@ class TimesheetMultiUpdate extends AbstractType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => TimesheetMultiUpdateDTO::class,
|
||||
|
||||
@@ -17,67 +17,38 @@ use App\Entity\Project;
|
||||
use App\Entity\Tag;
|
||||
use App\Entity\TimesheetMeta;
|
||||
use App\Entity\User;
|
||||
use App\Validator\Constraints as Constraints;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* @App\Validator\Constraints\TimesheetMultiUpdate
|
||||
* @internal
|
||||
*/
|
||||
class TimesheetMultiUpdateDTO extends MultiUpdateTableDTO implements EntityWithMetaFields
|
||||
#[Constraints\TimesheetMultiUpdate]
|
||||
final class TimesheetMultiUpdateDTO extends MultiUpdateTableDTO implements EntityWithMetaFields
|
||||
{
|
||||
/**
|
||||
* @var Tag[]|ArrayCollection|iterable
|
||||
* @var iterable<Tag>
|
||||
*/
|
||||
private $tags = [];
|
||||
private iterable $tags = [];
|
||||
private bool $replaceTags = false;
|
||||
private bool $recalculateRates = false;
|
||||
private ?Customer $customer = null;
|
||||
private ?Project $project = null;
|
||||
private ?Activity $activity = null;
|
||||
private ?User $user = null;
|
||||
private ?bool $exported = null;
|
||||
private ?bool $billable = null;
|
||||
private ?float $fixedRate = null;
|
||||
private ?float $hourlyRate = null;
|
||||
/**
|
||||
* @var bool
|
||||
* @var Collection<TimesheetMeta>
|
||||
*/
|
||||
private $replaceTags = false;
|
||||
private Collection $meta;
|
||||
/**
|
||||
* @var bool
|
||||
* @var array<string>
|
||||
*/
|
||||
private $recalculateRates = false;
|
||||
/**
|
||||
* @var Customer|null
|
||||
*/
|
||||
private $customer;
|
||||
/**
|
||||
* @var Project|null
|
||||
*/
|
||||
private $project;
|
||||
/**
|
||||
* @var Activity|null
|
||||
*/
|
||||
private $activity;
|
||||
/**
|
||||
* @var User|null
|
||||
*/
|
||||
private $user;
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private $exported = null;
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private $billable = null;
|
||||
/**
|
||||
* @var float|null
|
||||
*/
|
||||
private $fixedRate = null;
|
||||
/**
|
||||
* @var float|null
|
||||
*/
|
||||
private $hourlyRate = null;
|
||||
/**
|
||||
* @var TimesheetMeta[]|Collection
|
||||
*/
|
||||
private $meta;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $updateMeta = [];
|
||||
private array $updateMeta = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -115,7 +86,7 @@ class TimesheetMultiUpdateDTO extends MultiUpdateTableDTO implements EntityWithM
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tag[]|ArrayCollection|iterable
|
||||
* @return iterable<Tag>
|
||||
*/
|
||||
public function getTags(): iterable
|
||||
{
|
||||
|
||||
@@ -16,12 +16,9 @@ use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PasswordResetForm extends AbstractType
|
||||
final class PasswordResetForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('plainPassword', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
@@ -30,16 +27,13 @@ class PasswordResetForm extends AbstractType
|
||||
'autocomplete' => 'new-password',
|
||||
],
|
||||
],
|
||||
'first_options' => ['label' => 'label.password'],
|
||||
'second_options' => ['label' => 'label.password_repeat'],
|
||||
'first_options' => ['label' => 'password'],
|
||||
'second_options' => ['label' => 'password_repeat'],
|
||||
'invalid_message' => 'The entered passwords don\'t match.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
@@ -47,10 +41,7 @@ class PasswordResetForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'password_resetting';
|
||||
}
|
||||
|
||||
@@ -15,12 +15,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectCommentForm extends AbstractType
|
||||
final class ProjectCommentForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('message', TextareaType::class, [
|
||||
@@ -29,10 +26,7 @@ class ProjectCommentForm extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ProjectComment::class,
|
||||
|
||||
@@ -12,7 +12,9 @@ namespace App\Form;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Form\Type\CustomerType;
|
||||
use App\Form\Type\DateTimePickerType;
|
||||
use App\Form\Type\DatePickerType;
|
||||
use App\Form\Type\InvoiceLabelType;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Form\Type\YesNoType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
@@ -24,19 +26,16 @@ class ProjectEditForm extends AbstractType
|
||||
{
|
||||
use EntityFormTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$customer = null;
|
||||
$id = null;
|
||||
$isNew = false;
|
||||
$options['currency'] = null;
|
||||
|
||||
if (isset($options['data'])) {
|
||||
/** @var Project $entry */
|
||||
$entry = $options['data'];
|
||||
$id = $entry->getId();
|
||||
$isNew = $entry->getId() === null;
|
||||
|
||||
if (null !== $entry->getCustomer()) {
|
||||
$customer = $entry->getCustomer();
|
||||
@@ -53,62 +52,63 @@ class ProjectEditForm extends AbstractType
|
||||
$dateTimeOptions['format'] = $options['date_format'];
|
||||
}
|
||||
|
||||
$timeIncrement = 1;
|
||||
if ($options['time_increment'] >= 1 && $options['time_increment'] <= 60) {
|
||||
$timeIncrement = $options['time_increment'];
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
'label' => 'name',
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
],
|
||||
])
|
||||
->add('comment', TextareaType::class, [
|
||||
'label' => 'label.description',
|
||||
'required' => false,
|
||||
])
|
||||
->add('invoiceText', TextareaType::class, [
|
||||
'label' => 'label.invoiceText',
|
||||
'label' => 'description',
|
||||
'required' => false,
|
||||
])
|
||||
->add('invoiceText', InvoiceLabelType::class)
|
||||
->add('orderNumber', TextType::class, [
|
||||
'label' => 'label.orderNumber',
|
||||
'label' => 'orderNumber',
|
||||
'required' => false,
|
||||
])
|
||||
->add('orderDate', DateTimePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'label.orderDate',
|
||||
->add('orderDate', DatePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'orderDate',
|
||||
'required' => false,
|
||||
'time_increment' => $timeIncrement,
|
||||
'force_time' => 'start',
|
||||
]))
|
||||
->add('start', DateTimePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'label.project_start',
|
||||
->add('start', DatePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'project_start',
|
||||
'required' => false,
|
||||
'time_increment' => $timeIncrement,
|
||||
'force_time' => 'start',
|
||||
]))
|
||||
->add('end', DateTimePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'label.project_end',
|
||||
->add('end', DatePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'project_end',
|
||||
'required' => false,
|
||||
'time_increment' => $timeIncrement,
|
||||
'force_time' => 'end',
|
||||
]))
|
||||
->add('customer', CustomerType::class, [
|
||||
'placeholder' => (null === $id && null === $customer) ? '' : false,
|
||||
'placeholder' => ($isNew && null === $customer) ? '' : false,
|
||||
'customers' => $customer,
|
||||
'query_builder_for_user' => true,
|
||||
])
|
||||
->add('globalActivities', YesNoType::class, [
|
||||
'label' => 'label.globalActivities',
|
||||
'label' => 'globalActivities',
|
||||
'help' => 'help.globalActivities'
|
||||
])
|
||||
;
|
||||
|
||||
if ($isNew) {
|
||||
$builder
|
||||
->add('teams', TeamType::class, [
|
||||
'required' => false,
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
'by_reference' => false,
|
||||
'help' => 'help.teams',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addCommonFields($builder, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Project::class,
|
||||
@@ -120,7 +120,6 @@ class ProjectEditForm extends AbstractType
|
||||
'include_budget' => false,
|
||||
'include_time' => false,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'time_increment' => 1,
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.projectUpdate'
|
||||
],
|
||||
|
||||
@@ -15,10 +15,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectRateForm extends AbstractRateForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$currency = null;
|
||||
|
||||
@@ -34,10 +31,7 @@ class ProjectRateForm extends AbstractRateForm
|
||||
$this->addFields($builder, $currency);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ProjectRate::class,
|
||||
|
||||
@@ -15,12 +15,9 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ProjectTeamPermissionForm extends AbstractType
|
||||
final class ProjectTeamPermissionForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('teams', TeamType::class, [
|
||||
@@ -31,10 +28,7 @@ class ProjectTeamPermissionForm extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Project::class,
|
||||
|
||||
@@ -23,19 +23,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\All;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
|
||||
class QuickEntryForm extends AbstractType
|
||||
final class QuickEntryForm extends AbstractType
|
||||
{
|
||||
private $configuration;
|
||||
|
||||
public function __construct(SystemConfiguration $configuration)
|
||||
public function __construct(private SystemConfiguration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addModelTransformer(new CallbackTransformer(
|
||||
function ($value) {
|
||||
@@ -92,10 +86,7 @@ class QuickEntryForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$start = new \DateTime();
|
||||
$start->setTime(0, 0, 0);
|
||||
|
||||
@@ -10,35 +10,29 @@
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Role;
|
||||
use FOS\RestBundle\Validator\Constraints\Regex;
|
||||
use App\Validator\Constraints\RoleName;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* The form used to edit roles.
|
||||
*/
|
||||
class RoleType extends AbstractType
|
||||
final class RoleType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
'help' => 'Allowed character: A-Z and _',
|
||||
'constraints' => [
|
||||
new Regex(['pattern' => '/^[a-zA-Z_]{5,}$/'])
|
||||
],
|
||||
'attr' => [
|
||||
'maxlength' => 50
|
||||
]
|
||||
])
|
||||
;
|
||||
$builder->add('name', TextType::class, [
|
||||
'label' => 'name',
|
||||
'help' => 'Allowed character: A-Z and _',
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
new RoleName(),
|
||||
],
|
||||
'attr' => [
|
||||
'maxlength' => 50
|
||||
]
|
||||
]);
|
||||
|
||||
// help the user to figure out the allowed name
|
||||
$builder->get('name')->addViewTransformer(
|
||||
@@ -47,7 +41,6 @@ class RoleType extends AbstractType
|
||||
if (\is_string($roleName)) {
|
||||
$roleName = str_replace(' ', '_', $roleName);
|
||||
$roleName = str_replace('-', '_', $roleName);
|
||||
$roleName = strtoupper($roleName);
|
||||
}
|
||||
|
||||
return $roleName;
|
||||
@@ -59,10 +52,7 @@ class RoleType extends AbstractType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Role::class,
|
||||
|
||||
@@ -17,16 +17,13 @@ use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class SelfRegistrationForm extends AbstractType
|
||||
final class SelfRegistrationForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, ['label' => 'label.email'])
|
||||
->add('username', null, ['label' => 'Username', 'translation_domain' => 'AdminLTEBundle'])
|
||||
->add('email', EmailType::class, ['label' => 'email'])
|
||||
->add('username', null, ['label' => 'Username', 'translation_domain' => 'TablerBundle'])
|
||||
->add('plainPassword', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'options' => [
|
||||
@@ -34,17 +31,14 @@ class SelfRegistrationForm extends AbstractType
|
||||
'autocomplete' => 'new-password',
|
||||
],
|
||||
],
|
||||
'first_options' => ['label' => 'label.password'],
|
||||
'second_options' => ['label' => 'label.password_repeat'],
|
||||
'first_options' => ['label' => 'password'],
|
||||
'second_options' => ['label' => 'password_repeat'],
|
||||
'invalid_message' => 'The entered passwords don\'t match.',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
@@ -52,10 +46,7 @@ class SelfRegistrationForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'user_registration';
|
||||
}
|
||||
|
||||
@@ -19,12 +19,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Defines the form used to edit one section within the system configuration.
|
||||
*/
|
||||
class SystemConfigurationForm extends AbstractType
|
||||
final class SystemConfigurationForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('configuration', CollectionType::class, [
|
||||
'entry_type' => SystemConfigurationType::class,
|
||||
@@ -35,10 +32,7 @@ class SystemConfigurationForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => SystemConfiguration::class,
|
||||
|
||||
@@ -19,14 +19,11 @@ class TagEditForm extends AbstractType
|
||||
{
|
||||
use ColorTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
'label' => 'name',
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
],
|
||||
@@ -39,10 +36,7 @@ class TagEditForm extends AbstractType
|
||||
$this->addColor($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Tag::class,
|
||||
|
||||
@@ -15,12 +15,9 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TeamCustomerForm extends AbstractType
|
||||
final class TeamCustomerForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('customers', CustomerType::class, [
|
||||
@@ -33,10 +30,7 @@ class TeamCustomerForm extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Team::class,
|
||||
|
||||
@@ -22,10 +22,7 @@ class TeamEditForm extends AbstractType
|
||||
{
|
||||
use ColorTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$users = [];
|
||||
|
||||
@@ -37,7 +34,7 @@ class TeamEditForm extends AbstractType
|
||||
|
||||
$builder
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'label.name',
|
||||
'label' => 'name',
|
||||
'attr' => [
|
||||
'autofocus' => 'autofocus'
|
||||
],
|
||||
@@ -58,7 +55,8 @@ class TeamEditForm extends AbstractType
|
||||
'allow_add' => true,
|
||||
'by_reference' => false,
|
||||
'allow_delete' => true,
|
||||
'label' => 'label.user',
|
||||
'label' => 'team.member',
|
||||
'translation_domain' => 'teams',
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
@@ -89,10 +87,7 @@ class TeamEditForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Team::class,
|
||||
|
||||
@@ -15,12 +15,9 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TeamProjectForm extends AbstractType
|
||||
final class TeamProjectForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('projects', ProjectType::class, [
|
||||
@@ -34,10 +31,7 @@ class TeamProjectForm extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Team::class,
|
||||
|
||||
@@ -13,7 +13,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class TimesheetAdminEditForm extends TimesheetEditForm
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$options['allow_begin_datetime'] = true;
|
||||
$options['allow_end_datetime'] = true;
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Form\Type\DateTimePickerType;
|
||||
use App\Form\Type\DatePickerType;
|
||||
use App\Form\Type\DescriptionType;
|
||||
use App\Form\Type\DurationType;
|
||||
use App\Form\Type\FixedRateType;
|
||||
use App\Form\Type\HourlyRateType;
|
||||
use App\Form\Type\MetaFieldsCollectionType;
|
||||
use App\Form\Type\TagsType;
|
||||
use App\Form\Type\TimePickerType;
|
||||
use App\Form\Type\TimesheetBillableType;
|
||||
use App\Form\Type\UserType;
|
||||
use App\Form\Type\YesNoType;
|
||||
@@ -28,6 +30,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* Defines the form used to manipulate Timesheet entries.
|
||||
@@ -36,20 +39,11 @@ class TimesheetEditForm extends AbstractType
|
||||
{
|
||||
use FormTrait;
|
||||
|
||||
/**
|
||||
* @var CustomerRepository
|
||||
*/
|
||||
private $customers;
|
||||
|
||||
public function __construct(CustomerRepository $customer)
|
||||
public function __construct(private CustomerRepository $customers, private SystemConfiguration $systemConfiguration)
|
||||
{
|
||||
$this->customers = $customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$activity = null;
|
||||
$project = null;
|
||||
@@ -65,7 +59,7 @@ class TimesheetEditForm extends AbstractType
|
||||
|
||||
$activity = $entry->getActivity();
|
||||
$project = $entry->getProject();
|
||||
$customer = null === $project ? null : $project->getCustomer();
|
||||
$customer = $project?->getCustomer();
|
||||
|
||||
if (null !== $entry->getId()) {
|
||||
$isNew = false;
|
||||
@@ -110,8 +104,12 @@ class TimesheetEditForm extends AbstractType
|
||||
$this->addCustomer($builder, $customer);
|
||||
}
|
||||
|
||||
$allowCreate = (bool) $this->systemConfiguration->find('activity.allow_inline_create');
|
||||
|
||||
$this->addProject($builder, $isNew, $project, $customer);
|
||||
$this->addActivity($builder, $activity, $project);
|
||||
$this->addActivity($builder, $activity, $project, [
|
||||
'allow_create' => $allowCreate && $options['create_activity'],
|
||||
]);
|
||||
|
||||
$descriptionOptions = ['required' => false];
|
||||
if (!$isNew) {
|
||||
@@ -144,34 +142,129 @@ class TimesheetEditForm extends AbstractType
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function addBegin(FormBuilderInterface $builder, array $dateTimeOptions, array $options = [])
|
||||
protected function addBegin(FormBuilderInterface $builder, array $dateTimeOptions, array $options = []): void
|
||||
{
|
||||
if ($options['begin_minutes'] >= 1 && $options['begin_minutes'] <= 60) {
|
||||
$dateTimeOptions['time_increment'] = $options['begin_minutes'];
|
||||
}
|
||||
|
||||
$builder->add('begin', DateTimePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'label.begin',
|
||||
$dateOptions = $dateTimeOptions;
|
||||
$builder->add('begin_date', DatePickerType::class, array_merge($dateOptions, [
|
||||
'label' => 'date',
|
||||
'mapped' => false,
|
||||
'constraints' => [
|
||||
new NotBlank()
|
||||
]
|
||||
]));
|
||||
|
||||
$timeOptions = $dateTimeOptions;
|
||||
|
||||
$builder->add('begin_time', TimePickerType::class, array_merge($timeOptions, [
|
||||
'label' => 'starttime',
|
||||
'mapped' => false,
|
||||
'constraints' => [
|
||||
new NotBlank()
|
||||
]
|
||||
]));
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::POST_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
$begin = $timesheet->getBegin();
|
||||
|
||||
if (null !== $begin) {
|
||||
$event->getForm()->get('begin_date')->setData($begin);
|
||||
$event->getForm()->get('begin_time')->setData($begin);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// map single fields to original datetime object
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
/** @var Timesheet $data */
|
||||
$data = $event->getData();
|
||||
|
||||
/** @var \DateTime|null $date */
|
||||
$date = $event->getForm()->get('begin_date')->getData();
|
||||
$time = $event->getForm()->get('begin_time')->getData();
|
||||
|
||||
if ($date === null || $time === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// mutable datetime are a problem for doctrine
|
||||
$newDate = clone $date;
|
||||
$newDate->setTime($time->format('H'), $time->format('i'));
|
||||
|
||||
if ($data->getBegin() === null || $data->getBegin()->getTimestamp() !== $newDate->getTimestamp()) {
|
||||
$data->setBegin($newDate);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addEnd(FormBuilderInterface $builder, array $dateTimeOptions, array $options = [])
|
||||
protected function addEnd(FormBuilderInterface $builder, array $dateTimeOptions, array $options = []): void
|
||||
{
|
||||
if ($options['end_minutes'] >= 1 && $options['end_minutes'] <= 60) {
|
||||
$dateTimeOptions['time_increment'] = (int) $options['end_minutes'];
|
||||
}
|
||||
|
||||
$builder->add('end', DateTimePickerType::class, array_merge($dateTimeOptions, [
|
||||
'label' => 'label.end',
|
||||
$builder->add('end_time', TimePickerType::class, array_merge($dateTimeOptions, [
|
||||
'required' => false,
|
||||
'label' => 'endtime',
|
||||
'mapped' => false
|
||||
]));
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::POST_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
/** @var Timesheet|null $data */
|
||||
$data = $event->getData();
|
||||
if (null !== $data->getEnd()) {
|
||||
$event->getForm()->get('end_time')->setData($data->getEnd());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// make sure that date & time fields are mapped back to begin & end fields
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) {
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
$oldEnd = $timesheet->getEnd();
|
||||
|
||||
$end = $event->getForm()->get('end_time')->getData();
|
||||
if ($end === null || $end === false) {
|
||||
$timesheet->setEnd(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// mutable datetime are a problem for doctrine
|
||||
$end = clone $end;
|
||||
|
||||
// end is assumed to be the same day then start, if not we raise the day by one
|
||||
//$time = $event->getForm()->get('begin_time')->getData();
|
||||
$time = $timesheet->getBegin();
|
||||
if ($time === null) {
|
||||
throw new \Exception('Cannot work with timesheets without start time');
|
||||
}
|
||||
$newEnd = clone $time;
|
||||
$newEnd->setTime($end->format('H'), $end->format('i'));
|
||||
|
||||
if ($newEnd < $time) {
|
||||
$newEnd->modify('+ 1 day');
|
||||
}
|
||||
|
||||
if ($oldEnd === null || $oldEnd->getTimestamp() !== $newEnd->getTimestamp()) {
|
||||
$timesheet->setEnd($newEnd);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addDuration(FormBuilderInterface $builder, array $options, bool $forceApply = false, bool $autofocus = false)
|
||||
protected function addDuration(FormBuilderInterface $builder, array $options, bool $forceApply = false, bool $autofocus = false): void
|
||||
{
|
||||
$durationOptions = [
|
||||
'required' => false,
|
||||
'docu_chapter' => 'duration-format.html',
|
||||
//'toggle' => true,
|
||||
'attr' => [
|
||||
'placeholder' => '0:00',
|
||||
],
|
||||
@@ -200,9 +293,9 @@ class TimesheetEditForm extends AbstractType
|
||||
$builder->addEventListener(
|
||||
FormEvents::POST_SET_DATA,
|
||||
function (FormEvent $event) {
|
||||
/** @var Timesheet|null $data */
|
||||
$data = $event->getData();
|
||||
if (null === $data || null === $data->getEnd()) {
|
||||
/** @var Timesheet|null $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
if (null === $timesheet || null === $timesheet->getEnd()) {
|
||||
$event->getForm()->get('duration')->setData(null);
|
||||
}
|
||||
}
|
||||
@@ -212,21 +305,31 @@ class TimesheetEditForm extends AbstractType
|
||||
$builder->addEventListener(
|
||||
FormEvents::SUBMIT,
|
||||
function (FormEvent $event) use ($forceApply) {
|
||||
/** @var Timesheet $data */
|
||||
$data = $event->getData();
|
||||
$duration = $data->getDuration();
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
|
||||
$newDuration = $event->getForm()->get('duration')->getData();
|
||||
if ($newDuration !== null && $newDuration > 0 && $newDuration !== $timesheet->getDuration()) {
|
||||
// TODO allow to use a duration that differs from end-start by adding a system configuration check here
|
||||
if ($timesheet->getEnd() === null) {
|
||||
$timesheet->setDuration($newDuration);
|
||||
}
|
||||
}
|
||||
|
||||
$duration = $timesheet->getDuration() ?? 0;
|
||||
|
||||
// only apply the duration, if the end is not yet set
|
||||
// without that check, the end would be overwritten and the real end time would be lost
|
||||
if (($forceApply && null !== $duration) || (null !== $duration && null === $data->getEnd())) {
|
||||
$end = clone $data->getBegin();
|
||||
if (($forceApply && $duration > 0) || ($duration > 0 && null === $timesheet->getEnd())) {
|
||||
$end = clone $timesheet->getBegin();
|
||||
$end->modify('+ ' . $duration . 'seconds');
|
||||
$data->setEnd($end);
|
||||
$timesheet->setEnd($end);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addRates(FormBuilderInterface $builder, $currency, array $options)
|
||||
protected function addRates(FormBuilderInterface $builder, $currency, array $options): void
|
||||
{
|
||||
if (!$options['include_rate']) {
|
||||
return;
|
||||
@@ -241,7 +344,7 @@ class TimesheetEditForm extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addUser(FormBuilderInterface $builder, array $options)
|
||||
protected function addUser(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if (!$options['include_user']) {
|
||||
return;
|
||||
@@ -250,18 +353,18 @@ class TimesheetEditForm extends AbstractType
|
||||
$builder->add('user', UserType::class);
|
||||
}
|
||||
|
||||
protected function addExported(FormBuilderInterface $builder, array $options)
|
||||
protected function addExported(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if (!$options['include_exported']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$builder->add('exported', YesNoType::class, [
|
||||
'label' => 'label.exported'
|
||||
'label' => 'exported'
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addBillable(FormBuilderInterface $builder, array $options)
|
||||
protected function addBillable(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
if ($options['include_billable']) {
|
||||
$builder->add('billableMode', TimesheetBillableType::class, []);
|
||||
@@ -281,18 +384,21 @@ class TimesheetEditForm extends AbstractType
|
||||
},
|
||||
function (Timesheet $record) {
|
||||
$billable = new BillableCalculator();
|
||||
$billable->calculate($record);
|
||||
$billable->calculate($record, []);
|
||||
|
||||
return $record;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$maxMinutes = $this->systemConfiguration->getTimesheetLongRunningDuration();
|
||||
$maxHours = 8;
|
||||
if ($maxMinutes > 0) {
|
||||
$maxHours = (int) ($maxMinutes / 60);
|
||||
}
|
||||
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Timesheet::class,
|
||||
'csrf_protection' => true,
|
||||
@@ -302,6 +408,7 @@ class TimesheetEditForm extends AbstractType
|
||||
'include_exported' => false,
|
||||
'include_billable' => true,
|
||||
'include_rate' => true,
|
||||
'create_activity' => false,
|
||||
'docu_chapter' => 'timesheet.html',
|
||||
'method' => 'POST',
|
||||
'date_format' => null,
|
||||
@@ -311,9 +418,7 @@ class TimesheetEditForm extends AbstractType
|
||||
'allow_end_datetime' => true,
|
||||
'allow_duration' => false,
|
||||
'duration_minutes' => null,
|
||||
'duration_hours' => 10,
|
||||
'begin_minutes' => 1,
|
||||
'end_minutes' => 1,
|
||||
'duration_hours' => $maxHours,
|
||||
'attr' => [
|
||||
'data-form-event' => 'kimai.timesheetUpdate',
|
||||
'data-msg-success' => 'action.update.success',
|
||||
|
||||
@@ -12,11 +12,10 @@ namespace App\Form;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Form\Type\UserType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TimesheetMultiUserEditForm extends TimesheetAdminEditForm
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$options['allow_begin_datetime'] = true;
|
||||
$options['allow_end_datetime'] = true;
|
||||
@@ -25,6 +24,7 @@ class TimesheetMultiUserEditForm extends TimesheetAdminEditForm
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->add('users', UserType::class, [
|
||||
'label' => 'users',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
]);
|
||||
@@ -34,9 +34,4 @@ class TimesheetMultiUserEditForm extends TimesheetAdminEditForm
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Values that are allowed to be pre-set via URL.
|
||||
*/
|
||||
class TimesheetPreCreateForm extends AbstractType
|
||||
final class TimesheetPreCreateForm extends AbstractType
|
||||
{
|
||||
use FormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addProject($builder, true, null, null, ['required' => false]);
|
||||
$this->addActivity($builder, null, null, ['required' => false]);
|
||||
@@ -34,7 +34,7 @@ class TimesheetPreCreateForm extends AbstractType
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'csrf_protection' => false,
|
||||
|
||||
@@ -9,454 +9,25 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Form\Type\ActivityType;
|
||||
use App\Form\Type\CustomerType;
|
||||
use App\Form\Type\DateRangeType;
|
||||
use App\Form\Type\PageSizeType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\SearchTermType;
|
||||
use App\Form\Type\TagsType;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Form\Type\UserRoleType;
|
||||
use App\Form\Type\UserType;
|
||||
use App\Form\Type\VisibilityType;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\ActivityFormTypeQuery;
|
||||
use App\Repository\Query\BaseQuery;
|
||||
use App\Repository\Query\CustomerFormTypeQuery;
|
||||
use App\Repository\Query\ProjectFormTypeQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Validator\Constraints\Choice;
|
||||
|
||||
/**
|
||||
* Defines the base form used for all toolbars.
|
||||
*
|
||||
* Extend this class and stack the elements defined here, they are coupled to each other and with the toolbar.js.
|
||||
* Extend this class and stack the elements defined here, they are coupled to each other and connected with javascript.
|
||||
*
|
||||
* @deprecated since 2.0, will be removed with 2.1 - use ToolbarFormTrait instead
|
||||
*/
|
||||
abstract class AbstractToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* Dirty hack to enable easy handling of GET form in controller and javascript.
|
||||
* Cleans up the name of all form elements (and unfortunately of the form itself).
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
@trigger_error('The "AbstractToolbarForm" is deprecated and will be removed with 2.1, use the "ToolbarFormTrait" instead', E_USER_DEPRECATED);
|
||||
|
||||
// Dirty hack to enable easy handling of GET form in controller and javascript.
|
||||
// Cleans up the name of all form elements (and unfortunately of the form itself).
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function addUserChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('user', UserType::class, [
|
||||
'label' => 'label.user',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addUsersChoice(FormBuilderInterface $builder, string $field = 'users', array $options = [])
|
||||
{
|
||||
$builder->add($field, UserType::class, array_merge([
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'User ID'],
|
||||
'description' => 'Array of user IDs',
|
||||
],
|
||||
'label' => 'label.user',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
], $options));
|
||||
}
|
||||
|
||||
protected function addTeamChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('team', TeamType::class, [
|
||||
'label' => 'label.team',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addTeamsChoice(FormBuilderInterface $builder, string $field = 'teams', array $options = [])
|
||||
{
|
||||
$builder->add($field, TeamType::class, array_merge([
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Team ID'],
|
||||
'description' => 'Array of team IDs',
|
||||
],
|
||||
'label' => 'label.team',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
], $options));
|
||||
}
|
||||
|
||||
protected function addCustomerChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
|
||||
{
|
||||
$this->addCustomerSelect($builder, $options, false, $multiProject);
|
||||
}
|
||||
|
||||
protected function addCustomerMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
|
||||
{
|
||||
$this->addCustomerSelect($builder, $options, true, $multiProject);
|
||||
}
|
||||
|
||||
private function addCustomerSelect(FormBuilderInterface $builder, array $options, bool $multiCustomer, bool $multiProject)
|
||||
{
|
||||
$name = 'customer';
|
||||
if ($multiCustomer) {
|
||||
$name = 'customers';
|
||||
}
|
||||
|
||||
// just a fake field for having this field at the right position in the frontend
|
||||
$builder->add($name, CustomerType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Customer ID'],
|
||||
'description' => 'Array of customer IDs',
|
||||
],
|
||||
'choices' => [],
|
||||
'multiple' => $multiCustomer,
|
||||
]);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject) {
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, CustomerType::class, array_merge([
|
||||
'multiple' => $multiCustomer,
|
||||
'required' => false,
|
||||
'project_enabled' => $multiCustomer ? 'customers' : 'customer',
|
||||
'project_select' => $multiProject ? 'projects' : 'project',
|
||||
'end_date_param' => '%daterange%',
|
||||
'start_date_param' => '%daterange%',
|
||||
'query_builder' => function (CustomerRepository $repo) use ($builder, $data, $name, $multiCustomer) {
|
||||
$query = new CustomerFormTypeQuery();
|
||||
$query->setUser($builder->getOption('user'));
|
||||
|
||||
if (isset($data[$name]) && !empty($data[$name])) {
|
||||
if ($multiCustomer) {
|
||||
$query->setCustomers($data[$name]);
|
||||
} else {
|
||||
$query->addCustomer($data[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
], $options));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addVisibilityChoice(FormBuilderInterface $builder, string $label = 'label.visible')
|
||||
{
|
||||
$builder->add('visibility', VisibilityType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'label' => $label,
|
||||
'search' => false
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addPageSizeChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('pageSize', PageSizeType::class, [
|
||||
'required' => false,
|
||||
'search' => false
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addUserRoleChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('role', UserRoleType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addDateRange(FormBuilderInterface $builder, array $options, $allowEmpty = true, $required = false)
|
||||
{
|
||||
$params = [
|
||||
'required' => $required,
|
||||
'allow_empty' => $allowEmpty,
|
||||
];
|
||||
|
||||
if (\array_key_exists('timezone', $options)) {
|
||||
$params['timezone'] = $options['timezone'];
|
||||
}
|
||||
|
||||
$builder->add('daterange', DateRangeType::class, $params);
|
||||
}
|
||||
|
||||
protected function addDateRangeChoice(FormBuilderInterface $builder, $allowEmpty = true, $required = false)
|
||||
{
|
||||
$this->addDateRange($builder, [], $allowEmpty, $required);
|
||||
}
|
||||
|
||||
protected function addProjectChoice(FormBuilderInterface $builder, array $options = [], bool $multiCustomer = false, bool $multiActivity = false)
|
||||
{
|
||||
$this->addProjectSelect($builder, $options, false, $multiCustomer, $multiActivity);
|
||||
}
|
||||
|
||||
protected function addProjectMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiCustomer = false, bool $multiActivity = false)
|
||||
{
|
||||
$this->addProjectSelect($builder, $options, true, $multiCustomer, $multiActivity);
|
||||
}
|
||||
|
||||
private function addProjectSelect(FormBuilderInterface $builder, array $options, bool $multiProject, bool $multiCustomer, bool $multiActivity)
|
||||
{
|
||||
$name = 'project';
|
||||
if ($multiProject) {
|
||||
$name = 'projects';
|
||||
}
|
||||
// just a fake field for having this field at the right position in the frontend
|
||||
$builder->add($name, ProjectType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Project ID'],
|
||||
'description' => 'Array of project IDs',
|
||||
],
|
||||
'choices' => [],
|
||||
'multiple' => $multiProject,
|
||||
]);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject, $multiActivity) {
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, ProjectType::class, array_merge([
|
||||
'multiple' => $multiProject,
|
||||
'required' => false,
|
||||
'activity_enabled' => $multiProject ? 'projects' : 'project',
|
||||
'activity_select' => $multiActivity ? 'activities' : 'activity',
|
||||
'query_builder' => function (ProjectRepository $repo) use ($builder, $data, $options, $multiCustomer, $multiProject) {
|
||||
$query = new ProjectFormTypeQuery();
|
||||
$query->setUser($builder->getOption('user'));
|
||||
|
||||
$name = $multiCustomer ? 'customers' : 'customer';
|
||||
if (isset($data[$name]) && !empty($data[$name])) {
|
||||
if (\is_array($data[$name])) {
|
||||
$query->setCustomers($data[$name]);
|
||||
} else {
|
||||
$query->addCustomer($data[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
$name = $multiProject ? 'projects' : 'project';
|
||||
if (isset($data[$name]) && !empty($data[$name])) {
|
||||
if (\is_array($data[$name])) {
|
||||
$query->setProjects($data[$name]);
|
||||
} else {
|
||||
$query->addProject($data[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['ignore_date']) && true === $options['ignore_date']) {
|
||||
$query->setIgnoreDate(true);
|
||||
}
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
], $options));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addActivityChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
|
||||
{
|
||||
$this->addActivitySelect($builder, $options, false, $multiProject);
|
||||
}
|
||||
|
||||
protected function addActivityMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false)
|
||||
{
|
||||
$this->addActivitySelect($builder, $options, true, $multiProject);
|
||||
}
|
||||
|
||||
private function addActivitySelect(FormBuilderInterface $builder, array $options = [], bool $multiActivity = false, bool $multiProject = false)
|
||||
{
|
||||
$name = 'activity';
|
||||
if ($multiActivity) {
|
||||
$name = 'activities';
|
||||
}
|
||||
|
||||
// just a fake field for having this field at the right position in the frontend
|
||||
$builder->add($name, ActivityType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Activity ID'],
|
||||
'description' => 'Array of activity IDs',
|
||||
],
|
||||
'choices' => [],
|
||||
'multiple' => $multiActivity,
|
||||
]);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($name, $multiActivity, $multiProject) {
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, ActivityType::class, [
|
||||
'multiple' => $multiActivity,
|
||||
'required' => false,
|
||||
'query_builder' => function (ActivityRepository $repo) use ($data, $multiActivity, $multiProject) {
|
||||
$query = new ActivityFormTypeQuery();
|
||||
|
||||
$name = $multiActivity ? 'activities' : 'activity';
|
||||
if (isset($data[$name]) && !empty($data[$name])) {
|
||||
// we need to pre-fetch the activities to see if they are global, see ActivityFormTypeQuery::isGlobalsOnly()
|
||||
$activities = $data[$name];
|
||||
if (!\is_array($activities)) {
|
||||
$activities = [$activities];
|
||||
}
|
||||
foreach ($activities as $activity) {
|
||||
if ($activity instanceof Activity) {
|
||||
$query->addActivity($activity);
|
||||
} elseif ($activity !== null) {
|
||||
$tmp = $repo->find($activity);
|
||||
if (null !== $tmp) {
|
||||
$query->addActivity($tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$name = $multiProject ? 'projects' : 'project';
|
||||
if (isset($data[$name]) && !empty($data[$name])) {
|
||||
if ($multiProject) {
|
||||
$query->setProjects($data[$name]);
|
||||
} else {
|
||||
$query->addProject($data[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addHiddenPagination(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('page', HiddenType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Page number. Default: 1',
|
||||
],
|
||||
'empty_data' => 1
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addHiddenOrder(FormBuilderInterface $builder)
|
||||
{
|
||||
@trigger_error('addHiddenOrder() is deprecated and will be removed with 2.0, use the new search modal instead', E_USER_DEPRECATED);
|
||||
|
||||
$builder->add('order', HiddenType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
'description' => 'The order for returned items',
|
||||
],
|
||||
'constraints' => [
|
||||
new Choice(['choices' => [BaseQuery::ORDER_ASC, BaseQuery::ORDER_DESC]])
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addOrder(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('order', ChoiceType::class, [
|
||||
'documentation' => [
|
||||
'description' => 'The order for returned items',
|
||||
],
|
||||
'label' => 'label.order',
|
||||
'choices' => [
|
||||
'label.asc' => BaseQuery::ORDER_ASC,
|
||||
'label.desc' => BaseQuery::ORDER_DESC
|
||||
],
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addHiddenOrderBy(FormBuilderInterface $builder, array $allowedColumns)
|
||||
{
|
||||
@trigger_error('addHiddenOrderBy() is deprecated and will be removed with 2.0, use the new search modal instead', E_USER_DEPRECATED);
|
||||
|
||||
$builder->add('orderBy', HiddenType::class, [
|
||||
'constraints' => [
|
||||
new Choice(['choices' => $allowedColumns])
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addOrderBy(FormBuilderInterface $builder, array $allowedColumns)
|
||||
{
|
||||
$all = [];
|
||||
foreach ($allowedColumns as $id => $name) {
|
||||
$label = \is_int($id) ? 'label.' . $name : $id;
|
||||
$all[$label] = $name;
|
||||
}
|
||||
$builder->add('orderBy', ChoiceType::class, [
|
||||
'label' => 'label.orderBy',
|
||||
'choices' => $all,
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addTagInputField(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('tags', TagsType::class, [
|
||||
'required' => false
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addSearchTermInputField(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('searchTerm', SearchTermType::class);
|
||||
}
|
||||
|
||||
protected function addTimesheetStateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('state', ChoiceType::class, [
|
||||
'label' => 'label.entryState',
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
'choices' => [
|
||||
'entryState.all' => TimesheetQuery::STATE_ALL,
|
||||
'entryState.running' => TimesheetQuery::STATE_RUNNING,
|
||||
'entryState.stopped' => TimesheetQuery::STATE_STOPPED
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addExportStateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('exported', ChoiceType::class, [
|
||||
'label' => 'label.exported',
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
'choices' => [
|
||||
'entryState.all' => TimesheetQuery::STATE_ALL,
|
||||
'entryState.exported' => TimesheetQuery::STATE_EXPORTED,
|
||||
'entryState.not_exported' => TimesheetQuery::STATE_NOT_EXPORTED
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addBillableChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('billable', BillableType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\ActivityQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -17,12 +18,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Defines the form used for filtering the activities.
|
||||
*/
|
||||
class ActivityToolbarForm extends AbstractToolbarForm
|
||||
final class ActivityToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$newOptions = [];
|
||||
if ($options['ignore_date'] === true) {
|
||||
@@ -40,7 +40,7 @@ class ActivityToolbarForm extends AbstractToolbarForm
|
||||
'search' => false,
|
||||
'placeholder' => null,
|
||||
'required' => false,
|
||||
'label' => 'label.globalsOnly',
|
||||
'label' => 'globalsOnly',
|
||||
]);
|
||||
$this->addVisibilityChoice($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
@@ -49,10 +49,7 @@ class ActivityToolbarForm extends AbstractToolbarForm
|
||||
$this->addOrderBy($builder, ActivityQuery::ACTIVITY_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ActivityQuery::class,
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering the customer.
|
||||
*/
|
||||
class CustomerToolbarForm extends AbstractToolbarForm
|
||||
final class CustomerToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addVisibilityChoice($builder);
|
||||
@@ -31,10 +31,7 @@ class CustomerToolbarForm extends AbstractToolbarForm
|
||||
$this->addOrderBy($builder, CustomerQuery::CUSTOMER_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => CustomerQuery::class,
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\MarkAsExportedType;
|
||||
use App\Repository\Query\ExportQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -18,44 +18,35 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Defines the form used for filtering timesheet entries for exports.
|
||||
*/
|
||||
class ExportToolbarForm extends AbstractToolbarForm
|
||||
final class ExportToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addBillableChoice($builder);
|
||||
$this->addExportStateChoice($builder);
|
||||
$this->addTimesheetStateChoice($builder);
|
||||
if ($options['include_user']) {
|
||||
$this->addUsersChoice($builder);
|
||||
$this->addTeamsChoice($builder);
|
||||
}
|
||||
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
|
||||
$this->addCustomerMultiChoice($builder, ['start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true], true);
|
||||
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
|
||||
$this->addActivityMultiChoice($builder, [], true);
|
||||
$this->addExportRenderer($builder);
|
||||
$this->addTagInputField($builder);
|
||||
if ($options['include_user']) {
|
||||
$this->addUsersChoice($builder);
|
||||
$this->addTeamsChoice($builder);
|
||||
}
|
||||
$this->addTimesheetStateChoice($builder);
|
||||
$this->addBillableChoice($builder);
|
||||
$this->addExportStateChoice($builder);
|
||||
$builder->add('renderer', HiddenType::class, []);
|
||||
if ($options['include_export']) {
|
||||
$builder->add('markAsExported', MarkAsExportedType::class);
|
||||
$builder->add('markAsExported', HiddenType::class, [
|
||||
'label' => 'mark_as_exported',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormBuilderInterface $builder
|
||||
*/
|
||||
protected function addExportRenderer(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('renderer', HiddenType::class, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ExportQuery::class,
|
||||
|
||||
@@ -11,32 +11,30 @@ namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\InvoiceStatusType;
|
||||
use App\Repository\Query\InvoiceArchiveQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering timesheet entries for invoices.
|
||||
*/
|
||||
class InvoiceArchiveForm extends AbstractToolbarForm
|
||||
final class InvoiceArchiveForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
|
||||
$this->addCustomerMultiChoice($builder, ['required' => false, 'start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true, 'placeholder' => ''], true);
|
||||
$builder->add('status', InvoiceStatusType::class, ['required' => false]);
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
$this->addOrder($builder);
|
||||
$this->addOrderBy($builder, InvoiceArchiveQuery::INVOICE_ARCHIVE_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => InvoiceArchiveQuery::class,
|
||||
|
||||
@@ -9,20 +9,26 @@
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\DatePickerType;
|
||||
use App\Form\Type\InvoiceTemplateType;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering timesheet entries for invoices.
|
||||
*/
|
||||
class InvoiceToolbarForm extends InvoiceToolbarSimpleForm
|
||||
final class InvoiceToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
$this->addTemplateChoice($builder);
|
||||
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
|
||||
$this->addCustomerMultiChoice($builder, ['required' => false, 'start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true, 'placeholder' => ''], true);
|
||||
$this->addProjectMultiChoice($builder, ['ignore_date' => true], false, true);
|
||||
$this->addSearchTermInputField($builder);
|
||||
if ($options['include_user']) {
|
||||
$this->addUsersChoice($builder);
|
||||
@@ -31,14 +37,26 @@ class InvoiceToolbarForm extends InvoiceToolbarSimpleForm
|
||||
$this->addActivityMultiChoice($builder, $options, true);
|
||||
$this->addTagInputField($builder);
|
||||
$this->addExportStateChoice($builder);
|
||||
$builder->add('invoiceDate', DatePickerType::class, [
|
||||
'required' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
protected function addTemplateChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
$resolver->setDefault('include_user', true);
|
||||
$builder->add('template', InvoiceTemplateType::class, [
|
||||
'required' => true,
|
||||
'placeholder' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => InvoiceQuery::class,
|
||||
'csrf_protection' => false,
|
||||
'include_user' => true,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +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\Form\Toolbar;
|
||||
|
||||
use App\Form\Type\InvoiceTemplateType;
|
||||
use App\Form\Type\MarkAsExportedType;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering timesheet entries for invoices.
|
||||
*/
|
||||
class InvoiceToolbarSimpleForm extends AbstractToolbarForm
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$this->addTemplateChoice($builder);
|
||||
$this->addDateRange($builder, ['timezone' => $options['timezone']]);
|
||||
$this->addCustomerMultiChoice($builder, ['required' => false, 'start_date_param' => null, 'end_date_param' => null, 'ignore_date' => true, 'placeholder' => ''], true);
|
||||
$this->addProjectMultiChoice($builder, ['ignore_date' => true], true, true);
|
||||
if ($options['include_export']) {
|
||||
$builder->add('markAsExported', MarkAsExportedType::class);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addTemplateChoice(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('template', InvoiceTemplateType::class, [
|
||||
'required' => true,
|
||||
'placeholder' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => InvoiceQuery::class,
|
||||
'csrf_protection' => false,
|
||||
'include_user' => true,
|
||||
'include_export' => true,
|
||||
'timezone' => date_default_timezone_get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -10,18 +10,18 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\ProjectQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering the projects.
|
||||
*/
|
||||
class ProjectToolbarForm extends AbstractToolbarForm
|
||||
final class ProjectToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addCustomerMultiChoice($builder, [], false);
|
||||
@@ -32,10 +32,7 @@ class ProjectToolbarForm extends AbstractToolbarForm
|
||||
$this->addOrderBy($builder, ProjectQuery::PROJECT_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => ProjectQuery::class,
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\TagQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TagToolbarForm extends AbstractToolbarForm
|
||||
final class TagToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addPageSizeChoice($builder);
|
||||
@@ -27,10 +27,7 @@ class TagToolbarForm extends AbstractToolbarForm
|
||||
$this->addOrderBy($builder, TagQuery::TAG_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => TagQuery::class,
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\TeamQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class TeamToolbarForm extends AbstractToolbarForm
|
||||
final class TeamToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addUsersChoice($builder);
|
||||
$this->addSearchTermInputField($builder);
|
||||
@@ -28,10 +28,7 @@ class TeamToolbarForm extends AbstractToolbarForm
|
||||
$this->addOrderBy($builder, TeamQuery::TEAM_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => TeamQuery::class,
|
||||
|
||||
@@ -10,23 +10,18 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering the timesheet.
|
||||
*/
|
||||
class TimesheetExportToolbarForm extends AbstractToolbarForm
|
||||
final class TimesheetExportToolbarForm extends AbstractType
|
||||
{
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'export';
|
||||
}
|
||||
use ToolbarFormTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$newOptions = [];
|
||||
if ($options['ignore_date'] === true) {
|
||||
@@ -49,10 +44,7 @@ class TimesheetExportToolbarForm extends AbstractToolbarForm
|
||||
$this->addOrderBy($builder, TimesheetQuery::TIMESHEET_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => TimesheetQuery::class,
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering the timesheet.
|
||||
*/
|
||||
class TimesheetToolbarForm extends AbstractToolbarForm
|
||||
final class TimesheetToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$newOptions = [];
|
||||
if ($options['ignore_date'] === true) {
|
||||
@@ -47,10 +47,7 @@ class TimesheetToolbarForm extends AbstractToolbarForm
|
||||
$this->addOrderBy($builder, TimesheetQuery::TIMESHEET_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => TimesheetQuery::class,
|
||||
|
||||
436
src/Form/Toolbar/ToolbarFormTrait.php
Normal file
436
src/Form/Toolbar/ToolbarFormTrait.php
Normal file
@@ -0,0 +1,436 @@
|
||||
<?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\Form\Toolbar;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Form\Type\ActivityType;
|
||||
use App\Form\Type\BillableSearchType;
|
||||
use App\Form\Type\CustomerType;
|
||||
use App\Form\Type\DateRangeType;
|
||||
use App\Form\Type\PageSizeType;
|
||||
use App\Form\Type\ProjectType;
|
||||
use App\Form\Type\SearchTermType;
|
||||
use App\Form\Type\TagsType;
|
||||
use App\Form\Type\TeamType;
|
||||
use App\Form\Type\UserRoleType;
|
||||
use App\Form\Type\UserType;
|
||||
use App\Form\Type\VisibilityType;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\Query\ActivityFormTypeQuery;
|
||||
use App\Repository\Query\BaseQuery;
|
||||
use App\Repository\Query\CustomerFormTypeQuery;
|
||||
use App\Repository\Query\ProjectFormTypeQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
|
||||
/**
|
||||
* Defines the base form used for all toolbars.
|
||||
*
|
||||
* Extend this class and stack the elements defined here, they are coupled to each other and with the toolbar.js.
|
||||
*/
|
||||
trait ToolbarFormTrait
|
||||
{
|
||||
protected function addUserChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('user', UserType::class, [
|
||||
'label' => 'user',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addUsersChoice(FormBuilderInterface $builder, string $field = 'users', array $options = []): void
|
||||
{
|
||||
$builder->add($field, UserType::class, array_merge([
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'User ID'],
|
||||
'description' => 'Array of user IDs',
|
||||
],
|
||||
'label' => 'user',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
], $options));
|
||||
}
|
||||
|
||||
protected function addTeamChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('team', TeamType::class, [
|
||||
'label' => 'team',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addTeamsChoice(FormBuilderInterface $builder, string $field = 'teams', array $options = []): void
|
||||
{
|
||||
$builder->add($field, TeamType::class, array_merge([
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Team ID'],
|
||||
'description' => 'Array of team IDs',
|
||||
],
|
||||
'label' => 'team',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
], $options));
|
||||
}
|
||||
|
||||
protected function addCustomerChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false): void
|
||||
{
|
||||
$this->addCustomerSelect($builder, $options, false, $multiProject);
|
||||
}
|
||||
|
||||
protected function addCustomerMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false): void
|
||||
{
|
||||
$this->addCustomerSelect($builder, $options, true, $multiProject);
|
||||
}
|
||||
|
||||
private function addCustomerSelect(FormBuilderInterface $builder, array $options, bool $multiCustomer, bool $multiProject): void
|
||||
{
|
||||
$name = 'customer';
|
||||
if ($multiCustomer) {
|
||||
$name = 'customers';
|
||||
}
|
||||
|
||||
// just a fake field for having this field at the right position in the frontend
|
||||
$builder->add($name, CustomerType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Customer ID'],
|
||||
'description' => 'Array of customer IDs',
|
||||
],
|
||||
'choices' => [],
|
||||
'multiple' => $multiCustomer,
|
||||
]);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject) {
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, CustomerType::class, array_merge([
|
||||
'multiple' => $multiCustomer,
|
||||
'required' => false,
|
||||
'project_enabled' => $multiCustomer ? 'customers[]' : 'customer',
|
||||
'project_select' => $multiProject ? 'projects' : 'project',
|
||||
'end_date_param' => '%daterange%',
|
||||
'start_date_param' => '%daterange%',
|
||||
'query_builder' => function (CustomerRepository $repo) use ($builder, $data, $name) {
|
||||
$query = new CustomerFormTypeQuery();
|
||||
$query->setUser($builder->getOption('user'));
|
||||
|
||||
if (\array_key_exists($name, $data) && $data[$name] !== null && $data[$name] !== '') {
|
||||
$customers = \is_array($data[$name]) ? $data[$name] : [$data[$name]];
|
||||
foreach ($customers as $customer) {
|
||||
$customer = \is_string($customer) ? (int) $customer : $customer;
|
||||
if (!\is_int($customer) && !($customer instanceof Customer)) {
|
||||
throw new \Exception('Need a customer object or an ID for customer select');
|
||||
}
|
||||
$query->addCustomer($customer);
|
||||
}
|
||||
}
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
], $options));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addVisibilityChoice(FormBuilderInterface $builder, string $label = 'visible'): void
|
||||
{
|
||||
$builder->add('visibility', VisibilityType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'label' => $label,
|
||||
'search' => false
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addPageSizeChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('pageSize', PageSizeType::class, [
|
||||
'required' => false,
|
||||
'search' => false
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addUserRoleChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('role', UserRoleType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addDateRange(FormBuilderInterface $builder, array $options, $allowEmpty = true, $required = false): void
|
||||
{
|
||||
$params = [
|
||||
'required' => $required,
|
||||
'allow_empty' => $allowEmpty,
|
||||
];
|
||||
|
||||
if (\array_key_exists('timezone', $options)) {
|
||||
$params['timezone'] = $options['timezone'];
|
||||
}
|
||||
|
||||
$builder->add('daterange', DateRangeType::class, $params);
|
||||
}
|
||||
|
||||
protected function addDateRangeChoice(FormBuilderInterface $builder, $allowEmpty = true, $required = false): void
|
||||
{
|
||||
$this->addDateRange($builder, [], $allowEmpty, $required);
|
||||
}
|
||||
|
||||
protected function addProjectChoice(FormBuilderInterface $builder, array $options = [], bool $multiCustomer = false, bool $multiActivity = false): void
|
||||
{
|
||||
$this->addProjectSelect($builder, $options, false, $multiCustomer, $multiActivity);
|
||||
}
|
||||
|
||||
protected function addProjectMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiCustomer = false, bool $multiActivity = false): void
|
||||
{
|
||||
$this->addProjectSelect($builder, $options, true, $multiCustomer, $multiActivity);
|
||||
}
|
||||
|
||||
private function addProjectSelect(FormBuilderInterface $builder, array $options, bool $multiProject, bool $multiCustomer, bool $multiActivity): void
|
||||
{
|
||||
$name = 'project';
|
||||
if ($multiProject) {
|
||||
$name = 'projects';
|
||||
}
|
||||
// just a fake field for having this field at the right position in the frontend
|
||||
$builder->add($name, ProjectType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Project ID'],
|
||||
'description' => 'Array of project IDs',
|
||||
],
|
||||
'choices' => [],
|
||||
'multiple' => $multiProject,
|
||||
]);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($builder, $options, $name, $multiCustomer, $multiProject, $multiActivity) {
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, ProjectType::class, array_merge([
|
||||
'multiple' => $multiProject,
|
||||
'required' => false,
|
||||
'activity_enabled' => $multiProject ? 'projects[]' : 'project',
|
||||
'activity_select' => $multiActivity ? 'activities' : 'activity',
|
||||
'query_builder' => function (ProjectRepository $repo) use ($builder, $data, $options, $multiCustomer, $multiProject) {
|
||||
$query = new ProjectFormTypeQuery();
|
||||
$query->setUser($builder->getOption('user'));
|
||||
|
||||
$name = $multiCustomer ? 'customers' : 'customer';
|
||||
if (\array_key_exists($name, $data) && $data[$name] !== null && $data[$name] !== '') {
|
||||
$customers = \is_array($data[$name]) ? $data[$name] : [$data[$name]];
|
||||
foreach ($customers as $customer) {
|
||||
$customer = \is_string($customer) ? (int) $customer : $customer;
|
||||
if (!\is_int($customer) && !($customer instanceof Customer)) {
|
||||
throw new \Exception('Need a customer object or an ID for project select');
|
||||
}
|
||||
$query->addCustomer($customer);
|
||||
}
|
||||
}
|
||||
|
||||
$name = $multiProject ? 'projects' : 'project';
|
||||
if (\array_key_exists($name, $data) && $data[$name] !== null && $data[$name] !== '') {
|
||||
$projects = \is_array($data[$name]) ? $data[$name] : [$data[$name]];
|
||||
foreach ($projects as $project) {
|
||||
$project = \is_string($project) ? (int) $project : $project;
|
||||
if (!\is_int($project) && !($project instanceof Project)) {
|
||||
throw new \Exception('Need a project object or an ID for project select');
|
||||
}
|
||||
$query->addProject($project);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['ignore_date']) && true === $options['ignore_date']) {
|
||||
$query->setIgnoreDate(true);
|
||||
}
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
], $options));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addActivityChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false): void
|
||||
{
|
||||
$this->addActivitySelect($builder, $options, false, $multiProject);
|
||||
}
|
||||
|
||||
protected function addActivityMultiChoice(FormBuilderInterface $builder, array $options = [], bool $multiProject = false): void
|
||||
{
|
||||
$this->addActivitySelect($builder, $options, true, $multiProject);
|
||||
}
|
||||
|
||||
private function addActivitySelect(FormBuilderInterface $builder, array $options = [], bool $multiActivity = false, bool $multiProject = false): void
|
||||
{
|
||||
$name = 'activity';
|
||||
if ($multiActivity) {
|
||||
$name = 'activities';
|
||||
}
|
||||
|
||||
// just a fake field for having this field at the right position in the frontend
|
||||
$builder->add($name, ActivityType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'array',
|
||||
'items' => ['type' => 'integer', 'description' => 'Activity ID'],
|
||||
'description' => 'Array of activity IDs',
|
||||
],
|
||||
'choices' => [],
|
||||
'multiple' => $multiActivity,
|
||||
]);
|
||||
|
||||
$builder->addEventListener(
|
||||
FormEvents::PRE_SUBMIT,
|
||||
function (FormEvent $event) use ($name, $multiActivity, $multiProject) {
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $event->getData();
|
||||
$event->getForm()->add($name, ActivityType::class, [
|
||||
'multiple' => $multiActivity,
|
||||
'required' => false,
|
||||
'query_builder' => function (ActivityRepository $repo) use ($data, $multiActivity, $multiProject) {
|
||||
$query = new ActivityFormTypeQuery();
|
||||
|
||||
$name = $multiActivity ? 'activities' : 'activity';
|
||||
if (\array_key_exists($name, $data) && $data[$name] !== null && $data[$name] !== '') {
|
||||
// we need to pre-fetch the activities to see if they are global, see ActivityFormTypeQuery::isGlobalsOnly()
|
||||
$activities = \is_array($data[$name]) ? $data[$name] : [$data[$name]];
|
||||
foreach ($activities as $activity) {
|
||||
$activity = \is_string($activity) ? (int) $activity : $activity;
|
||||
if (!\is_int($activity) && !($activity instanceof Activity)) {
|
||||
throw new \Exception('Need an activity object or an ID for activity select');
|
||||
}
|
||||
$query->addActivity($activity);
|
||||
}
|
||||
}
|
||||
|
||||
$name = $multiProject ? 'projects' : 'project';
|
||||
if (\array_key_exists($name, $data) && $data[$name] !== null && $data[$name] !== '') {
|
||||
$projects = \is_array($data[$name]) ? $data[$name] : [$data[$name]];
|
||||
foreach ($projects as $project) {
|
||||
$project = \is_string($project) ? (int) $project : $project;
|
||||
if (!\is_int($project) && !($project instanceof Project)) {
|
||||
throw new \Exception('Need a project object or an ID for activity select');
|
||||
}
|
||||
$query->addProject($project);
|
||||
}
|
||||
}
|
||||
|
||||
return $repo->getQueryBuilderForFormType($query);
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected function addHiddenPagination(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('page', HiddenType::class, [
|
||||
'documentation' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Page number. Default: 1',
|
||||
],
|
||||
'empty_data' => 1
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addOrder(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('order', ChoiceType::class, [
|
||||
'documentation' => [
|
||||
'description' => 'The order for returned items',
|
||||
],
|
||||
'label' => 'order',
|
||||
'choices' => [
|
||||
'asc' => BaseQuery::ORDER_ASC,
|
||||
'desc' => BaseQuery::ORDER_DESC
|
||||
],
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addOrderBy(FormBuilderInterface $builder, array $allowedColumns): void
|
||||
{
|
||||
$all = [];
|
||||
foreach ($allowedColumns as $id => $name) {
|
||||
$label = \is_int($id) ? $name : $id;
|
||||
$all[$label] = $name;
|
||||
}
|
||||
$builder->add('orderBy', ChoiceType::class, [
|
||||
'label' => 'orderBy',
|
||||
'choices' => $all,
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addTagInputField(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('tags', TagsType::class, [
|
||||
'required' => false,
|
||||
'allow_create' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addSearchTermInputField(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('searchTerm', SearchTermType::class);
|
||||
}
|
||||
|
||||
protected function addTimesheetStateChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('state', ChoiceType::class, [
|
||||
'label' => 'entryState',
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
'choices' => [
|
||||
'all' => TimesheetQuery::STATE_ALL,
|
||||
'entryState.running' => TimesheetQuery::STATE_RUNNING,
|
||||
'entryState.stopped' => TimesheetQuery::STATE_STOPPED
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addExportStateChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('exported', ChoiceType::class, [
|
||||
'label' => 'exported',
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
'choices' => [
|
||||
'all' => TimesheetQuery::STATE_ALL,
|
||||
'yes' => TimesheetQuery::STATE_EXPORTED,
|
||||
'no' => TimesheetQuery::STATE_NOT_EXPORTED
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function addBillableChoice(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder->add('billable', BillableSearchType::class, [
|
||||
'required' => false,
|
||||
'placeholder' => null,
|
||||
'search' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -10,33 +10,30 @@
|
||||
namespace App\Form\Toolbar;
|
||||
|
||||
use App\Repository\Query\UserQuery;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Defines the form used for filtering the user.
|
||||
*/
|
||||
class UserToolbarForm extends AbstractToolbarForm
|
||||
final class UserToolbarForm extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
use ToolbarFormTrait;
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addSearchTermInputField($builder);
|
||||
$this->addUserRoleChoice($builder);
|
||||
$this->addTeamsChoice($builder, 'searchTeams');
|
||||
$this->addVisibilityChoice($builder, 'label.active');
|
||||
$this->addVisibilityChoice($builder, 'active');
|
||||
$this->addPageSizeChoice($builder);
|
||||
$this->addHiddenPagination($builder);
|
||||
$this->addOrder($builder);
|
||||
$this->addOrderBy($builder, UserQuery::USER_ORDER_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => UserQuery::class,
|
||||
|
||||
@@ -24,15 +24,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Custom form field type to select an activity.
|
||||
*/
|
||||
class ActivityType extends AbstractType
|
||||
final class ActivityType extends AbstractType
|
||||
{
|
||||
private $activityHelper;
|
||||
private $projectHelper;
|
||||
|
||||
public function __construct(ActivityHelper $activityHelper, ProjectHelper $projectHelper)
|
||||
public function __construct(private ActivityHelper $activityHelper, private ProjectHelper $projectHelper)
|
||||
{
|
||||
$this->activityHelper = $activityHelper;
|
||||
$this->projectHelper = $projectHelper;
|
||||
}
|
||||
|
||||
public function getChoiceLabel(Activity $activity): string
|
||||
@@ -40,10 +35,7 @@ class ActivityType extends AbstractType
|
||||
return $this->activityHelper->getChoiceLabel($activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function groupBy(Activity $activity, $key, $index)
|
||||
public function groupBy(Activity $activity, $key, $index): ?string
|
||||
{
|
||||
if (null === $activity->getProject()) {
|
||||
return null;
|
||||
@@ -56,21 +48,18 @@ class ActivityType extends AbstractType
|
||||
* @param Activity $activity
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
* @return array<string, string|int|null>
|
||||
*/
|
||||
public function getChoiceAttributes(Activity $activity, $key, $value)
|
||||
public function getChoiceAttributes(Activity $activity, $key, $value): array
|
||||
{
|
||||
if (null !== ($project = $activity->getProject())) {
|
||||
return ['data-project' => $project->getId(), 'data-currency' => $project->getCustomer()->getCurrency()];
|
||||
return ['data-project' => $project->getId(), 'data-currency' => $project->getCustomer()?->getCurrency()];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
// documentation is for NelmioApiDocBundle
|
||||
@@ -78,7 +67,7 @@ class ActivityType extends AbstractType
|
||||
'type' => 'integer',
|
||||
'description' => 'Activity ID',
|
||||
],
|
||||
'label' => 'label.activity',
|
||||
'label' => 'activity',
|
||||
'class' => Activity::class,
|
||||
'choice_label' => [$this, 'getChoiceLabel'],
|
||||
'choice_attr' => [$this, 'getChoiceAttributes'],
|
||||
@@ -90,8 +79,17 @@ class ActivityType extends AbstractType
|
||||
'activities' => null,
|
||||
// @var Activity|null
|
||||
'ignore_activity' => null,
|
||||
'allow_create' => false,
|
||||
]);
|
||||
|
||||
$resolver->setDefault('api_data', function (Options $options) {
|
||||
if (false !== $options['allow_create']) {
|
||||
return ['create' => 'post_activity'];
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
$resolver->setDefault('query_builder', function (Options $options) {
|
||||
return function (ActivityRepository $repo) use ($options) {
|
||||
$query = new ActivityFormTypeQuery($options['activities'], $options['projects']);
|
||||
@@ -109,17 +107,14 @@ class ActivityType extends AbstractType
|
||||
});
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'data-option-pattern' => $this->activityHelper->getChoicePattern(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return EntityType::class;
|
||||
}
|
||||
|
||||
@@ -18,26 +18,20 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
/**
|
||||
* Select the pattern that will be used when rendering an activity select.
|
||||
*/
|
||||
class ActivityTypePatternType extends AbstractType
|
||||
final class ActivityTypePatternType extends AbstractType
|
||||
{
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
public function __construct(private TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$name = $this->translator->trans('label.name');
|
||||
$comment = $this->translator->trans('label.description');
|
||||
$name = $this->translator->trans('name');
|
||||
$comment = $this->translator->trans('description');
|
||||
$spacer = ActivityHelper::SPACER;
|
||||
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.choice_pattern',
|
||||
'label' => 'choice_pattern',
|
||||
'choices' => [
|
||||
$name => ActivityHelper::PATTERN_NAME,
|
||||
$comment => ActivityHelper::PATTERN_COMMENT,
|
||||
@@ -46,10 +40,7 @@ class ActivityTypePatternType extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class ArrayToCommaStringType extends AbstractType
|
||||
final class ArrayToCommaStringType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addModelTransformer(new CallbackTransformer(
|
||||
function ($value) {
|
||||
@@ -32,10 +32,7 @@ class ArrayToCommaStringType extends AbstractType
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
@@ -13,22 +13,16 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class AvatarType extends AbstractType
|
||||
final class AvatarType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.avatar',
|
||||
'label' => 'avatar',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
@@ -7,33 +7,27 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Form\Toolbar;
|
||||
namespace App\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class BillableType extends AbstractType
|
||||
final class BillableSearchType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.billable',
|
||||
'label' => 'billable',
|
||||
'choices' => [
|
||||
'entryState.all' => null,
|
||||
'all' => null,
|
||||
'yes' => true,
|
||||
'no' => false,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
@@ -13,24 +13,19 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select if something is billable.
|
||||
* Form field type to select if something is billable.
|
||||
*/
|
||||
class BillableType extends AbstractType
|
||||
final class BillableType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.billable',
|
||||
'label' => 'billable',
|
||||
'help' => 'help.billable',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return YesNoType::class;
|
||||
}
|
||||
|
||||
@@ -16,31 +16,25 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Custom form field type to select the type of budget.
|
||||
*/
|
||||
class BudgetType extends AbstractType
|
||||
final class BudgetType extends AbstractType
|
||||
{
|
||||
public const TYPE_MONTH = 'month';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.budgetType',
|
||||
'label' => 'budgetType',
|
||||
// not yet translated in enough languages
|
||||
//'placeholder' => 'label.budgetType_full',
|
||||
//'placeholder' => 'budgetType_full',
|
||||
'required' => false,
|
||||
'search' => false,
|
||||
'choices' => [
|
||||
'label.budgetType_month' => self::TYPE_MONTH,
|
||||
'budgetType_month' => self::TYPE_MONTH,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
/**
|
||||
* Select the pattern that will be used for calendar entry titles.
|
||||
*/
|
||||
class CalendarTitlePatternType extends AbstractType
|
||||
final class CalendarTitlePatternType extends AbstractType
|
||||
{
|
||||
public const PATTERN_CUSTOMER = '{customer}';
|
||||
public const PATTERN_PROJECT = '{project}';
|
||||
@@ -29,22 +29,19 @@ class CalendarTitlePatternType extends AbstractType
|
||||
public const PATTERN_CUSTOMER_DESCRIPTION = self::PATTERN_CUSTOMER . self::SPACER . self::PATTERN_DESCRIPTION;
|
||||
public const PATTERN_PROJECT_CUSTOMER = self::PATTERN_PROJECT . self::SPACER . self::PATTERN_CUSTOMER;
|
||||
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
public function __construct(private TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$customer = $this->translator->trans('label.customer');
|
||||
$project = $this->translator->trans('label.project');
|
||||
$activity = $this->translator->trans('label.activity');
|
||||
$description = $this->translator->trans('label.description');
|
||||
$customer = $this->translator->trans('customer');
|
||||
$project = $this->translator->trans('project');
|
||||
$activity = $this->translator->trans('activity');
|
||||
$description = $this->translator->trans('description');
|
||||
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.choice_pattern',
|
||||
'label' => 'choice_pattern',
|
||||
'choices' => [
|
||||
$activity => CalendarTitlePatternType::PATTERN_ACTIVITY,
|
||||
$project => CalendarTitlePatternType::PATTERN_PROJECT,
|
||||
|
||||
@@ -16,25 +16,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Custom form field type to select a calendar view.
|
||||
*/
|
||||
class CalendarViewType extends AbstractType
|
||||
final class CalendarViewType extends AbstractType
|
||||
{
|
||||
public const DEFAULT_VIEW = 'month';
|
||||
|
||||
public const ALLOWED_VIEWS = [
|
||||
'month',
|
||||
'agendaWeek',
|
||||
'agendaDay',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$choices = [];
|
||||
foreach (self::ALLOWED_VIEWS as $name) {
|
||||
$choices[$name] = $name;
|
||||
}
|
||||
$choices = [
|
||||
'month' => 'month',
|
||||
'agendaWeek' => 'agendaWeek',
|
||||
'agendaDay' => 'agendaDay',
|
||||
];
|
||||
|
||||
$resolver->setDefaults([
|
||||
'required' => true,
|
||||
@@ -43,10 +35,7 @@ class CalendarViewType extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
@@ -14,72 +14,53 @@ use App\Constants;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ColorType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ColorChoiceType extends AbstractType implements DataTransformerInterface
|
||||
final class ColorChoiceType extends AbstractType implements DataTransformerInterface
|
||||
{
|
||||
public const DEFAULT_COLOR = Constants::DEFAULT_COLOR;
|
||||
|
||||
private $systemConfiguration;
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private $limitedColors;
|
||||
|
||||
public function __construct(SystemConfiguration $systemConfiguration)
|
||||
public function __construct(private SystemConfiguration $systemConfiguration)
|
||||
{
|
||||
$this->systemConfiguration = $systemConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addViewTransformer($this);
|
||||
}
|
||||
|
||||
private function isLimitedColors(): bool
|
||||
{
|
||||
if (null === $this->limitedColors) {
|
||||
$this->limitedColors = $this->systemConfiguration->isThemeColorsLimited();
|
||||
}
|
||||
|
||||
return $this->limitedColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$options = [
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
'description' => sprintf('The hexadecimal color code (default: %s)', self::DEFAULT_COLOR),
|
||||
'description' => sprintf('The hexadecimal color code (default: %s)', Constants::DEFAULT_COLOR),
|
||||
],
|
||||
'label' => 'label.color',
|
||||
'label' => 'color',
|
||||
'empty_data' => null,
|
||||
];
|
||||
|
||||
if ($this->isLimitedColors()) {
|
||||
$choices = [];
|
||||
$colors = $this->convertStringToColorArray($this->systemConfiguration->getThemeColorChoices());
|
||||
$choices = [];
|
||||
$colors = $this->convertStringToColorArray($this->systemConfiguration->getThemeColorChoices());
|
||||
|
||||
foreach ($colors as $name => $color) {
|
||||
$choices[$name] = $color;
|
||||
}
|
||||
|
||||
$options['choices'] = $choices;
|
||||
$options['search'] = false;
|
||||
$options['attr']['data-renderer'] = 'color';
|
||||
foreach ($colors as $name => $color) {
|
||||
$choices[$name] = $color;
|
||||
}
|
||||
|
||||
$options['choices'] = $choices;
|
||||
$options['search'] = false;
|
||||
|
||||
$resolver->setDefaults($options);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'data-renderer' => 'color',
|
||||
]);
|
||||
}
|
||||
|
||||
private function convertStringToColorArray(string $config): array
|
||||
{
|
||||
$config = explode(',', $config);
|
||||
@@ -111,39 +92,18 @@ class ColorChoiceType extends AbstractType implements DataTransformerInterface
|
||||
return array_unique($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($data)
|
||||
public function transform(mixed $data): mixed
|
||||
{
|
||||
if (empty($data) && !$this->isLimitedColors()) {
|
||||
return self::DEFAULT_COLOR;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reverseTransform($data)
|
||||
public function reverseTransform(mixed $value): mixed
|
||||
{
|
||||
if (null === $data && !$this->isLimitedColors()) {
|
||||
return self::DEFAULT_COLOR;
|
||||
}
|
||||
|
||||
return null === $data ? null : $data;
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
if ($this->isLimitedColors()) {
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
return ColorType::class;
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,37 +16,28 @@ use Symfony\Component\Form\Extension\Core\Type\ColorType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ColorPickerType extends AbstractType implements DataTransformerInterface
|
||||
final class ColorPickerType extends AbstractType implements DataTransformerInterface
|
||||
{
|
||||
public const DEFAULT_COLOR = Constants::DEFAULT_COLOR;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addViewTransformer($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'documentation' => [
|
||||
'type' => 'string',
|
||||
'description' => sprintf('The hexadecimal color code (default: %s)', self::DEFAULT_COLOR),
|
||||
],
|
||||
'label' => 'label.color',
|
||||
'label' => 'color',
|
||||
'empty_data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($data)
|
||||
public function transform(mixed $data): mixed
|
||||
{
|
||||
if (empty($data)) {
|
||||
return self::DEFAULT_COLOR;
|
||||
@@ -55,18 +46,12 @@ class ColorPickerType extends AbstractType implements DataTransformerInterface
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reverseTransform($data)
|
||||
public function reverseTransform(mixed $value): mixed
|
||||
{
|
||||
return null === $data ? self::DEFAULT_COLOR : $data;
|
||||
return null === $value ? self::DEFAULT_COLOR : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return ColorType::class;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Custom form field type to select a customer.
|
||||
*/
|
||||
class CustomerType extends AbstractType
|
||||
final class CustomerType extends AbstractType
|
||||
{
|
||||
private $customerHelper;
|
||||
|
||||
public function __construct(CustomerHelper $customerHelper)
|
||||
public function __construct(private CustomerHelper $customerHelper)
|
||||
{
|
||||
$this->customerHelper = $customerHelper;
|
||||
}
|
||||
|
||||
public function getChoiceLabel(Customer $customer): string
|
||||
@@ -43,10 +40,7 @@ class CustomerType extends AbstractType
|
||||
return ['data-currency' => $customer->getCurrency()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
// documentation is for NelmioApiDocBundle
|
||||
@@ -54,15 +48,15 @@ class CustomerType extends AbstractType
|
||||
'type' => 'integer',
|
||||
'description' => 'Customer ID',
|
||||
],
|
||||
'label' => 'label.customer',
|
||||
'label' => 'customer',
|
||||
'class' => Customer::class,
|
||||
'choice_label' => [$this, 'getChoiceLabel'],
|
||||
'choice_attr' => [$this, 'getChoiceAttributes'],
|
||||
'query_builder_for_user' => true,
|
||||
'project_enabled' => false,
|
||||
'project_select' => 'project',
|
||||
'start_date_param' => '%begin%',
|
||||
'end_date_param' => '%end%',
|
||||
'start_date_param' => '%begin_date%',
|
||||
'end_date_param' => '%end_date%',
|
||||
'ignore_date' => false,
|
||||
'project_visibility' => ProjectQuery::SHOW_VISIBLE,
|
||||
// @var Customer|null
|
||||
@@ -108,6 +102,7 @@ class CustomerType extends AbstractType
|
||||
}
|
||||
|
||||
return [
|
||||
'reload' => 'get_customers',
|
||||
'select' => $options['project_select'],
|
||||
'route' => 'get_projects',
|
||||
'route_params' => $routeParams,
|
||||
@@ -119,17 +114,14 @@ class CustomerType extends AbstractType
|
||||
});
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'data-option-pattern' => $this->customerHelper->getChoicePattern(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return EntityType::class;
|
||||
}
|
||||
|
||||
@@ -18,28 +18,22 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
/**
|
||||
* Select the pattern that will be used when rendering a custom select.
|
||||
*/
|
||||
class CustomerTypePatternType extends AbstractType
|
||||
final class CustomerTypePatternType extends AbstractType
|
||||
{
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
public function __construct(private TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$name = $this->translator->trans('label.name');
|
||||
$company = $this->translator->trans('label.company');
|
||||
$number = $this->translator->trans('label.number');
|
||||
$comment = $this->translator->trans('label.description');
|
||||
$name = $this->translator->trans('name');
|
||||
$company = $this->translator->trans('company');
|
||||
$number = $this->translator->trans('number');
|
||||
$comment = $this->translator->trans('description');
|
||||
$spacer = CustomerHelper::SPACER;
|
||||
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.choice_pattern',
|
||||
'label' => 'choice_pattern',
|
||||
'choices' => [
|
||||
$name => CustomerHelper::PATTERN_NAME,
|
||||
$company => CustomerHelper::PATTERN_COMPANY,
|
||||
@@ -56,10 +50,7 @@ class CustomerTypePatternType extends AbstractType
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Utils\LocaleSettings;
|
||||
use App\Configuration\LocaleService;
|
||||
use App\Utils\FormFormatConverter;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
@@ -21,45 +22,51 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
class DatePickerType extends AbstractType
|
||||
{
|
||||
private $localeSettings;
|
||||
|
||||
public function __construct(LocaleSettings $localeSettings)
|
||||
public function __construct(private LocaleService $localeService)
|
||||
{
|
||||
$this->localeSettings = $localeSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$pickerFormat = $this->localeSettings->getDatePickerFormat();
|
||||
$dateFormat = $this->localeSettings->getDateTypeFormat();
|
||||
$builder->addModelTransformer(new CallbackTransformer(
|
||||
function ($transform) {
|
||||
return $transform;
|
||||
},
|
||||
function ($reverseTransform) use ($options) {
|
||||
if ($reverseTransform === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($reverseTransform instanceof \DateTime && $options['force_time']) {
|
||||
if ($options['force_time'] === 'start') {
|
||||
$reverseTransform->setTime(0, 0, 0);
|
||||
} elseif ($options['force_time'] === 'end') {
|
||||
$reverseTransform->setTime(23, 59, 59);
|
||||
}
|
||||
}
|
||||
|
||||
return $reverseTransform;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$format = $this->localeService->getDateFormat(\Locale::getDefault());
|
||||
$converter = new FormFormatConverter();
|
||||
$formFormat = $converter->convert($format);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'format' => $dateFormat,
|
||||
'format_picker' => $pickerFormat,
|
||||
'format' => $formFormat,
|
||||
'model_timezone' => date_default_timezone_get(),
|
||||
'view_timezone' => date_default_timezone_get(),
|
||||
'force_time' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'data-datepickerenable' => 'on',
|
||||
'autocomplete' => 'off',
|
||||
'placeholder' => strtoupper($options['format']),
|
||||
'data-format' => $options['format_picker'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return DateType::class;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,16 @@
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Configuration\LocaleService;
|
||||
use App\Entity\User;
|
||||
use App\Form\Model\DateRange;
|
||||
use App\Utils\LocaleSettings;
|
||||
use App\Timesheet\DateTimeFactory;
|
||||
use App\Utils\FormFormatConverter;
|
||||
use IntlDateFormatter;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
@@ -23,105 +28,76 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
/**
|
||||
* Custom form field type to select a date range
|
||||
*/
|
||||
class DateRangeType extends AbstractType
|
||||
final class DateRangeType extends AbstractType
|
||||
{
|
||||
public const DATE_SPACER = ' - ';
|
||||
|
||||
private $localeSettings;
|
||||
|
||||
public function __construct(LocaleSettings $localeSettings)
|
||||
public function __construct(private LocaleService $localeService)
|
||||
{
|
||||
$this->localeSettings = $localeSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$pickerFormat = $this->localeSettings->getDatePickerFormat();
|
||||
$dateFormat = $this->localeSettings->getDateFormat();
|
||||
$format = $this->localeService->getDateFormat(\Locale::getDefault());
|
||||
$converter = new FormFormatConverter();
|
||||
$formFormat = $converter->convert($format);
|
||||
$pattern = $converter->convertToPattern($formFormat);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'label' => 'label.daterange',
|
||||
'format' => $dateFormat,
|
||||
'label' => 'daterange',
|
||||
'format' => $formFormat,
|
||||
'separator' => self::DATE_SPACER,
|
||||
'format_picker' => $pickerFormat,
|
||||
'allow_empty' => true,
|
||||
'attr' => [
|
||||
'pattern' => $pattern . self::DATE_SPACER . $pattern
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $options['user'];
|
||||
$factory = DateTimeFactory::createByUser($user);
|
||||
|
||||
$view->vars['ranges'] = [
|
||||
'yesterday' => [$factory->createDateTime('-1 day 00:00:00'), $factory->createDateTime('-1 day 23:59:59')],
|
||||
'thisWeek' => [$factory->getStartOfWeek(), $factory->getEndOfWeek()],
|
||||
'lastWeek' => [$factory->getStartOfWeek('-1 week'), $factory->getEndOfWeek('-1 week')],
|
||||
'thisMonth' => [$factory->getStartOfMonth(), $factory->getEndOfMonth()],
|
||||
'lastMonth' => [$factory->getStartOfMonth('-1 month'), $factory->getEndOfMonth('-1 month')],
|
||||
'thisYear' => [$factory->createStartOfYear(), $factory->createEndOfYear()],
|
||||
'thisYearUntilNow' => [$factory->createStartOfYear(), $factory->createDateTime('23:59:59')],
|
||||
'lastYear' => [$factory->createStartOfYear('-1 year'), $factory->createEndOfYear('-1 year')],
|
||||
'allTime' => [null, null],
|
||||
];
|
||||
$view->vars['rangeFormat'] = $options['format'];
|
||||
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'autocomplete' => 'off',
|
||||
'placeholder' => strtoupper($options['format_picker']) . $options['separator'] . strtoupper($options['format_picker']),
|
||||
'data-format' => $options['format_picker'],
|
||||
'data-separator' => $options['separator'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A better way would be to use the Intl NumberFormatter, but if that is not available
|
||||
* and the Symfony polyfill is used, this method would not work properly.
|
||||
*
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function convertArabicPersian($string)
|
||||
{
|
||||
return strtr(
|
||||
$string,
|
||||
[
|
||||
'۰' => '0',
|
||||
'۱' => '1',
|
||||
'۲' => '2',
|
||||
'۳' => '3',
|
||||
'۴' => '4',
|
||||
'۵' => '5',
|
||||
'۶' => '6',
|
||||
'۷' => '7',
|
||||
'۸' => '8',
|
||||
'۹' => '9',
|
||||
'٠' => '0',
|
||||
'١' => '1',
|
||||
'٢' => '2',
|
||||
'٣' => '3',
|
||||
'٤' => '4',
|
||||
'٥' => '5',
|
||||
'٦' => '6',
|
||||
'٧' => '7',
|
||||
'٨' => '8',
|
||||
'٩' => '9'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
protected function formatToPattern(string $format, string $separator)
|
||||
{
|
||||
$format = preg_quote($format, '/');
|
||||
|
||||
$pattern = str_replace('d', '[0-9]{2}', $format);
|
||||
$pattern = str_replace('m', '[0-9]{2}', $pattern);
|
||||
$pattern = str_replace('Y', '[0-9]{4}', $pattern);
|
||||
|
||||
return '/^' . $pattern . $separator . $pattern . '$/';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$formatDate = $options['format'];
|
||||
$separator = $options['separator'];
|
||||
$allowEmpty = $options['allow_empty'];
|
||||
$timezone = new \DateTimeZone($options['timezone']);
|
||||
$pattern = $this->formatToPattern($formatDate, $separator);
|
||||
$timezone = $options['timezone'];
|
||||
$pattern = (new FormFormatConverter())->convertToPattern($formatDate . $separator . $formatDate, false);
|
||||
|
||||
$builder->addModelTransformer(new CallbackTransformer(
|
||||
function ($range) use ($formatDate, $separator) {
|
||||
function ($range) use ($formatDate, $separator, $timezone) {
|
||||
$dateFormatter = new IntlDateFormatter(
|
||||
\Locale::getDefault(),
|
||||
IntlDateFormatter::MEDIUM,
|
||||
IntlDateFormatter::MEDIUM,
|
||||
$timezone,
|
||||
IntlDateFormatter::GREGORIAN,
|
||||
$formatDate
|
||||
);
|
||||
|
||||
if (null === $range) {
|
||||
return '';
|
||||
}
|
||||
@@ -134,40 +110,46 @@ class DateRangeType extends AbstractType
|
||||
return '';
|
||||
}
|
||||
|
||||
$display = $range->getBegin()->format($formatDate);
|
||||
$display = $dateFormatter->format($range->getBegin());
|
||||
if (null !== $range->getEnd()) {
|
||||
$display .= $separator . $range->getEnd()->format($formatDate);
|
||||
$display .= $separator . $dateFormatter->format($range->getEnd());
|
||||
}
|
||||
|
||||
return $display;
|
||||
},
|
||||
function ($dates) use ($formatDate, $pattern, $separator, $allowEmpty, $timezone) {
|
||||
$transformer = new DateTimeToLocalizedStringTransformer(
|
||||
$timezone,
|
||||
$timezone,
|
||||
IntlDateFormatter::MEDIUM,
|
||||
IntlDateFormatter::MEDIUM,
|
||||
IntlDateFormatter::GREGORIAN,
|
||||
$formatDate
|
||||
);
|
||||
|
||||
$range = new DateRange();
|
||||
|
||||
if (empty($dates) && $allowEmpty) {
|
||||
return $range;
|
||||
}
|
||||
|
||||
$dates = $this->convertArabicPersian($dates);
|
||||
|
||||
if (preg_match($pattern, $dates) !== 1) {
|
||||
throw new TransformationFailedException('Invalid date range given');
|
||||
}
|
||||
|
||||
$values = explode($separator, $dates);
|
||||
|
||||
if (\count($values) !== 2) {
|
||||
throw new TransformationFailedException('Invalid date range given');
|
||||
}
|
||||
|
||||
$begin = \DateTime::createFromFormat($formatDate, $values[0], $timezone);
|
||||
if ($begin === false) {
|
||||
$begin = $transformer->reverseTransform($values[0]);
|
||||
if ($begin === null) {
|
||||
throw new TransformationFailedException('Invalid begin date given');
|
||||
}
|
||||
$range->setBegin($begin);
|
||||
|
||||
$end = \DateTime::createFromFormat($formatDate, $values[1], $timezone);
|
||||
if ($end === false) {
|
||||
$end = $transformer->reverseTransform($values[1]);
|
||||
if ($end === null) {
|
||||
throw new TransformationFailedException('Invalid end date given');
|
||||
}
|
||||
$range->setEnd($end);
|
||||
@@ -181,18 +163,12 @@ class DateRangeType extends AbstractType
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'daterange';
|
||||
}
|
||||
|
||||
@@ -10,33 +10,73 @@
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\API\BaseApiController;
|
||||
use App\Entity\User;
|
||||
use App\Utils\DateFormatConverter;
|
||||
use App\Utils\LocaleSettings;
|
||||
use App\Utils\MomentFormatConverter;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to display the date-time input fields.
|
||||
*/
|
||||
class DateTimePickerType extends AbstractType
|
||||
{
|
||||
private $localeSettings;
|
||||
|
||||
public function __construct(LocaleSettings $localeSettings)
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->localeSettings = $localeSettings;
|
||||
$parts = ['year', 'month', 'day', 'hour', 'minute'];
|
||||
$dateParts = ['year', 'month', 'day'];
|
||||
$timeParts = ['hour', 'minute'];
|
||||
|
||||
// Only pass a subset of the options to children
|
||||
$dateOptions = array_intersect_key($options, array_flip([
|
||||
'years',
|
||||
'months',
|
||||
'days',
|
||||
'placeholder',
|
||||
'choice_translation_domain',
|
||||
'required',
|
||||
'translation_domain',
|
||||
'invalid_message',
|
||||
'invalid_message_parameters',
|
||||
]));
|
||||
|
||||
$timeOptions = array_intersect_key($options, array_flip([
|
||||
'choice_translation_domain',
|
||||
'required',
|
||||
'translation_domain',
|
||||
'invalid_message',
|
||||
'invalid_message_parameters',
|
||||
]));
|
||||
|
||||
if (false === $options['label']) {
|
||||
$dateOptions['label'] = false;
|
||||
$timeOptions['label'] = false;
|
||||
}
|
||||
|
||||
if (null !== $options['date_label']) {
|
||||
$dateOptions['label'] = $options['date_label'];
|
||||
}
|
||||
|
||||
if (null !== $options['time_label']) {
|
||||
$timeOptions['label'] = $options['time_label'];
|
||||
}
|
||||
|
||||
$dateOptions['input'] = $timeOptions['input'] = 'array';
|
||||
$dateOptions['error_bubbling'] = $timeOptions['error_bubbling'] = true;
|
||||
|
||||
$builder
|
||||
->addViewTransformer(new DataTransformerChain([
|
||||
new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts),
|
||||
new ArrayToPartsTransformer([
|
||||
'date' => $dateParts,
|
||||
'time' => $timeParts,
|
||||
]),
|
||||
]))
|
||||
->add('date', DatePickerType::class, $dateOptions)
|
||||
->add('time', TimePickerType::class, $timeOptions)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'documentation' => [
|
||||
@@ -44,49 +84,43 @@ class DateTimePickerType extends AbstractType
|
||||
'format' => 'date-time',
|
||||
'example' => (new \DateTime())->format(BaseApiController::DATE_FORMAT_PHP),
|
||||
],
|
||||
'label' => 'label.begin',
|
||||
'widget' => 'single_text',
|
||||
'html5' => false,
|
||||
'format' => function (Options $options) {
|
||||
/** @var User $user */
|
||||
$user = $options['user'];
|
||||
$converter = new DateFormatConverter();
|
||||
|
||||
return $this->localeSettings->getDateTypeFormat() . ' ' . $converter->convert($user->getTimeFormat()); // PHP
|
||||
'input' => 'datetime',
|
||||
'model_timezone' => null,
|
||||
'view_timezone' => null,
|
||||
// Don't modify \DateTime classes by reference, we treat
|
||||
// them like immutable value objects
|
||||
'by_reference' => false,
|
||||
'error_bubbling' => false,
|
||||
// If initialized with a \DateTime object, FormType initializes
|
||||
// this option to "\DateTime". Since the internal, normalized
|
||||
// representation is not \DateTime, but an array, we need to unset
|
||||
// this option.
|
||||
'data_class' => null,
|
||||
'compound' => true,
|
||||
'label' => 'begin',
|
||||
'date_label' => null,
|
||||
'time_label' => null,
|
||||
'empty_data' => function (Options $options) {
|
||||
return $options['compound'] ? [] : '';
|
||||
},
|
||||
'format_picker' => function (Options $options) {
|
||||
$converter = new MomentFormatConverter();
|
||||
'invalid_message' => 'Please enter a valid date and time.',
|
||||
]);
|
||||
|
||||
return $converter->convert($options['format']); // JS
|
||||
},
|
||||
'with_seconds' => false,
|
||||
'time_increment' => 1,
|
||||
// Don't add some defaults in order to preserve the defaults
|
||||
// set in DateType and TimeType
|
||||
$resolver->setDefined([
|
||||
'placeholder',
|
||||
'choice_translation_domain',
|
||||
]);
|
||||
|
||||
$resolver->setAllowedValues('input', [
|
||||
'datetime',
|
||||
'string',
|
||||
]);
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
$attr = array_merge($view->vars['attr'], [
|
||||
'data-datetimepicker' => 'on',
|
||||
'autocomplete' => 'off',
|
||||
'placeholder' => strtoupper($options['format']),
|
||||
'data-format' => $options['format_picker'],
|
||||
]);
|
||||
|
||||
if ($options['time_increment'] !== null) {
|
||||
if ($options['time_increment'] >= 1) {
|
||||
$attr['data-time-picker-increment'] = $options['time_increment'];
|
||||
}
|
||||
}
|
||||
|
||||
$view->vars['attr'] = $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return DateTimeType::class;
|
||||
return 'date_time';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,9 @@ namespace App\Form\Type;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
|
||||
class DateTimeTextType extends AbstractType
|
||||
final class DateTimeTextType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
@@ -14,19 +14,16 @@ use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
|
||||
class DayTimeType extends AbstractType
|
||||
final class DayTimeType extends AbstractType
|
||||
{
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
{
|
||||
$view->vars['attr'] = array_merge($view->vars['attr'], [
|
||||
'placeholder' => 'hh:mm'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
@@ -13,22 +13,16 @@ use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class DescriptionType extends AbstractType
|
||||
final class DescriptionType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'label' => 'label.description',
|
||||
'label' => 'description',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
public function getParent(): string
|
||||
{
|
||||
return TextareaType::class;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user