TYPO3 CMS  TYPO3_8-7
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 ($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],
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],
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  if (count($groupedDisplayConditions) < 2) {
213  throw new \RuntimeException(
214  'With multiple conditions combined by "' . $logicalOperator . '", there must be at least two sub conditions',
215  1481464101
216  );
217  }
218  $conditionArray = [
219  'type' => $logicalOperator,
220  'subConditions' => [],
221  ];
222  foreach ($groupedDisplayConditions as $key => $singleDisplayCondition) {
223  $key = strtoupper((string)$key);
224  if (($key === 'AND' || $key === 'OR') && is_array($singleDisplayCondition)) {
225  // Recursion statement: condition is 'AND' or 'OR' and is pointing to an array (should be conditions again)
226  $conditionArray['subConditions'][] = $this->parseConditionRecursive(
227  [$key => $singleDisplayCondition],
228  $databaseRow,
229  $flexContext
230  );
231  } else {
232  $conditionArray['subConditions'][] = $this->parseConditionRecursive(
233  $singleDisplayCondition,
234  $databaseRow,
235  $flexContext
236  );
237  }
238  }
239  }
240  } else {
241  throw new \RuntimeException(
242  'Condition must be either an array with sub conditions or a single condition string, type ' . gettype($condition) . ' given.',
243  1481381058
244  );
245  }
246  return $conditionArray;
247  }
248 
259  protected function parseSingleConditionString(string $conditionString, array $databaseRow, array $flexContext = []): array
260  {
261  $conditionArray = GeneralUtility::trimExplode(':', $conditionString, false, 4);
262  $namedConditionArray = [
263  'type' => $conditionArray[0],
264  ];
265  switch ($namedConditionArray['type']) {
266  case 'FIELD':
267  if (empty($conditionArray[1])) {
268  throw new \RuntimeException(
269  'Field condition "' . $conditionString . '" must have a field name as second part, none given.'
270  . 'Example: "FIELD:myField:=:myValue"',
271  1481385695
272  );
273  }
274  $fieldName = $conditionArray[1];
275  $allowedOperators = [ 'REQ', '>', '<', '>=', '<=', '-', '!-', '=', '!=', 'IN', '!IN', 'BIT', '!BIT' ];
276  if (empty($conditionArray[2]) || !in_array($conditionArray[2], $allowedOperators)) {
277  throw new \RuntimeException(
278  'Field condition "' . $conditionString . '" must have a valid operator as third part, non or invalid one given.'
279  . ' Valid operators are: "' . implode('", "', $allowedOperators) . '".'
280  . ' Example: "FIELD:myField:=:4"',
281  1481386239
282  );
283  }
284  $namedConditionArray['operator'] = $conditionArray[2];
285  if (!isset($conditionArray[3])) {
286  throw new \RuntimeException(
287  'Field condition "' . $conditionString . '" must have an operand as fourth part, none given.'
288  . ' Example: "FIELD:myField:=:4"',
289  1481401543
290  );
291  }
292  $operand = $conditionArray[3];
293  if ($namedConditionArray['operator'] === 'REQ') {
294  $operand = strtolower($operand);
295  if ($operand === 'true') {
296  $namedConditionArray['operand'] = true;
297  } elseif ($operand === 'false') {
298  $namedConditionArray['operand'] = false;
299  } else {
300  throw new \RuntimeException(
301  'Field condition "' . $conditionString . '" must have "true" or "false" as fourth part.'
302  . ' Example: "FIELD:myField:REQ:true',
303  1481401892
304  );
305  }
306  } elseif (in_array($namedConditionArray['operator'], [ '>', '<', '>=', '<=', 'BIT', '!BIT' ])) {
308  throw new \RuntimeException(
309  'Field condition "' . $conditionString . '" with comparison operator ' . $namedConditionArray['operator']
310  . ' must have a number as fourth part, ' . $operand . ' given. Example: "FIELD:myField:>:42"',
311  1481456806
312  );
313  }
314  $namedConditionArray['operand'] = (int)$operand;
315  } elseif ($namedConditionArray['operator'] === '-' || $namedConditionArray['operator'] === '!-') {
316  list($minimum, $maximum) = GeneralUtility::trimExplode('-', $operand);
318  throw new \RuntimeException(
319  'Field condition "' . $conditionString . '" with comparison operator ' . $namedConditionArray['operator']
320  . ' must have two numbers as fourth part, separated by dash, ' . $operand . ' given. Example: "FIELD:myField:-:1-3"',
321  1481457277
322  );
323  }
324  $namedConditionArray['operand'] = '';
325  $namedConditionArray['min'] = (int)$minimum;
326  $namedConditionArray['max'] = (int)$maximum;
327  } elseif ($namedConditionArray['operator'] === 'IN' || $namedConditionArray['operator'] === '!IN'
328  || $namedConditionArray['operator'] === '=' || $namedConditionArray['operator'] === '!='
329  ) {
330  $namedConditionArray['operand'] = $operand;
331  }
332  $namedConditionArray['fieldValue'] = $this->findFieldValue($fieldName, $databaseRow, $flexContext);
333  break;
334  case 'HIDE_FOR_NON_ADMINS':
335  break;
336  case 'REC':
337  if (empty($conditionArray[1]) || $conditionArray[1] !== 'NEW') {
338  throw new \RuntimeException(
339  'Record condition "' . $conditionString . '" must contain "NEW" keyword: either "REC:NEW:true" or "REC:NEW:false"',
340  1481384784
341  );
342  }
343  if (empty($conditionArray[2])) {
344  throw new \RuntimeException(
345  'Record condition "' . $conditionString . '" must have an operand "true" or "false", none given. Example: "REC:NEW:true"',
346  1481384947
347  );
348  }
349  $operand = strtolower($conditionArray[2]);
350  if ($operand === 'true') {
351  $namedConditionArray['isNew'] = true;
352  } elseif ($operand === 'false') {
353  $namedConditionArray['isNew'] = false;
354  } else {
355  throw new \RuntimeException(
356  'Record condition "' . $conditionString . '" must have an operand "true" or "false, example "REC:NEW:true", given: ' . $operand,
357  1481385173
358  );
359  }
360  // Programming error: There must be a uid available, other data providers should have taken care of that already
361  if (!array_key_exists('uid', $databaseRow)) {
362  throw new \RuntimeException(
363  'Required [\'databaseRow\'][\'uid\'] not found in data array',
364  1481467208
365  );
366  }
367  // May contain "NEW123..."
368  $namedConditionArray['uid'] = $databaseRow['uid'];
369  break;
370  case 'VERSION':
371  if (empty($conditionArray[1]) || $conditionArray[1] !== 'IS') {
372  throw new \RuntimeException(
373  'Version condition "' . $conditionString . '" must contain "IS" keyword: either "VERSION:IS:false" or "VERSION:IS:true"',
374  1481383660
375  );
376  }
377  if (empty($conditionArray[2])) {
378  throw new \RuntimeException(
379  'Version condition "' . $conditionString . '" must have an operand "true" or "false", none given. Example: "VERSION:IS:true',
380  1481383888
381  );
382  }
383  $operand = strtolower($conditionArray[2]);
384  if ($operand === 'true') {
385  $namedConditionArray['isVersion'] = true;
386  } elseif ($operand === 'false') {
387  $namedConditionArray['isVersion'] = false;
388  } else {
389  throw new \RuntimeException(
390  'Version condition "' . $conditionString . '" must have a "true" or "false" operand, example "VERSION:IS:true", given: ' . $operand,
391  1481384123
392  );
393  }
394  // Programming error: There must be a uid available, other data providers should have taken care of that already
395  if (!array_key_exists('uid', $databaseRow)) {
396  throw new \RuntimeException(
397  'Required [\'databaseRow\'][\'uid\'] not found in data array',
398  1481469854
399  );
400  }
401  $namedConditionArray['uid'] = $databaseRow['uid'];
402  if (array_key_exists('pid', $databaseRow)) {
403  $namedConditionArray['pid'] = $databaseRow['pid'];
404  }
405  if (array_key_exists('_ORIG_pid', $databaseRow)) {
406  $namedConditionArray['_ORIG_pid'] = $databaseRow['_ORIG_pid'];
407  }
408  break;
409  case 'USER':
410  if (empty($conditionArray[1])) {
411  throw new \RuntimeException(
412  'User function condition "' . $conditionString . '" must have a user function defined a second part, none given.'
413  . ' Correct format is USER:\My\User\Func->match:more:arguments,'
414  . ' given: ' . $conditionString,
415  1481382954
416  );
417  }
418  $namedConditionArray['function'] = $conditionArray[1];
419  array_shift($conditionArray);
420  array_shift($conditionArray);
421  $parameters = count($conditionArray) < 2
422  ? $conditionArray
423  : array_merge(
424  [$conditionArray[0]],
425  GeneralUtility::trimExplode(':', $conditionArray[1])
426  );
427  $namedConditionArray['parameters'] = $parameters;
428  $namedConditionArray['record'] = $databaseRow;
429  $namedConditionArray['flexContext'] = $flexContext;
430  break;
431  default:
432  throw new \RuntimeException(
433  'Unknown condition rule type "' . $namedConditionArray['type'] . '" with display condition "' . $conditionString . '"".',
434  1481381950
435  );
436  }
437  return $namedConditionArray;
438  }
439 
451  protected function findFieldValue(string $givenFieldName, array $databaseRow, array $flexContext = [])
452  {
453  $fieldValue = null;
454 
455  // Early return for "normal" tca fields
456  if (empty($flexContext)) {
457  if (array_key_exists($givenFieldName, $databaseRow)) {
458  $fieldValue = $databaseRow[$givenFieldName];
459  }
460  return $fieldValue;
461  }
462  if ($flexContext['context'] === 'flexSheet') {
463  // A display condition on a flex form sheet. Relatively simple: fieldName is either
464  // "parentRec.fieldName" pointing to a databaseRow field name, or "sheetName.fieldName" pointing
465  // to a field value from a neighbor field.
466  if (strpos($givenFieldName, 'parentRec.') === 0) {
467  $fieldName = substr($givenFieldName, 10);
468  if (array_key_exists($fieldName, $databaseRow)) {
469  $fieldValue = $databaseRow[$fieldName];
470  }
471  } else {
472  if (array_key_exists($givenFieldName, $flexContext['sheetNameFieldNames'])) {
473  if ($flexContext['currentSheetName'] === $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName']) {
474  throw new \RuntimeException(
475  'Configuring displayCond to "' . $givenFieldName . '" on flex form sheet "'
476  . $flexContext['currentSheetName'] . '" referencing a value from the same sheet does not make sense.',
477  1481485705
478  );
479  }
480  }
481  $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName'];
482  $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName'];
483  if (!isset($flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'])) {
484  throw new \RuntimeException(
485  'Flex form displayCond on sheet "' . $flexContext['currentSheetName'] . '" references field "' . $fieldName
486  . '" of sheet "' . $sheetName . '", but that field does not exist in current data structure',
487  1481488492
488  );
489  }
490  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'];
491  }
492  } elseif ($flexContext['context'] === 'flexField') {
493  // A display condition on a flex field. Handle "parentRec." similar to sheet conditions,
494  // get a list of "local" field names and see if they are used as reference, else see if a
495  // "sheetName.fieldName" field reference is given
496  if (strpos($givenFieldName, 'parentRec.') === 0) {
497  $fieldName = substr($givenFieldName, 10);
498  if (array_key_exists($fieldName, $databaseRow)) {
499  $fieldValue = $databaseRow[$fieldName];
500  }
501  } else {
502  $listOfLocalFlexFieldNames = array_keys(
503  $flexContext['flexFormDataStructure']['sheets'][$flexContext['currentSheetName']]['ROOT']['el']
504  );
505  if (in_array($givenFieldName, $listOfLocalFlexFieldNames, true)) {
506  // Condition references field name of the same sheet
507  $sheetName = $flexContext['currentSheetName'];
508  if (!isset($flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$givenFieldName]['vDEF'])) {
509  throw new \RuntimeException(
510  'Flex form displayCond on field "' . $flexContext['currentFieldName'] . '" on flex form sheet "'
511  . $flexContext['currentSheetName'] . '" references field "' . $givenFieldName . '", but a field value'
512  . ' does not exist in this sheet',
513  1481492953
514  );
515  }
516  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$givenFieldName]['vDEF'];
517  } elseif (in_array($givenFieldName, array_keys($flexContext['sheetNameFieldNames'], true))) {
518  // Condition references field name including a sheet name
519  $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName'];
520  $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName'];
521  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'];
522  } else {
523  throw new \RuntimeException(
524  'Flex form displayCond on field "' . $flexContext['currentFieldName'] . '" on flex form sheet "'
525  . $flexContext['currentSheetName'] . '" references a field or field / sheet combination "'
526  . $givenFieldName . '" that might be defined in given data structure but is not found in data values.',
527  1481496170
528  );
529  }
530  }
531  } elseif ($flexContext['context'] === 'flexContainerElement') {
532  // A display condition on a flex form section container element. Handle "parentRec.", compare to a
533  // list of local field names, compare to a list of field names from same sheet, compare to a list
534  // of sheet fields from other sheets.
535  if (strpos($givenFieldName, 'parentRec.') === 0) {
536  $fieldName = substr($givenFieldName, 10);
537  if (array_key_exists($fieldName, $databaseRow)) {
538  $fieldValue = $databaseRow[$fieldName];
539  }
540  } else {
541  $currentSheetName = $flexContext['currentSheetName'];
542  $currentFieldName = $flexContext['currentFieldName'];
543  $currentContainerIdentifier = $flexContext['currentContainerIdentifier'];
544  $currentContainerElementName = $flexContext['currentContainerElementName'];
545  $listOfLocalContainerElementNames = array_keys(
546  $flexContext['flexFormDataStructure']['sheets'][$currentSheetName]['ROOT']
547  ['el'][$currentFieldName]
548  ['children'][$currentContainerIdentifier]
549  ['el']
550  );
551  $listOfLocalContainerElementNamesWithSheetName = [];
552  foreach ($listOfLocalContainerElementNames as $aContainerElementName) {
553  $listOfLocalContainerElementNamesWithSheetName[$currentSheetName . '.' . $aContainerElementName] = [
554  'containerElementName' => $aContainerElementName,
555  ];
556  }
557  $listOfLocalFlexFieldNames = array_keys(
558  $flexContext['flexFormDataStructure']['sheets'][$currentSheetName]['ROOT']['el']
559  );
560  if (in_array($givenFieldName, $listOfLocalContainerElementNames, true)) {
561  // Condition references field of same container instance
562  $containerType = array_shift(array_keys(
563  $flexContext['flexFormRowData']['data'][$currentSheetName]
564  ['lDEF'][$currentFieldName]
565  ['el'][$currentContainerIdentifier]
566  ));
567  $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName]
568  ['lDEF'][$currentFieldName]
569  ['el'][$currentContainerIdentifier]
570  [$containerType]
571  ['el'][$givenFieldName]['vDEF'];
572  } elseif (in_array($givenFieldName, array_keys($listOfLocalContainerElementNamesWithSheetName, true))) {
573  // Condition references field name of same container instance and has sheet name included
574  $containerType = array_shift(array_keys(
575  $flexContext['flexFormRowData']['data'][$currentSheetName]
576  ['lDEF'][$currentFieldName]
577  ['el'][$currentContainerIdentifier]
578  ));
579  $fieldName = $listOfLocalContainerElementNamesWithSheetName[$givenFieldName]['containerElementName'];
580  $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName]
581  ['lDEF'][$currentFieldName]
582  ['el'][$currentContainerIdentifier]
583  [$containerType]
584  ['el'][$fieldName]['vDEF'];
585  } elseif (in_array($givenFieldName, $listOfLocalFlexFieldNames, true)) {
586  // Condition reference field name of sheet this section container is in
587  $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName]
588  ['lDEF'][$givenFieldName]['vDEF'];
589  } elseif (in_array($givenFieldName, array_keys($flexContext['sheetNameFieldNames'], true))) {
590  $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName'];
591  $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName'];
592  $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'];
593  } else {
594  $containerType = array_shift(array_keys(
595  $flexContext['flexFormRowData']['data'][$currentSheetName]
596  ['lDEF'][$currentFieldName]
597  ['el'][$currentContainerIdentifier]
598  ));
599  throw new \RuntimeException(
600  'Flex form displayCond on section container field "' . $currentContainerElementName . '" of container type "'
601  . $containerType . '" on flex form sheet "'
602  . $flexContext['currentSheetName'] . '" references a field or field / sheet combination "'
603  . $givenFieldName . '" that might be defined in given data structure but is not found in data values.',
604  1481634649
605  );
606  }
607  }
608  }
609 
610  return $fieldValue;
611  }
612 
620  protected function evaluateConditions(array $result): array
621  {
622  // Evaluate normal tca fields first
623  $listOfFlexFieldNames = [];
624  foreach ($result['processedTca']['columns'] as $columnName => $columnConfiguration) {
625  $conditionResult = true;
626  if (isset($columnConfiguration['displayCond'])) {
627  $conditionResult = $this->evaluateConditionRecursive($columnConfiguration['displayCond']);
628  if (!$conditionResult) {
629  unset($result['processedTca']['columns'][$columnName]);
630  } else {
631  // Always unset the whole parsed display condition to save some memory, we're done with them
632  unset($result['processedTca']['columns'][$columnName]['displayCond']);
633  }
634  }
635  // If field was not removed and if it is a flex field, add to list of flex fields to scan
636  if ($conditionResult && $columnConfiguration['config']['type'] === 'flex') {
637  $listOfFlexFieldNames[] = $columnName;
638  }
639  }
640 
641  // Search for flex fields and evaluate sheet conditions throwing them away if needed
642  foreach ($listOfFlexFieldNames as $columnName) {
643  $columnConfiguration = $result['processedTca']['columns'][$columnName];
644  foreach ($columnConfiguration['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
645  if (is_array($sheetConfiguration['ROOT']['displayCond'])) {
646  if (!$this->evaluateConditionRecursive($sheetConfiguration['ROOT']['displayCond'])) {
647  unset($result['processedTca']['columns'][$columnName]['config']['ds']['sheets'][$sheetName]);
648  } else {
649  unset($result['processedTca']['columns'][$columnName]['config']['ds']['sheets'][$sheetName]['ROOT']['displayCond']);
650  }
651  }
652  }
653  }
654 
655  // With full sheets gone we loop over display conditions of single fields in flex to throw fields away if needed
656  $listOfFlexSectionContainers = [];
657  foreach ($listOfFlexFieldNames as $columnName) {
658  $columnConfiguration = $result['processedTca']['columns'][$columnName];
659  if (is_array($columnConfiguration['config']['ds']['sheets'])) {
660  foreach ($columnConfiguration['config']['ds']['sheets'] as $sheetName => $sheetConfiguration) {
661  if (is_array($sheetConfiguration['ROOT']['el'])) {
662  foreach ($sheetConfiguration['ROOT']['el'] as $flexField => $flexConfiguration) {
663  $conditionResult = true;
664  if (is_array($flexConfiguration['displayCond'])) {
665  $conditionResult = $this->evaluateConditionRecursive($flexConfiguration['displayCond']);
666  if (!$conditionResult) {
667  unset(
668  $result['processedTca']['columns'][$columnName]['config']['ds']
669  ['sheets'][$sheetName]['ROOT']
670  ['el'][$flexField]
671  );
672  } else {
673  unset(
674  $result['processedTca']['columns'][$columnName]['config']['ds']
675  ['sheets'][$sheetName]['ROOT']
676  ['el'][$flexField]['displayCond']
677  );
678  }
679  }
680  // If it was not removed and if the field is a section container, add it to the section container list
681  if ($conditionResult
682  && isset($flexConfiguration['type']) && $flexConfiguration['type'] === 'array'
683  && isset($flexConfiguration['section']) && $flexConfiguration['section'] == 1
684  && isset($flexConfiguration['children']) && is_array($flexConfiguration['children'])
685  ) {
686  $listOfFlexSectionContainers[] = [
687  'columnName' => $columnName,
688  'sheetName' => $sheetName,
689  'flexField' => $flexField,
690  ];
691  }
692  }
693  }
694  }
695  }
696  }
697 
698  // Loop over found section container elements and evaluate their conditions
699  foreach ($listOfFlexSectionContainers as $flexSectionContainerPosition) {
700  $columnName = $flexSectionContainerPosition['columnName'];
701  $sheetName = $flexSectionContainerPosition['sheetName'];
702  $flexField = $flexSectionContainerPosition['flexField'];
703  $sectionElement = $result['processedTca']['columns'][$columnName]['config']['ds']
704  ['sheets'][$sheetName]['ROOT']
705  ['el'][$flexField];
706  foreach ($sectionElement['children'] as $containerInstanceName => $containerDataStructure) {
707  if (isset($containerDataStructure['el']) && is_array($containerDataStructure['el'])) {
708  foreach ($containerDataStructure['el'] as $containerElementName => $containerElementConfiguration) {
709  if (is_array($containerElementConfiguration['displayCond'])) {
710  if (!$this->evaluateConditionRecursive($containerElementConfiguration['displayCond'])) {
711  unset(
712  $result['processedTca']['columns'][$columnName]['config']['ds']
713  ['sheets'][$sheetName]['ROOT']
714  ['el'][$flexField]
715  ['children'][$containerInstanceName]
716  ['el'][$containerElementName]
717  );
718  } else {
719  unset(
720  $result['processedTca']['columns'][$columnName]['config']['ds']
721  ['sheets'][$sheetName]['ROOT']
722  ['el'][$flexField]
723  ['children'][$containerInstanceName]
724  ['el'][$containerElementName]['displayCond']
725  );
726  }
727  }
728  }
729  }
730  }
731  }
732 
733  return $result;
734  }
735 
742  protected function evaluateConditionRecursive(array $conditionArray): bool
743  {
744  switch ($conditionArray['type']) {
745  case 'AND':
746  $result = true;
747  foreach ($conditionArray['subConditions'] as $subCondition) {
748  $result = $result && $this->evaluateConditionRecursive($subCondition);
749  }
750  return $result;
751  case 'OR':
752  $result = false;
753  foreach ($conditionArray['subConditions'] as $subCondition) {
754  $result = $result || $this->evaluateConditionRecursive($subCondition);
755  }
756  return $result;
757  case 'FIELD':
758  return $this->matchFieldCondition($conditionArray);
759  case 'HIDE_FOR_NON_ADMINS':
760  return (bool)$this->getBackendUser()->isAdmin();
761  case 'REC':
762  return $this->matchRecordCondition($conditionArray);
763  case 'VERSION':
764  return $this->matchVersionCondition($conditionArray);
765  case 'USER':
766  return $this->matchUserCondition($conditionArray);
767  }
768  return false;
769  }
770 
780  protected function matchFieldCondition(array $condition): bool
781  {
782  $operator = $condition['operator'];
783  $operand = $condition['operand'];
784  $fieldValue = $condition['fieldValue'];
785  $result = false;
786  switch ($operator) {
787  case 'REQ':
788  if (is_array($fieldValue) && count($fieldValue) <= 1) {
789  $fieldValue = array_shift($fieldValue);
790  }
791  if ($operand) {
792  $result = (bool)$fieldValue;
793  } else {
794  $result = !$fieldValue;
795  }
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  $result = $fieldValue < $operand;
808  break;
809  case '>=':
810  if (is_array($fieldValue) && count($fieldValue) <= 1) {
811  $fieldValue = array_shift($fieldValue);
812  }
813  if ($fieldValue === null) {
814  // If field value is null, this is NOT greater than or equal 0
815  // See test set "Field is not greater than or equal to zero if empty array given"
816  $result = false;
817  } else {
818  $result = $fieldValue >= $operand;
819  }
820  break;
821  case '<=':
822  if (is_array($fieldValue) && count($fieldValue) <= 1) {
823  $fieldValue = array_shift($fieldValue);
824  }
825  $result = $fieldValue <= $operand;
826  break;
827  case '-':
828  case '!-':
829  if (is_array($fieldValue) && count($fieldValue) <= 1) {
830  $fieldValue = array_shift($fieldValue);
831  }
832  $min = $condition['min'];
833  $max = $condition['max'];
834  $result = $fieldValue >= $min && $fieldValue <= $max;
835  if ($operator[0] === '!') {
836  $result = !$result;
837  }
838  break;
839  case '=':
840  case '!=':
841  if (is_array($fieldValue) && count($fieldValue) <= 1) {
842  $fieldValue = array_shift($fieldValue);
843  }
844  $result = $fieldValue == $operand;
845  if ($operator[0] === '!') {
846  $result = !$result;
847  }
848  break;
849  case 'IN':
850  case '!IN':
851  if (is_array($fieldValue)) {
852  $result = count(array_intersect($fieldValue, GeneralUtility::trimExplode(',', $operand))) > 0;
853  } else {
854  $result = GeneralUtility::inList($operand, $fieldValue);
855  }
856  if ($operator[0] === '!') {
857  $result = !$result;
858  }
859  break;
860  case 'BIT':
861  case '!BIT':
862  $result = (bool)((int)$fieldValue & $operand);
863  if ($operator[0] === '!') {
864  $result = !$result;
865  }
866  break;
867  }
868  return $result;
869  }
870 
880  protected function matchRecordCondition(array $condition): bool
881  {
882  if ($condition['isNew']) {
883  return !((int)$condition['uid'] > 0);
884  }
885  return (int)$condition['uid'] > 0;
886  }
887 
894  protected function matchVersionCondition(array $condition): bool
895  {
896  $isNewRecord = !((int)$condition['uid'] > 0);
897  // Detection of version can be done by detecting the workspace of the user
898  $isUserInWorkspace = $this->getBackendUser()->workspace > 0;
899  if ((array_key_exists('pid', $condition) && (int)$condition['pid'] === -1)
900  || (array_key_exists('_ORIG_pid', $condition) && (int)$condition['_ORIG_pid'] === -1)
901  ) {
902  $isRecordDetectedAsVersion = true;
903  } else {
904  $isRecordDetectedAsVersion = false;
905  }
906  // New records in a workspace are not handled as a version record
907  // if it's no new version, we detect versions like this:
908  // * if user is in workspace: always TRUE
909  // * if editor is in live ws: only TRUE if pid == -1
910  $result = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord;
911  if (!$condition['isVersion']) {
912  $result = !$result;
913  }
914  return $result;
915  }
916 
923  protected function matchUserCondition(array $condition): bool
924  {
925  $parameter = [
926  'record' => $condition['record'],
927  'flexContext' => $condition['flexContext'],
928  'flexformValueKey' => 'vDEF',
929  'conditionParameters' => $condition['parameters'],
930  ];
931  return (bool)GeneralUtility::callUserFunction($condition['function'], $parameter, $this);
932  }
933 
939  protected function getBackendUser()
940  {
941  return $GLOBALS['BE_USER'];
942  }
943 }
parseSingleConditionString(string $conditionString, array $databaseRow, array $flexContext=[])
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
parseConditionRecursive($condition, array $databaseRow, array $flexContext=[])
findFieldValue(string $givenFieldName, array $databaseRow, array $flexContext=[])
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']