eae870b6a462fc37e1b569087d7cf53374d29d5e
[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 textfile 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 }
244 return $result;
245 }
246
247 /**
248 * Evaluates TRUE if current backend user is an admin.
249 *
250 * @return boolean
251 */
252 protected function matchHideForNonAdminsCondition() {
253 return (bool) $this->getBackendUser()->isAdmin();
254 }
255
256 /**
257 * Evaluates whether the field is a value for the default language.
258 * Works only for <langChildren>=1, otherwise it has no effect.
259 *
260 * @param string $condition
261 * @return boolean
262 */
263 protected function matchHideL10nSiblingsCondition($condition) {
264 $result = FALSE;
265 if ($this->flexformValueKey === 'vDEF') {
266 $result = TRUE;
267 } elseif ($condition === 'except_admin' && $this->getBackendUser()->isAdmin()) {
268 $result = TRUE;
269 }
270 return $result;
271 }
272
273 /**
274 * Evaluates conditions concerning the status of the current record.
275 * Requires a record set via ->setRecord()
276 *
277 * Example:
278 * "REC:NEW:FALSE" => TRUE, if the record is already persisted (has a uid > 0)
279 *
280 * @param string $condition
281 * @return boolean
282 */
283 protected function matchRecordCondition($condition) {
284 $result = FALSE;
285 list($operator, $operand) = explode(':', $condition, 2);
286 if ($operator === 'NEW') {
287 if (strtoupper($operand) === 'TRUE') {
288 $result = !(intval($this->record['uid']) > 0);
289 } elseif (strtoupper($operand) === 'FALSE') {
290 $result = (intval($this->record['uid']) > 0);
291 }
292 }
293 return $result;
294 }
295
296 /**
297 * Evaluates whether the current record is versioned.
298 * Requires a record set via ->setRecord()
299 *
300 * @param string $condition
301 * @return boolean
302 */
303 protected function matchVersionCondition($condition) {
304 $result = FALSE;
305 list($operator, $operand) = explode(':', $condition, 2);
306 if ($operator === 'IS') {
307 $isNewRecord = !(intval($this->record['uid']) > 0);
308 // Detection of version can be done be detecting the workspace of the user
309 $isUserInWorkspace = $this->getBackendUser()->workspace > 0;
310 if (intval($this->record['pid']) == -1 || intval($this->record['_ORIG_pid']) == -1) {
311 $isRecordDetectedAsVersion = TRUE;
312 } else {
313 $isRecordDetectedAsVersion = FALSE;
314 }
315 // New records in a workspace are not handled as a version record
316 // if it's no new version, we detect versions like this:
317 // -- if user is in workspace: always TRUE
318 // -- if editor is in live ws: only TRUE if pid == -1
319 $isVersion = ($isUserInWorkspace || $isRecordDetectedAsVersion) && !$isNewRecord;
320 if (strtoupper($operand) === 'TRUE') {
321 $result = $isVersion;
322 } elseif (strtoupper($operand) === 'FALSE') {
323 $result = !$isVersion;
324 }
325 }
326 return $result;
327 }
328
329 /**
330 * Get current backend user
331 *
332 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
333 */
334 protected function getBackendUser() {
335 return $GLOBALS['BE_USER'];
336 }
337 }
338
339 ?>