From 37f1a3a16e4eef55c1b5d85ee69afdcf0c2d789e Mon Sep 17 00:00:00 2001 From: Lukas Eichenauer Date: Fri, 15 May 2026 08:41:27 +0200 Subject: [PATCH] UI: Fix InvalidArgumentException when user submits a 5-digit year See: https://mantis.ilias.de/view.php?id=46714 --- .../Component/Input/Field/DateTime.php | 28 ++++++++++++++++ .../Component/Input/Field/Factory.php | 2 +- .../Component/Input/Field/Renderer.php | 18 ++++++---- .../Input/Field/DateTimeInputTest.php | 33 +++++++++++++++++++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php index 55004ce53f81..76e4b87906c5 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/DateTime.php @@ -20,10 +20,12 @@ namespace ILIAS\UI\Implementation\Component\Input\Field; +use ILIAS\Language\Language; use ILIAS\UI\Component as C; use ILIAS\Data\Factory as DataFactory; use ILIAS\Data\DateFormat\DateFormat; use ILIAS\UI\Implementation\Component\ComponentHelper; +use ILIAS\UI\Implementation\Component\Input\InputData; use ILIAS\UI\Implementation\Component\JavaScriptBindable; use DateTimeImmutable; use ILIAS\Refinery\Custom\Transformation; @@ -40,6 +42,8 @@ class DateTime extends FormInput implements C\Input\Field\DateTime public const TIME_FORMAT = 'HH:mm'; + protected Language $lng; + protected DateFormat $format; protected ?DateTimeImmutable $min_date = null; protected ?DateTimeImmutable $max_date = null; @@ -55,10 +59,12 @@ class DateTime extends FormInput implements C\Input\Field\DateTime public function __construct( DataFactory $data_factory, \ILIAS\Refinery\Factory $refinery, + Language $lng, string $label, ?string $byline ) { parent::__construct($data_factory, $refinery, $label, $byline); + $this->lng = $lng; $this->format = $data_factory->dateFormat()->standard(); @@ -97,6 +103,28 @@ public function withValue($value): self return parent::withValue($value); } + /** + * @inheritdoc + * + * User input may be unparseable as a datetime (e.g. a 5-digit year submitted + * by the browser). Surface that as a soft validation error rather than letting + * the strict isClientSideValueOk check raise InvalidArgumentException out of + * withValue. See Mantis #46714. + */ + public function withInput(InputData $input): self + { + if (!$this->isDisabled() && $this->getName() !== null) { + $value = $input->getOr($this->getName(), null); + if (is_string($value) && $value !== '' && !$this->isClientSideValueOk($value)) { + $clone = clone $this; + $clone->value = $value; + $clone->content = $this->data_factory->error($this->lng->txt('exc_date_not_valid')); + return $clone->withError($this->lng->txt('exc_date_not_valid')); + } + } + return parent::withInput($input); + } + public function withFormat(DateFormat $format): self { $clone = clone $this; diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php index ecf1ccce7157..8d4183a62447 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php @@ -164,7 +164,7 @@ public function multiSelect(string $label, array $options, ?string $byline = nul */ public function dateTime(string $label, ?string $byline = null): I\DateTime { - return new DateTime($this->data_factory, $this->refinery, $label, $byline); + return new DateTime($this->data_factory, $this->refinery, $this->lng, $label, $byline); } /** diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php index e536ee615414..7f89121d399c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php @@ -714,12 +714,18 @@ protected function internalRenderDateTimeField(F\DateTime $component, RendererIn $this->applyValue($component, $tpl, function (?string $value) use ($dt_type) { if ($value !== null) { - $value = new \DateTimeImmutable($value); - return $value->format(match ($dt_type) { - self::TYPE_DATETIME => self::HTML5_NATIVE_DATETIME_FORMAT, - self::TYPE_DATE => self::HTML5_NATIVE_DATE_FORMAT, - self::TYPE_TIME => self::HTML5_NATIVE_TIME_FORMAT, - }); + try { + $datetime = new \DateTimeImmutable($value); + return $datetime->format(match ($dt_type) { + self::TYPE_DATETIME => self::HTML5_NATIVE_DATETIME_FORMAT, + self::TYPE_DATE => self::HTML5_NATIVE_DATE_FORMAT, + self::TYPE_TIME => self::HTML5_NATIVE_TIME_FORMAT, + }); + } catch (\Exception) { + // Display unparseable user input verbatim so the + // submitter can correct it (Mantis #46714). + return $value; + } } return null; }); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php index f8a8d6999780..0914a3452970 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php @@ -175,6 +175,39 @@ public function testWithInvalidValue(): void ->withValue("this is no datetime..."); } + /** + * Mantis #46714: a browser may submit a 5-digit year (e.g. "12345-01-01 10:00"), + * which PHP's DateTimeImmutable cannot parse. withInput must produce a soft + * validation error rather than letting InvalidArgumentException escape. + */ + public function testWithInputFiveDigitYearProducesErrorNotException(): void + { + $datetime = $this->factory->datetime('label', 'byline') + ->withUseTime(true) + ->withNameFrom($this->name_source); + $post_data = new DefInputData(['name_0' => '12345-01-01 10:00']); + + $clone = $datetime->withInput($post_data); + + $this->assertNotNull($clone->getError()); + $content = $clone->getContent(); + $this->assertTrue($content->isError()); + $this->assertSame('12345-01-01 10:00', $clone->getValue()); + } + + public function testWithInputUnparseableValueRendersRawValue(): void + { + $datetime = $this->factory->datetime('label', 'byline') + ->withUseTime(true) + ->withNameFrom($this->name_source); + $post_data = new DefInputData(['name_0' => '12345-01-01 10:00']); + + $clone = $datetime->withInput($post_data); + + $html = $this->getDefaultRenderer()->render($clone); + $this->assertStringContainsString('12345-01-01 10:00', $html); + } + public function testRender(): void { $datetime = $this->factory->dateTime('label', 'byline');