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