| Total Complexity | 196 | 
| Total Lines | 907 | 
| Duplicated Lines | 17.31 % | 
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like EvaluateDisplayConditions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use EvaluateDisplayConditions, and based on these observations, apply Extract Interface, too.
| 1 | <?php | ||
| 27 | class EvaluateDisplayConditions implements FormDataProviderInterface | ||
| 28 | { | ||
| 29 | /** | ||
| 30 | * Remove fields from processedTca columns that should not be displayed. | ||
| 31 | * | ||
| 32 | * Strategy of the parser is to first find all displayCond in given tca | ||
| 33 | * and within all type=flex fields to parse them into an array. This condition | ||
| 34 | * array contains all information to evaluate that condition in a second | ||
| 35 | * step that - depending on evaluation result - then throws away or keeps the field. | ||
| 36 | * | ||
| 37 | * @param array $result | ||
| 38 | * @return array | ||
| 39 | */ | ||
| 40 | public function addData(array $result): array | ||
| 41 |     { | ||
| 42 | $result = $this->parseDisplayConditions($result); | ||
| 43 | $result = $this->evaluateConditions($result); | ||
| 44 | return $result; | ||
| 45 | } | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Find all 'displayCond' in TCA and flex forms and substitute them with an | ||
| 49 | * array representation that contains all relevant data to | ||
| 50 | * evaluate the condition later. For "FIELD" conditions the helper methods | ||
| 51 | * findFieldValue() is used to find the value of the referenced field to put | ||
| 52 | * that value into the returned array, too. This is important since the referenced | ||
| 53 | * field is "relative" to the position of the field that has the display condition. | ||
| 54 | * For instance, "FIELD:aField:=:foo" within a flex form field references a field | ||
| 55 | * value from the same sheet, and there are many more complex scenarios to resolve. | ||
| 56 | * | ||
| 57 | * @param array $result Incoming result array | ||
| 58 | * @throws \RuntimeException | ||
| 59 | * @return array Modified result array with all displayCond parsed into arrays | ||
| 60 | */ | ||
| 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 | |||
| 188 | /** | ||
| 189 | * Parse a condition into an array representation and validate syntax. Handles nested conditions combined with AND and OR. | ||
| 190 | * Calls itself recursive for nesting and logically combined conditions. | ||
| 191 | * | ||
| 192 | * @param mixed $condition Either an array with multiple conditions combined with AND or OR, or a single condition string | ||
| 193 | * @param array $databaseRow Incoming full database row | ||
| 194 | * @param array $flexContext Detailed flex context if display condition is within a flex field, needed to determine field value for "FIELD" conditions | ||
| 195 | * @throws \RuntimeException | ||
| 196 | * @return array Array representation of that condition, see unit tests for details on syntax | ||
| 197 | */ | ||
| 198 | protected function parseConditionRecursive($condition, array $databaseRow, array $flexContext = []): array | ||
| 247 | } | ||
| 248 | |||
| 249 | /** | ||
| 250 | * Parse a single condition string into pieces, validate them and return | ||
| 251 | * an array representation. | ||
| 252 | * | ||
| 253 | * @param string $conditionString Given condition string like "VERSION:IS:true" | ||
| 254 | * @param array $databaseRow Incoming full database row | ||
| 255 | * @param array $flexContext Detailed flex context if display condition is within a flex field, needed to determine field value for "FIELD" conditions | ||
| 256 | * @return array Validated name array, example: [ type="VERSION", isVersion="true" ] | ||
| 257 | * @throws \RuntimeException | ||
| 258 | */ | ||
| 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 | View Code Duplication |                 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 | View Code Duplication |                     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' ])) { | ||
| 307 | View Code Duplication |                     if (!MathUtility::canBeInterpretedAsInteger($operand)) { | |
| 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); | ||
| 317 | View Code Duplication |                     if (!MathUtility::canBeInterpretedAsInteger($minimum) || !MathUtility::canBeInterpretedAsInteger($maximum)) { | |
| 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 | View Code Duplication |                 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 | View Code Duplication |                 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 | View Code Duplication |                 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 | View Code Duplication |                 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 | View Code Duplication |                 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 | $namedConditionArray['parameters'] = $conditionArray; | ||
| 422 | $namedConditionArray['record'] = $databaseRow; | ||
| 423 | break; | ||
| 424 | default: | ||
| 425 | throw new \RuntimeException( | ||
| 426 | 'Unknown condition rule type "' . $namedConditionArray['type'] . '" with display condition "' . $conditionString . '"".', | ||
| 427 | 1481381950 | ||
| 428 | ); | ||
| 429 | } | ||
| 430 | return $namedConditionArray; | ||
| 431 | } | ||
| 432 | |||
| 433 | /** | ||
| 434 | * Find field value the condition refers to for "FIELD:" conditions. For "normal" TCA fields this is the value of | ||
| 435 | * a "neighbor" field, but in flex form context it can be prepended with a sheet name. The method sorts out the | ||
| 436 | * details and returns the current field value. | ||
| 437 | * | ||
| 438 | * @param string $givenFieldName The full name used in displayCond. Can have sheet names included in flex context | ||
| 439 | * @param array $databaseRow Incoming database row values | ||
| 440 | * @param array $flexContext Detailed flex context if display condition is within a flex field, needed to determine field value for "FIELD" conditions | ||
| 441 | * @throws \RuntimeException | ||
| 442 | * @return mixed The current field value from database row or a deeper flex form structure field. | ||
| 443 | */ | ||
| 444 | protected function findFieldValue(string $givenFieldName, array $databaseRow, array $flexContext = []) | ||
| 445 |     { | ||
| 446 | $fieldValue = null; | ||
| 447 | |||
| 448 | // Early return for "normal" tca fields | ||
| 449 |         if (empty($flexContext)) { | ||
| 450 |             if (array_key_exists($givenFieldName, $databaseRow)) { | ||
| 451 | $fieldValue = $databaseRow[$givenFieldName]; | ||
| 452 | } | ||
| 453 | return $fieldValue; | ||
| 454 | } | ||
| 455 |         if ($flexContext['context'] === 'flexSheet') { | ||
| 456 | // A display condition on a flex form sheet. Relatively simple: fieldName is either | ||
| 457 | // "parentRec.fieldName" pointing to a databaseRow field name, or "sheetName.fieldName" pointing | ||
| 458 | // to a field value from a neighbor field. | ||
| 459 |             if (strpos($givenFieldName, 'parentRec.') === 0) { | ||
| 460 | $fieldName = substr($givenFieldName, 10); | ||
| 461 |                 if (array_key_exists($fieldName, $databaseRow)) { | ||
| 462 | $fieldValue = $databaseRow[$fieldName]; | ||
| 463 | } | ||
| 464 |             } else { | ||
| 465 |                 if (array_key_exists($givenFieldName, $flexContext['sheetNameFieldNames'])) { | ||
| 466 |                     if ($flexContext['currentSheetName'] === $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName']) { | ||
| 467 | throw new \RuntimeException( | ||
| 468 | 'Configuring displayCond to "' . $givenFieldName . '" on flex form sheet "' | ||
| 469 | . $flexContext['currentSheetName'] . '" referencing a value from the same sheet does not make sense.', | ||
| 470 | 1481485705 | ||
| 471 | ); | ||
| 472 | } | ||
| 473 | } | ||
| 474 | $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName']; | ||
| 475 | $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName']; | ||
| 476 | View Code Duplication |                 if (!isset($flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF'])) { | |
| 477 | throw new \RuntimeException( | ||
| 478 | 'Flex form displayCond on sheet "' . $flexContext['currentSheetName'] . '" references field "' . $fieldName | ||
| 479 | . '" of sheet "' . $sheetName . '", but that field does not exist in current data structure', | ||
| 480 | 1481488492 | ||
| 481 | ); | ||
| 482 | } | ||
| 483 | $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF']; | ||
| 484 | } | ||
| 485 |         } elseif ($flexContext['context'] === 'flexField') { | ||
| 486 | // A display condition on a flex field. Handle "parentRec." similar to sheet conditions, | ||
| 487 | // get a list of "local" field names and see if they are used as reference, else see if a | ||
| 488 | // "sheetName.fieldName" field reference is given | ||
| 489 |             if (strpos($givenFieldName, 'parentRec.') === 0) { | ||
| 490 | $fieldName = substr($givenFieldName, 10); | ||
| 491 |                 if (array_key_exists($fieldName, $databaseRow)) { | ||
| 492 | $fieldValue = $databaseRow[$fieldName]; | ||
| 493 | } | ||
| 494 |             } else { | ||
| 495 | $listOfLocalFlexFieldNames = array_keys( | ||
| 496 | $flexContext['flexFormDataStructure']['sheets'][$flexContext['currentSheetName']]['ROOT']['el'] | ||
| 497 | ); | ||
| 498 |                 if (in_array($givenFieldName, $listOfLocalFlexFieldNames, true)) { | ||
| 499 | // Condition references field name of the same sheet | ||
| 500 | $sheetName = $flexContext['currentSheetName']; | ||
| 501 | View Code Duplication |                     if (!isset($flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$givenFieldName]['vDEF'])) { | |
| 502 | throw new \RuntimeException( | ||
| 503 | 'Flex form displayCond on field "' . $flexContext['currentFieldName'] . '" on flex form sheet "' | ||
| 504 | . $flexContext['currentSheetName'] . '" references field "' . $givenFieldName . '", but a field value' | ||
| 505 | . ' does not exist in this sheet', | ||
| 506 | 1481492953 | ||
| 507 | ); | ||
| 508 | } | ||
| 509 | $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$givenFieldName]['vDEF']; | ||
| 510 | View Code Duplication |                 } elseif (in_array($givenFieldName, array_keys($flexContext['sheetNameFieldNames'], true))) { | |
| 511 | // Condition references field name including a sheet name | ||
| 512 | $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName']; | ||
| 513 | $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName']; | ||
| 514 | $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF']; | ||
| 515 |                 } else { | ||
| 516 | throw new \RuntimeException( | ||
| 517 | 'Flex form displayCond on field "' . $flexContext['currentFieldName'] . '" on flex form sheet "' | ||
| 518 | . $flexContext['currentSheetName'] . '" references a field or field / sheet combination "' | ||
| 519 | . $givenFieldName . '" that might be defined in given data structure but is not found in data values.', | ||
| 520 | 1481496170 | ||
| 521 | ); | ||
| 522 | } | ||
| 523 | } | ||
| 524 |         } elseif ($flexContext['context'] === 'flexContainerElement') { | ||
| 525 | // A display condition on a flex form section container element. Handle "parentRec.", compare to a | ||
| 526 | // list of local field names, compare to a list of field names from same sheet, compare to a list | ||
| 527 | // of sheet fields from other sheets. | ||
| 528 |             if (strpos($givenFieldName, 'parentRec.') === 0) { | ||
| 529 | $fieldName = substr($givenFieldName, 10); | ||
| 530 |                 if (array_key_exists($fieldName, $databaseRow)) { | ||
| 531 | $fieldValue = $databaseRow[$fieldName]; | ||
| 532 | } | ||
| 533 |             } else { | ||
| 534 | $currentSheetName = $flexContext['currentSheetName']; | ||
| 535 | $currentFieldName = $flexContext['currentFieldName']; | ||
| 536 | $currentContainerIdentifier = $flexContext['currentContainerIdentifier']; | ||
| 537 | $currentContainerElementName = $flexContext['currentContainerElementName']; | ||
| 538 | $listOfLocalContainerElementNames = array_keys( | ||
| 539 | $flexContext['flexFormDataStructure']['sheets'][$currentSheetName]['ROOT'] | ||
| 540 | ['el'][$currentFieldName] | ||
| 541 | ['children'][$currentContainerIdentifier] | ||
| 542 | ['el'] | ||
| 543 | ); | ||
| 544 | $listOfLocalContainerElementNamesWithSheetName = []; | ||
| 545 |                 foreach ($listOfLocalContainerElementNames as $aContainerElementName) { | ||
| 546 | $listOfLocalContainerElementNamesWithSheetName[$currentSheetName . '.' . $aContainerElementName] = [ | ||
| 547 | 'containerElementName' => $aContainerElementName, | ||
| 548 | ]; | ||
| 549 | } | ||
| 550 | $listOfLocalFlexFieldNames = array_keys( | ||
| 551 | $flexContext['flexFormDataStructure']['sheets'][$currentSheetName]['ROOT']['el'] | ||
| 552 | ); | ||
| 553 |                 if (in_array($givenFieldName, $listOfLocalContainerElementNames, true)) { | ||
| 554 | // Condition references field of same container instance | ||
| 555 | $containerType = array_shift(array_keys( | ||
|  | |||
| 556 | $flexContext['flexFormRowData']['data'][$currentSheetName] | ||
| 557 | ['lDEF'][$currentFieldName] | ||
| 558 | ['el'][$currentContainerIdentifier] | ||
| 559 | )); | ||
| 560 | $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName] | ||
| 561 | ['lDEF'][$currentFieldName] | ||
| 562 | ['el'][$currentContainerIdentifier] | ||
| 563 | [$containerType] | ||
| 564 | ['el'][$givenFieldName]['vDEF']; | ||
| 565 |                 } elseif (in_array($givenFieldName, array_keys($listOfLocalContainerElementNamesWithSheetName, true))) { | ||
| 566 | // Condition references field name of same container instance and has sheet name included | ||
| 567 | $containerType = array_shift(array_keys( | ||
| 568 | $flexContext['flexFormRowData']['data'][$currentSheetName] | ||
| 569 | ['lDEF'][$currentFieldName] | ||
| 570 | ['el'][$currentContainerIdentifier] | ||
| 571 | )); | ||
| 572 | $fieldName = $listOfLocalContainerElementNamesWithSheetName[$givenFieldName]['containerElementName']; | ||
| 573 | $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName] | ||
| 574 | ['lDEF'][$currentFieldName] | ||
| 575 | ['el'][$currentContainerIdentifier] | ||
| 576 | [$containerType] | ||
| 577 | ['el'][$fieldName]['vDEF']; | ||
| 578 |                 } elseif (in_array($givenFieldName, $listOfLocalFlexFieldNames, true)) { | ||
| 579 | // Condition reference field name of sheet this section container is in | ||
| 580 | $fieldValue = $flexContext['flexFormRowData']['data'][$currentSheetName] | ||
| 581 | ['lDEF'][$givenFieldName]['vDEF']; | ||
| 582 | View Code Duplication |                 } elseif (in_array($givenFieldName, array_keys($flexContext['sheetNameFieldNames'], true))) { | |
| 583 | $sheetName = $flexContext['sheetNameFieldNames'][$givenFieldName]['sheetName']; | ||
| 584 | $fieldName = $flexContext['sheetNameFieldNames'][$givenFieldName]['fieldName']; | ||
| 585 | $fieldValue = $flexContext['flexFormRowData']['data'][$sheetName]['lDEF'][$fieldName]['vDEF']; | ||
| 586 |                 } else { | ||
| 587 | $containerType = array_shift(array_keys( | ||
| 588 | $flexContext['flexFormRowData']['data'][$currentSheetName] | ||
| 589 | ['lDEF'][$currentFieldName] | ||
| 590 | ['el'][$currentContainerIdentifier] | ||
| 591 | )); | ||
| 592 | throw new \RuntimeException( | ||
| 593 | 'Flex form displayCond on section container field "' . $currentContainerElementName . '" of container type "' | ||
| 594 | . $containerType . '" on flex form sheet "' | ||
| 595 | . $flexContext['currentSheetName'] . '" references a field or field / sheet combination "' | ||
| 596 | . $givenFieldName . '" that might be defined in given data structure but is not found in data values.', | ||
| 597 | 1481634649 | ||
| 598 | ); | ||
| 599 | } | ||
| 600 | } | ||
| 601 | } | ||
| 602 | |||
| 603 | return $fieldValue; | ||
| 604 | } | ||
| 605 | |||
| 606 | /** | ||
| 607 | * Loop through TCA, find prepared conditions and evaluate them. Delete either the | ||
| 608 | * field itself if the condition did not match, or the 'displayCond' in TCA. | ||
| 609 | * | ||
| 610 | * @param array $result | ||
| 611 | * @return array | ||
| 612 | */ | ||
| 613 | protected function evaluateConditions(array $result): array | ||
| 727 | } | ||
| 728 | |||
| 729 | /** | ||
| 730 | * Evaluate a condition recursive by evaluating the single condition type | ||
| 731 | * | ||
| 732 | * @param array $conditionArray The condition to evaluate, possibly with subConditions for AND and OR types | ||
| 733 | * @return bool true if the condition matched | ||
| 734 | */ | ||
| 735 | protected function evaluateConditionRecursive(array $conditionArray): bool | ||
| 736 |     { | ||
| 737 |         switch ($conditionArray['type']) { | ||
| 738 | View Code Duplication | case 'AND': | |
| 739 | $result = true; | ||
| 740 |                 foreach ($conditionArray['subConditions'] as $subCondition) { | ||
| 741 | $result = $result && $this->evaluateConditionRecursive($subCondition); | ||
| 742 | } | ||
| 743 | return $result; | ||
| 744 | View Code Duplication | case 'OR': | |
| 745 | $result = false; | ||
| 746 |                 foreach ($conditionArray['subConditions'] as $subCondition) { | ||
| 747 | $result = $result || $this->evaluateConditionRecursive($subCondition); | ||
| 748 | } | ||
| 749 | return $result; | ||
| 750 | case 'FIELD': | ||
| 751 | return $this->matchFieldCondition($conditionArray); | ||
| 752 | case 'HIDE_FOR_NON_ADMINS': | ||
| 753 | return (bool)$this->getBackendUser()->isAdmin(); | ||
| 754 | case 'REC': | ||
| 755 | return $this->matchRecordCondition($conditionArray); | ||
| 756 | case 'VERSION': | ||
| 757 | return $this->matchVersionCondition($conditionArray); | ||
| 758 | case 'USER': | ||
| 759 | return $this->matchUserCondition($conditionArray); | ||
| 760 | } | ||
| 761 | return false; | ||
| 762 | } | ||
| 763 | |||
| 764 | /** | ||
| 765 | * Evaluates conditions concerning a field of the current record. | ||
| 766 | * | ||
| 767 | * Example: | ||
| 768 | * "FIELD:sys_language_uid:>:0" => TRUE, if the field 'sys_language_uid' is greater than 0 | ||
| 769 | * | ||
| 770 | * @param array $condition Condition array | ||
| 771 | * @return bool | ||
| 772 | */ | ||
| 773 | protected function matchFieldCondition(array $condition): bool | ||
| 774 |     { | ||
| 775 | $operator = $condition['operator']; | ||
| 776 | $operand = $condition['operand']; | ||
| 777 | $fieldValue = $condition['fieldValue']; | ||
| 778 | $result = false; | ||
| 779 |         switch ($operator) { | ||
| 780 | case 'REQ': | ||
| 781 |                 if (is_array($fieldValue) && count($fieldValue) <= 1) { | ||
| 782 | $fieldValue = array_shift($fieldValue); | ||
| 783 | } | ||
| 784 |                 if ($operand) { | ||
| 785 | $result = (bool)$fieldValue; | ||
| 786 |                 } else { | ||
| 787 | $result = !$fieldValue; | ||
| 788 | } | ||
| 789 | break; | ||
| 790 | View Code Duplication | case '>': | |
| 791 |                 if (is_array($fieldValue) && count($fieldValue) <= 1) { | ||
| 792 | $fieldValue = array_shift($fieldValue); | ||
| 793 | } | ||
| 794 | $result = $fieldValue > $operand; | ||
| 795 | break; | ||
| 796 | View Code Duplication | case '<': | |
| 797 |                 if (is_array($fieldValue) && count($fieldValue) <= 1) { | ||
| 798 | $fieldValue = array_shift($fieldValue); | ||
| 799 | } | ||
| 800 | $result = $fieldValue < $operand; | ||
| 801 | break; | ||
| 802 | case '>=': | ||
| 803 |                 if (is_array($fieldValue) && count($fieldValue) <= 1) { | ||
| 804 | $fieldValue = array_shift($fieldValue); | ||
| 805 | } | ||
| 806 |                 if ($fieldValue === null) { | ||
| 807 | // If field value is null, this is NOT greater than or equal 0 | ||
| 808 | // See test set "Field is not greater than or equal to zero if empty array given" | ||
| 809 | $result = false; | ||
| 810 |                 } else { | ||
| 811 | $result = $fieldValue >= $operand; | ||
| 812 | } | ||
| 813 | break; | ||
| 814 | View Code Duplication | case '<=': | |
| 815 |                 if (is_array($fieldValue) && count($fieldValue) <= 1) { | ||
| 816 | $fieldValue = array_shift($fieldValue); | ||
| 817 | } | ||
| 818 | $result = $fieldValue <= $operand; | ||
| 819 | break; | ||
| 820 | case '-': | ||
| 821 | case '!-': | ||
| 822 |                 if (is_array($fieldValue) && count($fieldValue) <= 1) { | ||
| 823 | $fieldValue = array_shift($fieldValue); | ||
| 824 | } | ||
| 825 | $min = $condition['min']; | ||
| 826 | $max = $condition['max']; | ||
| 827 | $result = $fieldValue >= $min && $fieldValue <= $max; | ||
| 828 |                 if ($operator[0] === '!') { | ||
| 829 | $result = !$result; | ||
| 830 | } | ||
| 831 | break; | ||
| 832 | case '=': | ||
| 833 | case '!=': | ||
| 834 |                 if (is_array($fieldValue) && count($fieldValue) <= 1) { | ||
| 835 | $fieldValue = array_shift($fieldValue); | ||
| 836 | } | ||
| 837 | $result = $fieldValue == $operand; | ||
| 838 |                 if ($operator[0] === '!') { | ||
| 839 | $result = !$result; | ||
| 840 | } | ||
| 841 | break; | ||
| 842 | case 'IN': | ||
| 843 | case '!IN': | ||
| 844 |                 if (is_array($fieldValue)) { | ||
| 845 |                     $result = count(array_intersect($fieldValue, GeneralUtility::trimExplode(',', $operand))) > 0; | ||
| 846 |                 } else { | ||
| 847 | $result = GeneralUtility::inList($operand, $fieldValue); | ||
| 848 | } | ||
| 849 |                 if ($operator[0] === '!') { | ||
| 850 | $result = !$result; | ||
| 851 | } | ||
| 852 | break; | ||
| 853 | case 'BIT': | ||
| 854 | case '!BIT': | ||
| 855 | $result = (bool)((int)$fieldValue & $operand); | ||
| 856 |                 if ($operator[0] === '!') { | ||
| 857 | $result = !$result; | ||
| 858 | } | ||
| 859 | break; | ||
| 860 | } | ||
| 861 | return $result; | ||
| 862 | } | ||
| 863 | |||
| 864 | /** | ||
| 865 | * Evaluates conditions concerning the status of the current record. | ||
| 866 | * | ||
| 867 | * Example: | ||
| 868 | * "REC:NEW:FALSE" => TRUE, if the record is already persisted (has a uid > 0) | ||
| 869 | * | ||
| 870 | * @param array $condition Condition array | ||
| 871 | * @return bool | ||
| 872 | */ | ||
| 873 | protected function matchRecordCondition(array $condition): bool | ||
| 874 |     { | ||
| 875 |         if ($condition['isNew']) { | ||
| 876 | return !((int)$condition['uid'] > 0); | ||
| 877 | } | ||
| 878 | return (int)$condition['uid'] > 0; | ||
| 879 | } | ||
| 880 | |||
| 881 | /** | ||
| 882 | * Evaluates whether the current record is versioned. | ||
| 883 | * | ||
| 884 | * @param array $condition Condition array | ||
| 885 | * @return bool | ||
| 886 | */ | ||
| 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 ((array_key_exists('pid', $condition) && (int)$condition['pid'] === -1) | ||
| 893 |             || (array_key_exists('_ORIG_pid', $condition) && (int)$condition['_ORIG_pid'] === -1) | ||
| 894 |         ) { | ||
| 895 | $isRecordDetectedAsVersion = true; | ||
| 896 |         } else { | ||
| 897 | $isRecordDetectedAsVersion = false; | ||
| 898 | } | ||
| 899 | // New records in a workspace are not handled as a version record | ||
| 900 | // if it's no new version, we detect versions like this: | ||
| 901 | // * if user is in workspace: always TRUE | ||
| 902 | // * if editor is in live ws: only TRUE if pid == -1 | ||
| 903 | $result = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord; | ||
| 904 |         if (!$condition['isVersion']) { | ||
| 905 | $result = !$result; | ||
| 906 | } | ||
| 907 | return $result; | ||
| 908 | } | ||
| 909 | |||
| 910 | /** | ||
| 911 | * Evaluates via the referenced user-defined method | ||
| 912 | * | ||
| 913 | * @param array $condition Condition array | ||
| 914 | * @return bool | ||
| 915 | */ | ||
| 916 | protected function matchUserCondition(array $condition): bool | ||
| 917 |     { | ||
| 918 | $parameter = [ | ||
| 919 | 'record' => $condition['record'], | ||
| 920 | 'flexformValueKey' => 'vDEF', | ||
| 921 | 'conditionParameters' => $condition['parameters'], | ||
| 922 | ]; | ||
| 923 | return (bool)GeneralUtility::callUserFunction($condition['function'], $parameter, $this); | ||
| 924 | } | ||
| 925 | |||
| 926 | /** | ||
| 927 | * Get current backend user | ||
| 928 | * | ||
| 929 | * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication | ||
| 930 | */ | ||
| 931 | protected function getBackendUser() | ||
| 934 | } | ||
| 935 | } | ||
| 936 |