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