[BUGFIX] Fix override field handling in form engine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Container / SingleFieldContainer.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\Container;
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\Backend\Form\ElementConditionMatcher;
18 use TYPO3\CMS\Backend\Form\NodeFactory;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
22 use TYPO3\CMS\Lang\LanguageService;
23 use TYPO3\CMS\Backend\Utility\BackendUtility;
24 use TYPO3\CMS\Backend\Utility\IconUtility;
25 use TYPO3\CMS\Core\Utility\DiffUtility;
26 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
27 use TYPO3\CMS\Core\Database\RelationHandler;
28 use TYPO3\CMS\Backend\Form\Element\NoneElement;
29
30 /**
31 * Container around a "single field".
32 *
33 * This container is the last one in the chain before processing is hand over to single element classes.
34 * If a single field is of type flex or inline, it however creates FlexFormContainer or InlineControlContainer.
35 *
36 * The container does various checks and processing for a given single fields, for example it resolves
37 * display conditions and the HTML to compare compare different languages.
38 */
39 class SingleFieldContainer extends AbstractContainer {
40
41 /**
42 * Entry method
43 *
44 * @return array As defined in initializeResultArray() of AbstractNode
45 */
46 public function render() {
47 $backendUser = $this->getBackendUserAuthentication();
48 $languageService = $this->getLanguageService();
49 $resultArray = $this->initializeResultArray();
50
51 $table = $this->globalOptions['table'];
52 $row = $this->globalOptions['databaseRow'];
53 $fieldName = $this->globalOptions['fieldName'];
54
55 if (!is_array($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
56 return $resultArray;
57 }
58
59 $parameterArray = array();
60 $parameterArray['extra'] = $this->globalOptions['fieldExtra'];
61 $parameterArray['fieldConf'] = $GLOBALS['TCA'][$table]['columns'][$fieldName];
62
63 // A couple of early returns in case the field should not be rendered
64 // Check if this field is configured and editable according to exclude fields and other configuration
65 if (
66 $parameterArray['fieldConf']['exclude'] && !$backendUser->check('non_exclude_fields', $table . ':' . $fieldName)
67 || $parameterArray['fieldConf']['config']['type'] === 'passthrough'
68 || !$backendUser->isRTE() && $parameterArray['fieldConf']['config']['showIfRTE']
69 || $GLOBALS['TCA'][$table]['ctrl']['languageField'] && !$parameterArray['fieldConf']['l10n_display'] && $parameterArray['fieldConf']['l10n_mode'] === 'exclude' && ($row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0)
70 || $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $this->globalOptions['localizationMode'] && $this->globalOptions['localizationMode'] !== $parameterArray['fieldConf']['l10n_cat']
71 || $this->inlineFieldShouldBeSkipped()
72 ) {
73 return $resultArray;
74 }
75 // Evaluate display condition
76 if ($parameterArray['fieldConf']['displayCond'] && is_array($row)) {
77 // @todo: isn't $row = array() safe somewhere above already?
78 /** @var $elementConditionMatcher ElementConditionMatcher */
79 $elementConditionMatcher = GeneralUtility::makeInstance(ElementConditionMatcher::class);
80 if (!$elementConditionMatcher->match($parameterArray['fieldConf']['displayCond'], $row)) {
81 return $resultArray;
82 }
83 }
84 // Fetching the TSconfig for the current table/field. This includes the $row which means that
85 $parameterArray['fieldTSConfig'] = FormEngineUtility::getTSconfigForTableRow($table, $row, $fieldName);
86 if ($parameterArray['fieldTSConfig']['disabled']) {
87 return $resultArray;
88 }
89
90 // Override fieldConf by fieldTSconfig:
91 $parameterArray['fieldConf']['config'] = FormEngineUtility::overrideFieldConf($parameterArray['fieldConf']['config'], $parameterArray['fieldTSConfig']);
92 $parameterArray['itemFormElName'] = $this->globalOptions['prependFormFieldNames'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
93 // Form field name, in case of file uploads
94 $parameterArray['itemFormElName_file'] = $this->globalOptions['prependFormFieldNames_file'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
95 // Form field name, to activate elements
96 // If the "eval" list contains "null", elements can be deactivated which results in storing NULL to database
97 $parameterArray['itemFormElNameActive'] = $this->globalOptions['prependFormFieldNamesActive'] . '[' . $table . '][' . $row['uid'] . '][' . $fieldName . ']';
98 $parameterArray['itemFormElID'] = $this->globalOptions['prependFormFieldNames'] . '_' . $table . '_' . $row['uid'] . '_' . $fieldName;
99
100 // The value to show in the form field.
101 $parameterArray['itemFormElValue'] = $row[$fieldName];
102 // Set field to read-only if configured for translated records to show default language content as readonly
103 if ($parameterArray['fieldConf']['l10n_display']
104 && GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly')
105 && $row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0
106 ) {
107 $parameterArray['fieldConf']['config']['readOnly'] = TRUE;
108 $parameterArray['itemFormElValue'] = $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']][$fieldName];
109 }
110
111 if (strpos($GLOBALS['TCA'][$table]['ctrl']['type'], ':') === FALSE) {
112 $typeField = $GLOBALS['TCA'][$table]['ctrl']['type'];
113 } else {
114 $typeField = substr($GLOBALS['TCA'][$table]['ctrl']['type'], 0, strpos($GLOBALS['TCA'][$table]['ctrl']['type'], ':'));
115 }
116 // Create a JavaScript code line which will ask the user to save/update the form due to changing the element.
117 // This is used for eg. "type" fields and others configured with "requestUpdate"
118 if (
119 !empty($GLOBALS['TCA'][$table]['ctrl']['type'])
120 && $fieldName === $typeField
121 || !empty($GLOBALS['TCA'][$table]['ctrl']['requestUpdate'])
122 && GeneralUtility::inList(str_replace(' ', '', $GLOBALS['TCA'][$table]['ctrl']['requestUpdate']), $fieldName)
123 ) {
124 if ($backendUser->jsConfirmation(1)) {
125 $alertMsgOnChange = 'if (confirm(TBE_EDITOR.labels.onChangeAlert) && TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
126 } else {
127 $alertMsgOnChange = 'if (TBE_EDITOR.checkSubmit(-1)){ TBE_EDITOR.submitForm() };';
128 }
129 } else {
130 $alertMsgOnChange = '';
131 }
132
133
134 if (in_array($fieldName, $this->globalOptions['hiddenFieldListArray'], TRUE)) {
135 // Render as a hidden field if this field had a forced value in overrideVals
136 // @todo: This is an ugly concept ... search for overrideVals and defVals for a full picture of this madness
137 $resultArray = $this->initializeResultArray();
138 // This hidden field can not just be returned as casual html since upper containers will then render a label and wrapping stuff - this is not wanted here
139 $resultArray['additionalHiddenFields'][] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
140 } else {
141 // JavaScript code for event handlers:
142 $parameterArray['fieldChangeFunc'] = array();
143 $parameterArray['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = 'TBE_EDITOR.fieldChanged(' . GeneralUtility::quoteJSvalue($table) . ',' . GeneralUtility::quoteJSvalue($row['uid']) . ',' . GeneralUtility::quoteJSvalue($fieldName) . ',' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ');';
144 $parameterArray['fieldChangeFunc']['alert'] = $alertMsgOnChange;
145
146 // If this is the child of an inline type and it is the field creating the label
147 if ($this->isInlineChildAndLabelField($table, $fieldName)) {
148 /** @var InlineStackProcessor $inlineStackProcessor */
149 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
150 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
151 $inlineDomObjectId = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
152 $inlineObjectId = implode(
153 '-',
154 array(
155 $inlineDomObjectId,
156 $table,
157 $row['uid']
158 )
159 );
160 $parameterArray['fieldChangeFunc']['inline'] = 'inline.handleChangedField(\'' . $parameterArray['itemFormElName'] . '\',\'' . $inlineObjectId . '\');';
161 }
162
163 // Based on the type of the item, call a render function on a child element
164 $options = $this->globalOptions;
165 $options['parameterArray'] = $parameterArray;
166 /** @var NodeFactory $childFactory */
167 $childFactory = GeneralUtility::makeInstance(NodeFactory::class);
168 $childElement = $childFactory->create($parameterArray['fieldConf']['config']['type']);
169 $resultArray = $childElement->setGlobalOptions($options)->render();
170 $html = $resultArray['html'];
171
172 // Add language + diff
173 $renderLanguageDiff = TRUE;
174 if (
175 $parameterArray['fieldConf']['l10n_display'] && (GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'hideDiff')
176 || GeneralUtility::inList($parameterArray['fieldConf']['l10n_display'], 'defaultAsReadonly'))
177 ) {
178 $renderLanguageDiff = FALSE;
179 }
180 if ($renderLanguageDiff) {
181 $html = $this->renderDefaultLanguageContent($table, $fieldName, $row, $html);
182 $html = $this->renderDefaultLanguageDiff($table, $fieldName, $row, $html);
183 }
184
185 if (isset($parameterArray['fieldConf']['config']['mode']) && $parameterArray['fieldConf']['config']['mode'] === 'useOrOverridePlaceholder') {
186 $placeholder = $this->getPlaceholderValue($table, $parameterArray['fieldConf']['config'], $row);
187 $onChange = 'typo3form.fieldTogglePlaceholder(' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', !this.checked)';
188 $checked = $parameterArray['itemFormElValue'] === NULL ? '' : ' checked="checked"';
189
190 $resultArray['additionalJavaScriptPost'][] = 'typo3form.fieldTogglePlaceholder('
191 . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ', ' . ($checked ? 'false' : 'true') . ');';
192
193 // Renders a input or textarea field depending on type of "parent"
194 $options = array();
195 $options['databaseRow'] = array();
196 $options['table'] = '';
197 $options['parameterArray'] = $parameterArray;
198 $options['parameterArray']['itemFormElValue'] = GeneralUtility::fixed_lgd_cs($placeholder, 30);
199 /** @var NoneElement $noneElement */
200 $noneElement = GeneralUtility::makeInstance(NoneElement::class);
201 $noneElementResult = $noneElement->setGlobalOptions($options)->render();
202 $noneElementHtml = $noneElementResult['html'];
203
204 $html = '
205 <input type="hidden" name="' . htmlspecialchars($parameterArray['itemFormElNameActive']) . '" value="0" />
206 <div class="checkbox">
207 <label>
208 <input type="checkbox" name="' . htmlspecialchars($parameterArray['itemFormElNameActive']) . '" value="1" id="tce-forms-textfield-use-override-' . $fieldName . '-' . $row['uid'] . '" onchange="' . htmlspecialchars($onChange) . '"' . $checked . ' />
209 ' . sprintf($languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.placeholder.override'), BackendUtility::getRecordTitlePrep($placeholder, 20)) . '
210 </label>
211 </div>
212 <div class="t3js-formengine-placeholder-placeholder">
213 ' . $noneElementHtml . '
214 </div>
215 <div class="t3js-formengine-placeholder-formfield">' . $html . '</div>';
216 }
217
218 $resultArray['html'] = $html;
219 }
220 return $resultArray;
221 }
222
223 /**
224 * Renders the display of default language record content around current field.
225 * Will render content if any is found in the internal array, $this->defaultLanguageData,
226 * depending on registerDefaultLanguageData() being called prior to this.
227 *
228 * @param string $table Table name of the record being edited
229 * @param string $field Field name represented by $item
230 * @param array $row Record array of the record being edited
231 * @param string $item HTML of the form field. This is what we add the content to.
232 * @return string Item string returned again, possibly with the original value added to.
233 */
234 protected function renderDefaultLanguageContent($table, $field, $row, $item) {
235 if (is_array($this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']])) {
236 $defaultLanguageValue = BackendUtility::getProcessedValue(
237 $table,
238 $field,
239 $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']][$field],
240 0,
241 1,
242 FALSE,
243 $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']]['uid']
244 );
245 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$field];
246 // Don't show content if it's for IRRE child records:
247 if ($fieldConfig['config']['type'] !== 'inline') {
248 if ($defaultLanguageValue !== '') {
249 $item .= '<div class="t3-form-original-language">' . FormEngineUtility::getLanguageIcon($table, $row, 0)
250 . $this->getMergeBehaviourIcon($fieldConfig['l10n_mode'])
251 . $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $field) . '</div>';
252 }
253 $additionalPreviewLanguages = $this->globalOptions['additionalPreviewLanguages'];
254 foreach ($additionalPreviewLanguages as $previewLanguage) {
255 $defaultLanguageValue = BackendUtility::getProcessedValue(
256 $table,
257 $field,
258 $this->globalOptions['additionalPreviewLanguageData'][$table . ':' . $row['uid']][$previewLanguage['uid']][$field],
259 0,
260 1
261 );
262 if ($defaultLanguageValue !== '') {
263 $item .= '<div class="t3-form-original-language">'
264 . FormEngineUtility::getLanguageIcon($table, $row, ('v' . $previewLanguage['ISOcode']))
265 . $this->getMergeBehaviourIcon($fieldConfig['l10n_mode'])
266 . $this->previewFieldValue($defaultLanguageValue, $fieldConfig, $field) . '</div>';
267 }
268 }
269 }
270 }
271 return $item;
272 }
273
274 /**
275 * Renders an icon to indicate the way the translation and the original is merged (if this is relevant).
276 *
277 * If a field is defined as 'mergeIfNotBlank' this is useful information for an editor. He/she can leave the field blank and
278 * the original value will be used. Without this hint editors are likely to copy the contents even if it is not necessary.
279 *
280 * @param string $l10nMode Localization mode from TCA
281 * @return string
282 */
283 protected function getMergeBehaviourIcon($l10nMode) {
284 $icon = '';
285 if ($l10nMode === 'mergeIfNotBlank') {
286 $icon = IconUtility::getSpriteIcon(
287 'actions-edit-merge-localization',
288 array('title' => $this->getLanguageService()->sL('LLL:EXT:lang/locallang_misc.xlf:localizeMergeIfNotBlank'))
289 );
290 }
291 return $icon;
292 }
293
294 /**
295 * Renders the diff-view of default language record content compared with what the record was originally translated from.
296 * Will render content if any is found in the internal array, $this->defaultLanguageData,
297 * depending on registerDefaultLanguageData() being called prior to this.
298 *
299 * @param string $table Table name of the record being edited
300 * @param string $field Field name represented by $item
301 * @param array $row Record array of the record being edited
302 * @param string $item HTML of the form field. This is what we add the content to.
303 * @return string Item string returned again, possibly with the original value added to.
304 */
305 protected function renderDefaultLanguageDiff($table, $field, $row, $item) {
306 if (is_array($this->globalOptions['defaultLanguageDataDiff'][$table . ':' . $row['uid']])) {
307 // Initialize:
308 $dLVal = array(
309 'old' => $this->globalOptions['defaultLanguageDataDiff'][$table . ':' . $row['uid']],
310 'new' => $this->globalOptions['defaultLanguageData'][$table . ':' . $row['uid']]
311 );
312 // There must be diff-data:
313 if (isset($dLVal['old'][$field])) {
314 if ((string)$dLVal['old'][$field] !== (string)$dLVal['new'][$field]) {
315 // Create diff-result:
316 $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
317 $diffres = $diffUtility->makeDiffDisplay(
318 BackendUtility::getProcessedValue($table, $field, $dLVal['old'][$field], 0, 1),
319 BackendUtility::getProcessedValue($table, $field, $dLVal['new'][$field], 0, 1)
320 );
321 $item .= '<div class="t3-form-original-language-diff">
322 <div class="t3-form-original-language-diffheader">' .
323 htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.changeInOrig')) .
324 '</div>
325 <div class="t3-form-original-language-diffcontent">' . $diffres . '</div>
326 </div>';
327 }
328 }
329 }
330 return $item;
331 }
332
333 /**
334 * Checks if the $table is the child of a inline type AND the $field is the label field of this table.
335 * This function is used to dynamically update the label while editing. This has no effect on labels,
336 * that were processed by a FormEngine-hook on saving.
337 *
338 * @param string $table The table to check
339 * @param string $field The field on this table to check
340 * @return bool Is inline child and field is responsible for the label
341 */
342 protected function isInlineChildAndLabelField($table, $field) {
343 /** @var InlineStackProcessor $inlineStackProcessor */
344 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
345 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
346 $level = $inlineStackProcessor->getStructureLevel(-1);
347 if ($level['config']['foreign_label']) {
348 $label = $level['config']['foreign_label'];
349 } else {
350 $label = $GLOBALS['TCA'][$table]['ctrl']['label'];
351 }
352 return $level['config']['foreign_table'] === $table && $label == $field ? TRUE : FALSE;
353 }
354
355 /**
356 * Rendering of inline fields should be skipped under certain circumstances
357 *
358 * @return boolean TRUE if field should be skipped based on inline configuration
359 */
360 protected function inlineFieldShouldBeSkipped() {
361 $table = $this->globalOptions['table'];
362 $row = $this->globalOptions['databaseRow'];
363 $fieldName = $this->globalOptions['fieldName'];
364 $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
365
366 /** @var InlineStackProcessor $inlineStackProcessor */
367 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
368 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
369 $structureDepth = $inlineStackProcessor->getStructureDepth();
370
371 $skipThisField = FALSE;
372 if ($structureDepth > 0) {
373 $searchArray = array(
374 '%OR' => array(
375 'config' => array(
376 0 => array(
377 '%AND' => array(
378 'foreign_table' => $table,
379 '%OR' => array(
380 '%AND' => array(
381 'appearance' => array('useCombination' => TRUE),
382 'foreign_selector' => $fieldName
383 ),
384 'MM' => $fieldConfig['MM']
385 )
386 )
387 ),
388 1 => array(
389 '%AND' => array(
390 'foreign_table' => $fieldConfig['foreign_table'],
391 'foreign_selector' => $fieldConfig['foreign_field']
392 )
393 )
394 )
395 )
396 );
397 // Get the parent record from structure stack
398 $level = $inlineStackProcessor->getStructureLevel(-1);
399 // If we have symmetric fields, check on which side we are and hide fields, that are set automatically:
400 if (RelationHandler::isOnSymmetricSide($level['uid'], $level['config'], $row)) {
401 $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $fieldName;
402 $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $fieldName;
403 } else {
404 $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $fieldName;
405 $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $fieldName;
406 }
407 $skipThisField = $this->arrayCompareComplex($level, $searchArray);
408 }
409 return $skipThisField;
410 }
411
412 /**
413 * Handles complex comparison requests on an array.
414 * A request could look like the following:
415 *
416 * $searchArray = array(
417 * '%AND' => array(
418 * 'key1' => 'value1',
419 * 'key2' => 'value2',
420 * '%OR' => array(
421 * 'subarray' => array(
422 * 'subkey' => 'subvalue'
423 * ),
424 * 'key3' => 'value3',
425 * 'key4' => 'value4'
426 * )
427 * )
428 * );
429 *
430 * It is possible to use the array keys '%AND.1', '%AND.2', etc. to prevent
431 * overwriting the sub-array. It could be necessary, if you use complex comparisons.
432 *
433 * The example above means, key1 *AND* key2 (and their values) have to match with
434 * the $subjectArray and additional one *OR* key3 or key4 have to meet the same
435 * condition.
436 * It is also possible to compare parts of a sub-array (e.g. "subarray"), so this
437 * function recurses down one level in that sub-array.
438 *
439 * @param array $subjectArray The array to search in
440 * @param array $searchArray The array with keys and values to search for
441 * @param string $type Use '%AND' or '%OR' for comparison
442 * @return bool The result of the comparison
443 */
444 protected function arrayCompareComplex($subjectArray, $searchArray, $type = '') {
445 $localMatches = 0;
446 $localEntries = 0;
447 if (is_array($searchArray) && count($searchArray)) {
448 // If no type was passed, try to determine
449 if (!$type) {
450 reset($searchArray);
451 $type = key($searchArray);
452 $searchArray = current($searchArray);
453 }
454 // We use '%AND' and '%OR' in uppercase
455 $type = strtoupper($type);
456 // Split regular elements from sub elements
457 foreach ($searchArray as $key => $value) {
458 $localEntries++;
459 // Process a sub-group of OR-conditions
460 if ($key === '%OR') {
461 $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
462 } elseif ($key === '%AND') {
463 $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
464 } elseif (is_array($value) && $this->isAssociativeArray($searchArray)) {
465 $localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
466 } elseif (is_array($value)) {
467 $localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
468 } else {
469 if (isset($subjectArray[$key]) && isset($value)) {
470 // Boolean match:
471 if (is_bool($value)) {
472 $localMatches += !($subjectArray[$key] xor $value) ? 1 : 0;
473 } elseif (is_numeric($subjectArray[$key]) && is_numeric($value)) {
474 $localMatches += $subjectArray[$key] == $value ? 1 : 0;
475 } else {
476 $localMatches += $subjectArray[$key] === $value ? 1 : 0;
477 }
478 }
479 }
480 // If one or more matches are required ('OR'), return TRUE after the first successful match
481 if ($type === '%OR' && $localMatches > 0) {
482 return TRUE;
483 }
484 // If all matches are required ('AND') and we have no result after the first run, return FALSE
485 if ($type === '%AND' && $localMatches == 0) {
486 return FALSE;
487 }
488 }
489 }
490 // Return the result for '%AND' (if nothing was checked, TRUE is returned)
491 return $localEntries == $localMatches ? TRUE : FALSE;
492 }
493
494 /**
495 * Checks whether an object is an associative array.
496 *
497 * @param mixed $object The object to be checked
498 * @return bool Returns TRUE, if the object is an associative array
499 */
500 protected function isAssociativeArray($object) {
501 return is_array($object) && count($object) && array_keys($object) !== range(0, sizeof($object) - 1) ? TRUE : FALSE;
502 }
503
504 /**
505 * @return BackendUserAuthentication
506 */
507 protected function getBackendUserAuthentication() {
508 return $GLOBALS['BE_USER'];
509 }
510
511 /**
512 * @return LanguageService
513 */
514 protected function getLanguageService() {
515 return $GLOBALS['LANG'];
516 }
517
518 }