‪TYPO3CMS  10.4
EvaluateDisplayConditions.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 
19 
23 
30 {
42  public function ‪addData(array $result): array
43  {
44  $result = $this->‪parseDisplayConditions($result);
45  $result = $this->‪evaluateConditions($result);
46  return $result;
47  }
48 
63  protected function ‪parseDisplayConditions(array $result): array
64  {
65  $flexColumns = [];
66  foreach ($result['processedTca']['columns'] as $columnName => $columnConfiguration) {
67  if (isset($columnConfiguration['config']['type']) && $columnConfiguration['config']['type'] === 'flex') {
68  $flexColumns[$columnName] = $columnConfiguration;
69  }
70  if (!isset($columnConfiguration['displayCond'])) {
71  continue;
72  }
73  $result['processedTca']['columns'][$columnName]['displayCond'] = $this->‪parseConditionRecursive(
74  $columnConfiguration['displayCond'],
75  $result['databaseRow']
76  );
77  }
78 
79  foreach ($flexColumns as $columnName => $flexColumn) {
80  $sheetNameFieldNames = [];
81  foreach ($flexColumn['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
82  // Create a list of all sheet names with field names combinations for later 'sheetName.fieldName' lookups
83  // 'one.sheet.one.field' as key, with array of "sheetName" and "fieldName" as value
84  if (isset($sheetConfiguration['ROOT']['el']) && is_array($sheetConfiguration['ROOT']['el'])) {
85  foreach ($sheetConfiguration['ROOT']['el'] as $flexElementName => $flexElementConfiguration) {
86  // section container have no value in its own
87  if (isset($flexElementConfiguration['type']) && $flexElementConfiguration['type'] === 'array'
88  && isset($flexElementConfiguration['section']) && $flexElementConfiguration['section'] == 1
89  ) {
90  continue;
91  }
92  $combinedKey = $sheetName . '.' . $flexElementName;
93  if (array_key_exists($combinedKey, $sheetNameFieldNames)) {
94  throw new \RuntimeException(
95  'Ambiguous sheet name and field name combination: Sheet "' . $sheetNameFieldNames[$combinedKey]['sheetName']
96  . '" with field name "' . $sheetNameFieldNames[$combinedKey]['fieldName'] . '" overlaps with sheet "'
97  . $sheetName . '" and field name "' . $flexElementName . '". Do not do that.',
98  1481483061
99  );
100  }
101  $sheetNameFieldNames[$combinedKey] = [
102  'sheetName' => $sheetName,
103  'fieldName' => $flexElementName,
104  ];
105  }
106  }
107  }
108  foreach ($flexColumn['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
109  if (isset($sheetConfiguration['ROOT']['displayCond'])) {
110  // Condition on a flex sheet
111  $flexContext = [
112  'context' => 'flexSheet',
113  'sheetNameFieldNames' => $sheetNameFieldNames,
114  'currentSheetName' => $sheetName,
115  'flexFormRowData' => $result['databaseRow'][$columnName] ?? null,
116  ];
117  $parsedDisplayCondition = $this->‪parseConditionRecursive(
118  $sheetConfiguration['ROOT']['displayCond'],
119  $result['databaseRow'],
120  $flexContext
121  );
122  $result['processedTca']['columns'][$columnName]['config']['ds']
123  ['sheets'][$sheetName]['ROOT']['displayCond']
124  = $parsedDisplayCondition;
125  }
126  if (isset($sheetConfiguration['ROOT']['el']) && is_array($sheetConfiguration['ROOT']['el'])) {
127  foreach ($sheetConfiguration['ROOT']['el'] as $flexElementName => $flexElementConfiguration) {
128  if (isset($flexElementConfiguration['displayCond'])) {
129  // Condition on a flex element
130  $flexContext = [
131  'context' => 'flexField',
132  'sheetNameFieldNames' => $sheetNameFieldNames,
133  'currentSheetName' => $sheetName,
134  'currentFieldName' => $flexElementName,
135  'flexFormDataStructure' => $result['processedTca']['columns'][$columnName]['config']['ds'],
136  'flexFormRowData' => $result['databaseRow'][$columnName] ?? null,
137  ];
138  $parsedDisplayCondition = $this->‪parseConditionRecursive(
139  $flexElementConfiguration['displayCond'],
140  $result['databaseRow'],
141  $flexContext
142  );
143  $result['processedTca']['columns'][$columnName]['config']['ds']
144  ['sheets'][$sheetName]['ROOT']
145  ['el'][$flexElementName]['displayCond']
146  = $parsedDisplayCondition;
147  }
148  if (isset($flexElementConfiguration['type']) && $flexElementConfiguration['type'] === 'array'
149  && isset($flexElementConfiguration['section']) && $flexElementConfiguration['section'] == 1
150  && isset($flexElementConfiguration['children']) && is_array($flexElementConfiguration['children'])
151  ) {
152  // Conditions on flex container section elements
153  foreach ($flexElementConfiguration['children'] as $containerIdentifier => $containerElements) {
154  if (isset($containerElements['el']) && is_array($containerElements['el'])) {
155  foreach ($containerElements['el'] as $containerElementName => $containerElementConfiguration) {
156  if (isset($containerElementConfiguration['displayCond'])) {
157  $flexContext = [
158  'context' => 'flexContainerElement',
159  'sheetNameFieldNames' => $sheetNameFieldNames,
160  'currentSheetName' => $sheetName,
161  'currentFieldName' => $flexElementName,
162  'currentContainerIdentifier' => $containerIdentifier,
163  'currentContainerElementName' => $containerElementName,
164  'flexFormDataStructure' => $result['processedTca']['columns'][$columnName]['config']['ds'],
165  'flexFormRowData' => $result['databaseRow'][$columnName],
166  ];
167  $parsedDisplayCondition = $this->‪parseConditionRecursive(
168  $containerElementConfiguration['displayCond'],
169  $result['databaseRow'],
170  $flexContext
171  );
172  $result['processedTca']['columns'][$columnName]['config']['ds']
173  ['sheets'][$sheetName]['ROOT']
174  ['el'][$flexElementName]
175  ['children'][$containerIdentifier]
176  ['el'][$containerElementName]['displayCond']
177  = $parsedDisplayCondition;
178  }
179  }
180  }
181  }
182  }
183  }
184  }
185  }
186  }
187  return $result;
188  }
189 
200  protected function ‪parseConditionRecursive($condition, array $databaseRow, array $flexContext = []): array
201  {
202  $conditionArray = [];
203  if (is_string($condition)) {
204  $conditionArray = $this->‪parseSingleConditionString($condition, $databaseRow, $flexContext);
205  } elseif (is_array($condition)) {
206  foreach ($condition as $logicalOperator => $groupedDisplayConditions) {
207  $logicalOperator = strtoupper(is_string($logicalOperator) ? $logicalOperator : '');
208  if (($logicalOperator !== 'AND' && $logicalOperator !== 'OR') || !is_array($groupedDisplayConditions)) {
209  throw new \RuntimeException(
210  'Multiple conditions must have boolean operator "OR" or "AND", "' . $logicalOperator . '" given.',
211  1481380393
212  );
213  }
214  $conditionArray = [
215  'type' => $logicalOperator,
216  'subConditions' => [],
217  ];
218  foreach ($groupedDisplayConditions as $key => $singleDisplayCondition) {
219  $key = strtoupper((string)$key);
220  if (($key === 'AND' || $key === 'OR') && is_array($singleDisplayCondition)) {
221  // Recursion statement: condition is 'AND' or 'OR' and is pointing to an array (should be conditions again)
222  $conditionArray['subConditions'][] = $this->‪parseConditionRecursive(
223  [$key => $singleDisplayCondition],
224  $databaseRow,
225  $flexContext
226  );
227  } else {
228  $conditionArray['subConditions'][] = $this->‪parseConditionRecursive(
229  $singleDisplayCondition,
230  $databaseRow,
231  $flexContext
232  );
233  }
234  }
235  }
236  } else {
237  throw new \RuntimeException(
238  'Condition must be either an array with sub conditions or a single condition string, type ' . gettype($condition) . ' given.',
239  1481381058
240  );
241  }
242  return $conditionArray;
243  }
244 
255  protected function ‪parseSingleConditionString(string $conditionString, array $databaseRow, array $flexContext = []): array
256  {
257  $conditionArray = ‪GeneralUtility::trimExplode(':', $conditionString, false, 4);
258  $namedConditionArray = [
259  'type' => $conditionArray[0],
260  ];
261  switch ($namedConditionArray['type']) {
262  case 'FIELD':
263  if (empty($conditionArray[1])) {
264  throw new \RuntimeException(
265  'Field condition "' . $conditionString . '" must have a field name as second part, none given.'
266  . 'Example: "FIELD:myField:=:myValue"',
267  1481385695
268  );
269  }
270  $fieldName = $conditionArray[1];
271  $allowedOperators = ['REQ', '>', '<', '>=', '<=', '-', '!-', '=', '!=', 'IN', '!IN', 'BIT', '!BIT'];
272  if (empty($conditionArray[2]) || !in_array($conditionArray[2], $allowedOperators)) {
273  throw new \RuntimeException(
274  'Field condition "' . $conditionString . '" must have a valid operator as third part, non or invalid one given.'
275  . ' Valid operators are: "' . implode('", "', $allowedOperators) . '".'
276  . ' Example: "FIELD:myField:=:4"',
277  1481386239
278  );
279  }
280  $namedConditionArray['operator'] = $conditionArray[2];
281  if (!isset($conditionArray[3])) {
282  throw new \RuntimeException(
283  'Field condition "' . $conditionString . '" must have an operand as fourth part, none given.'
284  . ' Example: "FIELD:myField:=:4"',
285  1481401543
286  );
287  }
288  $operand = $conditionArray[3];
289  if ($namedConditionArray['operator'] === 'REQ') {
290  $operand = strtolower($operand);
291  if ($operand === 'true') {
292  $namedConditionArray['operand'] = true;
293  } elseif ($operand === 'false') {
294  $namedConditionArray['operand'] = false;
295  } else {
296  throw new \RuntimeException(
297  'Field condition "' . $conditionString . '" must have "true" or "false" as fourth part.'
298  . ' Example: "FIELD:myField:REQ:true',
299  1481401892
300  );
301  }
302  } elseif (in_array($namedConditionArray['operator'], ['>', '<', '>=', '<=', 'BIT', '!BIT'])) {
304  throw new \RuntimeException(
305  'Field condition "' . $conditionString . '" with comparison operator ' . $namedConditionArray['operator']
306  . ' must have a number as fourth part, ' . $operand . ' given. Example: "FIELD:myField:>:42"',
307  1481456806
308  );
309  }
310  $namedConditionArray['operand'] = (int)$operand;
311  } elseif ($namedConditionArray['operator'] === '-' || $namedConditionArray['operator'] === '!-') {
312  [$minimum, $maximum] = ‪GeneralUtility::trimExplode('-', $operand);
314  throw new \RuntimeException(
315  'Field condition "' . $conditionString . '" with comparison operator ' . $namedConditionArray['operator']
316  . ' must have two numbers as fourth part, separated by dash, ' . $operand . ' given. Example: "FIELD:myField:-:1-3"',
317  1481457277
318  );
319  }
320  $namedConditionArray['operand'] = '';
321  $namedConditionArray['min'] = (int)$minimum;
322  $namedConditionArray['max'] = (int)$maximum;
323  } elseif ($namedConditionArray['operator'] === 'IN' || $namedConditionArray['operator'] === '!IN'
324  || $namedConditionArray['operator'] === '=' || $namedConditionArray['operator'] === '!='
325  ) {
326  $namedConditionArray['operand'] = $operand;
327  }
328  $namedConditionArray['fieldValue'] = $this->‪findFieldValue($fieldName, $databaseRow, $flexContext);
329  break;
330  case 'HIDE_FOR_NON_ADMINS':
331  break;
332  case 'REC':
333  if (empty($conditionArray[1]) || $conditionArray[1] !== 'NEW') {
334  throw new \RuntimeException(
335  'Record condition "' . $conditionString . '" must contain "NEW" keyword: either "REC:NEW:true" or "REC:NEW:false"',
336  1481384784
337  );
338  }
339  if (empty($conditionArray[2])) {
340  throw new \RuntimeException(
341  'Record condition "' . $conditionString . '" must have an operand "true" or "false", none given. Example: "REC:NEW:true"',
342  1481384947
343  );
344  }
345  $operand = strtolower($conditionArray[2]);
346  if ($operand === 'true') {
347  $namedConditionArray['isNew'] = true;
348  } elseif ($operand === 'false') {
349  $namedConditionArray['isNew'] = false;
350  } else {
351  throw new \RuntimeException(
352  'Record condition "' . $conditionString . '" must have an operand "true" or "false, example "REC:NEW:true", given: ' . $operand,
353  1481385173
354  );
355  }
356  // Programming error: There must be a uid available, other data providers should have taken care of that already
357  if (!array_key_exists('uid', $databaseRow)) {
358  throw new \RuntimeException(
359  'Required [\'databaseRow\'][\'uid\'] not found in data array',
360  1481467208
361  );
362  }
363  // May contain "NEW123..."
364  $namedConditionArray['uid'] = $databaseRow['uid'];
365  break;
366  case 'VERSION':
367  if (empty($conditionArray[1]) || $conditionArray[1] !== 'IS') {
368  throw new \RuntimeException(
369  'Version condition "' . $conditionString . '" must contain "IS" keyword: either "VERSION:IS:false" or "VERSION:IS:true"',
370  1481383660
371  );
372  }
373  if (empty($conditionArray[2])) {
374  throw new \RuntimeException(
375  'Version condition "' . $conditionString . '" must have an operand "true" or "false", none given. Example: "VERSION:IS:true',
376  1481383888
377  );
378  }
379  $operand = strtolower($conditionArray[2]);
380  if ($operand === 'true') {
381  $namedConditionArray['isVersion'] = true;
382  } elseif ($operand === 'false') {
383  $namedConditionArray['isVersion'] = false;
384  } else {
385  throw new \RuntimeException(
386  'Version condition "' . $conditionString . '" must have a "true" or "false" operand, example "VERSION:IS:true", given: ' . $operand,
387  1481384123
388  );
389  }
390  // Programming error: There must be a uid available, other data providers should have taken care of that already
391  if (!array_key_exists('uid', $databaseRow)) {
392  throw new \RuntimeException(
393  'Required [\'databaseRow\'][\'uid\'] not found in data array',
394  1481469854
395  );
396  }
397  $namedConditionArray['uid'] = $databaseRow['uid'];
398  if (array_key_exists('t3ver_oid', $databaseRow)) {
399  $namedConditionArray['t3ver_oid'] = $databaseRow['t3ver_oid'];
400  }
401  if (array_key_exists('pid', $databaseRow)) {
402  $namedConditionArray['pid'] = $databaseRow['pid'];
403  }
404  if (array_key_exists('_ORIG_pid', $databaseRow)) {
405  $namedConditionArray['_ORIG_pid'] = $databaseRow['_ORIG_pid'];
406  }
407  break;
408  case 'USER':
409  if (empty($conditionArray[1])) {
410  throw new \RuntimeException(
411  'User function condition "' . $conditionString . '" must have a user function defined a second part, none given.'
412  . ' Correct format is USER:\My\User\Func->match:more:arguments,'
413  . ' given: ' . $conditionString,
414  1481382954
415  );
416  }
417  $namedConditionArray['function'] = $conditionArray[1];
418  array_shift($conditionArray);
419  array_shift($conditionArray);
420  $parameters = count($conditionArray) < 2
421  ? $conditionArray
422  : array_merge(
423  [$conditionArray[0]],
424  ‪GeneralUtility::trimExplode(':', $conditionArray[1])
425  );
426  $namedConditionArray['parameters'] = $parameters;
427  $namedConditionArray['record'] = $databaseRow;
428  $namedConditionArray['flexContext'] = $flexContext;
429  break;
430  default:
431  throw new \RuntimeException(
432  'Unknown condition rule type "' . $namedConditionArray['type'] . '" with display condition "' . $conditionString . '".',
433  1481381950
434  );
435  }
436  return $namedConditionArray;
437  }
438 
450  protected function ‪findFieldValue(string $givenFieldName, array $databaseRow, array $flexContext = [])
451  {
452  $fieldValue = null;
453 
454  // Early return for "normal" tca fields
455  if (empty($flexContext)) {
456  if (array_key_exists($givenFieldName, $databaseRow)) {
457  $fieldValue = $databaseRow[$givenFieldName];
458  }
459  return $fieldValue;
460  }
461  if ($flexContext['context'] === 'flexSheet') {
462  // A display condition on a flex form sheet. Relatively simple: fieldName is either
463  // "parentRec.fieldName" pointing to a databaseRow field name, or "sheetName.fieldName" pointing
464  // to a field value from a neighbor field.
465  if (strpos($givenFieldName, 'parentRec.') === 0) {
466  $fieldName = substr($givenFieldName, 10);
467  if (array_key_exists($fieldName, $databaseRow)) {
468  $fieldValue = $databaseRow[$fieldName];
469  }
470  } else {
471  if (array_key_exists($givenFieldName, $flexContext['sheetNameFieldNames'])) {
472  if ($flexContext['currentSheetName'] === $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName']) {
473  throw new \RuntimeException(
474  'Configuring displayCond to "' . $givenFieldName . '" on flex form sheet "'
475  . $flexContext['currentSheetName'] . '" referencing a value from the same sheet does not make sense.',
476  1481485705
477  );
478  }
479  }
480  $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName'] ?? null;
481  $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName'] ?? null;
482  if (!isset($flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'])) {
483  throw new \RuntimeException(
484  'Flex form displayCond on sheet "' . $flexContext['currentSheetName'] . '" references field "' . $fieldName
485  . '" of sheet "' . $sheetName . '", but that field does not exist in current data structure',
486  1481488492
487  );
488  }
489  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'];
490  }
491  } elseif ($flexContext['context'] === 'flexField') {
492  // A display condition on a flex field. Handle "parentRec." similar to sheet conditions,
493  // get a list of "local" field names and see if they are used as reference, else see if a
494  // "sheetName.fieldName" field reference is given
495  if (strpos($givenFieldName, 'parentRec.') === 0) {
496  $fieldName = substr($givenFieldName, 10);
497  if (array_key_exists($fieldName, $databaseRow)) {
498  $fieldValue = $databaseRow[$fieldName];
499  }
500  } else {
501  $listOfLocalFlexFieldNames = array_keys(
502  $flexContext['flexFormDataStructure']['sheets'][$flexContext['currentSheetName']]['ROOT']['el']
503  );
504  if (in_array($givenFieldName, $listOfLocalFlexFieldNames, true)) {
505  // Condition references field name of the same sheet
506  $sheetName = $flexContext['currentSheetName'];
507  if (!isset($flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$givenFieldName]['vDEF'])) {
508  throw new \RuntimeException(
509  'Flex form displayCond on field "' . $flexContext['currentFieldName'] . '" on flex form sheet "'
510  . $flexContext['currentSheetName'] . '" references field "' . $givenFieldName . '", but a field value'
511  . ' does not exist in this sheet',
512  1481492953
513  );
514  }
515  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$givenFieldName]['vDEF'];
516  } elseif (in_array($givenFieldName, array_keys($flexContext['sheetNameFieldNames'], true))) {
517  // Condition references field name including a sheet name
518  $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName'];
519  $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName'];
520  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'];
521  } else {
522  throw new \RuntimeException(
523  'Flex form displayCond on field "' . $flexContext['currentFieldName'] . '" on flex form sheet "'
524  . $flexContext['currentSheetName'] . '" references a field or field / sheet combination "'
525  . $givenFieldName . '" that might be defined in given data structure but is not found in data values.',
526  1481496170
527  );
528  }
529  }
530  } elseif ($flexContext['context'] === 'flexContainerElement') {
531  // A display condition on a flex form section container element. Handle "parentRec.", compare to a
532  // list of local field names, compare to a list of field names from same sheet, compare to a list
533  // of sheet fields from other sheets.
534  if (strpos($givenFieldName, 'parentRec.') === 0) {
535  $fieldName = substr($givenFieldName, 10);
536  if (array_key_exists($fieldName, $databaseRow)) {
537  $fieldValue = $databaseRow[$fieldName];
538  }
539  } else {
540  $currentSheetName = $flexContext['currentSheetName'];
541  $currentFieldName = $flexContext['currentFieldName'];
542  $currentContainerIdentifier = $flexContext['currentContainerIdentifier'];
543  $currentContainerElementName = $flexContext['currentContainerElementName'];
544  $listOfLocalContainerElementNames = array_keys(
545  $flexContext['flexFormDataStructure']['sheets'][$currentSheetName]['ROOT']
546  ['el'][$currentFieldName]
547  ['children'][$currentContainerIdentifier]
548  ['el']
549  );
550  $listOfLocalContainerElementNamesWithSheetName = [];
551  foreach ($listOfLocalContainerElementNames as $aContainerElementName) {
552  $listOfLocalContainerElementNamesWithSheetName[$currentSheetName . '.' . $aContainerElementName] = [
553  'containerElementName' => $aContainerElementName,
554  ];
555  }
556  $listOfLocalFlexFieldNames = array_keys(
557  $flexContext['flexFormDataStructure']['sheets'][$currentSheetName]['ROOT']['el']
558  );
559  if (in_array($givenFieldName, $listOfLocalContainerElementNames, true)) {
560  // Condition references field of same container instance
561  $containerType = current(array_keys(
562  $flexContext['flexFormRowData']['data'][$currentSheetName]
563  ['lDEF'][$currentFieldName]
564  ['el'][$currentContainerIdentifier]
565  ));
566  $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName]
567  ['lDEF'][$currentFieldName]
568  ['el'][$currentContainerIdentifier]
569  [$containerType]
570  ['el'][$givenFieldName]['vDEF'];
571  } elseif (in_array($givenFieldName, array_keys($listOfLocalContainerElementNamesWithSheetName, true))) {
572  // Condition references field name of same container instance and has sheet name included
573  $containerType = current(array_keys(
574  $flexContext['flexFormRowData']['data'][$currentSheetName]
575  ['lDEF'][$currentFieldName]
576  ['el'][$currentContainerIdentifier]
577  ));
578  $fieldName = $listOfLocalContainerElementNamesWithSheetName[$givenFieldName]['containerElementName'];
579  $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName]
580  ['lDEF'][$currentFieldName]
581  ['el'][$currentContainerIdentifier]
582  [$containerType]
583  ['el'][$fieldName]['vDEF'];
584  } elseif (in_array($givenFieldName, $listOfLocalFlexFieldNames, true)) {
585  // Condition reference field name of sheet this section container is in
586  $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName]
587  ['lDEF'][$givenFieldName]['vDEF'];
588  } elseif (in_array($givenFieldName, array_keys($flexContext['sheetNameFieldNames'], true))) {
589  $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName'];
590  $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName'];
591  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'];
592  } else {
593  $containerType = current(array_keys(
594  $flexContext['flexFormRowData']['data'][$currentSheetName]
595  ['lDEF'][$currentFieldName]
596  ['el'][$currentContainerIdentifier]
597  ));
598  throw new \RuntimeException(
599  'Flex form displayCond on section container field "' . $currentContainerElementName . '" of container type "'
600  . $containerType . '" on flex form sheet "'
601  . $flexContext['currentSheetName'] . '" references a field or field / sheet combination "'
602  . $givenFieldName . '" that might be defined in given data structure but is not found in data values.',
603  1481634649
604  );
605  }
606  }
607  }
608 
609  return $fieldValue;
610  }
611 
619  protected function ‪evaluateConditions(array $result): array
620  {
621  // Evaluate normal tca fields first
622  $listOfFlexFieldNames = [];
623  foreach ($result['processedTca']['columns'] as $columnName => $columnConfiguration) {
624  $conditionResult = true;
625  if (isset($columnConfiguration['displayCond'])) {
626  $conditionResult = $this->‪evaluateConditionRecursive($columnConfiguration['displayCond']);
627  if (!$conditionResult) {
628  unset($result['processedTca']['columns'][$columnName]);
629  } else {
630  // Always unset the whole parsed display condition to save some memory, we're done with them
631  unset($result['processedTca']['columns'][$columnName]['displayCond']);
632  }
633  }
634  // If field was not removed and if it is a flex field, add to list of flex fields to scan
635  if ($conditionResult && $columnConfiguration['config']['type'] === 'flex') {
636  $listOfFlexFieldNames[] = $columnName;
637  }
638  }
639 
640  // Search for flex fields and evaluate sheet conditions throwing them away if needed
641  foreach ($listOfFlexFieldNames as $columnName) {
642  $columnConfiguration = $result['processedTca']['columns'][$columnName];
643  foreach ($columnConfiguration['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
644  if (isset($sheetConfiguration['ROOT']['displayCond']) && is_array($sheetConfiguration['ROOT']['displayCond'])) {
645  if (!$this->‪evaluateConditionRecursive($sheetConfiguration['ROOT']['displayCond'])) {
646  unset($result['processedTca']['columns'][$columnName]['config']['ds']['sheets'][$sheetName]);
647  } else {
648  unset($result['processedTca']['columns'][$columnName]['config']['ds']['sheets'][$sheetName]['ROOT']['displayCond']);
649  }
650  }
651  }
652  }
653 
654  // With full sheets gone we loop over display conditions of single fields in flex to throw fields away if needed
655  $listOfFlexSectionContainers = [];
656  foreach ($listOfFlexFieldNames as $columnName) {
657  $columnConfiguration = $result['processedTca']['columns'][$columnName];
658  if (is_array($columnConfiguration['config']['ds']['sheets'])) {
659  foreach ($columnConfiguration['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
660  if (isset($sheetConfiguration['ROOT']['el']) && is_array($sheetConfiguration['ROOT']['el'])) {
661  foreach ($sheetConfiguration['ROOT']['el'] as $flexField => $flexConfiguration) {
662  $conditionResult = true;
663  if (isset($flexConfiguration['displayCond']) && is_array($flexConfiguration['displayCond'])) {
664  $conditionResult = $this->‪evaluateConditionRecursive($flexConfiguration['displayCond']);
665  if (!$conditionResult) {
666  unset(
667  $result['processedTca']['columns'][$columnName]['config']['ds']
668  ['sheets'][$sheetName]['ROOT']
669  ['el'][$flexField]
670  );
671  } else {
672  unset(
673  $result['processedTca']['columns'][$columnName]['config']['ds']
674  ['sheets'][$sheetName]['ROOT']
675  ['el'][$flexField]['displayCond']
676  );
677  }
678  }
679  // If it was not removed and if the field is a section container, add it to the section container list
680  if ($conditionResult
681  && isset($flexConfiguration['type']) && $flexConfiguration['type'] === 'array'
682  && isset($flexConfiguration['section']) && $flexConfiguration['section'] == 1
683  && isset($flexConfiguration['children']) && is_array($flexConfiguration['children'])
684  ) {
685  $listOfFlexSectionContainers[] = [
686  'columnName' => $columnName,
687  'sheetName' => $sheetName,
688  'flexField' => $flexField,
689  ];
690  }
691  }
692  }
693  }
694  }
695  }
696 
697  // Loop over found section container elements and evaluate their conditions
698  foreach ($listOfFlexSectionContainers as $flexSectionContainerPosition) {
699  $columnName = $flexSectionContainerPosition['columnName'];
700  $sheetName = $flexSectionContainerPosition['sheetName'];
701  $flexField = $flexSectionContainerPosition['flexField'];
702  $sectionElement = $result['processedTca']['columns'][$columnName]['config']['ds']
703  ['sheets'][$sheetName]['ROOT']
704  ['el'][$flexField];
705  foreach ($sectionElement['children'] as $containerInstanceName => $containerDataStructure) {
706  if (isset($containerDataStructure['el']) && is_array($containerDataStructure['el'])) {
707  foreach ($containerDataStructure['el'] as $containerElementName => $containerElementConfiguration) {
708  if (isset($containerElementConfiguration['displayCond']) && is_array($containerElementConfiguration['displayCond'])) {
709  if (!$this->‪evaluateConditionRecursive($containerElementConfiguration['displayCond'])) {
710  unset(
711  $result['processedTca']['columns'][$columnName]['config']['ds']
712  ['sheets'][$sheetName]['ROOT']
713  ['el'][$flexField]
714  ['children'][$containerInstanceName]
715  ['el'][$containerElementName]
716  );
717  } else {
718  unset(
719  $result['processedTca']['columns'][$columnName]['config']['ds']
720  ['sheets'][$sheetName]['ROOT']
721  ['el'][$flexField]
722  ['children'][$containerInstanceName]
723  ['el'][$containerElementName]['displayCond']
724  );
725  }
726  }
727  }
728  }
729  }
730  }
731 
732  return $result;
733  }
734 
741  protected function ‪evaluateConditionRecursive(array $conditionArray): bool
742  {
743  switch ($conditionArray['type']) {
744  case 'AND':
745  $result = true;
746  foreach ($conditionArray['subConditions'] as $subCondition) {
747  $result = $result && $this->‪evaluateConditionRecursive($subCondition);
748  }
749  return $result;
750  case 'OR':
751  $result = false;
752  foreach ($conditionArray['subConditions'] as $subCondition) {
753  $result = $result || $this->‪evaluateConditionRecursive($subCondition);
754  }
755  return $result;
756  case 'FIELD':
757  return $this->‪matchFieldCondition($conditionArray);
758  case 'HIDE_FOR_NON_ADMINS':
759  return (bool)$this->‪getBackendUser()->‪isAdmin();
760  case 'REC':
761  return $this->‪matchRecordCondition($conditionArray);
762  case 'VERSION':
763  return $this->‪matchVersionCondition($conditionArray);
764  case 'USER':
765  return $this->‪matchUserCondition($conditionArray);
766  }
767  return false;
768  }
769 
779  protected function ‪matchFieldCondition(array $condition): bool
780  {
781  $operator = $condition['operator'];
782  $operand = $condition['operand'];
783  $fieldValue = $condition['fieldValue'];
784  $result = false;
785  switch ($operator) {
786  case 'REQ':
787  if (is_array($fieldValue) && count($fieldValue) <= 1) {
788  $fieldValue = array_shift($fieldValue);
789  }
790  if ($operand) {
791  $result = (bool)$fieldValue;
792  } else {
793  $result = !$fieldValue;
794  }
795  break;
796  case '>':
797  if (is_array($fieldValue) && count($fieldValue) <= 1) {
798  $fieldValue = array_shift($fieldValue);
799  }
800  $result = $fieldValue > $operand;
801  break;
802  case '<':
803  if (is_array($fieldValue) && count($fieldValue) <= 1) {
804  $fieldValue = array_shift($fieldValue);
805  }
806  $result = $fieldValue < $operand;
807  break;
808  case '>=':
809  if (is_array($fieldValue) && count($fieldValue) <= 1) {
810  $fieldValue = array_shift($fieldValue);
811  }
812  if ($fieldValue === null) {
813  // If field value is null, this is NOT greater than or equal 0
814  // See test set "Field is not greater than or equal to zero if empty array given"
815  $result = false;
816  } else {
817  $result = $fieldValue >= $operand;
818  }
819  break;
820  case '<=':
821  if (is_array($fieldValue) && count($fieldValue) <= 1) {
822  $fieldValue = array_shift($fieldValue);
823  }
824  $result = $fieldValue <= $operand;
825  break;
826  case '-':
827  case '!-':
828  if (is_array($fieldValue) && count($fieldValue) <= 1) {
829  $fieldValue = array_shift($fieldValue);
830  }
831  $min = $condition['min'];
832  $max = $condition['max'];
833  $result = $fieldValue >= $min && $fieldValue <= $max;
834  if ($operator[0] === '!') {
835  $result = !$result;
836  }
837  break;
838  case '=':
839  case '!=':
840  if (is_array($fieldValue) && count($fieldValue) <= 1) {
841  $fieldValue = array_shift($fieldValue);
842  }
843  $result = $fieldValue == $operand;
844  if ($operator[0] === '!') {
845  $result = !$result;
846  }
847  break;
848  case 'IN':
849  case '!IN':
850  if (is_array($fieldValue)) {
851  $result = count(array_intersect($fieldValue, ‪GeneralUtility::trimExplode(',', $operand))) > 0;
852  } else {
853  $result = GeneralUtility::inList($operand, $fieldValue);
854  }
855  if ($operator[0] === '!') {
856  $result = !$result;
857  }
858  break;
859  case 'BIT':
860  case '!BIT':
861  $result = (bool)((int)$fieldValue & $operand);
862  if ($operator[0] === '!') {
863  $result = !$result;
864  }
865  break;
866  }
867  return $result;
868  }
869 
879  protected function ‪matchRecordCondition(array $condition): bool
880  {
881  if ($condition['isNew']) {
882  return !((int)$condition['uid'] > 0);
883  }
884  return (int)$condition['uid'] > 0;
885  }
886 
893  protected function ‪matchVersionCondition(array $condition): bool
894  {
895  $isNewRecord = !((int)$condition['uid'] > 0);
896  // Detection of version can be done by detecting the workspace of the user
897  $isUserInWorkspace = $this->‪getBackendUser()->workspace > 0;
898  if ((int)($condition['t3ver_oid'] ?? 0) > 0) {
899  $isRecordDetectedAsVersion = true;
900  } else {
901  $isRecordDetectedAsVersion = false;
902  }
903  // New records in a workspace are not handled as a version record
904  // if it's no new version, we detect versions like this:
905  // * if user is in workspace: always TRUE
906  // * if editor is in live ws: only TRUE if t3ver_oid > 0
907  $result = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord;
908  if (!$condition['isVersion']) {
909  $result = !$result;
910  }
911  return $result;
912  }
913 
920  protected function ‪matchUserCondition(array $condition): bool
921  {
922  $parameter = [
923  'record' => $condition['record'],
924  'flexContext' => $condition['flexContext'],
925  'flexformValueKey' => 'vDEF',
926  'conditionParameters' => $condition['parameters'],
927  ];
928  return (bool)GeneralUtility::callUserFunction($condition['function'], $parameter, $this);
929  }
930 
936  protected function ‪getBackendUser()
937  {
938  return ‪$GLOBALS['BE_USER'];
939  }
940 }
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\parseDisplayConditions
‪array parseDisplayConditions(array $result)
Definition: EvaluateDisplayConditions.php:63
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\addData
‪array addData(array $result)
Definition: EvaluateDisplayConditions.php:42
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\parseConditionRecursive
‪array parseConditionRecursive($condition, array $databaseRow, array $flexContext=[])
Definition: EvaluateDisplayConditions.php:200
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\matchRecordCondition
‪bool matchRecordCondition(array $condition)
Definition: EvaluateDisplayConditions.php:879
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\evaluateConditionRecursive
‪bool evaluateConditionRecursive(array $conditionArray)
Definition: EvaluateDisplayConditions.php:741
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication\isAdmin
‪bool isAdmin()
Definition: BackendUserAuthentication.php:292
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\parseSingleConditionString
‪array parseSingleConditionString(string $conditionString, array $databaseRow, array $flexContext=[])
Definition: EvaluateDisplayConditions.php:255
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\findFieldValue
‪mixed findFieldValue(string $givenFieldName, array $databaseRow, array $flexContext=[])
Definition: EvaluateDisplayConditions.php:450
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\matchFieldCondition
‪bool matchFieldCondition(array $condition)
Definition: EvaluateDisplayConditions.php:779
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\evaluateConditions
‪array evaluateConditions(array $result)
Definition: EvaluateDisplayConditions.php:619
‪TYPO3\CMS\Backend\Form\FormDataProvider
Definition: AbstractDatabaseRecordProvider.php:16
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions
Definition: EvaluateDisplayConditions.php:30
‪TYPO3\CMS\Backend\Form\FormDataProviderInterface
Definition: FormDataProviderInterface.php:23
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\matchVersionCondition
‪bool matchVersionCondition(array $condition)
Definition: EvaluateDisplayConditions.php:893
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\matchUserCondition
‪bool matchUserCondition(array $condition)
Definition: EvaluateDisplayConditions.php:920
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\getBackendUser
‪TYPO3 CMS Core Authentication BackendUserAuthentication getBackendUser()
Definition: EvaluateDisplayConditions.php:936