[TASK] Add functional tests for QueryGeneratorTest
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / QueryGenerator.php
1 <?php
2 namespace TYPO3\CMS\Core\Database;
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\Module\BaseScriptClass;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Database\Query\QueryHelper;
21 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
22 use TYPO3\CMS\Core\Localization\LanguageService;
23 use TYPO3\CMS\Core\Type\Bitmask\Permission;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\StringUtility;
26
27 /**
28 * Class for generating front end for building queries
29 */
30 class QueryGenerator
31 {
32 /**
33 * @var array
34 */
35 public $lang = [
36 'OR' => 'or',
37 'AND' => 'and',
38 'comparison' => [
39 // Type = text offset = 0
40 '0_' => 'contains',
41 '1_' => 'does not contain',
42 '2_' => 'starts with',
43 '3_' => 'does not start with',
44 '4_' => 'ends with',
45 '5_' => 'does not end with',
46 '6_' => 'equals',
47 '7_' => 'does not equal',
48 // Type = number , offset = 32
49 '32_' => 'equals',
50 '33_' => 'does not equal',
51 '34_' => 'is greater than',
52 '35_' => 'is less than',
53 '36_' => 'is between',
54 '37_' => 'is not between',
55 '38_' => 'is in list',
56 '39_' => 'is not in list',
57 '40_' => 'binary AND equals',
58 '41_' => 'binary AND does not equal',
59 '42_' => 'binary OR equals',
60 '43_' => 'binary OR does not equal',
61 // Type = multiple, relation, files , offset = 64
62 '64_' => 'equals',
63 '65_' => 'does not equal',
64 '66_' => 'contains',
65 '67_' => 'does not contain',
66 '68_' => 'is in list',
67 '69_' => 'is not in list',
68 '70_' => 'binary AND equals',
69 '71_' => 'binary AND does not equal',
70 '72_' => 'binary OR equals',
71 '73_' => 'binary OR does not equal',
72 // Type = date,time offset = 96
73 '96_' => 'equals',
74 '97_' => 'does not equal',
75 '98_' => 'is greater than',
76 '99_' => 'is less than',
77 '100_' => 'is between',
78 '101_' => 'is not between',
79 '102_' => 'binary AND equals',
80 '103_' => 'binary AND does not equal',
81 '104_' => 'binary OR equals',
82 '105_' => 'binary OR does not equal',
83 // Type = boolean, offset = 128
84 '128_' => 'is True',
85 '129_' => 'is False',
86 // Type = binary , offset = 160
87 '160_' => 'equals',
88 '161_' => 'does not equal',
89 '162_' => 'contains',
90 '163_' => 'does not contain'
91 ]
92 ];
93
94 /**
95 * @var array
96 */
97 public $compSQL = [
98 // Type = text offset = 0
99 '0' => '#FIELD# LIKE \'%#VALUE#%\'',
100 '1' => '#FIELD# NOT LIKE \'%#VALUE#%\'',
101 '2' => '#FIELD# LIKE \'#VALUE#%\'',
102 '3' => '#FIELD# NOT LIKE \'#VALUE#%\'',
103 '4' => '#FIELD# LIKE \'%#VALUE#\'',
104 '5' => '#FIELD# NOT LIKE \'%#VALUE#\'',
105 '6' => '#FIELD# = \'#VALUE#\'',
106 '7' => '#FIELD# != \'#VALUE#\'',
107 // Type = number, offset = 32
108 '32' => '#FIELD# = \'#VALUE#\'',
109 '33' => '#FIELD# != \'#VALUE#\'',
110 '34' => '#FIELD# > #VALUE#',
111 '35' => '#FIELD# < #VALUE#',
112 '36' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
113 '37' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
114 '38' => '#FIELD# IN (#VALUE#)',
115 '39' => '#FIELD# NOT IN (#VALUE#)',
116 '40' => '(#FIELD# & #VALUE#)=#VALUE#',
117 '41' => '(#FIELD# & #VALUE#)!=#VALUE#',
118 '42' => '(#FIELD# | #VALUE#)=#VALUE#',
119 '43' => '(#FIELD# | #VALUE#)!=#VALUE#',
120 // Type = multiple, relation, files , offset = 64
121 '64' => '#FIELD# = \'#VALUE#\'',
122 '65' => '#FIELD# != \'#VALUE#\'',
123 '66' => '#FIELD# LIKE \'%#VALUE#%\' AND #FIELD# LIKE \'%#VALUE1#%\'',
124 '67' => '(#FIELD# NOT LIKE \'%#VALUE#%\' OR #FIELD# NOT LIKE \'%#VALUE1#%\')',
125 '68' => '#FIELD# IN (#VALUE#)',
126 '69' => '#FIELD# NOT IN (#VALUE#)',
127 '70' => '(#FIELD# & #VALUE#)=#VALUE#',
128 '71' => '(#FIELD# & #VALUE#)!=#VALUE#',
129 '72' => '(#FIELD# | #VALUE#)=#VALUE#',
130 '73' => '(#FIELD# | #VALUE#)!=#VALUE#',
131 // Type = date, offset = 32
132 '96' => '#FIELD# = \'#VALUE#\'',
133 '97' => '#FIELD# != \'#VALUE#\'',
134 '98' => '#FIELD# > #VALUE#',
135 '99' => '#FIELD# < #VALUE#',
136 '100' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
137 '101' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
138 '102' => '(#FIELD# & #VALUE#)=#VALUE#',
139 '103' => '(#FIELD# & #VALUE#)!=#VALUE#',
140 '104' => '(#FIELD# | #VALUE#)=#VALUE#',
141 '105' => '(#FIELD# | #VALUE#)!=#VALUE#',
142 // Type = boolean, offset = 128
143 '128' => '#FIELD# = \'1\'',
144 '129' => '#FIELD# != \'1\'',
145 // Type = binary = 160
146 '160' => '#FIELD# = \'#VALUE#\'',
147 '161' => '#FIELD# != \'#VALUE#\'',
148 '162' => '(#FIELD# & #VALUE#)=#VALUE#',
149 '163' => '(#FIELD# & #VALUE#)=0'
150 ];
151
152 /**
153 * @var array
154 */
155 public $comp_offsets = [
156 'text' => 0,
157 'number' => 1,
158 'multiple' => 2,
159 'relation' => 2,
160 'files' => 2,
161 'date' => 3,
162 'time' => 3,
163 'boolean' => 4,
164 'binary' => 5
165 ];
166
167 /**
168 * @var string
169 */
170 public $noWrap = ' nowrap';
171
172 /**
173 * Form data name prefix
174 *
175 * @var string
176 */
177 public $name;
178
179 /**
180 * Table for the query
181 *
182 * @var string
183 */
184 public $table;
185
186 /**
187 * @var array
188 */
189 public $tableArray;
190
191 /**
192 * Field list
193 *
194 * @var string
195 */
196 public $fieldList;
197
198 /**
199 * Array of the fields possible
200 *
201 * @var array
202 */
203 public $fields = [];
204
205 /**
206 * @var array
207 */
208 public $extFieldLists = [];
209
210 /**
211 * The query config
212 *
213 * @var array
214 */
215 public $queryConfig = [];
216
217 /**
218 * @var bool
219 */
220 public $enablePrefix = false;
221
222 /**
223 * @var bool
224 */
225 public $enableQueryParts = false;
226
227 /**
228 * @var string
229 */
230 protected $formName = '';
231
232 /**
233 * @var int
234 */
235 protected $limitBegin;
236
237 /**
238 * @var int
239 */
240 protected $limitLength;
241
242 /**
243 * @var string
244 */
245 protected $fieldName;
246
247 /**
248 * Make a list of fields for current table
249 *
250 * @return string Separated list of fields
251 */
252 public function makeFieldList()
253 {
254 $fieldListArr = [];
255 if (is_array($GLOBALS['TCA'][$this->table])) {
256 $fieldListArr = array_keys($GLOBALS['TCA'][$this->table]['columns']);
257 $fieldListArr[] = 'uid';
258 $fieldListArr[] = 'pid';
259 $fieldListArr[] = 'deleted';
260 if ($GLOBALS['TCA'][$this->table]['ctrl']['tstamp']) {
261 $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['tstamp'];
262 }
263 if ($GLOBALS['TCA'][$this->table]['ctrl']['crdate']) {
264 $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['crdate'];
265 }
266 if ($GLOBALS['TCA'][$this->table]['ctrl']['cruser_id']) {
267 $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['cruser_id'];
268 }
269 if ($GLOBALS['TCA'][$this->table]['ctrl']['sortby']) {
270 $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['sortby'];
271 }
272 }
273 return implode(',', $fieldListArr);
274 }
275
276 /**
277 * Init function
278 *
279 * @param string $name The name
280 * @param string $table The table name
281 * @param string $fieldList The field list
282 */
283 public function init($name, $table, $fieldList = '')
284 {
285 // Analysing the fields in the table.
286 if (is_array($GLOBALS['TCA'][$table])) {
287 $this->name = $name;
288 $this->table = $table;
289 $this->fieldList = $fieldList ? $fieldList : $this->makeFieldList();
290 $fieldArr = GeneralUtility::trimExplode(',', $this->fieldList, true);
291 foreach ($fieldArr as $fieldName) {
292 $fC = $GLOBALS['TCA'][$this->table]['columns'][$fieldName];
293 $this->fields[$fieldName] = $fC['config'];
294 $this->fields[$fieldName]['exclude'] = $fC['exclude'];
295 if ($this->fields[$fieldName]['type'] === 'user' && !isset($this->fields[$fieldName]['type']['userFunc'])) {
296 unset($this->fields[$fieldName]);
297 continue;
298 }
299 if (is_array($fC) && $fC['label']) {
300 $this->fields[$fieldName]['label'] = rtrim(trim($this->getLanguageService()->sL($fC['label'])), ':');
301 switch ($this->fields[$fieldName]['type']) {
302 case 'input':
303 if (preg_match('/int|year/i', $this->fields[$fieldName]['eval'])) {
304 $this->fields[$fieldName]['type'] = 'number';
305 } elseif (preg_match('/time/i', $this->fields[$fieldName]['eval'])) {
306 $this->fields[$fieldName]['type'] = 'time';
307 } elseif (preg_match('/date/i', $this->fields[$fieldName]['eval'])) {
308 $this->fields[$fieldName]['type'] = 'date';
309 } else {
310 $this->fields[$fieldName]['type'] = 'text';
311 }
312 break;
313 case 'check':
314 if (!$this->fields[$fieldName]['items'] || count($this->fields[$fieldName]['items']) <= 1) {
315 $this->fields[$fieldName]['type'] = 'boolean';
316 } else {
317 $this->fields[$fieldName]['type'] = 'binary';
318 }
319 break;
320 case 'radio':
321 $this->fields[$fieldName]['type'] = 'multiple';
322 break;
323 case 'select':
324 $this->fields[$fieldName]['type'] = 'multiple';
325 if ($this->fields[$fieldName]['foreign_table']) {
326 $this->fields[$fieldName]['type'] = 'relation';
327 }
328 if ($this->fields[$fieldName]['special']) {
329 $this->fields[$fieldName]['type'] = 'text';
330 }
331 break;
332 case 'group':
333 $this->fields[$fieldName]['type'] = 'files';
334 if ($this->fields[$fieldName]['internal_type'] === 'db') {
335 $this->fields[$fieldName]['type'] = 'relation';
336 }
337 break;
338 case 'user':
339 case 'flex':
340 case 'passthrough':
341 case 'none':
342 case 'text':
343 default:
344 $this->fields[$fieldName]['type'] = 'text';
345 }
346 } else {
347 $this->fields[$fieldName]['label'] = '[FIELD: ' . $fieldName . ']';
348 switch ($fieldName) {
349 case 'pid':
350 $this->fields[$fieldName]['type'] = 'relation';
351 $this->fields[$fieldName]['allowed'] = 'pages';
352 break;
353 case 'cruser_id':
354 $this->fields[$fieldName]['type'] = 'relation';
355 $this->fields[$fieldName]['allowed'] = 'be_users';
356 break;
357 case 'tstamp':
358 case 'crdate':
359 $this->fields[$fieldName]['type'] = 'time';
360 break;
361 case 'deleted':
362 $this->fields[$fieldName]['type'] = 'boolean';
363 break;
364 default:
365 $this->fields[$fieldName]['type'] = 'number';
366 }
367 }
368 }
369 }
370 /* // EXAMPLE:
371 $this->queryConfig = array(
372 array(
373 'operator' => 'AND',
374 'type' => 'FIELD_spaceBefore',
375 ),
376 array(
377 'operator' => 'AND',
378 'type' => 'FIELD_records',
379 'negate' => 1,
380 'inputValue' => 'foo foo'
381 ),
382 array(
383 'type' => 'newlevel',
384 'nl' => array(
385 array(
386 'operator' => 'AND',
387 'type' => 'FIELD_spaceBefore',
388 'negate' => 1,
389 'inputValue' => 'foo foo'
390 ),
391 array(
392 'operator' => 'AND',
393 'type' => 'FIELD_records',
394 'negate' => 1,
395 'inputValue' => 'foo foo'
396 )
397 )
398 ),
399 array(
400 'operator' => 'OR',
401 'type' => 'FIELD_maillist',
402 )
403 );
404 */
405 $this->initUserDef();
406 }
407
408 /**
409 * Set and clean up external lists
410 *
411 * @param string $name The name
412 * @param string $list The list
413 * @param string $force
414 */
415 public function setAndCleanUpExternalLists($name, $list, $force = '')
416 {
417 $fields = array_unique(GeneralUtility::trimExplode(',', $list . ',' . $force, true));
418 $reList = [];
419 foreach ($fields as $fieldName) {
420 if ($this->fields[$fieldName]) {
421 $reList[] = $fieldName;
422 }
423 }
424 $this->extFieldLists[$name] = implode(',', $reList);
425 }
426
427 /**
428 * Process data
429 *
430 * @param string $qC Query config
431 */
432 public function procesData($qC = '')
433 {
434 $this->queryConfig = $qC;
435 $POST = GeneralUtility::_POST();
436 // If delete...
437 if ($POST['qG_del']) {
438 // Initialize array to work on, save special parameters
439 $ssArr = $this->getSubscript($POST['qG_del']);
440 $workArr = &$this->queryConfig;
441 $ssArrSize = count($ssArr) - 1;
442 $i = 0;
443 for (; $i < $ssArrSize; $i++) {
444 $workArr = &$workArr[$ssArr[$i]];
445 }
446 // Delete the entry and move the other entries
447 unset($workArr[$ssArr[$i]]);
448 $workArrSize = count($workArr);
449 for ($j = $ssArr[$i]; $j < $workArrSize; $j++) {
450 $workArr[$j] = $workArr[$j + 1];
451 unset($workArr[$j + 1]);
452 }
453 }
454 // If insert...
455 if ($POST['qG_ins']) {
456 // Initialize array to work on, save special parameters
457 $ssArr = $this->getSubscript($POST['qG_ins']);
458 $workArr = &$this->queryConfig;
459 $ssArrSize = count($ssArr) - 1;
460 $i = 0;
461 for (; $i < $ssArrSize; $i++) {
462 $workArr = &$workArr[$ssArr[$i]];
463 }
464 // Move all entries above position where new entry is to be inserted
465 $workArrSize = count($workArr);
466 for ($j = $workArrSize; $j > $ssArr[$i]; $j--) {
467 $workArr[$j] = $workArr[$j - 1];
468 }
469 // Clear new entry position
470 unset($workArr[$ssArr[$i] + 1]);
471 $workArr[$ssArr[$i] + 1]['type'] = 'FIELD_';
472 }
473 // If move up...
474 if ($POST['qG_up']) {
475 // Initialize array to work on
476 $ssArr = $this->getSubscript($POST['qG_up']);
477 $workArr = &$this->queryConfig;
478 $ssArrSize = count($ssArr) - 1;
479 $i = 0;
480 for (; $i < $ssArrSize; $i++) {
481 $workArr = &$workArr[$ssArr[$i]];
482 }
483 // Swap entries
484 $qG_tmp = $workArr[$ssArr[$i]];
485 $workArr[$ssArr[$i]] = $workArr[$ssArr[$i] - 1];
486 $workArr[$ssArr[$i] - 1] = $qG_tmp;
487 }
488 // If new level...
489 if ($POST['qG_nl']) {
490 // Initialize array to work on
491 $ssArr = $this->getSubscript($POST['qG_nl']);
492 $workArr = &$this->queryConfig;
493 $ssArraySize = count($ssArr) - 1;
494 $i = 0;
495 for (; $i < $ssArraySize; $i++) {
496 $workArr = &$workArr[$ssArr[$i]];
497 }
498 // Do stuff:
499 $tempEl = $workArr[$ssArr[$i]];
500 if (is_array($tempEl)) {
501 if ($tempEl['type'] !== 'newlevel') {
502 $workArr[$ssArr[$i]] = [
503 'type' => 'newlevel',
504 'operator' => $tempEl['operator'],
505 'nl' => [$tempEl]
506 ];
507 }
508 }
509 }
510 // If collapse level...
511 if ($POST['qG_remnl']) {
512 // Initialize array to work on
513 $ssArr = $this->getSubscript($POST['qG_remnl']);
514 $workArr = &$this->queryConfig;
515 $ssArrSize = count($ssArr) - 1;
516 $i = 0;
517 for (; $i < $ssArrSize; $i++) {
518 $workArr = &$workArr[$ssArr[$i]];
519 }
520 // Do stuff:
521 $tempEl = $workArr[$ssArr[$i]];
522 if (is_array($tempEl)) {
523 if ($tempEl['type'] === 'newlevel') {
524 $a1 = array_slice($workArr, 0, $ssArr[$i]);
525 $a2 = array_slice($workArr, $ssArr[$i]);
526 array_shift($a2);
527 $a3 = $tempEl['nl'];
528 $a3[0]['operator'] = $tempEl['operator'];
529 $workArr = array_merge($a1, $a3, $a2);
530 }
531 }
532 }
533 }
534
535 /**
536 * Clean up query config
537 *
538 * @param array $queryConfig Query config
539 * @return array
540 */
541 public function cleanUpQueryConfig($queryConfig)
542 {
543 // Since we don't traverse the array using numeric keys in the upcoming while-loop make sure it's fresh and clean before displaying
544 if (is_array($queryConfig)) {
545 ksort($queryConfig);
546 } else {
547 // queryConfig should never be empty!
548 if (!isset($queryConfig[0]) || empty($queryConfig[0]['type'])) {
549 // Make sure queryConfig is an array
550 $queryConfig = [];
551 $queryConfig[0] = ['type' => 'FIELD_'];
552 }
553 }
554 // Traverse:
555 foreach ($queryConfig as $key => $conf) {
556 $fieldName = '';
557 if (substr($conf['type'], 0, 6) === 'FIELD_') {
558 $fieldName = substr($conf['type'], 6);
559 $fieldType = $this->fields[$fieldName]['type'];
560 } elseif ($conf['type'] === 'newlevel') {
561 $fieldType = $conf['type'];
562 } else {
563 $fieldType = 'ignore';
564 }
565 switch ($fieldType) {
566 case 'newlevel':
567 if (!$queryConfig[$key]['nl']) {
568 $queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
569 }
570 $queryConfig[$key]['nl'] = $this->cleanUpQueryConfig($queryConfig[$key]['nl']);
571 break;
572 case 'userdef':
573 $queryConfig[$key] = $this->userDefCleanUp($queryConfig[$key]);
574 break;
575 case 'ignore':
576 default:
577 $verifiedName = $this->verifyType($fieldName);
578 $queryConfig[$key]['type'] = 'FIELD_' . $this->verifyType($verifiedName);
579 if ($conf['comparison'] >> 5 != $this->comp_offsets[$fieldType]) {
580 $conf['comparison'] = $this->comp_offsets[$fieldType] << 5;
581 }
582 $queryConfig[$key]['comparison'] = $this->verifyComparison($conf['comparison'], $conf['negate'] ? 1 : 0);
583 $queryConfig[$key]['inputValue'] = $this->cleanInputVal($queryConfig[$key]);
584 $queryConfig[$key]['inputValue1'] = $this->cleanInputVal($queryConfig[$key], 1);
585 }
586 }
587 return $queryConfig;
588 }
589
590 /**
591 * Get form elements
592 *
593 * @param int $subLevel
594 * @param string $queryConfig
595 * @param string $parent
596 * @return array
597 */
598 public function getFormElements($subLevel = 0, $queryConfig = '', $parent = '')
599 {
600 $codeArr = [];
601 if (!is_array($queryConfig)) {
602 $queryConfig = $this->queryConfig;
603 }
604 $c = 0;
605 $arrCount = 0;
606 $loopCount = 0;
607 foreach ($queryConfig as $key => $conf) {
608 $fieldName = '';
609 $subscript = $parent . '[' . $key . ']';
610 $lineHTML = [];
611 $lineHTML[] = $this->mkOperatorSelect($this->name . $subscript, $conf['operator'], $c, $conf['type'] !== 'FIELD_');
612 if (substr($conf['type'], 0, 6) === 'FIELD_') {
613 $fieldName = substr($conf['type'], 6);
614 $this->fieldName = $fieldName;
615 $fieldType = $this->fields[$fieldName]['type'];
616 if ($conf['comparison'] >> 5 != $this->comp_offsets[$fieldType]) {
617 $conf['comparison'] = $this->comp_offsets[$fieldType] << 5;
618 }
619 //nasty nasty...
620 //make sure queryConfig contains _actual_ comparevalue.
621 //mkCompSelect don't care, but getQuery does.
622 $queryConfig[$key]['comparison'] += isset($conf['negate']) - $conf['comparison'] % 2;
623 } elseif ($conf['type'] === 'newlevel') {
624 $fieldType = $conf['type'];
625 } else {
626 $fieldType = 'ignore';
627 }
628 $fieldPrefix = htmlspecialchars($this->name . $subscript);
629 switch ($fieldType) {
630 case 'ignore':
631 break;
632 case 'newlevel':
633 if (!$queryConfig[$key]['nl']) {
634 $queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
635 }
636 $lineHTML[] = '<input type="hidden" name="' . $fieldPrefix . '[type]" value="newlevel">';
637 $codeArr[$arrCount]['sub'] = $this->getFormElements($subLevel + 1, $queryConfig[$key]['nl'], $subscript . '[nl]');
638 break;
639 case 'userdef':
640 $lineHTML[] = $this->userDef($fieldPrefix, $conf, $fieldName, $fieldType);
641 break;
642 case 'date':
643 $lineHTML[] = '<div class="form-inline">';
644 $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
645 if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
646 // between
647 $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'date');
648 $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue1]', $conf['inputValue1'], 'date');
649 } else {
650 $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'date');
651 }
652 $lineHTML[] = '</div>';
653 break;
654 case 'time':
655 $lineHTML[] = '<div class="form-inline">';
656 $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
657 if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
658 // between:
659 $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'datetime');
660 $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue1]', $conf['inputValue1'], 'datetime');
661 } else {
662 $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'datetime');
663 }
664 $lineHTML[] = '</div>';
665 break;
666 case 'multiple':
667 case 'binary':
668 case 'relation':
669 $lineHTML[] = '<div class="form-inline">';
670 $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
671 if ($conf['comparison'] === 68 || $conf['comparison'] === 69 || $conf['comparison'] === 162 || $conf['comparison'] === 163) {
672 $lineHTML[] = '<select class="form-control" name="' . $fieldPrefix . '[inputValue]' . '[]" multiple="multiple">';
673 } elseif ($conf['comparison'] === 66 || $conf['comparison'] === 67) {
674 if (is_array($conf['inputValue'])) {
675 $conf['inputValue'] = implode(',', $conf['inputValue']);
676 }
677 $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]' . '">';
678 } else {
679 $lineHTML[] = '<select class="form-control t3js-submit-change" name="' . $fieldPrefix . '[inputValue]' . '">';
680 }
681 if ($conf['comparison'] != 66 && $conf['comparison'] != 67) {
682 $lineHTML[] = $this->makeOptionList($fieldName, $conf, $this->table);
683 $lineHTML[] = '</select>';
684 }
685 $lineHTML[] = '</div>';
686 break;
687 case 'files':
688 $lineHTML[] = '<div class="form-inline">';
689 $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
690 if ($conf['comparison'] === 68 || $conf['comparison'] === 69) {
691 $lineHTML[] = '<select class="form-control" name="' . $fieldPrefix . '[inputValue]' . '[]" multiple="multiple">';
692 } else {
693 $lineHTML[] = '<select class="form-control t3js-submit-change" name="' . $fieldPrefix . '[inputValue]' . '">';
694 }
695 $lineHTML[] = '<option value=""></option>' . $this->makeOptionList($fieldName, $conf, $this->table);
696 $lineHTML[] = '</select>';
697 if ($conf['comparison'] === 66 || $conf['comparison'] === 67) {
698 $lineHTML[] = ' + <input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue1']) . '" name="' . $fieldPrefix . '[inputValue1]' . '">';
699 }
700 $lineHTML[] = '</div>';
701 break;
702 case 'boolean':
703 $lineHTML[] = '<div class="form-inline">';
704 $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
705 $lineHTML[] = '<input type="hidden" value="1" name="' . $fieldPrefix . '[inputValue]' . '">';
706 $lineHTML[] = '</div>';
707 break;
708 default:
709 $lineHTML[] = '<div class="form-inline">';
710 $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
711 if ($conf['comparison'] === 37 || $conf['comparison'] === 36) {
712 // between:
713 $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]' . '">';
714 $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue1']) . '" name="' . $fieldPrefix . '[inputValue1]' . '">';
715 } else {
716 $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]' . '">';
717 }
718 $lineHTML[] = '</div>';
719 }
720 if ($fieldType !== 'ignore') {
721 $lineHTML[] = '<div class="btn-group action-button-group">';
722 $lineHTML[] = $this->updateIcon();
723 if ($loopCount) {
724 $lineHTML[] = '<button class="btn btn-default" title="Remove condition" name="qG_del' . htmlspecialchars($subscript) . '"><i class="fa fa-trash fa-fw"></i></button>';
725 }
726 $lineHTML[] = '<button class="btn btn-default" title="Add condition" name="qG_ins' . htmlspecialchars($subscript) . '"><i class="fa fa-plus fa-fw"></i></button>';
727 if ($c != 0) {
728 $lineHTML[] = '<button class="btn btn-default" title="Move up" name="qG_up' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-up fa-fw"></i></button>';
729 }
730 if ($c != 0 && $fieldType !== 'newlevel') {
731 $lineHTML[] = '<button class="btn btn-default" title="New level" name="qG_nl' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-right fa-fw"></i></button>';
732 }
733 if ($fieldType === 'newlevel') {
734 $lineHTML[] = '<button class="btn btn-default" title="Collapse new level" name="qG_remnl' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-left fa-fw"></i></button>';
735 }
736 $lineHTML[] = '</div>';
737 $codeArr[$arrCount]['html'] = implode(LF, $lineHTML);
738 $codeArr[$arrCount]['query'] = $this->getQuerySingle($conf, $c > 0 ? 0 : 1);
739 $arrCount++;
740 $c++;
741 }
742 $loopCount = 1;
743 }
744 $this->queryConfig = $queryConfig;
745 return $codeArr;
746 }
747
748 /**
749 * @param string $subscript
750 * @param string $fieldName
751 * @param array $conf
752 *
753 * @return string
754 */
755 protected function makeComparisonSelector($subscript, $fieldName, $conf)
756 {
757 $fieldPrefix = $this->name . $subscript;
758 $lineHTML = [];
759 $lineHTML[] = $this->mkTypeSelect($fieldPrefix . '[type]', $fieldName);
760 $lineHTML[] = ' <div class="input-group">';
761 $lineHTML[] = $this->mkCompSelect($fieldPrefix . '[comparison]', $conf['comparison'], $conf['negate'] ? 1 : 0);
762 $lineHTML[] = ' <div class="input-group-addon">';
763 $lineHTML[] = ' <input type="checkbox" class="checkbox t3js-submit-click"' . ($conf['negate'] ? ' checked' : '') . ' name="' . htmlspecialchars($fieldPrefix) . '[negate]' . '">';
764 $lineHTML[] = ' </div>';
765 $lineHTML[] = ' </div>';
766 return implode(LF, $lineHTML);
767 }
768
769 /**
770 * Make option list
771 *
772 * @param string $fieldName
773 * @param array $conf
774 * @param string $table
775 * @return string
776 */
777 public function makeOptionList($fieldName, $conf, $table)
778 {
779 $out = [];
780 $fieldSetup = $this->fields[$fieldName];
781 $languageService = $this->getLanguageService();
782 if ($fieldSetup['type'] === 'files') {
783 if ($conf['comparison'] === 66 || $conf['comparison'] === 67) {
784 $fileExtArray = explode(',', $fieldSetup['allowed']);
785 natcasesort($fileExtArray);
786 foreach ($fileExtArray as $fileExt) {
787 if (GeneralUtility::inList($conf['inputValue'], $fileExt)) {
788 $out[] = '<option value="' . htmlspecialchars($fileExt) . '" selected>.' . htmlspecialchars($fileExt) . '</option>';
789 } else {
790 $out[] = '<option value="' . htmlspecialchars($fileExt) . '">.' . htmlspecialchars($fileExt) . '</option>';
791 }
792 }
793 }
794 $d = dir(PATH_site . $fieldSetup['uploadfolder']);
795 while (false !== ($entry = $d->read())) {
796 if ($entry === '.' || $entry === '..') {
797 continue;
798 }
799 $fileArray[] = $entry;
800 }
801 $d->close();
802 natcasesort($fileArray);
803 foreach ($fileArray as $fileName) {
804 if (GeneralUtility::inList($conf['inputValue'], $fileName)) {
805 $out[] = '<option value="' . htmlspecialchars($fileName) . '" selected>' . htmlspecialchars($fileName) . '</option>';
806 } else {
807 $out[] = '<option value="' . htmlspecialchars($fileName) . '">' . htmlspecialchars($fileName) . '</option>';
808 }
809 }
810 }
811 if ($fieldSetup['type'] === 'multiple') {
812 foreach ($fieldSetup['items'] as $key => $val) {
813 if (substr($val[0], 0, 4) === 'LLL:') {
814 $value = $languageService->sL($val[0]);
815 } else {
816 $value = $val[0];
817 }
818 if (GeneralUtility::inList($conf['inputValue'], $val[1])) {
819 $out[] = '<option value="' . htmlspecialchars($val[1]) . '" selected>' . htmlspecialchars($value) . '</option>';
820 } else {
821 $out[] = '<option value="' . htmlspecialchars($val[1]) . '">' . htmlspecialchars($value) . '</option>';
822 }
823 }
824 }
825 if ($fieldSetup['type'] === 'binary') {
826 foreach ($fieldSetup['items'] as $key => $val) {
827 if (substr($val[0], 0, 4) === 'LLL:') {
828 $value = $languageService->sL($val[0]);
829 } else {
830 $value = $val[0];
831 }
832 if (GeneralUtility::inList($conf['inputValue'], pow(2, $key))) {
833 $out[] = '<option value="' . pow(2, $key) . '" selected>' . htmlspecialchars($value) . '</option>';
834 } else {
835 $out[] = '<option value="' . pow(2, $key) . '">' . htmlspecialchars($value) . '</option>';
836 }
837 }
838 }
839 if ($fieldSetup['type'] === 'relation') {
840 $useTablePrefix = 0;
841 $dontPrefixFirstTable = 0;
842 if ($fieldSetup['items']) {
843 foreach ($fieldSetup['items'] as $key => $val) {
844 if (substr($val[0], 0, 4) === 'LLL:') {
845 $value = $languageService->sL($val[0]);
846 } else {
847 $value = $val[0];
848 }
849 if (GeneralUtility::inList($conf['inputValue'], $val[1])) {
850 $out[] = '<option value="' . htmlspecialchars($val[1]) . '" selected>' . htmlspecialchars($value) . '</option>';
851 } else {
852 $out[] = '<option value="' . htmlspecialchars($val[1]) . '">' . htmlspecialchars($value) . '</option>';
853 }
854 }
855 }
856 if (stristr($fieldSetup['allowed'], ',')) {
857 $from_table_Arr = explode(',', $fieldSetup['allowed']);
858 $useTablePrefix = 1;
859 if (!$fieldSetup['prepend_tname']) {
860 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
861 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
862 $statement = $queryBuilder->select($fieldName)
863 ->from($table)
864 ->execute();
865 while ($row = $statement->fetch()) {
866 if (stristr($row[$fieldName], ',')) {
867 $checkContent = explode(',', $row[$fieldName]);
868 foreach ($checkContent as $singleValue) {
869 if (!stristr($singleValue, '_')) {
870 $dontPrefixFirstTable = 1;
871 }
872 }
873 } else {
874 $singleValue = $row[$fieldName];
875 if ($singleValue !== '' && !stristr($singleValue, '_')) {
876 $dontPrefixFirstTable = 1;
877 }
878 }
879 }
880 }
881 } else {
882 $from_table_Arr[0] = $fieldSetup['allowed'];
883 }
884 if ($fieldSetup['prepend_tname']) {
885 $useTablePrefix = 1;
886 }
887 if ($fieldSetup['foreign_table']) {
888 $from_table_Arr[0] = $fieldSetup['foreign_table'];
889 }
890 $counter = 0;
891 $tablePrefix = '';
892 $backendUserAuthentication = $this->getBackendUserAuthentication();
893 $module = $this->getModule();
894 $outArray = [];
895 $labelFieldSelect = [];
896 foreach ($from_table_Arr as $from_table) {
897 $useSelectLabels = false;
898 $useAltSelectLabels = false;
899 if ($useTablePrefix && !$dontPrefixFirstTable && $counter != 1 || $counter === 1) {
900 $tablePrefix = $from_table . '_';
901 }
902 $counter = 1;
903 if (is_array($GLOBALS['TCA'][$from_table])) {
904 $labelField = $GLOBALS['TCA'][$from_table]['ctrl']['label'];
905 $altLabelField = $GLOBALS['TCA'][$from_table]['ctrl']['label_alt'];
906 if ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items']) {
907 foreach ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] as $labelArray) {
908 if (substr($labelArray[0], 0, 4) === 'LLL:') {
909 $labelFieldSelect[$labelArray[1]] = $languageService->sL($labelArray[0]);
910 } else {
911 $labelFieldSelect[$labelArray[1]] = $labelArray[0];
912 }
913 }
914 $useSelectLabels = true;
915 }
916 $altLabelFieldSelect = [];
917 if ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items']) {
918 foreach ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] as $altLabelArray) {
919 if (substr($altLabelArray[0], 0, 4) === 'LLL:') {
920 $altLabelFieldSelect[$altLabelArray[1]] = $languageService->sL($altLabelArray[0]);
921 } else {
922 $altLabelFieldSelect[$altLabelArray[1]] = $altLabelArray[0];
923 }
924 }
925 $useAltSelectLabels = true;
926 }
927
928 if (!$this->tableArray[$from_table]) {
929 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
930 if ($module->MOD_SETTINGS['show_deleted']) {
931 $queryBuilder->getRestrictions()->removeAll();
932 } else {
933 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
934 }
935 $selectFields = ['uid', $labelField];
936 if ($altLabelField) {
937 $selectFields[] = $altLabelField;
938 }
939 $queryBuilder->select(...$selectFields)
940 ->from($from_table)
941 ->orderBy('uid');
942 if (!$backendUserAuthentication->isAdmin() && $GLOBALS['TYPO3_CONF_VARS']['BE']['lockBeUserToDBmounts']) {
943 $webMounts = $backendUserAuthentication->returnWebmounts();
944 $perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
945 $webMountPageTree = '';
946 $webMountPageTreePrefix = '';
947 foreach ($webMounts as $webMount) {
948 if ($webMountPageTree) {
949 $webMountPageTreePrefix = ',';
950 }
951 $webMountPageTree .= $webMountPageTreePrefix
952 . $this->getTreeList($webMount, 999, 0, $perms_clause);
953 }
954 if ($from_table === 'pages') {
955 $queryBuilder->where(
956 QueryHelper::stripLogicalOperatorPrefix($perms_clause),
957 $queryBuilder->expr()->in(
958 'uid',
959 $queryBuilder->createNamedParameter(
960 GeneralUtility::intExplode(',', $webMountPageTree),
961 Connection::PARAM_INT_ARRAY
962 )
963 )
964 );
965 } else {
966 $queryBuilder->where(
967 $queryBuilder->expr()->in(
968 'pid',
969 $queryBuilder->createNamedParameter(
970 GeneralUtility::intExplode(',', $webMountPageTree),
971 Connection::PARAM_INT_ARRAY
972 )
973 )
974 );
975 }
976 }
977 $statement = $queryBuilder->execute();
978 $this->tableArray[$from_table] = [];
979 while ($row = $statement->fetch()) {
980 $this->tableArray[$from_table][] = $row;
981 }
982 }
983
984 foreach ($this->tableArray[$from_table] as $key => $val) {
985 if ($useSelectLabels) {
986 $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($labelFieldSelect[$val[$labelField]]);
987 } elseif ($val[$labelField]) {
988 $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$labelField]);
989 } elseif ($useAltSelectLabels) {
990 $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($altLabelFieldSelect[$val[$altLabelField]]);
991 } else {
992 $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$altLabelField]);
993 }
994 }
995 if ($module->MOD_SETTINGS['options_sortlabel'] && is_array($outArray)) {
996 natcasesort($outArray);
997 }
998 }
999 }
1000 foreach ($outArray as $key2 => $val2) {
1001 if (GeneralUtility::inList($conf['inputValue'], $key2)) {
1002 $out[] = '<option value="' . htmlspecialchars($key2) . '" selected>[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
1003 } else {
1004 $out[] = '<option value="' . htmlspecialchars($key2) . '">[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
1005 }
1006 }
1007 }
1008 return implode(LF, $out);
1009 }
1010
1011 /**
1012 * Print code array
1013 *
1014 * @param array $codeArr
1015 * @param int $recursionLevel
1016 * @return string
1017 */
1018 public function printCodeArray($codeArr, $recursionLevel = 0)
1019 {
1020 $indent = 'row-group';
1021 if ($recursionLevel) {
1022 $indent = 'row-group indent indent-' . (int)$recursionLevel;
1023 }
1024 $out = [];
1025 foreach ($codeArr as $k => $v) {
1026 $out[] = '<div class="' . $indent . '">';
1027 $out[] = $v['html'];
1028
1029 if ($this->enableQueryParts) {
1030 $out[] = '<pre>';
1031 $out[] = htmlspecialchars($v['query']);
1032 $out[] = '</pre>';
1033 }
1034 if (is_array($v['sub'])) {
1035 $out[] = '<div class="' . $indent . '">';
1036 $out[] = $this->printCodeArray($v['sub'], $recursionLevel + 1);
1037 $out[] = '</div>';
1038 }
1039
1040 $out[] = '</div>';
1041 }
1042 return implode(LF, $out);
1043 }
1044
1045 /**
1046 * Make operator select
1047 *
1048 * @param string $name
1049 * @param string $op
1050 * @param bool $draw
1051 * @param bool $submit
1052 * @return string
1053 */
1054 public function mkOperatorSelect($name, $op, $draw, $submit)
1055 {
1056 $out = [];
1057 if ($draw) {
1058 $out[] = '<select class="form-control from-control-operator' . ($submit ? ' t3js-submit-change' : '') . '" name="' . htmlspecialchars($name) . '[operator]">';
1059 $out[] = ' <option value="AND"' . (!$op || $op === 'AND' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['AND']) . '</option>';
1060 $out[] = ' <option value="OR"' . ($op === 'OR' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['OR']) . '</option>';
1061 $out[] = '</select>';
1062 } else {
1063 $out[] = '<input type="hidden" value="' . htmlspecialchars($op) . '" name="' . htmlspecialchars($name) . '[operator]">';
1064 }
1065 return implode(LF, $out);
1066 }
1067
1068 /**
1069 * Make type select
1070 *
1071 * @param string $name
1072 * @param string $fieldName
1073 * @param string $prepend
1074 * @return string
1075 */
1076 public function mkTypeSelect($name, $fieldName, $prepend = 'FIELD_')
1077 {
1078 $out = [];
1079 $out[] = '<select class="form-control t3js-submit-change" name="' . htmlspecialchars($name) . '">';
1080 $out[] = '<option value=""></option>';
1081 foreach ($this->fields as $key => $value) {
1082 if (!$value['exclude'] || $this->getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
1083 $label = $this->fields[$key]['label'];
1084 $out[] = '<option value="' . htmlspecialchars($prepend . $key) . '"' . ($key === $fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
1085 }
1086 }
1087 $out[] = '</select>';
1088 return implode(LF, $out);
1089 }
1090
1091 /**
1092 * Verify type
1093 *
1094 * @param string $fieldName
1095 * @return string
1096 */
1097 public function verifyType($fieldName)
1098 {
1099 $first = '';
1100 foreach ($this->fields as $key => $value) {
1101 if (!$first) {
1102 $first = $key;
1103 }
1104 if ($key === $fieldName) {
1105 return $key;
1106 }
1107 }
1108 return $first;
1109 }
1110
1111 /**
1112 * Verify comparison
1113 *
1114 * @param string $comparison
1115 * @param int $neg
1116 * @return int
1117 */
1118 public function verifyComparison($comparison, $neg)
1119 {
1120 $compOffSet = $comparison >> 5;
1121 $first = -1;
1122 for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
1123 if ($first === -1) {
1124 $first = $i;
1125 }
1126 if ($i >> 1 === $comparison >> 1) {
1127 return $i;
1128 }
1129 }
1130 return $first;
1131 }
1132
1133 /**
1134 * Make field to input select
1135 *
1136 * @param string $name
1137 * @param string $fieldName
1138 * @return string
1139 */
1140 public function mkFieldToInputSelect($name, $fieldName)
1141 {
1142 $out = [];
1143 $out[] = '<div class="input-group">';
1144 $out[] = ' <div class="input-group-addon">';
1145 $out[] = ' <span class="input-group-btn">';
1146 $out[] = $this->updateIcon();
1147 $out[] = ' </span>';
1148 $out[] = ' </div>';
1149 $out[] = ' <input type="text" class="form-control t3js-clearable" value="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($name) . '">';
1150 $out[] = '</div>';
1151
1152 $out[] = '<select class="form-control t3js-addfield" name="_fieldListDummy" size="5" data-field="' . htmlspecialchars($name) . '">';
1153 foreach ($this->fields as $key => $value) {
1154 if (!$value['exclude'] || $this->getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
1155 $label = $this->fields[$key]['label'];
1156 $out[] = '<option value="' . htmlspecialchars($key) . '"' . ($key === $fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
1157 }
1158 }
1159 $out[] = '</select>';
1160 return implode(LF, $out);
1161 }
1162
1163 /**
1164 * Make table select
1165 *
1166 * @param string $name
1167 * @param string $cur
1168 * @return string
1169 */
1170 public function mkTableSelect($name, $cur)
1171 {
1172 $out = [];
1173 $out[] = '<select class="form-control t3js-submit-change" name="' . $name . '">';
1174 $out[] = '<option value=""></option>';
1175 foreach ($GLOBALS['TCA'] as $tN => $value) {
1176 if ($this->getBackendUserAuthentication()->check('tables_select', $tN)) {
1177 $out[] = '<option value="' . htmlspecialchars($tN) . '"' . ($tN === $cur ? ' selected' : '') . '>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tN]['ctrl']['title'])) . '</option>';
1178 }
1179 }
1180 $out[] = '</select>';
1181 return implode(LF, $out);
1182 }
1183
1184 /**
1185 * Make comparison select
1186 *
1187 * @param string $name
1188 * @param string $comparison
1189 * @param int $neg
1190 * @return string
1191 */
1192 public function mkCompSelect($name, $comparison, $neg)
1193 {
1194 $compOffSet = $comparison >> 5;
1195 $out = [];
1196 $out[] = '<select class="form-control t3js-submit-change" name="' . $name . '">';
1197 for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
1198 if ($this->lang['comparison'][$i . '_']) {
1199 $out[] = '<option value="' . $i . '"' . ($i >> 1 === $comparison >> 1 ? ' selected' : '') . '>' . htmlspecialchars($this->lang['comparison'][$i . '_']) . '</option>';
1200 }
1201 }
1202 $out[] = '</select>';
1203 return implode(LF, $out);
1204 }
1205
1206 /**
1207 * Get subscript
1208 *
1209 * @param array $arr
1210 * @return array
1211 */
1212 public function getSubscript($arr): array
1213 {
1214 $retArr = [];
1215 while (\is_array($arr)) {
1216 reset($arr);
1217 $key = key($arr);
1218 $retArr[] = $key;
1219 if (isset($arr[$key])) {
1220 $arr = $arr[$key];
1221 } else {
1222 break;
1223 }
1224 }
1225 return $retArr;
1226 }
1227
1228 /**
1229 * Init user definition
1230 */
1231 public function initUserDef()
1232 {
1233 }
1234
1235 /**
1236 * User definition
1237 *
1238 * @param string $fieldPrefix
1239 * @param array $conf
1240 * @param string $fieldName
1241 * @param string $fieldType
1242 *
1243 * @return string
1244 */
1245 public function userDef($fieldPrefix, $conf, $fieldName, $fieldType)
1246 {
1247 return '';
1248 }
1249
1250 /**
1251 * User definition clean up
1252 *
1253 * @param array $queryConfig
1254 * @return array
1255 */
1256 public function userDefCleanUp($queryConfig)
1257 {
1258 return $queryConfig;
1259 }
1260
1261 /**
1262 * Get query
1263 *
1264 * @param array $queryConfig
1265 * @param string $pad
1266 * @return string
1267 */
1268 public function getQuery($queryConfig, $pad = '')
1269 {
1270 $qs = '';
1271 // Since we don't traverse the array using numeric keys in the upcoming whileloop make sure it's fresh and clean
1272 ksort($queryConfig);
1273 $first = 1;
1274 foreach ($queryConfig as $key => $conf) {
1275 // Convert ISO-8601 timestamp (string) into unix timestamp (int)
1276 if (strtotime($conf['inputValue'])) {
1277 $conf['inputValue'] = strtotime($conf['inputValue']);
1278 if ($conf['inputValue1'] && strtotime($conf['inputValue1'])) {
1279 $conf['inputValue1'] = strtotime($conf['inputValue1']);
1280 }
1281 }
1282 switch ($conf['type']) {
1283 case 'newlevel':
1284 $qs .= LF . $pad . trim($conf['operator']) . ' (' . $this->getQuery(
1285 $queryConfig[$key]['nl'],
1286 $pad . ' '
1287 ) . LF . $pad . ')';
1288 break;
1289 case 'userdef':
1290 $qs .= LF . $pad . $this->getUserDefQuery($conf, $first);
1291 break;
1292 default:
1293 $qs .= LF . $pad . $this->getQuerySingle($conf, $first);
1294 }
1295 $first = 0;
1296 }
1297 return $qs;
1298 }
1299
1300 /**
1301 * Get single query
1302 *
1303 * @param array $conf
1304 * @param bool $first
1305 * @return string
1306 */
1307 public function getQuerySingle($conf, $first)
1308 {
1309 $qs = '';
1310 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
1311 $prefix = $this->enablePrefix ? $this->table . '.' : '';
1312 if (!$first) {
1313 // Is it OK to insert the AND operator if none is set?
1314 $qs .= trim($conf['operator'] ?: 'AND') . ' ';
1315 }
1316 $qsTmp = str_replace('#FIELD#', $prefix . trim(substr($conf['type'], 6)), $this->compSQL[$conf['comparison']]);
1317 $inputVal = $this->cleanInputVal($conf);
1318 if ($conf['comparison'] === 68 || $conf['comparison'] === 69) {
1319 $inputVal = explode(',', $inputVal);
1320 foreach ($inputVal as $key => $fileName) {
1321 $inputVal[$key] = '\'' . $fileName . '\'';
1322 }
1323 $inputVal = implode(',', $inputVal);
1324 $qsTmp = str_replace('#VALUE#', $inputVal, $qsTmp);
1325 } elseif ($conf['comparison'] === 162 || $conf['comparison'] === 163) {
1326 $inputValArray = explode(',', $inputVal);
1327 $inputVal = 0;
1328 foreach ($inputValArray as $fileName) {
1329 $inputVal += (int)$fileName;
1330 }
1331 $qsTmp = str_replace('#VALUE#', $inputVal, $qsTmp);
1332 } else {
1333 if (is_array($inputVal)) {
1334 $inputVal = $inputVal[0];
1335 }
1336 $qsTmp = str_replace('#VALUE#', trim($queryBuilder->quote($inputVal), '\''), $qsTmp);
1337 }
1338 if ($conf['comparison'] === 37 || $conf['comparison'] === 36 || $conf['comparison'] === 66 || $conf['comparison'] === 67 || $conf['comparison'] === 100 || $conf['comparison'] === 101) {
1339 // between:
1340 $inputVal = $this->cleanInputVal($conf, '1');
1341 $qsTmp = str_replace('#VALUE1#', trim($queryBuilder->quote($inputVal), '\''), $qsTmp);
1342 }
1343 $qs .= trim($qsTmp);
1344 return $qs;
1345 }
1346
1347 /**
1348 * Clear input value
1349 *
1350 * @param array $conf
1351 * @param string $suffix
1352 * @return string
1353 */
1354 public function cleanInputVal($conf, $suffix = '')
1355 {
1356 if ($conf['comparison'] >> 5 === 0 || ($conf['comparison'] === 32 || $conf['comparison'] === 33 || $conf['comparison'] === 64 || $conf['comparison'] === 65 || $conf['comparison'] === 66 || $conf['comparison'] === 67 || $conf['comparison'] === 96 || $conf['comparison'] === 97)) {
1357 $inputVal = $conf['inputValue' . $suffix];
1358 } elseif ($conf['comparison'] === 39 || $conf['comparison'] === 38) {
1359 // in list:
1360 $inputVal = implode(',', GeneralUtility::intExplode(',', $conf['inputValue' . $suffix]));
1361 } elseif ($conf['comparison'] === 68 || $conf['comparison'] === 69 || $conf['comparison'] === 162 || $conf['comparison'] === 163) {
1362 // in list:
1363 if (is_array($conf['inputValue' . $suffix])) {
1364 $inputVal = implode(',', $conf['inputValue' . $suffix]);
1365 } elseif ($conf['inputValue' . $suffix]) {
1366 $inputVal = $conf['inputValue' . $suffix];
1367 } else {
1368 $inputVal = 0;
1369 }
1370 } elseif (strtotime($conf['inputValue' . $suffix])) {
1371 $inputVal = $conf['inputValue' . $suffix];
1372 } else {
1373 $inputVal = (float)$conf['inputValue' . $suffix];
1374 }
1375 return $inputVal;
1376 }
1377
1378 /**
1379 * Get user definition query
1380 *
1381 * @param array $qcArr
1382 * @param bool $first
1383 */
1384 public function getUserDefQuery($qcArr, $first)
1385 {
1386 }
1387
1388 /**
1389 * Update icon
1390 *
1391 * @return string
1392 */
1393 public function updateIcon()
1394 {
1395 return '<button class="btn btn-default" title="Update" name="just_update"><i class="fa fa-refresh fa-fw"></i></button>';
1396 }
1397
1398 /**
1399 * Get label column
1400 *
1401 * @return string
1402 */
1403 public function getLabelCol()
1404 {
1405 return $GLOBALS['TCA'][$this->table]['ctrl']['label'];
1406 }
1407
1408 /**
1409 * Make selector table
1410 *
1411 * @param array $modSettings
1412 * @param string $enableList
1413 * @return string
1414 */
1415 public function makeSelectorTable($modSettings, $enableList = 'table,fields,query,group,order,limit')
1416 {
1417 $out = [];
1418 $enableArr = explode(',', $enableList);
1419 $userTsConfig = $this->getBackendUserAuthentication()->getTSConfig();
1420
1421 // Make output
1422 if (in_array('table', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableSelectATable']) {
1423 $out[] = '<div class="form-group">';
1424 $out[] = ' <label for="SET[queryTable]">Select a table:</label>';
1425 $out[] = $this->mkTableSelect('SET[queryTable]', $this->table);
1426 $out[] = '</div>';
1427 }
1428 if ($this->table) {
1429 // Init fields:
1430 $this->setAndCleanUpExternalLists('queryFields', $modSettings['queryFields'], 'uid,' . $this->getLabelCol());
1431 $this->setAndCleanUpExternalLists('queryGroup', $modSettings['queryGroup']);
1432 $this->setAndCleanUpExternalLists('queryOrder', $modSettings['queryOrder'] . ',' . $modSettings['queryOrder2']);
1433 // Limit:
1434 $this->extFieldLists['queryLimit'] = $modSettings['queryLimit'];
1435 if (!$this->extFieldLists['queryLimit']) {
1436 $this->extFieldLists['queryLimit'] = 100;
1437 }
1438 $parts = GeneralUtility::intExplode(',', $this->extFieldLists['queryLimit']);
1439 if ($parts[1]) {
1440 $this->limitBegin = $parts[0];
1441 $this->limitLength = $parts[1];
1442 } else {
1443 $this->limitLength = $this->extFieldLists['queryLimit'];
1444 }
1445 $this->extFieldLists['queryLimit'] = implode(',', array_slice($parts, 0, 2));
1446 // Insert Descending parts
1447 if ($this->extFieldLists['queryOrder']) {
1448 $descParts = explode(',', $modSettings['queryOrderDesc'] . ',' . $modSettings['queryOrder2Desc']);
1449 $orderParts = explode(',', $this->extFieldLists['queryOrder']);
1450 $reList = [];
1451 foreach ($orderParts as $kk => $vv) {
1452 $reList[] = $vv . ($descParts[$kk] ? ' DESC' : '');
1453 }
1454 $this->extFieldLists['queryOrder_SQL'] = implode(',', $reList);
1455 }
1456 // Query Generator:
1457 $this->procesData($modSettings['queryConfig'] ? unserialize($modSettings['queryConfig']) : '');
1458 $this->queryConfig = $this->cleanUpQueryConfig($this->queryConfig);
1459 $this->enableQueryParts = (bool)$modSettings['search_query_smallparts'];
1460 $codeArr = $this->getFormElements();
1461 $queryCode = $this->printCodeArray($codeArr);
1462 if (in_array('fields', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableSelectFields']) {
1463 $out[] = '<div class="form-group form-group-with-button-addon">';
1464 $out[] = ' <label for="SET[queryFields]">Select fields:</label>';
1465 $out[] = $this->mkFieldToInputSelect('SET[queryFields]', $this->extFieldLists['queryFields']);
1466 $out[] = '</div>';
1467 }
1468 if (in_array('query', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableMakeQuery']) {
1469 $out[] = '<div class="form-group">';
1470 $out[] = ' <label>Make Query:</label>';
1471 $out[] = $queryCode;
1472 $out[] = '</div>';
1473 }
1474 if (in_array('group', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableGroupBy']) {
1475 $out[] = '<div class="form-group form-inline">';
1476 $out[] = ' <label for="SET[queryGroup]">Group By:</label>';
1477 $out[] = $this->mkTypeSelect('SET[queryGroup]', $this->extFieldLists['queryGroup'], '');
1478 $out[] = '</div>';
1479 }
1480 if (in_array('order', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableOrderBy']) {
1481 $module = $this->getModule();
1482 $orderByArr = explode(',', $this->extFieldLists['queryOrder']);
1483 $orderBy = [];
1484 $orderBy[] = $this->mkTypeSelect('SET[queryOrder]', $orderByArr[0], '');
1485 $orderBy[] = '<div class="checkbox">';
1486 $orderBy[] = ' <label for="checkQueryOrderDesc">';
1487 $orderBy[] = BackendUtility::getFuncCheck($module->id, 'SET[queryOrderDesc]', $modSettings['queryOrderDesc'], '', '', 'id="checkQueryOrderDesc"') . ' Descending';
1488 $orderBy[] = ' </label>';
1489 $orderBy[] = '</div>';
1490
1491 if ($orderByArr[0]) {
1492 $orderBy[] = $this->mkTypeSelect('SET[queryOrder2]', $orderByArr[1], '');
1493 $orderBy[] = '<div class="checkbox">';
1494 $orderBy[] = ' <label for="checkQueryOrder2Desc">';
1495 $orderBy[] = BackendUtility::getFuncCheck($module->id, 'SET[queryOrder2Desc]', $modSettings['queryOrder2Desc'], '', '', 'id="checkQueryOrder2Desc"') . ' Descending';
1496 $orderBy[] = ' </label>';
1497 $orderBy[] = '</div>';
1498 }
1499 $out[] = '<div class="form-group form-inline">';
1500 $out[] = ' <label>Order By:</label>';
1501 $out[] = implode(LF, $orderBy);
1502 $out[] = '</div>';
1503 }
1504 if (in_array('limit', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableLimit']) {
1505 $limit = [];
1506 $limit[] = '<div class="input-group">';
1507 $limit[] = ' <div class="input-group-addon">';
1508 $limit[] = ' <span class="input-group-btn">';
1509 $limit[] = $this->updateIcon();
1510 $limit[] = ' </span>';
1511 $limit[] = ' </div>';
1512 $limit[] = ' <input type="text" class="form-control" value="' . htmlspecialchars($this->extFieldLists['queryLimit']) . '" name="SET[queryLimit]" id="queryLimit">';
1513 $limit[] = '</div>';
1514
1515 $prevLimit = $this->limitBegin - $this->limitLength < 0 ? 0 : $this->limitBegin - $this->limitLength;
1516 $prevButton = '';
1517 $nextButton = '';
1518
1519 if ($this->limitBegin) {
1520 $prevButton = '<input type="button" class="btn btn-default" value="previous ' . htmlspecialchars($this->limitLength) . '" data-value="' . htmlspecialchars($prevLimit . ',' . $this->limitLength) . '">';
1521 }
1522 if (!$this->limitLength) {
1523 $this->limitLength = 100;
1524 }
1525
1526 $nextLimit = $this->limitBegin + $this->limitLength;
1527 if ($nextLimit < 0) {
1528 $nextLimit = 0;
1529 }
1530 if ($nextLimit) {
1531 $nextButton = '<input type="button" class="btn btn-default" value="next ' . htmlspecialchars($this->limitLength) . '" data-value="' . htmlspecialchars($nextLimit . ',' . $this->limitLength) . '">';
1532 }
1533
1534 $out[] = '<div class="form-group form-group-with-button-addon">';
1535 $out[] = ' <label>Limit:</label>';
1536 $out[] = ' <div class="form-inline">';
1537 $out[] = implode(LF, $limit);
1538 $out[] = ' <div class="input-group">';
1539 $out[] = ' <div class="btn-group t3js-limit-submit">';
1540 $out[] = $prevButton;
1541 $out[] = $nextButton;
1542 $out[] = ' </div>';
1543 $out[] = ' <div class="btn-group t3js-limit-submit">';
1544 $out[] = ' <input type="button" class="btn btn-default" data-value="10" value="10">';
1545 $out[] = ' <input type="button" class="btn btn-default" data-value="20" value="20">';
1546 $out[] = ' <input type="button" class="btn btn-default" data-value="50" value="50">';
1547 $out[] = ' <input type="button" class="btn btn-default" data-value="100" value="100">';
1548 $out[] = ' </div>';
1549 $out[] = ' </div>';
1550 $out[] = ' </div>';
1551 $out[] = '</div>';
1552 }
1553 }
1554 return implode(LF, $out);
1555 }
1556
1557 /**
1558 * Recursively fetch all descendants of a given page
1559 *
1560 * @param int $id uid of the page
1561 * @param int $depth
1562 * @param int $begin
1563 * @param string $permClause
1564 * @return string comma separated list of descendant pages
1565 */
1566 public function getTreeList($id, $depth, $begin = 0, $permClause = '')
1567 {
1568 $depth = (int)$depth;
1569 $begin = (int)$begin;
1570 $id = (int)$id;
1571 if ($id < 0) {
1572 $id = abs($id);
1573 }
1574 if ($begin === 0) {
1575 $theList = $id;
1576 } else {
1577 $theList = '';
1578 }
1579 if ($id && $depth > 0) {
1580 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1581 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1582 $queryBuilder->select('uid')
1583 ->from('pages')
1584 ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
1585 ->orderBy('uid');
1586 if ($permClause !== '') {
1587 $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($permClause));
1588 }
1589 $statement = $queryBuilder->execute();
1590 while ($row = $statement->fetch()) {
1591 if ($begin <= 0) {
1592 $theList .= ',' . $row['uid'];
1593 }
1594 if ($depth > 1) {
1595 $theList .= $this->getTreeList($row['uid'], $depth - 1, $begin - 1, $permClause);
1596 }
1597 }
1598 }
1599 return $theList;
1600 }
1601
1602 /**
1603 * Get select query
1604 *
1605 * @param string $qString
1606 * @return string
1607 */
1608 public function getSelectQuery($qString = ''): string
1609 {
1610 $backendUserAuthentication = $this->getBackendUserAuthentication();
1611 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
1612 if ($this->getModule()->MOD_SETTINGS['show_deleted']) {
1613 $queryBuilder->getRestrictions()->removeAll();
1614 } else {
1615 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1616 }
1617 $fieldList = GeneralUtility::trimExplode(
1618 ',',
1619 $this->extFieldLists['queryFields']
1620 . ',pid'
1621 . ($GLOBALS['TCA'][$this->table]['ctrl']['delete'] ? ',' . $GLOBALS['TCA'][$this->table]['ctrl']['delete'] : '')
1622 );
1623 $queryBuilder->select(...$fieldList)
1624 ->from($this->table);
1625
1626 if ($this->extFieldLists['queryGroup']) {
1627 $queryBuilder->groupBy(...QueryHelper::parseGroupBy($this->extFieldLists['queryGroup']));
1628 }
1629 if ($this->extFieldLists['queryOrder']) {
1630 foreach (QueryHelper::parseOrderBy($this->extFieldLists['queryOrder_SQL']) as $orderPair) {
1631 list($fieldName, $order) = $orderPair;
1632 $queryBuilder->addOrderBy($fieldName, $order);
1633 }
1634 }
1635 if ($this->extFieldLists['queryLimit']) {
1636 $queryBuilder->setMaxResults((int)$this->extFieldLists['queryLimit']);
1637 }
1638
1639 if (!$backendUserAuthentication->isAdmin() && $GLOBALS['TYPO3_CONF_VARS']['BE']['lockBeUserToDBmounts']) {
1640 $webMounts = $backendUserAuthentication->returnWebmounts();
1641 $perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
1642 $webMountPageTree = '';
1643 $webMountPageTreePrefix = '';
1644 foreach ($webMounts as $webMount) {
1645 if ($webMountPageTree) {
1646 $webMountPageTreePrefix = ',';
1647 }
1648 $webMountPageTree .= $webMountPageTreePrefix
1649 . $this->getTreeList($webMount, 999, $begin = 0, $perms_clause);
1650 }
1651 // createNamedParameter() is not used here because the SQL fragment will only include
1652 // the :dcValueX placeholder when the query is returned as a string. The value for the
1653 // placeholder would be lost in the process.
1654 if ($this->table === 'pages') {
1655 $queryBuilder->where(
1656 QueryHelper::stripLogicalOperatorPrefix($perms_clause),
1657 $queryBuilder->expr()->in(
1658 'uid',
1659 GeneralUtility::intExplode(',', $webMountPageTree)
1660 )
1661 );
1662 } else {
1663 $queryBuilder->where(
1664 $queryBuilder->expr()->in(
1665 'pid',
1666 GeneralUtility::intExplode(',', $webMountPageTree)
1667 )
1668 );
1669 }
1670 }
1671 if (!$qString) {
1672 $qString = $this->getQuery($this->queryConfig);
1673 }
1674 $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($qString));
1675
1676 return $queryBuilder->getSQL();
1677 }
1678
1679 /**
1680 * @param string $name the field name
1681 * @param string $timestamp ISO-8601 timestamp
1682 * @param string $type [datetime, date, time, timesec, year]
1683 *
1684 * @return string
1685 */
1686 protected function getDateTimePickerField($name, $timestamp, $type)
1687 {
1688 $value = strtotime($timestamp) ? date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], strtotime($timestamp)) : '';
1689 $id = StringUtility::getUniqueId('dt_');
1690 $html = [];
1691 $html[] = '<div class="input-group" id="' . $id . '-wrapper">';
1692 $html[] = ' <input data-formengine-input-name="' . htmlspecialchars($name) . '" value="' . $value . '" class="form-control t3js-datetimepicker t3js-clearable" data-date-type="' . htmlspecialchars($type) . '" type="text" id="' . $id . '">';
1693 $html[] = ' <input name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($timestamp) . '" type="hidden">';
1694 $html[] = ' <span class="input-group-btn">';
1695 $html[] = ' <label class="btn btn-default" for="' . $id . '">';
1696 $html[] = ' <span class="fa fa-calendar"></span>';
1697 $html[] = ' </label>';
1698 $html[] = ' </span>';
1699 $html[] = '</div>';
1700 return implode(LF, $html);
1701 }
1702
1703 /**
1704 * Sets the current name of the input form.
1705 *
1706 * @param string $formName The name of the form.
1707 */
1708 public function setFormName($formName)
1709 {
1710 $this->formName = trim($formName);
1711 }
1712
1713 /**
1714 * @return BackendUserAuthentication
1715 */
1716 protected function getBackendUserAuthentication()
1717 {
1718 return $GLOBALS['BE_USER'];
1719 }
1720
1721 /**
1722 * @return BaseScriptClass
1723 */
1724 protected function getModule()
1725 {
1726 return $GLOBALS['SOBE'];
1727 }
1728
1729 /**
1730 * @return LanguageService
1731 */
1732 protected function getLanguageService()
1733 {
1734 return $GLOBALS['LANG'];
1735 }
1736 }