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