[BUGFIX] Evaluate displayConditions based on array values
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Tests / Unit / Form / FormDataProvider / EvaluateDisplayConditionsTest.php
1 <?php
2 namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Prophecy\Prophecy\ObjectProphecy;
18 use TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Tests\UnitTestCase;
21
22 /**
23 * Test case
24 */
25 class EvaluateDisplayConditionsTest extends UnitTestCase {
26
27 /**
28 * @var EvaluateDisplayConditions
29 */
30 protected $subject;
31
32 protected function setUp() {
33 $this->subject = new EvaluateDisplayConditions();
34 }
35
36 /**
37 * @test
38 */
39 public function addDataRemovesTcaColumnsHiddenByDisplayCondition() {
40 $input = [
41 'databaseRow' => [
42 'aField' => 'aField',
43 'bField' => 'bField',
44 'cField' => 1,
45 ],
46 'recordTypeValue' => 'aType',
47 'processedTca' => [
48 'types' => [
49 'aType' => [
50 'showitem' => '--palette--;aPalette;2,bField,cField'
51 ],
52 ],
53 'palettes' => [
54 '2' => array(
55 'showitem' => 'aField',
56 'canNotCollapse' => TRUE
57 ),
58 ],
59 'columns' => [
60 'aField' => [
61 'displayCond' => 'FIELD:cField:=:0',
62 'config' => [
63 'type' => 'input',
64 ]
65 ],
66 'bField' => [
67 'displayCond' => 'FIELD:cField:=:1',
68 'config' => [
69 'type' => 'input',
70 ]
71 ],
72 'cField' => [
73 'config' => [
74 'type' => 'input',
75 ]
76 ]
77 ]
78 ]
79 ];
80
81 $expected = $input;
82 unset($expected['processedTca']['columns']['aField']);
83
84 $this->assertSame($expected, $this->subject->addData($input));
85 }
86
87 /**
88 * @test
89 */
90 public function addDataRemovesFlexformSheetsHiddenByDisplayCondition() {
91 $input = [
92 'databaseRow' => [
93 'aField' => [
94 'data' => [
95 'sGeneral' => [
96 'lDEF' => [
97 'mmType' => [
98 'vDEF' => [
99 0 => 'video',
100 ],
101 ],
102 'mmUseHTML5' => [
103 'vDEF' => '0',
104 ],
105 ],
106 ],
107 'sVideo' => [
108 'lDEF' => [],
109 ],
110 'sAudio' => [
111 'lDEF' => []
112 ],
113 ]
114 ]
115 ],
116 'processedTca' => [
117 'columns' => [
118 'aField' => [
119 'config' => [
120 'type' => 'flex',
121 'ds' => [
122 'meta' => [],
123 'sheets' => [
124 'sGeneral' => [
125 'ROOT' => [
126 'type' => 'array',
127 'el' => [
128 'mmType' => [
129 'config' => [
130 'type' => 'select',
131 'items' => [],
132 ],
133 ],
134 'mmUseHTML5' => [
135 'displayCond' => 'FIELD:mmType:!=:audio',
136 'config' => [
137 'type' => 'check',
138 'default' => '0',
139 'items' => [],
140 ],
141 ],
142 ],
143 'sheetTitle' => 'sGeneral',
144 ],
145 ],
146 'sVideo' => [
147 'ROOT' => [
148 'type' => 'array',
149 'el' => [],
150 'sheetTitle' => 'sVideo',
151 'displayCond' => 'FIELD:sGeneral.mmType:!=:audio',
152 ],
153 ],
154 'sAudio' => [
155 'ROOT' => [
156 'type' => 'array',
157 'el' => [],
158 'sheetTitle' => 'sAudio',
159 'displayCond' => 'FIELD:sGeneral.mmType:=:audio',
160 ],
161 ],
162 ],
163 ],
164 ],
165 ],
166 ],
167 ],
168 ];
169
170 $expected = $input;
171 unset($expected['processedTca']['columns']['aField']['config']['ds']['sheets']['sAudio']);
172 $this->assertSame($expected, $this->subject->addData($input));
173 }
174
175 /**
176 * @test
177 */
178 public function addDataRemovesFlexformFieldsHiddenByDisplayCondition() {
179 $input = [
180 'databaseRow' => [
181 'aField' => [
182 'data' => [
183 'sGeneral' => [
184 'lDEF' => [
185 'mmType' => [
186 'vDEF' => [
187 0 => 'audio',
188 ],
189 ],
190 'mmUseHTML5' => [
191 'vDEF' => '0',
192 ],
193 ],
194 ],
195 ]
196 ]
197 ],
198 'processedTca' => [
199 'columns' => [
200 'aField' => [
201 'config' => [
202 'type' => 'flex',
203 'ds_pointerField' => 'list_type,CType',
204 'ds' => [
205 'meta' => [],
206 'sheets' => [
207 'sGeneral' => [
208 'ROOT' => [
209 'type' => 'array',
210 'el' => [
211 'mmType' => [
212 'config' => [
213 'type' => 'select',
214 'items' => [],
215 ],
216 ],
217 'mmUseHTML5' => [
218 'displayCond' => 'FIELD:mmType:!=:audio',
219 'config' => [
220 'type' => 'check',
221 'default' => '0',
222 'items' => [],
223 ],
224 ],
225 ],
226 'sheetTitle' => 'aTitle',
227 ],
228 ],
229 ],
230 ],
231 ],
232 ],
233 ],
234 ],
235 ];
236
237 $expected = $input;
238 unset($expected['processedTca']['columns']['aField']['config']['ds']['sheets']['sGeneral']['ROOT']['el']['mmUseHTML5']);
239
240 $this->assertSame($expected, $this->subject->addData($input));
241 }
242
243 /**
244 * @test
245 */
246 public function matchHideForNonAdminsReturnsTrueIfBackendUserIsAdmin() {
247 $input = [
248 'databaseRow' => [],
249 'processedTca' => [
250 'columns' => [
251 'aField' => [
252 'displayCond' => 'HIDE_FOR_NON_ADMINS',
253 'config' => [
254 'type' => 'input',
255 ]
256 ],
257 ]
258 ]
259 ];
260
261 /** @var BackendUserAuthentication|ObjectProphecy backendUserProphecy */
262 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
263 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
264 $backendUserProphecy->isAdmin()->shouldBeCalled()->willReturn(TRUE);
265
266 $expected = $input;
267
268 $this->assertSame($expected, $this->subject->addData($input));
269 }
270
271 /**
272 * @test
273 */
274 public function matchHideForNonAdminsReturnsFalseIfBackendUserIsNotAdmin() {
275 $input = [
276 'databaseRow' => [],
277 'processedTca' => [
278 'columns' => [
279 'aField' => [
280 'displayCond' => 'HIDE_FOR_NON_ADMINS',
281 'config' => [
282 'type' => 'input',
283 ]
284 ],
285 ]
286 ]
287 ];
288
289 /** @var BackendUserAuthentication|ObjectProphecy backendUserProphecy */
290 $backendUserProphecy = $this->prophesize(BackendUserAuthentication::class);
291 $GLOBALS['BE_USER'] = $backendUserProphecy->reveal();
292 $backendUserProphecy->isAdmin()->shouldBeCalled()->willReturn(FALSE);
293
294 $expected = $input;
295 unset($expected['processedTca']['columns']['aField']);
296 $this->assertSame($expected, $this->subject->addData($input));
297 }
298
299 /**
300 * The HIDE_L10N_SIBLINGS condition is deprecated, this test only ensures that it can be successfully parsed
301 *
302 * @test
303 */
304 public function matchHideL10NSiblingsReturnsTrue() {
305 $input = [
306 'databaseRow' => [],
307 'processedTca' => [
308 'columns' => [
309 'aField' => [
310 'displayCond' => 'HIDE_L10N_SIBLINGS',
311 'config' => [
312 'type' => 'input',
313 ]
314 ],
315 ]
316 ]
317 ];
318
319 $expected = $input;
320
321 $this->assertSame($expected, $this->subject->addData($input));
322 }
323
324 /**
325 * @test
326 */
327 public function matchHideL10NSiblingsExceptAdminReturnsTrue() {
328 $input = [
329 'databaseRow' => [],
330 'processedTca' => [
331 'columns' => [
332 'aField' => [
333 'displayCond' => 'HIDE_L10N_SIBLINGS:except_admin',
334 'config' => [
335 'type' => 'input',
336 ]
337 ],
338 ]
339 ]
340 ];
341
342 $expected = $input;
343
344 $this->assertSame($expected, $this->subject->addData($input));
345 }
346
347 /**
348 * Returns data sets for the test matchConditionStrings
349 * Each data set is an array with the following elements:
350 * - the condition string
351 * - the current record
352 * - the expected result
353 *
354 * @return array
355 */
356 public function conditionStringDataProvider() {
357 return [
358 'Invalid condition string' => [
359 'xINVALIDx:',
360 [],
361 FALSE,
362 ],
363 'Not loaded extension compares to loaded as FALSE' => [
364 'EXT:neverloadedext:LOADED:TRUE',
365 [],
366 FALSE,
367 ],
368 'Not loaded extension compares to not loaded as TRUE' => [
369 'EXT:neverloadedext:LOADED:FALSE',
370 [],
371 TRUE,
372 ],
373 'Loaded extension compares to TRUE' => [
374 'EXT:backend:LOADED:TRUE',
375 [],
376 TRUE,
377 ],
378 'Loaded extension compares to FALSE' => [
379 'EXT:backend:LOADED:FALSE',
380 [],
381 FALSE,
382 ],
383 'Field is not greater zero if not given' => [
384 'FIELD:uid:>:0',
385 [],
386 FALSE,
387 ],
388 'Field is not equal 0 if not given' => [
389 'FIELD:uid:=:0',
390 [],
391 FALSE,
392 ],
393 'Field value string comparison' => [
394 'FIELD:foo:=:bar',
395 ['foo' => 'bar'],
396 TRUE,
397 ],
398 'Field value comparison of 1 against multi-value field of 5 returns true' => [
399 'FIELD:content:BIT:1',
400 ['content' => '5'],
401 TRUE
402 ],
403 'Field value comparison of 2 against multi-value field of 5 returns false' => [
404 'FIELD:content:BIT:2',
405 ['content' => '5'],
406 FALSE
407 ],
408 'Field value of 5 negated comparison against multi-value field of 5 returns false' => [
409 'FIELD:content:!BIT:5',
410 ['content' => '5'],
411 FALSE
412 ],
413 'Field value comparison for required value is false for different value' => [
414 'FIELD:foo:REQ:FALSE',
415 ['foo' => 'bar'],
416 FALSE,
417 ],
418 'Field value string not equal comparison' => [
419 'FIELD:foo:!=:baz',
420 ['foo' => 'bar'],
421 TRUE,
422 ],
423 'Field value in range' => [
424 'FIELD:uid:-:3-42',
425 ['uid' => '23'],
426 TRUE,
427 ],
428 'Field value greater than' => [
429 'FIELD:uid:>=:42',
430 ['uid' => '23'],
431 FALSE,
432 ],
433 'Field is value for default language without flexform' => [
434 'HIDE_L10N_SIBLINGS',
435 [],
436 TRUE,
437 ],
438 'New is TRUE for new comparison with TRUE' => [
439 'REC:NEW:TRUE',
440 ['uid' => NULL],
441 TRUE,
442 ],
443 'New is FALSE for new comparison with FALSE' => [
444 'REC:NEW:FALSE',
445 ['uid' => NULL],
446 FALSE,
447 ],
448 'New is FALSE for not new element' => [
449 'REC:NEW:TRUE',
450 ['uid' => 42],
451 FALSE,
452 ],
453 'New is TRUE for not new element compared to FALSE' => [
454 'REC:NEW:FALSE',
455 ['uid' => 42],
456 TRUE,
457 ],
458 'Version is TRUE for versioned row' => [
459 'VERSION:IS:TRUE',
460 [
461 'uid' => 42,
462 'pid' => -1
463 ],
464 TRUE,
465 ],
466 'Version is TRUE for not versioned row compared with FALSE' => [
467 'VERSION:IS:FALSE',
468 [
469 'uid' => 42,
470 'pid' => 1
471 ],
472 TRUE,
473 ],
474 'Version is TRUE for NULL row compared with TRUE' => [
475 'VERSION:IS:TRUE',
476 [
477 'uid' => NULL,
478 'pid' => NULL,
479 ],
480 FALSE,
481 ],
482 'Multiple conditions with AND compare to TRUE if all are OK' => [
483 [
484 'AND' => [
485 'FIELD:testField:>:9',
486 'FIELD:testField:<:11',
487 ],
488 ],
489 [
490 'testField' => 10
491 ],
492 TRUE,
493 ],
494 'Multiple conditions with AND compare to FALSE if one fails' => [
495 [
496 'AND' => [
497 'FIELD:testField:>:9',
498 'FIELD:testField:<:11',
499 ]
500 ],
501 [
502 'testField' => 99
503 ],
504 FALSE,
505 ],
506 'Multiple conditions with OR compare to TRUE if one is OK' => [
507 [
508 'OR' => [
509 'FIELD:testField:<:9',
510 'FIELD:testField:<:11',
511 ],
512 ],
513 [
514 'testField' => 10
515 ],
516 TRUE,
517 ],
518 'Multiple conditions with OR compare to FALSE is all fail' => [
519 [
520 'OR' => [
521 'FIELD:testField:<:9',
522 'FIELD:testField:<:11',
523 ],
524 ],
525 [
526 'testField' => 99
527 ],
528 FALSE,
529 ],
530 'Multiple conditions without operator due to misconfiguration compare to TRUE' => [
531 [
532 '' => [
533 'FIELD:testField:<:9',
534 'FIELD:testField:>:11',
535 ]
536 ],
537 [
538 'testField' => 99
539 ],
540 TRUE,
541 ],
542 'Multiple nested conditions evaluate to TRUE' => [
543 [
544 'AND' => [
545 'FIELD:testField:>:9',
546 'OR' => [
547 'FIELD:testField:<:100',
548 'FIELD:testField:>:-100',
549 ],
550 ],
551 ],
552 [
553 'testField' => 10
554 ],
555 TRUE,
556 ],
557 'Multiple nested conditions evaluate to FALSE' => [
558 [
559 'AND' => [
560 'FIELD:testField:>:9',
561 'OR' => [
562 'FIELD:testField:<:100',
563 'FIELD:testField:>:-100',
564 ],
565 ],
566 ],
567 [
568 'testField' => -999
569 ],
570 FALSE,
571 ],
572 ];
573 }
574
575 /**
576 * @param string $condition
577 * @param array $record
578 * @param string $expectedResult
579 * @dataProvider conditionStringDataProvider
580 * @test
581 */
582 public function matchConditionStrings($condition, array $record, $expectedResult) {
583 $input = [
584 'databaseRow' => $record,
585 'processedTca' => [
586 'columns' => [
587 'testField' => [
588 'displayCond' => $condition,
589 'config' => [
590 'type' => 'input',
591 ]
592 ],
593 ]
594 ]
595 ];
596
597 $expected = $input;
598 if (!$expectedResult) {
599 unset($expected['processedTca']['columns']['testField']);
600 }
601 $this->assertSame($expected, $this->subject->addData($input));
602 }
603
604 /**
605 * @param string $condition
606 * @param array $record
607 * @param string $expectedResult
608 *
609 * @dataProvider conditionStringDataProvider
610 * @test
611 */
612 public function matchConditionStringsWithRecordTestFieldBeingArray($condition, array $record, $expectedResult) {
613 $input = [
614 'processedTca' => [
615 'columns' => [
616 'testField' => [
617 'displayCond' => $condition,
618 'config' => [
619 'type' => 'input',
620 ],
621 ],
622 ],
623 ],
624 ];
625 $input['databaseRow'] = $record ?: ['testField' => ['key' => $record['testField']]];
626
627 $expected = $input;
628 if (!$expectedResult) {
629 unset($expected['processedTca']['columns']['testField']);
630 }
631 $this->assertSame($expected, $this->subject->addData($input));
632 }
633
634 /**
635 * Returns data sets for the test matchConditionStrings
636 * Each data set is an array with the following elements:
637 * - the condition string
638 * - the current record
639 * - the expected result
640 *
641 * @return array
642 */
643 public function flexformConditionStringDataProvider() {
644 return [
645 'Flexform value invalid comparison' => [
646 'FIELD:foo:=:bar',
647 [
648 'foo' => 'bar',
649 'testField' => [
650 'data' => [
651 'sDEF' => [
652 'lDEF' => [],
653 ],
654 ],
655 ],
656 ],
657 FALSE,
658 ],
659 'Flexform value valid comparison' => [
660 'FIELD:parentRec.foo:=:bar',
661 [
662 'foo' => 'bar',
663 'testField' => [
664 'data' => [
665 'sDEF' => [
666 'lDEF' => [],
667 ],
668 ],
669 ],
670 ],
671 TRUE,
672 ],
673 ];
674 }
675
676 /**
677 * @param string $condition
678 * @param array $record
679 * @param string $expectedResult
680 * @dataProvider flexformConditionStringDataProvider
681 * @test
682 */
683 public function matchFlexformConditionStrings($condition, array $record, $expectedResult) {
684 $input = [
685 'databaseRow' => $record,
686 'processedTca' => [
687 'columns' => [
688 'testField' => [
689 'config' => [
690 'type' => 'flex',
691 'ds' => [
692 'meta' => [],
693 'sheets' => [
694 'sDEF' => [
695 'ROOT' => [
696 'type' => 'array',
697 'el' => [
698 'flexTestField' => [
699 'displayCond' => $condition,
700 'config' => [
701 'type' => 'input',
702 ],
703 ],
704 ],
705 ],
706 ],
707 ]
708 ],
709 ],
710 ],
711 ],
712 ],
713 ];
714
715 $expected = $input;
716 if (!$expectedResult) {
717 unset($expected['processedTca']['columns']['testField']['config']['ds']['sheets']['sDEF']['ROOT']['el']['flexTestField']);
718 }
719 $this->assertSame($expected, $this->subject->addData($input));
720 }
721
722 }