[FEATURE] Reusable getCategoryFieldsForTable itemsProcFunc
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Utility / DisplayConditionEvaluator.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Utility;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\SingletonInterface;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Utility class for evaluating TCA display conditions.
22 */
23 class DisplayConditionEvaluator implements SingletonInterface
24 {
25 /**
26 * Evaluates the provided condition and returns TRUE if the form
27 * element should be displayed.
28 *
29 * The condition string is separated by colons and the first part
30 * indicates what type of evaluation should be performed.
31 *
32 * @param string $displayCondition
33 * @param array $record
34 * @param bool $flexformContext
35 * @param int $recursionLevel Internal level of recursion
36 * @return bool TRUE if condition evaluates successfully
37 */
38 public function evaluateDisplayCondition($displayCondition, array $record = [], $flexformContext = false, $recursionLevel = 0)
39 {
40 if ($recursionLevel > 99) {
41 // This should not happen, treat as misconfiguration
42 return true;
43 }
44 if (!is_array($displayCondition)) {
45 // DisplayCondition is not an array - just get its value
46 $result = $this->evaluateSingleDisplayCondition($displayCondition, $record, $flexformContext);
47 } else {
48 // Multiple conditions given as array ('AND|OR' => condition array)
49 $conditionEvaluations = [
50 'AND' => [],
51 'OR' => [],
52 ];
53 foreach ($displayCondition as $logicalOperator => $groupedDisplayConditions) {
54 $logicalOperator = strtoupper($logicalOperator);
55 if (($logicalOperator !== 'AND' && $logicalOperator !== 'OR') || !is_array($groupedDisplayConditions)) {
56 // Invalid line. Skip it.
57 continue;
58 } else {
59 foreach ($groupedDisplayConditions as $key => $singleDisplayCondition) {
60 $key = strtoupper($key);
61 if (($key === 'AND' || $key === 'OR') && is_array($singleDisplayCondition)) {
62 // Recursion statement: condition is 'AND' or 'OR' and is pointing to an array (should be conditions again)
63 $conditionEvaluations[$logicalOperator][] = $this->evaluateDisplayCondition(
64 [$key => $singleDisplayCondition],
65 $record,
66 $flexformContext,
67 $recursionLevel + 1
68 );
69 } else {
70 // Condition statement: collect evaluation of this single condition.
71 $conditionEvaluations[$logicalOperator][] = $this->evaluateSingleDisplayCondition(
72 $singleDisplayCondition,
73 $record,
74 $flexformContext
75 );
76 }
77 }
78 }
79 }
80 if (!empty($conditionEvaluations['OR']) && in_array(true, $conditionEvaluations['OR'], true)) {
81 // There are OR conditions and at least one of them is TRUE
82 $result = true;
83 } elseif (!empty($conditionEvaluations['AND']) && !in_array(false, $conditionEvaluations['AND'], true)) {
84 // There are AND conditions and none of them is FALSE
85 $result = true;
86 } elseif (!empty($conditionEvaluations['OR']) || !empty($conditionEvaluations['AND'])) {
87 // There are some conditions. But no OR was TRUE and at least one AND was FALSE
88 $result = false;
89 } else {
90 // There are no proper conditions - misconfiguration. Return TRUE.
91 $result = true;
92 }
93 }
94 return $result;
95 }
96
97 /**
98 * Evaluates the provided condition and returns TRUE if the form
99 * element should be displayed.
100 *
101 * The condition string is separated by colons and the first part
102 * indicates what type of evaluation should be performed.
103 *
104 * @param string $displayCondition
105 * @param array $record
106 * @param bool $flexformContext
107 * @return bool
108 * @see evaluateDisplayCondition()
109 */
110 protected function evaluateSingleDisplayCondition($displayCondition, array $record = [], $flexformContext = false)
111 {
112 $result = false;
113 list($matchType, $condition) = explode(':', $displayCondition, 2);
114 switch ($matchType) {
115 case 'FIELD':
116 $result = $this->matchFieldCondition($condition, $record, $flexformContext);
117 break;
118 case 'HIDE_FOR_NON_ADMINS':
119 $result = $this->matchHideForNonAdminsCondition();
120 break;
121 case 'REC':
122 $result = $this->matchRecordCondition($condition, $record);
123 break;
124 case 'VERSION':
125 $result = $this->matchVersionCondition($condition, $record);
126 break;
127 case 'USER':
128 $result = $this->matchUserCondition($condition, $record);
129 break;
130 }
131 return $result;
132 }
133
134 /**
135 * Evaluates conditions concerning a field of the current record.
136 * Requires a record set via ->setRecord()
137 *
138 * Example:
139 * "FIELD:sys_language_uid:>:0" => TRUE, if the field 'sys_language_uid' is greater than 0
140 *
141 * @param string $condition
142 * @param array $record
143 * @param bool $flexformContext
144 * @return bool
145 */
146 protected function matchFieldCondition($condition, $record, $flexformContext = false)
147 {
148 list($fieldName, $operator, $operand) = explode(':', $condition, 3);
149 if ($flexformContext) {
150 if (strpos($fieldName, 'parentRec.') !== false) {
151 $fieldNameParts = explode('.', $fieldName, 2);
152 $fieldValue = $record['parentRec'][$fieldNameParts[1]];
153 } else {
154 $fieldValue = $record[$fieldName]['vDEF'];
155 }
156 } else {
157 $fieldValue = $record[$fieldName];
158 }
159 $result = false;
160 switch ($operator) {
161 case 'REQ':
162 if (is_array($fieldValue) && count($fieldValue) <= 1) {
163 $fieldValue = array_shift($fieldValue);
164 }
165 if (strtoupper($operand) === 'TRUE') {
166 $result = (bool)$fieldValue;
167 } else {
168 $result = !$fieldValue;
169 }
170 break;
171 case '>':
172 if (is_array($fieldValue) && count($fieldValue) <= 1) {
173 $fieldValue = array_shift($fieldValue);
174 }
175 $result = $fieldValue > $operand;
176 break;
177 case '<':
178 if (is_array($fieldValue) && count($fieldValue) <= 1) {
179 $fieldValue = array_shift($fieldValue);
180 }
181 $result = $fieldValue < $operand;
182 break;
183 case '>=':
184 if (is_array($fieldValue) && count($fieldValue) <= 1) {
185 $fieldValue = array_shift($fieldValue);
186 }
187 $result = $fieldValue >= $operand;
188 break;
189 case '<=':
190 if (is_array($fieldValue) && count($fieldValue) <= 1) {
191 $fieldValue = array_shift($fieldValue);
192 }
193 $result = $fieldValue <= $operand;
194 break;
195 case '-':
196 case '!-':
197 if (is_array($fieldValue) && count($fieldValue) <= 1) {
198 $fieldValue = array_shift($fieldValue);
199 }
200 list($minimum, $maximum) = explode('-', $operand);
201 $result = $fieldValue >= $minimum && $fieldValue <= $maximum;
202 if ($operator[0] === '!') {
203 $result = !$result;
204 }
205 break;
206 case '=':
207 case '!=':
208 if (is_array($fieldValue) && count($fieldValue) <= 1) {
209 $fieldValue = array_shift($fieldValue);
210 }
211 $result = $fieldValue == $operand;
212 if ($operator[0] === '!') {
213 $result = !$result;
214 }
215 break;
216 case 'IN':
217 case '!IN':
218 if (is_array($fieldValue)) {
219 $result = count(array_intersect($fieldValue, explode(',', $operand))) > 0;
220 } else {
221 $result = GeneralUtility::inList($operand, $fieldValue);
222 }
223 if ($operator[0] === '!') {
224 $result = !$result;
225 }
226 break;
227 case 'BIT':
228 case '!BIT':
229 $result = (bool)((int)$fieldValue & $operand);
230 if ($operator[0] === '!') {
231 $result = !$result;
232 }
233 break;
234 }
235 return $result;
236 }
237
238 /**
239 * Evaluates TRUE if current backend user is an admin.
240 *
241 * @return bool
242 */
243 protected function matchHideForNonAdminsCondition()
244 {
245 return (bool)$this->getBackendUser()->isAdmin();
246 }
247
248 /**
249 * Evaluates conditions concerning the status of the current record.
250 * Requires a record set via ->setRecord()
251 *
252 * Example:
253 * "REC:NEW:FALSE" => TRUE, if the record is already persisted (has a uid > 0)
254 *
255 * @param string $condition
256 * @param array $record
257 * @return bool
258 */
259 protected function matchRecordCondition($condition, $record)
260 {
261 $result = false;
262 list($operator, $operand) = explode(':', $condition, 2);
263 if ($operator === 'NEW') {
264 if (strtoupper($operand) === 'TRUE') {
265 $result = !((int)$record['uid'] > 0);
266 } elseif (strtoupper($operand) === 'FALSE') {
267 $result = ((int)$record['uid'] > 0);
268 }
269 }
270 return $result;
271 }
272
273 /**
274 * Evaluates whether the current record is versioned.
275 * Requires a record set via ->setRecord()
276 *
277 * @param string $condition
278 * @param array $record
279 * @return bool
280 */
281 protected function matchVersionCondition($condition, $record)
282 {
283 $result = false;
284 list($operator, $operand) = explode(':', $condition, 2);
285 if ($operator === 'IS') {
286 $isNewRecord = !((int)$record['uid'] > 0);
287 // Detection of version can be done be detecting the workspace of the user
288 $isUserInWorkspace = $this->getBackendUser()->workspace > 0;
289 if ((int)$record['pid'] === -1 || (int)$record['_ORIG_pid'] === -1) {
290 $isRecordDetectedAsVersion = true;
291 } else {
292 $isRecordDetectedAsVersion = false;
293 }
294 // New records in a workspace are not handled as a version record
295 // if it's no new version, we detect versions like this:
296 // -- if user is in workspace: always TRUE
297 // -- if editor is in live ws: only TRUE if pid == -1
298 $isVersion = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord;
299 if (strtoupper($operand) === 'TRUE') {
300 $result = $isVersion;
301 } elseif (strtoupper($operand) === 'FALSE') {
302 $result = !$isVersion;
303 }
304 }
305 return $result;
306 }
307
308 /**
309 * Evaluates via the referenced user-defined method
310 *
311 * @param string $condition
312 * @param array $record
313 * @return bool
314 */
315 protected function matchUserCondition($condition, $record)
316 {
317 $conditionParameters = explode(':', $condition);
318 $userFunction = array_shift($conditionParameters);
319
320 $parameter = [
321 'record' => $record,
322 'flexformValueKey' => 'vDEF',
323 'conditionParameters' => $conditionParameters
324 ];
325
326 return (bool)GeneralUtility::callUserFunction($userFunction, $parameter, $this);
327 }
328
329 /**
330 * Get current backend user
331 *
332 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
333 */
334 protected function getBackendUser()
335 {
336 return $GLOBALS['BE_USER'];
337 }
338 }