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