Release 2.57 (#5929)

This commit is contained in:
Kevin Papst
2026-05-21 22:14:10 +02:00
committed by GitHub
parent f0ce1c7bd6
commit 976d38e8a4
101 changed files with 4226 additions and 1119 deletions

View File

@@ -0,0 +1,44 @@
<?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\API;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class CommentApiForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('pinned', CheckboxType::class, [
'required' => false,
'documentation' => [
'default' => false,
'description' => 'Pinned comments always appear first'
],
]);
$builder->add('message', TextareaType::class, [
'label' => false,
'documentation' => [
'description' => 'The actual comment (markdown is supported)'
],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'csrf_protection' => false,
]);
}
}

View File

@@ -13,6 +13,9 @@ use App\Utils\SearchTerm;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
/**
* @implements DataTransformerInterface<SearchTerm, string>
*/
final class SearchTermTransformer implements DataTransformerInterface
{
/**

View File

@@ -62,31 +62,21 @@ trait FormTrait
/** @var array<string, mixed> $data */
$data = $event->getData();
$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 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 ($project !== null) {
if (!$project->getCustomer()->isVisible()) {
$customer = null;
$project = null;
} elseif (!$project->isVisible()) {
$project = null;
}
if ($isNew && $project instanceof 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');
}

View File

@@ -17,11 +17,14 @@ use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @template T of object
*/
final class MultiUpdateTable extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var EntityRepository $repository */
/** @var EntityRepository<T> $repository */
$repository = $options['repository'];
/** @var MultiUpdateTableDTO $dto */
$dto = $options['data'];

View File

@@ -19,6 +19,9 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @implements DataTransformerInterface<string, string>
*/
final class ColorChoiceType extends AbstractType implements DataTransformerInterface
{
public function __construct(private readonly SystemConfiguration $systemConfiguration)

View File

@@ -16,6 +16,9 @@ use Symfony\Component\Form\Extension\Core\Type\ColorType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @implements DataTransformerInterface<string, string>
*/
final class ColorPickerType extends AbstractType implements DataTransformerInterface
{
public const DEFAULT_COLOR = Constants::DEFAULT_COLOR;
@@ -37,15 +40,21 @@ final class ColorPickerType extends AbstractType implements DataTransformerInter
]);
}
/**
* @return string
*/
public function transform(mixed $data): mixed
{
if (empty($data)) {
if (!\is_string($data) || $data === '') {
return self::DEFAULT_COLOR;
}
return $data;
}
/**
* @return string
*/
public function reverseTransform(mixed $value): mixed
{
return null === $value ? self::DEFAULT_COLOR : $value;

View File

@@ -90,7 +90,11 @@ final class QuickEntryWeekType extends AbstractType
}
}
$event->getForm()->add('activity', ActivityType::class, $activityOptions);
// exported entries cause the dropdown to be deactivated
// we need to make sure to fetch the info before the field is replaced
// see https://github.com/kimai/kimai/issues/5642
$disabled = $event->getForm()->get('activity')->isDisabled();
$event->getForm()->add('activity', ActivityType::class, array_merge(['disabled' => $disabled], $activityOptions));
};
$builder->addEventListener(FormEvents::PRE_SUBMIT, $activityPreSubmitFunction);

View File

@@ -135,7 +135,7 @@ final class UserType extends AbstractType
return $a->getDisplayName() <=> $b->getDisplayName();
});
return array_values($userById);
return $userById;
});
}

View File

@@ -97,11 +97,6 @@ class UserEditType extends AbstractType
]);
}
$builder->add('systemAccount', YesNoType::class, [
'label' => 'system_account',
'help' => 'system_account.help',
]);
if ($options['include_supervisor']) {
$builder->add('supervisor', UserType::class, [
'required' => false,
@@ -111,6 +106,11 @@ class UserEditType extends AbstractType
}
if ($options['include_password_reset']) {
$builder->add('systemAccount', YesNoType::class, [
'label' => 'system_account',
'help' => 'system_account.help',
]);
$builder->add('requiresPasswordReset', YesNoType::class, [
'label' => 'force_password_change',
'help' => 'force_password_change_help',