Repository: pimcore/pimcore (language: php)

Issue:
[Improvement] Upgrade dependencies where possible without BC break
### Improvement description

Check all dependencies for new major versions and add support/upgrade to it when easily possible without major efforts or BC breaks.  
Preferred solution would be to support old and new major version now (due to LTS support) and remove support for old major version then in pimcore/pimcore 12.



Discussion / hints:
Like Symfony 7? :star_struck: 
egulias/email-validator 3.2.6 ~ 4.0.2 A library for validating email...
endroid/qr-code 4.8.5 ~ 5.1.0 Endroid QR Code
nesbot/carbon 2.72.5 ~ 3.8.0 An API extension for DateTime ...
presta/sitemap-bundle 3.3.1 ~ 4.1.1 A Symfony bundle that provides...
scheb/2fa-bundle 6.12.0 ~ 7.5.0 A generic interface to impleme...
scheb/2fa-google-authenticator 6.12.0 ~ 7.5.0 Extends scheb/2fa-bundle with ...
> Like Symfony 7? 🤩

without BC break :smile: 
Are there that many deprecations in Symfony 6 that require BC breaks to solve? :cry: 
> Are there that many deprecations in Symfony 6 that require BC breaks to solve? 😢

I'm not sure if the difference in the minimum PHP version (8.1 for Symfony 6.4 and 8.2 for Symfony 7.x) might cause any issues here. It's also possible that Pimcore itself should require PHP ^8.2 in the 12.x versions, especially since PHP 8.1 is now only receiving security updates, and its support will expire in just over a year.
@fashxp `scheb/2fa` 7.x versions requires `"php": "~8.2.0 || ~8.3.0"`. Should pimcore also drop support for php8.1 here?
It shouldn't be a problem if you support both library versions, the current one and additionally the next mayor version. Composer will then select the library versions matching with your PHP version.
@fashxp Hello, will anyone be reviewing this pull request? Should I tag someone, or is it pending a release cycle and will be reviewed by a member of the Pimcore team in some time?
@michalananapps thx very much for your efforts. yes, we will review the PR ... this whole topic is planned for die upcoming weeks
> Are there that many deprecations in Symfony 6 that require BC breaks to solve? 😢

@jdreesen Actually I don't know. Believe me, we also would like to upgrade to Symfony 7 as soon as possible. 
The thing is though: right now, we have our priority on studio and not so much resources left for additional work. Also we need to keep an eye on our customers and their migration efforts.
 
Since Symfony 6 gets security updates until end of 2027, we plan Symfony migration in course of 2025 and be ready for 2026.1. 
Maybe we even manage to have one version (2025.4) to support symfony ^6.4 || ^7 - but that we will see when we are there. 

In the meantime, if anybody can support us in getting rid of deprecations (without BC breaks) and so preparing for the symfony upgrade, we are happy to review and merge PRs 😄. 

@michalananapps thx again for your efforts and support in that regards.



Fix patch:
diff --git a/composer.json b/composer.json
index f4fd4bfbd14..f11344c4b58 100644
--- a/composer.json
+++ b/composer.json
@@ -47,15 +47,14 @@
     "composer/ca-bundle": "^1.2",
     "defuse/php-encryption": "^2.2.0",
     "doctrine/annotations": "^1.13.1 || ^2.0",
-    "doctrine/collections": "^1.6.8",
     "doctrine/dbal": "^3.8",
     "doctrine/doctrine-bundle": "^2.6.3",
     "doctrine/doctrine-migrations-bundle": "^3.2.2",
     "doctrine/inflector": "^2.0.2",
     "doctrine/orm": "^2.15",
     "doctrine/persistence": "^3.0.3",
-    "egulias/email-validator": "^3.0.0",
-    "endroid/qr-code": "^4",
+    "egulias/email-validator": "^3.0 || ^4.0",
+    "endroid/qr-code": "^4 || ^5.1",
     "friendsofsymfony/jsrouting-bundle": "^3.2.1",
     "guzzlehttp/guzzle": "^7.8",
     "http-interop/http-factory-guzzle": "^1.0.0",
@@ -69,13 +68,13 @@
     "mpratt/embera": "^2.0.14",
     "myclabs/deep-copy": "^1.11",
     "neitanod/forceutf8": "^2.0.4",
-    "nesbot/carbon": "^2.65",
+    "nesbot/carbon": "^2.72",
     "onnov/detect-encoding": "^2.0",
     "pear/net_url2": "^2.2",
-    "presta/sitemap-bundle": "^3.3",
+    "presta/sitemap-bundle": "^3.3 || ^4.1",
     "sabre/dav": "^4.1.2",
-    "scheb/2fa-bundle": "^6.0",
-    "scheb/2fa-google-authenticator": "^6.0",
+    "scheb/2fa-bundle": "^6.0 || ^7.5",
+    "scheb/2fa-google-authenticator": "^6.0 || ^7.5",
     "spatie/image-optimizer": "^1.6.2",
     "symfony-cmf/routing-bundle": "^3.0",
     "symfony/cache": "^6.4",
