‪TYPO3CMS  ‪main
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 
24 
31 {
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 (str_starts_with($givenFieldName, 'parentRec.')) {
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 (str_starts_with($givenFieldName, 'parentRec.')) {
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 (str_starts_with($givenFieldName, 'parentRec.')) {
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 
616  protected function ‪evaluateConditions(array $result): array
617  {
618  // Evaluate normal tca fields first
619  $listOfFlexFieldNames = [];
620  foreach ($result['processedTca']['columns'] as $columnName => $columnConfiguration) {
621  $conditionResult = true;
622  if (isset($columnConfiguration['displayCond'])) {
623  $conditionResult = $this->‪evaluateConditionRecursive($columnConfiguration['displayCond']);
624  if (!$conditionResult) {
625  unset($result['processedTca']['columns'][$columnName]);
626  } else {
627  // Always unset the whole parsed display condition to save some memory, we're done with them
628  unset($result['processedTca']['columns'][$columnName]['displayCond']);
629  }
630  }
631  // If field was not removed and if it is a flex field, add to list of flex fields to scan
632  if ($conditionResult && ($columnConfiguration['config']['type'] ?? false) === 'flex') {
633  $listOfFlexFieldNames[] = $columnName;
634  }
635  }
636 
637  // Search for flex fields and evaluate sheet conditions throwing them away if needed
638  foreach ($listOfFlexFieldNames as $columnName) {
639  $columnConfiguration = $result['processedTca']['columns'][$columnName] ?? [];
640  foreach ($columnConfiguration['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
641  if (is_array($sheetConfiguration['ROOT']['displayCond'] ?? false)) {
642  if (!$this->‪evaluateConditionRecursive($sheetConfiguration['ROOT']['displayCond'])) {
643  unset($result['processedTca']['columns'][$columnName]['config']['ds']['sheets'][$sheetName]);
644  } else {
645  unset($result['processedTca']['columns'][$columnName]['config']['ds']['sheets'][$sheetName]['ROOT']['displayCond']);
646  }
647  }
648  }
649  }
650 
651  // With full sheets gone we loop over display conditions of single fields in flex to throw fields away if needed
652  $listOfFlexSectionContainers = [];
653  foreach ($listOfFlexFieldNames as $columnName) {
654  $columnConfiguration = $result['processedTca']['columns'][$columnName];
655  if (is_array($columnConfiguration['config']['ds']['sheets'])) {
656  foreach ($columnConfiguration['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
657  if (isset($sheetConfiguration['ROOT']['el']) && is_array($sheetConfiguration['ROOT']['el'])) {
658  foreach ($sheetConfiguration['ROOT']['el'] as $flexField => $flexConfiguration) {
659  $conditionResult = true;
660  if (isset($flexConfiguration['displayCond']) && is_array($flexConfiguration['displayCond'])) {
661  $conditionResult = $this->‪evaluateConditionRecursive($flexConfiguration['displayCond']);
662  if (!$conditionResult) {
663  unset(
664  $result['processedTca']['columns'][$columnName]['config']['ds']
665  ['sheets'][$sheetName]['ROOT']
666  ['el'][$flexField]
667  );
668  } else {
669  unset(
670  $result['processedTca']['columns'][$columnName]['config']['ds']
671  ['sheets'][$sheetName]['ROOT']
672  ['el'][$flexField]['displayCond']
673  );
674  }
675  }
676  // If it was not removed and if the field is a section container, add it to the section container list
677  if ($conditionResult
678  && isset($flexConfiguration['type']) && $flexConfiguration['type'] === 'array'
679  && isset($flexConfiguration['section']) && $flexConfiguration['section'] == 1
680  && isset($flexConfiguration['children']) && is_array($flexConfiguration['children'])
681  ) {
682  $listOfFlexSectionContainers[] = [
683  'columnName' => $columnName,
684  'sheetName' => $sheetName,
685  'flexField' => $flexField,
686  ];
687  }
688  }
689  }
690  }
691  }
692  }
693 
694  // Loop over found section container elements and evaluate their conditions
695  foreach ($listOfFlexSectionContainers as $flexSectionContainerPosition) {
696  $columnName = $flexSectionContainerPosition['columnName'];
697  $sheetName = $flexSectionContainerPosition['sheetName'];
698  $flexField = $flexSectionContainerPosition['flexField'];
699  $sectionElement = $result['processedTca']['columns'][$columnName]['config']['ds']
700  ['sheets'][$sheetName]['ROOT']
701  ['el'][$flexField];
702  foreach ($sectionElement['children'] as $containerInstanceName => $containerDataStructure) {
703  if (isset($containerDataStructure['el']) && is_array($containerDataStructure['el'])) {
704  foreach ($containerDataStructure['el'] as $containerElementName => $containerElementConfiguration) {
705  if (isset($containerElementConfiguration['displayCond']) && is_array($containerElementConfiguration['displayCond'])) {
706  if (!$this->‪evaluateConditionRecursive($containerElementConfiguration['displayCond'])) {
707  unset(
708  $result['processedTca']['columns'][$columnName]['config']['ds']
709  ['sheets'][$sheetName]['ROOT']
710  ['el'][$flexField]
711  ['children'][$containerInstanceName]
712  ['el'][$containerElementName]
713  );
714  } else {
715  unset(
716  $result['processedTca']['columns'][$columnName]['config']['ds']
717  ['sheets'][$sheetName]['ROOT']
718  ['el'][$flexField]
719  ['children'][$containerInstanceName]
720  ['el'][$containerElementName]['displayCond']
721  );
722  }
723  }
724  }
725  }
726  }
727  }
728 
729  return $result;
730  }
731 
738  protected function ‪evaluateConditionRecursive(array $conditionArray): bool
739  {
740  switch ($conditionArray['type']) {
741  case 'AND':
742  $result = true;
743  foreach ($conditionArray['subConditions'] as $subCondition) {
744  $result = $result && $this->‪evaluateConditionRecursive($subCondition);
745  }
746  return $result;
747  case 'OR':
748  $result = false;
749  foreach ($conditionArray['subConditions'] as $subCondition) {
750  $result = $result || $this->‪evaluateConditionRecursive($subCondition);
751  }
752  return $result;
753  case 'FIELD':
754  return $this->‪matchFieldCondition($conditionArray);
755  case 'HIDE_FOR_NON_ADMINS':
756  return (bool)$this->‪getBackendUser()->isAdmin();
757  case 'REC':
758  return $this->‪matchRecordCondition($conditionArray);
759  case 'VERSION':
760  return $this->‪matchVersionCondition($conditionArray);
761  case 'USER':
762  return $this->‪matchUserCondition($conditionArray);
763  }
764  return false;
765  }
766 
775  protected function ‪matchFieldCondition(array $condition): bool
776  {
777  $operator = $condition['operator'];
778  $operand = $condition['operand'];
779  $fieldValue = $condition['fieldValue'];
780  $result = false;
781  switch ($operator) {
782  case 'REQ':
783  if (is_array($fieldValue) && count($fieldValue) <= 1) {
784  $fieldValue = array_shift($fieldValue);
785  }
786  if ($operand) {
787  $result = (bool)$fieldValue;
788  } else {
789  $result = !$fieldValue;
790  }
791  break;
792  case '>':
793  if (is_array($fieldValue) && count($fieldValue) <= 1) {
794  $fieldValue = array_shift($fieldValue);
795  }
796  $result = $fieldValue > $operand;
797  break;
798  case '<':
799  if (is_array($fieldValue) && count($fieldValue) <= 1) {
800  $fieldValue = array_shift($fieldValue);
801  }
802  $result = $fieldValue < $operand;
803  break;
804  case '>=':
805  if (is_array($fieldValue) && count($fieldValue) <= 1) {
806  $fieldValue = array_shift($fieldValue);
807  }
808  if ($fieldValue === null) {
809  // If field value is null, this is NOT greater than or equal 0
810  // See test set "Field is not greater than or equal to zero if empty array given"
811  $result = false;
812  } else {
813  $result = $fieldValue >= $operand;
814  }
815  break;
816  case '<=':
817  if (is_array($fieldValue) && count($fieldValue) <= 1) {
818  $fieldValue = array_shift($fieldValue);
819  }
820  $result = $fieldValue <= $operand;
821  break;
822  case '-':
823  case '!-':
824  if (is_array($fieldValue) && count($fieldValue) <= 1) {
825  $fieldValue = array_shift($fieldValue);
826  }
827  $min = $condition['min'];
828  $max = $condition['max'];
829  $result = $fieldValue >= $min && $fieldValue <= $max;
830  if ($operator[0] === '!') {
831  $result = !$result;
832  }
833  break;
834  case '=':
835  case '!=':
836  if (is_array($fieldValue) && count($fieldValue) <= 1) {
837  $fieldValue = array_shift($fieldValue);
838  }
839  $result = $fieldValue == $operand;
840  if ($operator[0] === '!') {
841  $result = !$result;
842  }
843  break;
844  case 'IN':
845  case '!IN':
846  if (is_array($fieldValue)) {
847  $result = count(array_intersect($fieldValue, ‪GeneralUtility::trimExplode(',', $operand))) > 0;
848  } else {
849  $result = ‪GeneralUtility::inList($operand, $fieldValue);
850  }
851  if ($operator[0] === '!') {
852  $result = !$result;
853  }
854  break;
855  case 'BIT':
856  case '!BIT':
857  $result = (bool)((int)$fieldValue & $operand);
858  if ($operator[0] === '!') {
859  $result = !$result;
860  }
861  break;
862  }
863  return $result;
864  }
865 
874  protected function ‪matchRecordCondition(array $condition): bool
875  {
876  if ($condition['isNew']) {
877  return !((int)$condition['uid'] > 0);
878  }
879  return (int)$condition['uid'] > 0;
880  }
881 
887  protected function ‪matchVersionCondition(array $condition): bool
888  {
889  $isNewRecord = !((int)$condition['uid'] > 0);
890  // Detection of version can be done by detecting the workspace of the user
891  $isUserInWorkspace = $this->‪getBackendUser()->workspace > 0;
892  if ((int)($condition['t3ver_oid'] ?? 0) > 0) {
893  $isRecordDetectedAsVersion = true;
894  } else {
895  $isRecordDetectedAsVersion = false;
896  }
897  // New records in a workspace are not handled as a version record
898  // if it's no new version, we detect versions like this:
899  // * if user is in workspace: always TRUE
900  // * if editor is in live ws: only TRUE if t3ver_oid > 0
901  $result = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord;
902  if (!$condition['isVersion']) {
903  $result = !$result;
904  }
905  return $result;
906  }
907 
913  protected function ‪matchUserCondition(array $condition): bool
914  {
915  $parameter = [
916  'record' => $condition['record'],
917  'flexContext' => $condition['flexContext'],
918  'flexformValueKey' => 'vDEF',
919  'conditionParameters' => $condition['parameters'],
920  ];
921  return (bool)GeneralUtility::callUserFunction($condition['function'], $parameter, $this);
922  }
923 
925  {
926  return ‪$GLOBALS['BE_USER'];
927  }
928 }
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\matchUserCondition
‪matchUserCondition(array $condition)
Definition: EvaluateDisplayConditions.php:913
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\parseDisplayConditions
‪array parseDisplayConditions(array $result)
Definition: EvaluateDisplayConditions.php:63
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\getBackendUser
‪getBackendUser()
Definition: EvaluateDisplayConditions.php:924
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\addData
‪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\evaluateConditionRecursive
‪bool evaluateConditionRecursive(array $conditionArray)
Definition: EvaluateDisplayConditions.php:738
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\matchFieldCondition
‪matchFieldCondition(array $condition)
Definition: EvaluateDisplayConditions.php:775
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\parseSingleConditionString
‪array parseSingleConditionString(string $conditionString, array $databaseRow, array $flexContext=[])
Definition: EvaluateDisplayConditions.php:255
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪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\matchRecordCondition
‪matchRecordCondition(array $condition)
Definition: EvaluateDisplayConditions.php:874
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\matchVersionCondition
‪matchVersionCondition(array $condition)
Definition: EvaluateDisplayConditions.php:887
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Backend\Form\FormDataProvider
Definition: AbstractDatabaseRecordProvider.php:16
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions
Definition: EvaluateDisplayConditions.php:31
‪TYPO3\CMS\Backend\Form\FormDataProviderInterface
Definition: FormDataProviderInterface.php:23
‪TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions\evaluateConditions
‪evaluateConditions(array $result)
Definition: EvaluateDisplayConditions.php:616
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:422
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822