From 80e78c98e901be7433d094bd632f4abce67c950b Mon Sep 17 00:00:00 2001
From: Alexander Schnitzler <git@alexanderschnitzler.de>
Date: Wed, 3 Jan 2018 17:38:30 +0100
Subject: [PATCH] [BUGFIX] Fix resolving of method validators

This is a regression bugfix. During the ClassSchema refactoring
the resolving of method validators broke due to stripping of
beginning dollar signs from tags.

Releases: master
Resolves: #83425
Change-Id: Ida33ade7bc61c0bb926fbf1895612eac9a94d948
Reviewed-on: https://review.typo3.org/55244
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../Classes/Reflection/ClassSchema.php        |   9 +-
 .../Mvc/Controller/ActionControllerTest.php   | 211 ++++++++++++++++++
 .../Fixture/Controller/TestController.php     |  71 ++++++
 .../Controller/Fixture/Domain/Model/Model.php |  25 +++
 .../Domain/Validator/ModelValidator.php       |  29 +++
 .../Validation/Validator/CustomValidator.php  |  29 +++
 6 files changed, 372 insertions(+), 2 deletions(-)
 create mode 100644 typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php
 create mode 100644 typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php
 create mode 100644 typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Model/Model.php
 create mode 100644 typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Validator/ModelValidator.php
 create mode 100644 typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Validation/Validator/CustomValidator.php

diff --git a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
index e0683f267a80..3c7f681d808c 100644
--- a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
+++ b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
@@ -323,8 +323,13 @@ class ClassSchema
                 if ($tag === 'validate') {
                     $this->methods[$methodName]['annotations']['validators'] = $values;
                 }