@@ -137,18 +136,18 @@
     "twig/twig": ">=3.9.0 <3.14.0"
   },
   "require-dev": {
+    "chrome-php/chrome": "^1.4.0",
     "codeception/codeception": "^5.0.3",
     "codeception/module-symfony": "^3.1.0",
+    "composer/composer": "*",
     "ergebnis/phpstan-rules": "^2.0",
+    "gotenberg/gotenberg-php": "^1.1 || ^2.4",
     "phpstan/phpstan": "1.12.7",
     "phpstan/phpstan-symfony": "^1.3.5",
     "phpunit/phpunit": "^9.3",
-    "gotenberg/gotenberg-php": "^1.1 || ^2.4",
-    "composer/composer": "*",
-    "chrome-php/chrome": "^1.4.0",
-    "webmozarts/console-parallelization": "^2.1",
     "symfony/dotenv": "^6.4",
-    "symfony/runtime": "^6.4"
+    "symfony/runtime": "^6.4",
+    "webmozarts/console-parallelization": "^2.1"
   },
   "suggest": {
     "ext-sockets": "*",


Test patch:
diff --git a/tests/Model/DataType/ClassificationStore/AbstractClassificationStoreTest.php b/tests/Model/DataType/ClassificationStore/AbstractClassificationStoreTest.php
index 6a7a3fc3ee0..49b45a33b69 100644
--- a/tests/Model/DataType/ClassificationStore/AbstractClassificationStoreTest.php
+++ b/tests/Model/DataType/ClassificationStore/AbstractClassificationStoreTest.php
@@ -128,6 +128,10 @@ protected function configureStore(Classificationstore\StoreConfig $store): void
                     case 'date':
                         $definition = new ClassDefinition\Data\Date();
 
+                        break;
+                    case 'dateRange':
+                        $definition = new ClassDefinition\Data\DateRange();
+
                         break;
                     case 'datetime':
                         $definition = new ClassDefinition\Data\Datetime();
diff --git a/tests/Support/Helper/DataType/TestDataHelper.php b/tests/Support/Helper/DataType/TestDataHelper.php
index 8e1f58e7eac..c6b956374c3 100644
--- a/tests/Support/Helper/DataType/TestDataHelper.php
+++ b/tests/Support/Helper/DataType/TestDataHelper.php
@@ -150,6 +150,18 @@ public function assertDate(Concrete $object, string $field, int $seed = 1): void
         );
     }
 
+    public function assertDatePeriod(Concrete $object, string $field, int $seed = 1): void
+    {
+        $getter = 'get' . ucfirst($field);
+
+        /** @var \Carbon\CarbonPeriod $value */
+        $value = $object->$getter();
+
+        $expected = new \Carbon\CarbonPeriod('2018-04-21', '3 days', '2018-04-27');
+
+        $this->assertIsEqual($object, $field, $expected, $value);
+    }
+
     public function assertEmail(Concrete $object, string $field, int $seed = 1): void
     {
         $getter = 'get' . ucfirst($field);
@@ -1012,6 +1024,15 @@ public function fillDate(Concrete $object, string $field, int $seed = 1): void
         $object->$setter($date);
     }
 
