[FEATURE] Support multiple display conditions in TCA 08/13508/7
authorJigal van Hemert <jigal@xs4all.nl>
Sun, 31 Mar 2013 12:16:40 +0000 (14:16 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Sun, 31 Mar 2013 15:02:44 +0000 (17:02 +0200)
The displayCond option is expanded with support for multiple
conditions and even the logical operators OR and AND.

Change-Id: I6cb38ef83093ed7399b47701e234ac13bdfa47dd
Fixes: #18211
Releases: 6.1
Reviewed-on: https://review.typo3.org/13508
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
Reviewed-by: Stefan Neufeind
Tested-by: Stefan Neufeind
Reviewed-by: Philipp Gampe
Tested-by: Philipp Gampe
Reviewed-by: Christian Kuhn
Tested-by: Christian Kuhn
typo3/sysext/backend/Classes/Form/ElementConditionMatcher.php
typo3/sysext/backend/Tests/Unit/Form/ElementConditionMatcherTest.php

index 8a439aa..1e3c48d 100644 (file)
@@ -53,9 +53,81 @@ class ElementConditionMatcher {
         * @param string $displayCondition
         * @param array $record
         * @param string $flexformValueKey
+        * @param integer $recursionLevel Internal level of recursion
+        * @return boolean TRUE if condition evaluates successfully
+        */
+       public function match($displayCondition, array $record = array(), $flexformValueKey = '', $recursionLevel = 0) {
+               if ($recursionLevel > 99) {
+                       // This should not happen, treat as misconfiguration
+                       return TRUE;
+               }
+               if (!is_array($displayCondition)) {
+                       // DisplayCondition is not an array - just get its value
+                       $result = $this->matchSingle($displayCondition, $record, $flexformValueKey);
+               } else {
+                       // Multiple conditions given as array ('AND|OR' => condition array)
+                       $conditionEvaluations = array(
+                               'AND' => array(),
+                               'OR' => array(),
+                       );
+                       foreach ($displayCondition as $logicalOperator => $groupedDisplayConditions) {
+                               $logicalOperator = strtoupper($logicalOperator);
+                               if (($logicalOperator !== 'AND' && $logicalOperator !== 'OR') || !is_array($groupedDisplayConditions)) {
+                                       // Invalid line. Skip it.
+                                       continue;
+                               } else {
+                                       foreach ($groupedDisplayConditions as $key => $singleDisplayCondition) {
+                                               $key = strtoupper($key);
+                                               if (($key === 'AND' || $key === 'OR') && is_array($singleDisplayCondition)) {
+                                                       // Recursion statement: condition is 'AND' or 'OR' and is pointing to an array (should be conditions again)
+                                                       $conditionEvaluations[$logicalOperator][] = $this->match(
+                                                               array($key => $singleDisplayCondition),
+                                                               $record,
+                                                               $flexformValueKey,
+                                                               $recursionLevel + 1
+                                                       );
+                                               } else {
+                                                       // Condition statement: collect evaluation of this single condition.
+                                                       $conditionEvaluations[$logicalOperator][] = $this->matchSingle(
+                                                               $singleDisplayCondition,
+                                                               $record,
+                                                               $flexformValueKey
+                                                       );
+                                               }
+                                       }
+                               }
+                       }
+                       if (count($conditionEvaluations['OR']) > 0 && in_array(TRUE, $conditionEvaluations['OR'], TRUE)) {
+                               // There are OR conditions and at least one of them is TRUE
+                               $result = TRUE;
+                       } elseif (count($conditionEvaluations['AND']) > 0 && !in_array(FALSE, $conditionEvaluations['AND'], TRUE)) {
+                               // There are AND conditions and none of them is FALSE
+                               $result = TRUE;
+                       } elseif (count($conditionEvaluations['OR']) > 0 || count($conditionEvaluations['AND']) > 0) {
+                               // There are some conditions. But no OR was TRUE and at least one AND was FALSE
+                               $result = FALSE;
+                       } else {
+                               // There are no proper conditions - misconfiguration. Return TRUE.
+                               $result = TRUE;
+                       }
+               }
+               return $result;
+       }
+
+       /**
+        * Evaluates the provided condition and returns TRUE if the form
+        * element should be displayed.
+        *
+        * The condition string is separated by colons and the first part
+        * indicates what type of evaluation should be performed.
+        *
+        * @param string $displayCondition
+        * @param array $record
+        * @param string $flexformValueKey
         * @return boolean
+        * @see match()
         */
-       public function match($displayCondition, array $record = array(), $flexformValueKey = '') {
+       protected function matchSingle($displayCondition, array $record = array(), $flexformValueKey = '') {
                $this->record = $record;
                $this->flexformValueKey = $flexformValueKey;
                $result = FALSE;
index 9b6d9d5..153738d 100644 (file)
@@ -61,76 +61,262 @@ class ElementConditionMatcherTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
        public function conditionStringDataProvider() {
                return array(
                        'Invalid condition string' => array(
-                               'xINVALIDx:', array(), NULL, FALSE,
+                               'xINVALIDx:',
+                               array(),
+                               NULL,
+                               FALSE,
                        ),
-                       'EXT (#1)' => array (
-                               'EXT:neverloadedext:LOADED:TRUE', array(), NULL, FALSE
+                       'Not loaded extension compares to loaded as FALSE' => array(
+                               'EXT:neverloadedext:LOADED:TRUE',
+                               array(),
+                               NULL,
+                               FALSE,
                        ),
-                       'EXT (#2)' => array (
-                               'EXT:neverloadedext:LOADED:FALSE', array(), NULL, TRUE
+                       'Not loaded extension compares to not loaded as TRUE' => array(
+                               'EXT:neverloadedext:LOADED:FALSE',
+                               array(),
+                               NULL,
+                               TRUE,
                        ),
-                       'EXT (#3)' => array (
-                               'EXT:backend:LOADED:TRUE', array(), NULL, TRUE
+                       'Loaded extension compares to TRUE' => array(
+                               'EXT:backend:LOADED:TRUE',
+                               array(),
+                               NULL,
+                               TRUE,
                        ),
-                       'EXT (#4)' => array (
-                               'EXT:backend:LOADED:FALSE', array(), NULL, FALSE
+                       'Loaded extension compares to FALSE' => array(
+                               'EXT:backend:LOADED:FALSE',
+                               array(),
+                               NULL,
+                               FALSE,
                        ),
-                       'FIELD (#1)' => array(
-                               'FIELD:uid:>:0', array(), NULL, FALSE
+                       'Field is not greater zero if not given' => array(
+                               'FIELD:uid:>:0',
+                               array(),
+                               NULL,
+                               FALSE,
                        ),
-                       'FIELD (#2)' => array(
-                               'FIELD:uid:=:0', array(), NULL, FALSE
+                       'Field is not equal 0 if not given' => array(
+                               'FIELD:uid:=:0',
+                               array(),
+                               NULL,
+                               FALSE,
                        ),
-                       'FIELD (#3)' => array(
-                               'FIELD:foo:=:bar', array('foo' => 'bar'), NULL, TRUE
+                       'Field value string comparison' => array(
+                               'FIELD:foo:=:bar',
+                               array('foo' => 'bar'),
+                               NULL,
+                               TRUE,
                        ),
-                       'FIELD (#4)' => array(
-                               'FIELD:foo:REQ:FALSE', array('foo' => 'bar'), NULL, FALSE
+                       'Field value comparison for required value is false for different value' => array(
+                               'FIELD:foo:REQ:FALSE',
+                               array('foo' => 'bar'),
+                               NULL,
+                               FALSE,
                        ),
-                       'FIELD (#5)' => array(
-                               'FIELD:foo:!=:baz', array('foo' => 'bar'), NULL, TRUE
+                       'Field value string not equal comparison' => array(
+                               'FIELD:foo:!=:baz',
+                               array('foo' => 'bar'),
+                               NULL,
+                               TRUE,
                        ),
-                       'FIELD (#6)' => array(
-                               'FIELD:uid:-:3-42', array('uid' => '23'), NULL, TRUE
+                       'Field value in range' => array(
+                               'FIELD:uid:-:3-42',
+                               array('uid' => '23'),
+                               NULL,
+                               TRUE,
                        ),
-                       'FIELD (#7)' => array(
-                               'FIELD:uid:>=:42', array('uid' => '23'), NULL, FALSE
+                       'Field value greater than' => array(
+                               'FIELD:uid:>=:42',
+                               array('uid' => '23'),
+                               NULL,
+                               FALSE,
                        ),
-                       'FIELD (#8)' => array(
-                               'FIELD:foo:=:bar', array('foo' => array('vDEF' => 'bar')), 'vDEF', TRUE
+                       'Flexform value invalid comparison' => array(
+                               'FIELD:foo:=:bar',
+                               array(
+                                       'foo' => array(
+                                               'vDEF' => 'bar'
+                                       ),
+                               ),
+                               'vDEF',
+                               TRUE,
                        ),
-                       'FIELD (#9)' => array(
-                               'FIELD:parentRec.foo:=:bar', array('parentRec' => array('foo' => 'bar')), 'vDEF', TRUE
+                       'Flexform value valid comparison' => array(
+                               'FIELD:parentRec.foo:=:bar',
+                               array(
+                                       'parentRec' => array(
+                                               'foo' => 'bar'
+                                       ),
+                               ),
+                               'vDEF',
+                               TRUE,
                        ),
-                       'HIDE_L10N_SIBLINGS (#1)' => array(
-                               'HIDE_L10N_SIBLINGS', array(), NULL, FALSE
+                       'Field is value for default languge without flexform' => array(
+                               'HIDE_L10N_SIBLINGS',
+                               array(),
+                               NULL,
+                               FALSE,
                        ),
-                       'HIDE_L10N_SIBLINGS (#2)' => array(
-                               'HIDE_L10N_SIBLINGS', array(), 'vDEF', TRUE
+                       'Field is value for default languge with flexform' => array(
+                               'HIDE_L10N_SIBLINGS',
+                               array(),
+                               'vDEF',
+                               TRUE,
                        ),
-                       'HIDE_L10N_SIBLINGS (#3)' => array(
-                               'HIDE_L10N_SIBLINGS', array(), 'vEN', FALSE
+                       'Field is value for default languge with sibling' => array(
+                               'HIDE_L10N_SIBLINGS',
+                               array(),
+                               'vEN',
+                               FALSE,
                        ),
-                       'REC (#1)' => array(
-                               'REC:NEW:TRUE', array('uid' => NULL), NULL, TRUE
+                       'New is TRUE for new comparison with TRUE' => array(
+                               'REC:NEW:TRUE',
+                               array('uid' => NULL),
+                               NULL,
+                               TRUE,
                        ),
-                       'REC (#2)' => array(
-                               'REC:NEW:FALSE', array('uid' => NULL), NULL, FALSE
+                       'New is FALSE for new comparison with FALSE' => array(
+                               'REC:NEW:FALSE',
+                               array('uid' => NULL),
+                               NULL,
+                               FALSE,
                        ),
-                       'REC (#3)' => array(
-                               'REC:NEW:TRUE', array('uid' => 42), NULL, FALSE
+                       'New is FALSE for not new element' => array(
+                               'REC:NEW:TRUE',
+                               array('uid' => 42),
+                               NULL,
+                               FALSE,
                        ),
-                       'REC (#4)' => array(
-                               'REC:NEW:FALSE', array('uid' => 42), NULL, TRUE
+                       'New is TRUE for not new element compared to FALSE' => array(
+                               'REC:NEW:FALSE',
+                               array('uid' => 42),
+                               NULL,
+                               TRUE,
                        ),
-                       'VERSION (#1)' => array(
-                               'VERSION:IS:TRUE', array('uid' => 42, 'pid' => -1), NULL, TRUE
+                       'Version is TRUE for versioned row' => array(
+                               'VERSION:IS:TRUE',
+                               array(
+                                       'uid' => 42,
+                                       'pid' => -1
+                               ),
+                               NULL,
+                               TRUE,
                        ),
-                       'VERSION (#2)' => array(
-                               'VERSION:IS:FALSE', array('uid' => 42, 'pid' => 1), NULL, TRUE
+                       'Version is TRUE for not versioned row compared with FALSE' => array(
+                               'VERSION:IS:FALSE',
+                               array(
+                                       'uid' => 42,
+                                       'pid' => 1
+                               ),
+                               NULL,
+                               TRUE,
                        ),
-                       'VERSION (#3)' => array(
-                               'VERSION:IS:TRUE', array('uid' => NULL, 'pid' => NULL), NULL, FALSE
+                       'Version is TRUE for NULL row compared with TRUE' => array(
+                               'VERSION:IS:TRUE',
+                               array(
+                                       'uid' => NULL,
+                                       'pid' => NULL,
+                               ),
+                               NULL,
+                               FALSE,
+                       ),
+                       'Multiple conditions with AND compare to TRUE if all are OK' => array(
+                               array(
+                                       'AND' => array(
+                                               'FIELD:testField:>:9',
+                                               'FIELD:testField:<:11',
+                                       ),
+                               ),
+                               array(
+                                       'testField' => 10
+                               ),
+                               NULL,
+                               TRUE,
+                       ),
+                       'Multiple conditions with AND compare to FALSE if one fails' => array(
+                               array(
+                                       'AND' => array(
+                                               'FIELD:testField:>:9',
+                                               'FIELD:testField:<:11',
+                                       )
+                               ),
+                               array(
+                                       'testField' => 99
+                               ),
+                               NULL,
+                               FALSE,
+                       ),
+                       'Multiple conditions with OR compare to TRUE if one is OK' => array(
+                               array(
+                                       'OR' => array(
+                                               'FIELD:testField:<:9',
+                                               'FIELD:testField:<:11',
+                                       ),
+                               ),
+                               array(
+                                       'testField' => 10
+                               ),
+                               NULL,
+                               TRUE,
+                       ),
+                       'Multiple conditions with OR compare to FALSE is all fail' => array(
+                               array(
+                                       'OR' => array(
+                                               'FIELD:testField:<:9',
+                                               'FIELD:testField:<:11',
+                                       ),
+                               ),
+                               array(
+                                       'testField' => 99
+                               ),
+                               NULL,
+                               FALSE,
+                       ),
+                       'Multiple conditions without operator due to misconfiguration compare to TRUE' => array(
+                               array(
+                                       '' => array(
+                                               'FIELD:testField:<:9',
+                                               'FIELD:testField:>:11',
+                                       )
+                               ),
+                               array(
+                                       'testField' => 99
+                               ),
+                               NULL,
+                               TRUE,
+                       ),
+                       'Multiple nested conditions evaluate to TRUE' => array(
+                               array(
+                                       'AND' => array(
+                                               'FIELD:testField:>:9',
+                                               'OR' => array(
+                                                       'FIELD:testField:<:100',
+                                                       'FIELD:testField:>:-100',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'testField' => 10
+                               ),
+                               NULL,
+                               TRUE,
+                       ),
+                       'Multiple nested conditions evaluate to FALSE' => array(
+                               array(
+                                       'AND' => array(
+                                               'FIELD:testField:>:9',
+                                               'OR' => array(
+                                                       'FIELD:testField:<:100',
+                                                       'FIELD:testField:>:-100',
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'testField' => -999
+                               ),
+                               NULL,
+                               FALSE,
                        ),
                );
        }