From 31202f88821d1691d8886d50461ad55ad188edee Mon Sep 17 00:00:00 2001 From: Daniel Siepmann Date: Fri, 10 Nov 2017 12:31:06 +0100 Subject: [PATCH] FEATURE: Provide form finisher for integration into form extension Provide a finisher, working as a proxy, to internal data handler, which is already used for Hooks in TYPO3 backend. --- .../Form/Finisher/DataHandlerFinisher.php | 71 +++++++++ Documentation/source/features.rst | 2 + Documentation/source/usage.rst | 28 ++++ Tests/Unit/AbstractUnitTestCase.php | 37 +++++ .../Form/Finisher/DataHandlerFinisherTest.php | 140 ++++++++++++++++++ 5 files changed, 278 insertions(+) create mode 100644 Classes/Integration/Form/Finisher/DataHandlerFinisher.php create mode 100644 Tests/Unit/Integration/Form/Finisher/DataHandlerFinisherTest.php diff --git a/Classes/Integration/Form/Finisher/DataHandlerFinisher.php b/Classes/Integration/Form/Finisher/DataHandlerFinisher.php new file mode 100644 index 0000000..02d6bde --- /dev/null +++ b/Classes/Integration/Form/Finisher/DataHandlerFinisher.php @@ -0,0 +1,71 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher; +use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException; + +/** + * Integrates search_core indexing into TYPO3 Form extension. + * + * Add this finisher AFTER all database operations, as search_core will fetch + * information from database. + */ +class DataHandlerFinisher extends AbstractFinisher +{ + /** + * @var \Codappix\SearchCore\Domain\Service\DataHandler + * @inject + */ + protected $dataHandler; + + /** + * @var array + */ + protected $defaultOptions = [ + 'indexIdentifier' => null, + 'recordUid' => null, + 'action' => '', + ]; + + protected function executeInternal() + { + $action = $this->parseOption('action'); + $record = ['uid' => (int) $this->parseOption('recordUid')]; + $tableName = $this->parseOption('indexIdentifier'); + + if ($action === '' || $tableName === '' || !is_string($tableName) || $record['uid'] === 0) { + throw new FinisherException('Not all necessary options were set.', 1510313095); + } + + switch ($action) { + case 'update': + $this->dataHandler->update($tableName, $record); + break; + case 'add': + $this->dataHandler->add($tableName, $record); + break; + case 'delete': + $this->dataHandler->delete($tableName, $record['uid']); + break; + } + } +} diff --git a/Documentation/source/features.rst b/Documentation/source/features.rst index 199192a..8baf453 100644 --- a/Documentation/source/features.rst +++ b/Documentation/source/features.rst @@ -15,6 +15,8 @@ configuration needs. Still it's possible to configure the indexer. Also custom classes can be used as indexers. +Furthermore a finisher for TYPO3 Form-Extension is provided to integrate indexing. + .. _features_search: Searching diff --git a/Documentation/source/usage.rst b/Documentation/source/usage.rst index 1ca50d2..fd94615 100644 --- a/Documentation/source/usage.rst +++ b/Documentation/source/usage.rst @@ -30,6 +30,34 @@ The tables have to be configured via :ref:`configuration_options_index`. Not all hook operations are supported yet, see :issue:`27`. +.. _usage_form_finisher: + +Form finisher +------------- + +A form finisher is provided to integrate indexing into form extension. + +Add form finisher to your available finishers and configure it like: + +.. code-block:: yaml + :linenos: + + - + identifier: SearchCoreIndexer + options: + action: 'delete' + indexIdentifier: 'fe_users' + recordUid: '{FeUser.user.uid}' + +All three options are necessary, where + +``action`` + Is one of ``delete``, ``update`` or ``add``. +``indexIdentifier`` + Is a configured index identifier. +``recordUid`` + Has to be the uid of the record to index. + .. _usage_searching: Searching / Frontend Plugin diff --git a/Tests/Unit/AbstractUnitTestCase.php b/Tests/Unit/AbstractUnitTestCase.php index a8ac167..c21209d 100644 --- a/Tests/Unit/AbstractUnitTestCase.php +++ b/Tests/Unit/AbstractUnitTestCase.php @@ -21,13 +21,23 @@ namespace Codappix\SearchCore\Tests\Unit; */ use TYPO3\CMS\Core\Tests\UnitTestCase as CoreTestCase; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Object\ObjectManager; +use TYPO3\CMS\Form\Service\TranslationService; abstract class AbstractUnitTestCase extends CoreTestCase { + /** + * @var array A backup of registered singleton instances + */ + protected $singletonInstances = []; + public function setUp() { parent::setUp(); + $this->singletonInstances = GeneralUtility::getSingletonInstances(); + // Disable caching backends to make TYPO3 parts work in unit test mode. \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( @@ -39,6 +49,12 @@ abstract class AbstractUnitTestCase extends CoreTestCase ]); } + public function tearDown() + { + GeneralUtility::resetSingletonInstances($this->singletonInstances); + parent::tearDown(); + } + /** * @return \TYPO3\CMS\Core\Log\LogManager */ @@ -58,4 +74,25 @@ abstract class AbstractUnitTestCase extends CoreTestCase return $logger; } + + /** + * Configure translation service mock for Form Finisher. + * + * This way parseOption will always return the configured value. + */ + protected function configureMockedTranslationService() + { + $translationService = $this->getMockBuilder(TranslationService::class)->getMock(); + $translationService->expects($this->any()) + ->method('translateFinisherOption') + ->willReturnCallback(function ($formRuntime, $finisherIdentifier, $optionKey, $optionValue) { + return $optionValue; + }); + $objectManager = $this->getMockBuilder(ObjectManager::class)->getMock(); + $objectManager->expects($this->any()) + ->method('get') + ->with(TranslationService::class) + ->willReturn($translationService); + GeneralUtility::setSingletonInstance(ObjectManager::class, $objectManager); + } } diff --git a/Tests/Unit/Integration/Form/Finisher/DataHandlerFinisherTest.php b/Tests/Unit/Integration/Form/Finisher/DataHandlerFinisherTest.php new file mode 100644 index 0000000..2211cc2 --- /dev/null +++ b/Tests/Unit/Integration/Form/Finisher/DataHandlerFinisherTest.php @@ -0,0 +1,140 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\SearchCore\Domain\Service\DataHandler; +use Codappix\SearchCore\Integration\Form\Finisher\DataHandlerFinisher; +use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase; +use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException; +use TYPO3\CMS\Form\Domain\Finishers\FinisherContext; + +class DataHandlerFinisherTest extends AbstractUnitTestCase +{ + /** + * @var DataHandlerFinisher + */ + protected $subject; + + /** + * @var DataHandler + */ + protected $dataHandlerMock; + + /** + * @var FinisherContext + */ + protected $finisherContextMock; + + public function setUp() + { + parent::setUp(); + + $this->configureMockedTranslationService(); + $this->dataHandlerMock = $this->getMockBuilder(DataHandler::class) + ->disableOriginalConstructor() + ->getMock(); + $this->finisherContextMock = $this->getMockBuilder(FinisherContext::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->subject = new DataHandlerFinisher(); + $this->inject($this->subject, 'dataHandler', $this->dataHandlerMock); + } + + /** + * @test + * @dataProvider possibleFinisherSetup + */ + public function validConfiguration(string $action, array $nonCalledActions, $expectedSecondArgument) + { + $this->subject->setOptions([ + 'indexIdentifier' => 'test_identifier', + 'recordUid' => '23', + 'action' => $action, + ]); + + foreach ($nonCalledActions as $nonCalledAction) { + $this->dataHandlerMock->expects($this->never())->method($nonCalledAction); + } + $this->dataHandlerMock->expects($this->once())->method($action) + ->with('test_identifier', $expectedSecondArgument); + + $this->subject->execute($this->finisherContextMock); + } + + public function possibleFinisherSetup() : array + { + return [ + 'valid add configuration' => [ + 'action' => 'add', + 'nonCalledActions' => ['delete', 'update'], + 'expectedSecondArgument' => ['uid' => 23], + ], + 'valid update configuration' => [ + 'action' => 'update', + 'nonCalledActions' => ['delete', 'add'], + 'expectedSecondArgument' => ['uid' => 23], + ], + 'valid delete configuration' => [ + 'action' => 'delete', + 'nonCalledActions' => ['update', 'add'], + 'expectedSecondArgument' => 23, + ], + ]; + } + + /** + * @test + * @dataProvider invalidFinisherSetup + */ + public function nothingHappensIfUnknownActionIsConfigured(array $options) + { + $this->subject->setOptions($options); + + foreach (['add', 'update', 'delete'] as $nonCalledAction) { + $this->dataHandlerMock->expects($this->never())->method($nonCalledAction); + } + + $this->expectException(FinisherException::class); + $this->subject->execute($this->finisherContextMock); + } + + public function invalidFinisherSetup() : array + { + return [ + 'missing options' => [ + 'options' => [], + ], + 'missing action option' => [ + 'options' => [ + 'indexIdentifier' => 'identifier', + 'recordUid' => '20', + ], + ], + 'missing record uid option' => [ + 'options' => [ + 'indexIdentifier' => 'identifier', + 'action' => 'update', + ], + ], + ]; + } +}