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