ee32d0e1464175be2d89f3efc00fa51da4331542
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Functional / DataHandling / DataHandler / HookTest.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\DataHandler;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Tests\Functional\DataHandling\AbstractDataHandlerActionTestCase;
19 use TYPO3\CMS\Core\Tests\Functional\DataHandling\DataHandler\Fixtures\HookFixture;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\StringUtility;
22
23 /**
24 * Tests triggering hook execution in DataHandler.
25 */
26 class HookTest extends AbstractDataHandlerActionTestCase
27 {
28 const VALUE_PageId = 89;
29 const VALUE_ContentId = 297;
30 const TABLE_Content = 'tt_content';
31 const TABLE_Hotel = 'tx_irretutorial_1nff_hotel';
32 const TABLE_Category = 'sys_category';
33 const FIELD_ContentHotel = 'tx_irretutorial_1nff_hotels';
34 const FIELD_Categories = 'categories';
35
36 /**
37 * @var HookFixture
38 */
39 protected $hookFixture;
40
41 /**
42 * @var string
43 */
44 protected $scenarioDataSetDirectory = 'typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/';
45
46 protected function setUp()
47 {
48 parent::setUp();
49 $this->importScenarioDataSet('LiveDefaultPages');
50 $this->importScenarioDataSet('LiveDefaultElements');
51 $this->backendUser->workspace = 0;
52
53 $this->hookFixture = GeneralUtility::makeInstance(HookFixture::class);
54 $this->hookFixture->purge();
55 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][__CLASS__] = HookFixture::class;
56 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][__CLASS__] = HookFixture::class;
57 }
58
59 protected function tearDown()
60 {
61 parent::tearDown();
62
63 unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][__CLASS__]);
64 unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][__CLASS__]);
65 unset($this->hookFixture);
66 }
67
68 /**
69 * @test
70 */
71 public function hooksAreExecutedForNewRecords()
72 {
73 $newTableIds = $this->actionService->createNewRecord(
74 self::TABLE_Content,
75 self::VALUE_PageId,
76 ['header' => 'Testing #1']
77 );
78 $this->recordIds['newContentId'] = $newTableIds[self::TABLE_Content][0];
79
80 $this->assertHookInvocationsCount([
81 'processDatamap_beforeStart',
82 'processDatamap_afterAllOperations'
83 ], 1);
84
85 $this->assertHookInvocationsPayload([
86 'processDatamap_preProcessFieldArray',
87 'processDatamap_postProcessFieldArray',
88 'processDatamap_afterDatabaseOperations',
89 ], [
90 [
91 'table' => self::TABLE_Content,
92 'fieldArray' => [ 'header' => 'Testing #1', 'pid' => self::VALUE_PageId ]
93 ]
94 ]);
95 }
96
97 /**
98 * @test
99 */
100 public function hooksAreExecutedForExistingRecords()
101 {
102 $this->actionService->modifyRecord(
103 self::TABLE_Content,
104 self::VALUE_ContentId,
105 ['header' => 'Testing #1']
106 );
107
108 $this->assertHookInvocationsCount([
109 'processDatamap_beforeStart',
110 'processDatamap_afterAllOperations'
111 ], 1);
112
113 $this->assertHookInvocationsPayload([
114 'processDatamap_preProcessFieldArray',
115 'processDatamap_postProcessFieldArray',
116 'processDatamap_afterDatabaseOperations',
117 ], [
118 [
119 'table' => self::TABLE_Content,
120 'fieldArray' => [ 'header' => 'Testing #1' ]
121 ]
122 ]);
123 }
124
125 /**
126 * @test
127 */
128 public function hooksAreExecutedForNewRelations()
129 {
130 $contentNewId = StringUtility::getUniqueId('NEW');
131 $hotelNewId = StringUtility::getUniqueId('NEW');
132 $categoryNewId = StringUtility::getUniqueId('NEW');
133
134 $this->actionService->modifyRecords(
135 self::VALUE_PageId,
136 [
137 self::TABLE_Content => [
138 'uid' => $contentNewId,
139 'header' => 'Testing #1',
140 self::FIELD_ContentHotel => $hotelNewId,
141 self::FIELD_Categories => $categoryNewId,
142 ],
143 self::TABLE_Hotel => [
144 'uid' => $hotelNewId,
145 'title' => 'Hotel #1',
146 ],
147 self::TABLE_Category => [
148 'uid' => $categoryNewId,
149 'title' => 'Category #1',
150 ],
151 ]
152 );
153
154 $this->assertHookInvocationsCount([
155 'processDatamap_beforeStart',
156 'processDatamap_afterAllOperations'
157 ], 1);
158
159 $this->assertHookInvocationPayload(
160 'processDatamap_preProcessFieldArray',
161 [
162 [
163 'table' => self::TABLE_Content,
164 'fieldArray' => [
165 'header' => 'Testing #1',
166 self::FIELD_ContentHotel => $hotelNewId,
167 self::FIELD_Categories => $categoryNewId,
168 ],
169 ],
170 [
171 'table' => self::TABLE_Hotel,
172 'fieldArray' => [ 'title' => 'Hotel #1' ],
173 ],
174 [
175 'table' => self::TABLE_Category,
176 'fieldArray' => [ 'title' => 'Category #1' ],
177 ],
178 ]
179 );
180
181 $this->assertHookInvocationPayload(
182 'processDatamap_postProcessFieldArray',
183 [
184 [
185 'table' => self::TABLE_Content,
186 'fieldArray' => [ 'header' => 'Testing #1' ],
187 ],
188 [
189 'table' => self::TABLE_Hotel,
190 'fieldArray' => [ 'title' => 'Hotel #1' ],
191 ],
192 [
193 'table' => self::TABLE_Category,
194 'fieldArray' => [ 'title' => 'Category #1' ],
195 ],
196 ]
197 );
198
199 $this->assertHookInvocationPayload(
200 'processDatamap_afterDatabaseOperations',
201 [
202 [
203 'table' => self::TABLE_Content,
204 'fieldArray' => [
205 'header' => 'Testing #1',
206 self::FIELD_ContentHotel => 1,
207 self::FIELD_Categories => 1,
208 ],
209 ],
210 [
211 'table' => self::TABLE_Hotel,
212 'fieldArray' => [ 'title' => 'Hotel #1' ],
213 ],
214 [
215 'table' => self::TABLE_Category,
216 'fieldArray' => [ 'title' => 'Category #1' ],
217 ],
218 ]
219 );
220 }
221
222 /**
223 * @test
224 */
225 public function hooksAreExecutedForExistingRelations()
226 {
227 $this->actionService->modifyRecord(
228 self::TABLE_Content,
229 self::VALUE_ContentId,
230 [
231 'header' => 'Testing #1',
232 self::FIELD_ContentHotel => '3,4,5',
233 self::FIELD_Categories => '28,29,30',
234 ]
235 );
236
237 $this->assertHookInvocationsCount([
238 'processDatamap_beforeStart',
239 'processDatamap_afterAllOperations'
240 ], 1);
241
242 $this->assertHookInvocationPayload(
243 'processDatamap_preProcessFieldArray',
244 [
245 [
246 'table' => self::TABLE_Content,
247 'fieldArray' => [
248 'header' => 'Testing #1',
249 self::FIELD_ContentHotel => '3,4,5',
250 self::FIELD_Categories => '28,29,30',
251 ]
252 ]
253 ]
254 );
255
256 $this->assertHookInvocationsPayload([
257 'processDatamap_postProcessFieldArray',
258 'processDatamap_afterDatabaseOperations',
259 ], [
260 [
261 'table' => self::TABLE_Content,
262 'fieldArray' => [
263 'header' => 'Testing #1',
264 self::FIELD_ContentHotel => 3,
265 self::FIELD_Categories => 3,
266 ]
267 ]
268 ]);
269 }
270
271 /**
272 * @param string[] $methodNames
273 * @param int $count
274 */
275 protected function assertHookInvocationsCount(array $methodNames, int $count)
276 {
277 $message = 'Unexpected invocations of method "%s"';
278 foreach ($methodNames as $methodName) {
279 $invocations = $this->hookFixture->findInvocationsByMethodName($methodName);
280 $this->assertCount(
281 $count,
282 $invocations,
283 sprintf($message, $methodName)
284 );
285 }
286 }
287
288 /**
289 * @param string[] $methodNames
290 * @param array $assertions
291 */
292 protected function assertHookInvocationsPayload(array $methodNames, array $assertions)
293 {
294 foreach ($methodNames as $methodName) {
295 $this->assertHookInvocationPayload($methodName, $assertions);
296 }
297 }
298
299 /**
300 * @param string $methodName
301 * @param array $assertions
302 */
303 protected function assertHookInvocationPayload(string $methodName, array $assertions)
304 {
305 $message = 'Unexpected hook payload amount found for method "%s"';
306 $invocations = $this->hookFixture->findInvocationsByMethodName($methodName);
307 $this->assertNotNull($invocations);
308
309 foreach ($assertions as $assertion) {
310 $indexes = $this->findAllArrayValuesInHaystack($invocations, $assertion);
311 $this->assertCount(
312 1,
313 $indexes,
314 sprintf($message, $methodName)
315 );
316 $index = $indexes[0];
317 unset($invocations[$index]);
318 }
319 }
320
321 /**
322 * @param array $haystack
323 * @param array $assertion
324 * @return int[]
325 */
326 protected function findAllArrayValuesInHaystack(array $haystack, array $assertion)
327 {
328 $found = [];
329 foreach ($haystack as $index => $item) {
330 if ($this->equals($assertion, $item)) {
331 $found[] = $index;
332 }
333 }
334 return $found;
335 }
336
337 /**
338 * @param array $left
339 * @param array $right
340 * @return bool
341 */
342 protected function equals(array $left, array $right)
343 {
344 foreach ($left as $key => $leftValue) {
345 $rightValue = $right[$key] ?? null;
346 if (!is_array($leftValue) && (string)$leftValue !== (string)$rightValue) {
347 return false;
348 } elseif (is_array($leftValue)) {
349 if (!$this->equals($leftValue, $rightValue)) {
350 return false;
351 }
352 }
353 }
354 return true;
355 }
356 }