Release 2.36.0 (#5514)
This commit is contained in:
@@ -63,24 +63,6 @@ final class ValidationFailedExceptionErrorHandler implements SubscribingHandlerI
|
||||
}
|
||||
|
||||
public function serializeValidationExceptionToJson(JsonSerializationVisitor $visitor, ValidationFailedException $exception, array $type, Context $context)
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
/** @var ConstraintViolationInterface $error */
|
||||
foreach (iterator_to_array($exception->getViolations()) as $error) {
|
||||
$errors[$error->getPropertyPath()]['errors'][] = $this->getErrorMessage($error);
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => '400',
|
||||
'message' => $this->translator->trans($exception->getMessage(), [], 'validators'),
|
||||
'errors' => [
|
||||
'children' => $errors
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getErrorMessage(ConstraintViolationInterface $error): string
|
||||
{
|
||||
$locale = \Locale::getDefault();
|
||||
/** @var User $user */
|
||||
@@ -90,6 +72,24 @@ final class ValidationFailedExceptionErrorHandler implements SubscribingHandlerI
|
||||
$locale = $user->getLanguage();
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
|
||||
/** @var ConstraintViolationInterface $error */
|
||||
foreach (iterator_to_array($exception->getViolations()) as $error) {
|
||||
$errors[$error->getPropertyPath()]['errors'][] = $this->getErrorMessage($error, $locale);
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => '400',
|
||||
'message' => $this->translator->trans($exception->getMessage(), [], 'validators', $locale),
|
||||
'errors' => [
|
||||
'children' => $errors
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function getErrorMessage(ConstraintViolationInterface $error, string $locale): string
|
||||
{
|
||||
if (null !== $error->getPlural()) {
|
||||
return $this->translator->trans($error->getMessageTemplate(), ['%count%' => $error->getPlural()] + $error->getParameters(), 'validators', $locale);
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ final class TimesheetController extends BaseApiController
|
||||
|
||||
if ($form->isValid()) {
|
||||
try {
|
||||
$this->service->saveNewTimesheet($timesheet);
|
||||
$this->service->saveTimesheet($timesheet);
|
||||
|
||||
$view = new View($timesheet, 200);
|
||||
|
||||
@@ -372,7 +372,7 @@ final class TimesheetController extends BaseApiController
|
||||
return $this->viewHandler->handle($view);
|
||||
}
|
||||
|
||||
$this->service->updateTimesheet($timesheet);
|
||||
$this->service->saveTimesheet($timesheet);
|
||||
|
||||
$view = new View($timesheet, Response::HTTP_OK);
|
||||
$view->getContext()->setGroups(self::GROUPS_ENTITY);
|
||||
@@ -547,7 +547,7 @@ final class TimesheetController extends BaseApiController
|
||||
$copyTimesheet = clone $timesheet;
|
||||
|
||||
$this->dispatcher->dispatch(new TimesheetDuplicatePreEvent($copyTimesheet, $timesheet));
|
||||
$this->service->saveNewTimesheet($copyTimesheet);
|
||||
$this->service->saveTimesheet($copyTimesheet);
|
||||
$this->dispatcher->dispatch(new TimesheetDuplicatePostEvent($copyTimesheet, $timesheet));
|
||||
|
||||
$view = new View($copyTimesheet, 200);
|
||||
@@ -571,7 +571,7 @@ final class TimesheetController extends BaseApiController
|
||||
|
||||
$timesheet->setExported(!$timesheet->isExported());
|
||||
|
||||
$this->service->updateTimesheet($timesheet);
|
||||
$this->service->saveTimesheet($timesheet);
|
||||
|
||||
$view = new View($timesheet, 200);
|
||||
$view->getContext()->setGroups(self::GROUPS_ENTITY);
|
||||
@@ -601,7 +601,7 @@ final class TimesheetController extends BaseApiController
|
||||
|
||||
$meta->setValue($paramFetcher->get('value'));
|
||||
|
||||
$this->service->updateTimesheet($timesheet);
|
||||
$this->service->saveTimesheet($timesheet);
|
||||
|
||||
$view = new View($timesheet, 200);
|
||||
$view->getContext()->setGroups(self::GROUPS_ENTITY);
|
||||
|
||||
@@ -97,7 +97,7 @@ class ActivityService
|
||||
$errors = $this->validator->validate($activity, null, $groups);
|
||||
|
||||
if ($errors->count() > 0) {
|
||||
throw new ValidationFailedException($errors, 'Validation Failed');
|
||||
throw new ValidationFailedException($errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,11 @@ final class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.35.1';
|
||||
public const VERSION = '2.36.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 23501;
|
||||
public const VERSION_ID = 23600;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -152,7 +152,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
try {
|
||||
$this->service->updateTimesheet($entry);
|
||||
$this->service->saveTimesheet($entry);
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
return $this->redirectToRoute($this->getTimesheetRoute());
|
||||
@@ -184,7 +184,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
|
||||
if ($createForm->isSubmitted() && $createForm->isValid()) {
|
||||
try {
|
||||
$this->service->saveNewTimesheet($entry);
|
||||
$this->service->saveTimesheet($entry);
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
return $this->redirectToRoute($this->getTimesheetRoute());
|
||||
@@ -216,7 +216,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$this->dispatcher->dispatch(new TimesheetDuplicatePreEvent($copyTimesheet, $timesheet));
|
||||
$this->service->saveNewTimesheet($copyTimesheet);
|
||||
$this->service->saveTimesheet($copyTimesheet);
|
||||
$this->dispatcher->dispatch(new TimesheetDuplicatePostEvent($copyTimesheet, $timesheet));
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ final class TimesheetTeamController extends TimesheetAbstractController
|
||||
}
|
||||
|
||||
foreach ($newTimesheets as $newTimesheet) {
|
||||
$this->service->saveNewTimesheet($newTimesheet);
|
||||
$this->service->saveTimesheet($newTimesheet);
|
||||
}
|
||||
|
||||
$this->flashSuccess('action.update.success');
|
||||
|
||||
@@ -101,7 +101,7 @@ final class CustomerService
|
||||
$errors = $this->validator->validate($customer, null, $groups);
|
||||
|
||||
if ($errors->count() > 0) {
|
||||
throw new ValidationFailedException($errors, 'Validation Failed');
|
||||
throw new ValidationFailedException($errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,7 @@ class TimesheetEditForm extends AbstractType
|
||||
$timezone = $options['timezone'];
|
||||
$isNew = true;
|
||||
|
||||
if (isset($options['data'])) {
|
||||
/** @var Timesheet $entry */
|
||||
if (isset($options['data']) && $options['data'] instanceof Timesheet) {
|
||||
$entry = $options['data'];
|
||||
|
||||
$activity = $entry->getActivity();
|
||||
@@ -338,7 +337,7 @@ class TimesheetEditForm extends AbstractType
|
||||
function (FormEvent $event) {
|
||||
/** @var Timesheet|null $timesheet */
|
||||
$timesheet = $event->getData();
|
||||
if (null === $timesheet || $timesheet->isRunning()) {
|
||||
if (null === $timesheet || ($timesheet instanceof Timesheet && $timesheet->isRunning())) {
|
||||
$event->getForm()->get('duration')->setData(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,9 +218,7 @@ final class InvoiceModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user who is currently creating the invoice.
|
||||
*
|
||||
* @return User|null
|
||||
* Returns the user currently creating the invoice.
|
||||
*/
|
||||
public function getUser(): ?User
|
||||
{
|
||||
|
||||
@@ -105,7 +105,7 @@ final class ProjectService
|
||||
$errors = $this->validator->validate($project, null, $groups);
|
||||
|
||||
if ($errors->count() > 0) {
|
||||
throw new ValidationFailedException($errors, 'Validation Failed');
|
||||
throw new ValidationFailedException($errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Saml;
|
||||
use App\Configuration\SamlConfigurationInterface;
|
||||
use App\Saml\Security\SamlAuthenticationFailureHandler;
|
||||
use App\Saml\Security\SamlAuthenticationSuccessHandler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
@@ -40,7 +41,8 @@ class SamlAuthenticator extends AbstractAuthenticator
|
||||
private readonly SamlAuthenticationFailureHandler $failureHandler,
|
||||
private readonly SamlAuthFactory $samlAuthFactory,
|
||||
private readonly SamlProvider $samlProvider,
|
||||
private readonly SamlConfigurationInterface $configuration
|
||||
private readonly SamlConfigurationInterface $configuration,
|
||||
private readonly LoggerInterface $logger
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -86,6 +88,7 @@ class SamlAuthenticator extends AbstractAuthenticator
|
||||
// file_put_contents(__DIR__ . '/../../var/log/saml.xml', $oneLoginAuth->getLastResponseXML());
|
||||
|
||||
if (\count($oneLoginAuth->getErrors()) > 0) {
|
||||
$this->logger->critical('SAML login failed: ' . $oneLoginAuth->getLastErrorReason());
|
||||
throw new AuthenticationException($oneLoginAuth->getLastErrorReason());
|
||||
}
|
||||
|
||||
@@ -102,7 +105,9 @@ class SamlAuthenticator extends AbstractAuthenticator
|
||||
|
||||
if (isset($this->options['username_attribute'])) {
|
||||
if (!\array_key_exists($this->options['username_attribute'], $attributes)) {
|
||||
throw new \Exception(\sprintf("Attribute '%s' not found in SAML data", $this->options['username_attribute']));
|
||||
$errorMessage = \sprintf("Attribute '%s' not found in SAML data", $this->options['username_attribute']);
|
||||
$this->logger->critical($errorMessage);
|
||||
throw new \Exception($errorMessage);
|
||||
}
|
||||
|
||||
$username = $attributes[$this->options['username_attribute']][0];
|
||||
|
||||
@@ -45,12 +45,12 @@ final class TimesheetService
|
||||
private array $doNotValidateCodes = [];
|
||||
|
||||
public function __construct(
|
||||
private SystemConfiguration $configuration,
|
||||
private TimesheetRepository $repository,
|
||||
private TrackingModeService $trackingModeService,
|
||||
private EventDispatcherInterface $dispatcher,
|
||||
private AuthorizationCheckerInterface $auth,
|
||||
private ValidatorInterface $validator
|
||||
private readonly SystemConfiguration $configuration,
|
||||
private readonly TimesheetRepository $repository,
|
||||
private readonly TrackingModeService $trackingModeService,
|
||||
private readonly EventDispatcherInterface $dispatcher,
|
||||
private readonly AuthorizationCheckerInterface $auth,
|
||||
private readonly ValidatorInterface $validator
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ final class TimesheetService
|
||||
public function restartTimesheet(Timesheet $timesheet, Timesheet $copyFrom): Timesheet
|
||||
{
|
||||
$this->dispatcher->dispatch(new TimesheetRestartPreEvent($timesheet, $copyFrom));
|
||||
$this->saveNewTimesheet($timesheet);
|
||||
$this->saveNewTimesheet($timesheet); // @phpstan-ignore method.deprecated
|
||||
$this->dispatcher->dispatch(new TimesheetRestartPostEvent($timesheet, $copyFrom));
|
||||
|
||||
return $timesheet;
|
||||
@@ -111,6 +111,7 @@ final class TimesheetService
|
||||
* @throws ValidationFailedException for invalid timesheets or running timesheets that should be stopped
|
||||
* @throws InvalidArgumentException for already persisted timesheets
|
||||
* @throws AccessDeniedException if user is not allowed to start timesheet
|
||||
* @deprecated since 2.36.0 - use saveTimesheet() instead
|
||||
*/
|
||||
public function saveNewTimesheet(Timesheet $timesheet): Timesheet
|
||||
{
|
||||
@@ -150,12 +151,19 @@ final class TimesheetService
|
||||
return $timesheet;
|
||||
}
|
||||
|
||||
public function saveTimesheet(Timesheet $timesheet): Timesheet
|
||||
{
|
||||
if ($timesheet->getId() === null) {
|
||||
return $this->saveNewTimesheet($timesheet); // @phpstan-ignore method.deprecated
|
||||
} else {
|
||||
return $this->updateTimesheet($timesheet); // @phpstan-ignore method.deprecated
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does NOT validate the given timesheet!
|
||||
* Does NOT validate the given timesheet.
|
||||
*
|
||||
* @param Timesheet $timesheet
|
||||
* @return Timesheet
|
||||
* @throws \Exception
|
||||
* @deprecated since 2.36.0 - use saveTimesheet() instead
|
||||
*/
|
||||
public function updateTimesheet(Timesheet $timesheet): Timesheet
|
||||
{
|
||||
@@ -245,7 +253,7 @@ final class TimesheetService
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ValidationFailedException($errors, 'Validation Failed');
|
||||
throw new ValidationFailedException($errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class UserService
|
||||
$errors = $this->validator->validate($user, null, $groups);
|
||||
|
||||
if ($errors->count() > 0) {
|
||||
throw new ValidationFailedException($errors, 'Validation Failed');
|
||||
throw new ValidationFailedException($errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
src/Validator/Constraints/TimesheetNegativeDuration.php
Normal file
26
src/Validator/Constraints/TimesheetNegativeDuration.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
final class TimesheetNegativeDuration extends TimesheetConstraint
|
||||
{
|
||||
public const NEGATIVE_DURATION_ERROR = 'kimai-timesheet-negative-duration-01';
|
||||
|
||||
protected const ERROR_NAMES = [
|
||||
self::NEGATIVE_DURATION_ERROR => 'Duration cannot be negative.',
|
||||
];
|
||||
|
||||
public string $message = 'Duration cannot be negative.';
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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\Validator\Constraints;
|
||||
|
||||
use App\Entity\Timesheet as TimesheetEntity;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
|
||||
final class TimesheetNegativeDurationValidator extends ConstraintValidator
|
||||
{
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (!($constraint instanceof TimesheetNegativeDuration)) {
|
||||
throw new UnexpectedTypeException($constraint, TimesheetNegativeDuration::class);
|
||||
}
|
||||
|
||||
if (!\is_object($value) || !($value instanceof TimesheetEntity)) {
|
||||
throw new UnexpectedTypeException($value, TimesheetEntity::class);
|
||||
}
|
||||
|
||||
if ($value->isRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$duration = $value->getCalculatedDuration();
|
||||
|
||||
if ($duration !== null && $duration < 0) {
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->atPath('duration')
|
||||
->setTranslationDomain('validators')
|
||||
->setCode(TimesheetNegativeDuration::NEGATIVE_DURATION_ERROR)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ final class TimesheetZeroDurationValidator extends ConstraintValidator
|
||||
$duration = $value->getCalculatedDuration();
|
||||
}
|
||||
|
||||
if ($duration <= 0) {
|
||||
if ($duration === 0) {
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->atPath('duration')
|
||||
->setTranslationDomain('validators')
|
||||
|
||||
@@ -14,7 +14,7 @@ final class ValidationException extends \RuntimeException
|
||||
public function __construct(string $message = null)
|
||||
{
|
||||
if ($message === null) {
|
||||
$message = 'Validation failed';
|
||||
$message = 'Validation Failed';
|
||||
}
|
||||
parent::__construct($message, 400);
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||
|
||||
final class ValidationFailedException extends \RuntimeException
|
||||
{
|
||||
public function __construct(private ConstraintViolationListInterface $violations, ?string $message = null)
|
||||
public function __construct(private readonly ConstraintViolationListInterface $violations, ?string $message = null)
|
||||
{
|
||||
if ($message === null) {
|
||||
$message = 'Validation failed';
|
||||
$message = 'Validation Failed';
|
||||
}
|
||||
parent::__construct($message, 400);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user