+    public function fillDateRange(Concrete $object, string $field, int $seed = 1): void
+    {
+        $setter = 'set' . ucfirst($field);
+
+        $period = new \Carbon\CarbonPeriod('2018-04-21', '3 days', '2018-04-27');
+
+        $object->$setter($period);
+    }
+
     public function fillEmail(Concrete $object, string $field, int $seed = 1): void
     {
         $setter = 'set' . ucfirst($field);
diff --git a/tests/Support/Helper/Model.php b/tests/Support/Helper/Model.php
index d4dbc4c092c..7f93791b9e4 100644
--- a/tests/Support/Helper/Model.php
+++ b/tests/Support/Helper/Model.php
@@ -425,6 +425,7 @@ public function setupPimcoreClass_Unittest(string $name = 'unittest', string $fi
 
             $panel->addChild($this->createDataChild('date'));
             $panel->addChild($this->createDataChild('datetime'));
+            $panel->addChild($this->createDataChild('dateRange'));
 
             $panel->addChild($this->createDataChild('email'));
 
diff --git a/tests/Support/Test/DataType/AbstractDataTypeTestCase.php b/tests/Support/Test/DataType/AbstractDataTypeTestCase.php
index 13b4acd3327..bb5b11188f8 100644
--- a/tests/Support/Test/DataType/AbstractDataTypeTestCase.php
+++ b/tests/Support/Test/DataType/AbstractDataTypeTestCase.php
@@ -99,6 +99,34 @@ protected function needsDb(): bool
         return true;
     }
 
+    abstract protected function createTestObject(array $fields = [], ?array &$params = []): Unittest;
+
+    abstract public function refreshObject(): void;
+
+    public function testMarshallDateRange(): void
+    {
+        $this->createTestObject([
+            [
+                'method' => 'fillDateRange',
+                'field' => 'dateRange',
+            ],
+        ]);
+
+        $before = $this->testObject->getDateRange();
+        $this->assertNotNull($before);
+
+        $version = $this->testObject->getLatestVersion(includingPublished: true);
+        $data = $version->loadData(true);
+
+        $after = $data->getDateRange();
+        $this->assertNotSame($before, $after);
+        $this->assertEquals($before->getEndDate(), $after->getEndDate());
+        $this->assertEquals($before->getRecurrences(), $after->getRecurrences());
+        $this->assertEquals($before->getStartDate(), $after->getStartDate());
+        $this->assertEquals($before->isEndExcluded(), $after->isEndExcluded());
+        $this->assertEquals($before->isStartExcluded(), $after->isStartExcluded());
+    }
+
     public function testBooleanSelect(): void
     {
         $this->createTestObject('booleanSelect');
@@ -107,10 +135,6 @@ public function testBooleanSelect(): void
         $this->testDataHelper->assertBooleanSelect($this->testObject, 'booleanSelect', $this->seed);
     }
 
-    abstract protected function createTestObject(array $fields = [], ?array &$params = []): Unittest;
-
-    abstract public function refreshObject(): void;
-
     public function testBricks(): void
     {
         $this->createTestObject([
@@ -259,6 +283,19 @@ public function testDate(): void
         $this->testDataHelper->assertDate($this->testObject, 'date', $this->seed);
     }
 
+    public function testDateRange(): void
+    {
+        $this->createTestObject([
+            [
+                'method' => 'fillDateRange',
+                'field' => 'dateRange',
+            ],
+        ]);
+
+        $this->refreshObject();
+        $this->testDataHelper->assertDatePeriod($this->testObject, 'dateRange', $this->seed);
+    }
+
     public function testDateTime(): void
     {
         $this->createTestObject([
diff --git a/tests/Twig/PimcoreDateTest.php b/tests/Twig/PimcoreDateTest.php
new file mode 100644
index 00000000000..f66e7d7d55c
--- /dev/null
+++ b/tests/Twig/PimcoreDateTest.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * Pimcore
+ *
+ * This source file is available under two different licenses:
+ * - GNU General Public License version 3 (GPLv3)
+ * - Pimcore Commercial License (PCL)
+ * Full copyright and license information is available in
+ * LICENSE.md which is distributed with this source code.
+ *
+ *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
+ *  @license    http://www.pimcore.org/license     GPLv3 and PCL
+ */
+
+namespace Pimcore\Tests\Twig;
+
+use Carbon\Carbon;
+use Pimcore;
+use Pimcore\Templating\TwigDefaultDelegatingEngine;
+use Pimcore\Tests\Support\Test\TestCase;
+use Twig\Loader\ArrayLoader;
+
+class PimcoreDateTest extends TestCase
+{
+    private TwigDefaultDelegatingEngine $engine;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+
+        /** @var TwigDefaultDelegatingEngine $templatingEngine */
+        $templatingEngine = Pimcore::getContainer()->get('pimcore.templating.engine.delegating');
+
+        $this->engine = $templatingEngine;
+    }
+
+    public function testPimcoreDateOutputFormat(): void
+    {
+        $backupLocale = setlocale(LC_TIME, '0');
+        setlocale(LC_TIME, 'en_US.UTF-8');
+
+        $this->engine->getTwigEnvironment()->setLoader(new ArrayLoader([
+            'twig' => <<<TWIG
+            {{ pimcore_date("myDate", {
+                "format": "d.m.Y",
+                "outputFormat": "%A, %B %e, %Y %I:%M"
+            }) }}
+            TWIG,
+        ]));
+        $snippet = new Pimcore\Model\Document\Snippet();
+        $date = (new Pimcore\Model\Document\Editable\Date())
+            ->setName('myDate')
+            ->setDataFromResource(1733954969)
+        ;
+        $snippet->setEditable($date);
+
+        $result = $this->engine->render(
+            'twig',
+            [
+                'document' => $snippet,
+            ]
+        );
+
+        $this->assertEquals('Wednesday, December 11, 2024 10:09', $result);
+
+        setlocale(LC_TIME, $backupLocale);
+    }
+
+    public function testPimcoreDateOutputIsoFormat(): void
+    {
+        $backupCarbonLocale = Carbon::getLocale();
+        Carbon::setLocale('de_DE.utf8');
+
+        $this->engine->getTwigEnvironment()->setLoader(new ArrayLoader([
+            'twig' => <<<TWIG
+            {{ pimcore_date("myDate", {
+                "format": "d.m.Y",
+                "outputIsoFormat": "dddd, MMMM D, YYYY h:mm"
+            }) }}
+            TWIG,
+        ]));
+        $snippet = new Pimcore\Model\Document\Snippet();
+        $date = (new Pimcore\Model\Document\Editable\Date())
+            ->setName('myDate')
+            ->setDataFromResource(1733954969)
+        ;
+        $snippet->setEditable($date);
+
+        $result = $this->engine->render(
+            'twig',
+            [
+                'document' => $snippet,
+            ]
+        );
+
+        $this->assertEquals('Mittwoch, Dezember 11, 2024 10:09', $result);
+
+        Carbon::setLocale($backupCarbonLocale);
+    }
+}