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