-                $this->methods[$methodName]['tags'][$tag] = array_map(function ($value) {
-                    return ltrim($value, '$');
+                $this->methods[$methodName]['tags'][$tag] = array_map(function ($value) use ($tag) {
+                    // not stripping the dollar sign for @validate annotations is just
+                    // a quick fix for a regression introduced in 9.0.0.
+                    // This exception to the rules will vanish once the resolving of
+                    // validators will take place inside this class and not in the
+                    // controller during runtime.
+                    return $tag === 'validate' ? $value : ltrim($value, '$');
                 }, $values);
             }
 
diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php
new file mode 100644
index 000000000000..ef9de7754397
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php
@@ -0,0 +1,211 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
+use TYPO3\CMS\Extbase\Mvc\Web\Request;
+use TYPO3\CMS\Extbase\Mvc\Web\Response;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
+use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator;
+use TYPO3\CMS\Extbase\Validation\Validator\StringValidator;
+
+/**
+ * Test case
+ */
+class ActionControllerTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
+{
+    /**
+     * @var \TYPO3\CMS\Extbase\Mvc\Web\Request
+     */
+    protected $request;
+
+    /**
+     * @var \TYPO3\CMS\Extbase\Mvc\Web\Response
+     */
+    protected $response;
+
+    /**
+     * @var \TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Controller\TestController
+     */
+    protected $controller;
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
+
+        $this->request = $objectManager->get(Request::class);
+        $this->request->setControllerVendorName('TYPO3\\CMS');
+        $this->request->setPluginName('Pi1');
+        $this->request->setControllerExtensionName('Extbase\\Tests\\Functional\\Mvc\\Controller\\Fixture');
+        $this->request->setControllerName('Test');
+        $this->request->setMethod('GET');
+        $this->request->setFormat('html');
+
+        $this->response = $objectManager->get(Response::class);
+
+        $this->controller = $objectManager->get(Fixture\Controller\TestController::class);
+    }
+
+    /**
+     * @test
+     */
+    public function modelValidatorsAreProperlyResolved()
+    {
+        // Setup
+        $this->request->setControllerActionName('foo');
+        $this->request->setArgument('fooParam', []);
+
+        // Test run
+        $this->controller->processRequest($this->request, $this->response);
+
+        // Open arguments property
+        $reflectionClass = new \ReflectionClass($this->controller);
+        $argumentsProperty = $reflectionClass->getProperty('arguments');
+        $argumentsProperty->setAccessible(true);
+
+        // Assertions
+
+        /** @var Arguments $arguments */
+        $arguments = $argumentsProperty->getValue($this->controller);
+        $argument = $arguments->getArgument('fooParam');
+
+        /** @var ConjunctionValidator $validator */
+        $conjunctionValidator = $argument->getValidator();
+        static::assertInstanceOf(ConjunctionValidator::class, $conjunctionValidator);
+
+        /** @var \SplObjectStorage $validators */
+        $validators = $conjunctionValidator->getValidators();
+        static::assertInstanceOf(\SplObjectStorage::class, $validators);
+
+        $validators->rewind();
+
+        /** @var ConjunctionValidator $subConjunctionValidator */
+        $subConjunctionValidator = $validators->current();
+        static::assertInstanceOf(ConjunctionValidator::class, $subConjunctionValidator);
+
+        /** @var \SplObjectStorage $subValidators */
+        $subValidators = $subConjunctionValidator->getValidators();
+        static::assertInstanceOf(\SplObjectStorage::class, $subValidators);
+
+        $subValidators->rewind();
+        static::assertInstanceOf(Fixture\Domain\Validator\ModelValidator::class, $subValidators->current());
+    }
+
+    /**
+     * @test
+     */
+    public function customValidatorsAreProperlyResolved()
+    {
+        // Setup
+        $this->request->setControllerActionName('bar');
+        $this->request->setArgument('barParam', '');
+
+        // Test run
+        $this->controller->processRequest($this->request, $this->response);
+
+        // Open arguments property
+        $reflectionClass = new \ReflectionClass($this->controller);
+        $argumentsProperty = $reflectionClass->getProperty('arguments');
+        $argumentsProperty->setAccessible(true);
+
+        // Assertions
+
+        /** @var Arguments $arguments */
+        $arguments = $argumentsProperty->getValue($this->controller);
+        $argument = $arguments->getArgument('barParam');
+
+        /** @var ConjunctionValidator $validator */
+        $conjunctionValidator = $argument->getValidator();
+        static::assertInstanceOf(ConjunctionValidator::class, $conjunctionValidator);
+
+        /** @var \SplObjectStorage $validators */
+        $validators = $conjunctionValidator->getValidators();
+        static::assertInstanceOf(\SplObjectStorage::class, $validators);
+
+        $validators->rewind();
+        static::assertInstanceOf(StringValidator::class, $validators->current());
+
+        $validators->next();
+        static::assertInstanceOf(Fixture\Validation\Validator\CustomValidator::class, $validators->current());
+
+        $validators->next();
+
+        /** @var ConjunctionValidator $subConjunctionValidator */
+        $subConjunctionValidator = $validators->current();
+        static::assertInstanceOf(ConjunctionValidator::class, $subConjunctionValidator);
+
+        // todo: It doesn't make sense that there is another conjunction
+        // todo: valididator with a StringValidator attached. We need to
+        // todo: find out what causes that and how to fix this.
+
+        /** @var \SplObjectStorage $subValidators */
+        $subValidators = $subConjunctionValidator->getValidators();
+        static::assertInstanceOf(\SplObjectStorage::class, $subValidators);
+
+        $subValidators->rewind();
+        static::assertInstanceOf(StringValidator::class, $subValidators->current());
+    }
+
+    /**
+     * @test
+     */
+    public function extbaseValidatorsAreProperlyResolved()
+    {
+        // Setup
+        $this->request->setControllerActionName('baz');
+        $this->request->setArgument('bazParam', [ 'notEmpty' ]);
+
+        // Test run
+        $this->controller->processRequest($this->request, $this->response);
+
+        // Open arguments property
+        $reflectionClass = new \ReflectionClass($this->controller);
+        $argumentsProperty = $reflectionClass->getProperty('arguments');
+        $argumentsProperty->setAccessible(true);
+
+        // Assertions
+
+        /** @var Arguments $arguments */
+        $arguments = $argumentsProperty->getValue($this->controller);
+        $argument = $arguments->getArgument('bazParam');
+
+        /** @var ConjunctionValidator $validator */
+        $conjunctionValidator = $argument->getValidator();
+        static::assertInstanceOf(ConjunctionValidator::class, $conjunctionValidator);
+
+        /** @var \SplObjectStorage $validators */
+        $validators = $conjunctionValidator->getValidators();
+        static::assertInstanceOf(\SplObjectStorage::class, $validators);
+
+        $validators->rewind();
+        static::assertInstanceOf(NotEmptyValidator::class, $validators->current());
+
+        $validators->next();
+
+        /** @var ConjunctionValidator $subConjunctionValidator */
+        $subConjunctionValidator = $validators->current();
+        static::assertInstanceOf(ConjunctionValidator::class, $subConjunctionValidator);
+
+        /** @var \SplObjectStorage $subValidators */
+        $subValidators = $subConjunctionValidator->getValidators();
+        static::assertInstanceOf(\SplObjectStorage::class, $subValidators);
+        static::assertEmpty($subValidators);
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php
new file mode 100644
index 000000000000..d595fe75ac49
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php
@@ -0,0 +1,71 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Controller;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+use TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfiguration;
+use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
+use TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Domain\Model\Model;
+
+/**
+ * Fixture controller
+ */
+class TestController extends ActionController
+{
+    public function initializeFooAction()
+    {
+        /** @var MvcPropertyMappingConfiguration $propertMappingConfiguration */
+        $propertMappingConfiguration = $this->arguments['fooParam']->getPropertyMappingConfiguration();
+        $propertMappingConfiguration->allowAllProperties();
+        $propertMappingConfiguration->setTypeConverterOption(
+            PersistentObjectConverter::class,
+            PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED,
+            true
+        );
+    }
+
+    /**
+     * @param \TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Domain\Model\Model $fooParam
+     * @return string
+     */
+    public function fooAction(Model $fooParam)
+    {
+        // return string so we don't need to mock a view
+        return '';
+    }
+
+    /**
+     * @param string $barParam
+     * @validate $barParam TYPO3.CMS.Extbase.Tests.Functional.Mvc.Controller.Fixture:CustomValidator
+     * @return string
+     */
+    public function barAction(string $barParam)
+    {
+        // return string so we don't need to mock a view
+        return '';
+    }
+
+    /**
+     * @param array $bazParam
+     * @validate $bazParam NotEmpty
+     * @return string
+     */
+    public function bazAction(array $bazParam)
+    {
+        // return string so we don't need to mock a view
+        return '';
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Model/Model.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Model/Model.php
new file mode 100644
index 000000000000..aa0fa5f889b3
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Model/Model.php
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Domain\Model;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
+
+/**
+ * Fixture model
+ */
+class Model extends AbstractEntity
+{
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Validator/ModelValidator.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Validator/ModelValidator.php
new file mode 100644
index 000000000000..04e046786b8f
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Domain/Validator/ModelValidator.php
@@ -0,0 +1,29 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Domain\Validator;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Fixture model validator
+ */
+class ModelValidator extends \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
+{
+    /**
+     * @param mixed $value
+     */
+    protected function isValid($value)
+    {
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Validation/Validator/CustomValidator.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Validation/Validator/CustomValidator.php
new file mode 100644
index 000000000000..0b76ca0f0837
--- /dev/null
+++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Validation/Validator/CustomValidator.php
@@ -0,0 +1,29 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Validation\Validator;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Fixture custom validator
+ */
+class CustomValidator extends \TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator
+{
+    /**
+     * @param mixed $value
+     */
+    protected function isValid($value)
+    {
+    }
+}
-- 
GitLab