QueryGenerator::getQuery()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 21
rs 9.7333
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Lowlevel\Database;
17
18
use Doctrine\DBAL\Exception as DBALException;
19
use TYPO3\CMS\Backend\Routing\UriBuilder;
20
use TYPO3\CMS\Backend\Utility\BackendUtility;
21
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22
use TYPO3\CMS\Core\Database\Connection;
23
use TYPO3\CMS\Core\Database\ConnectionPool;
24
use TYPO3\CMS\Core\Database\Query\QueryHelper;
25
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26
use TYPO3\CMS\Core\Imaging\Icon;
27
use TYPO3\CMS\Core\Imaging\IconFactory;
28
use TYPO3\CMS\Core\Localization\LanguageService;
29
use TYPO3\CMS\Core\Messaging\FlashMessage;
30
use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
31
use TYPO3\CMS\Core\Messaging\FlashMessageService;
32
use TYPO3\CMS\Core\Type\Bitmask\Permission;
33
use TYPO3\CMS\Core\Utility\CsvUtility;
34
use TYPO3\CMS\Core\Utility\DebugUtility;
35
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
36
use TYPO3\CMS\Core\Utility\GeneralUtility;
37
use TYPO3\CMS\Core\Utility\HttpUtility;
38
use TYPO3\CMS\Core\Utility\MathUtility;
39
use TYPO3\CMS\Core\Utility\StringUtility;
40
41
/**
42
 * Class used in module tools/dbint (advanced search) and which may hold code specific for that module
43
 *
44
 * @internal This class is a specific implementation for the lowlevel extension and is not part of the TYPO3's Core API.
45
 */
46
class QueryGenerator
47
{
48
    /**
49
     * @var string
50
     */
51
    protected $storeList = 'search_query_smallparts,search_result_labels,labels_noprefix,show_deleted,queryConfig,queryTable,queryFields,queryLimit,queryOrder,queryOrderDesc,queryOrder2,queryOrder2Desc,queryGroup,search_query_makeQuery';
52
53
    /**
54
     * @var int
55
     */
56
    protected $noDownloadB = 0;
57
58
    /**
59
     * @var array
60
     */
61
    protected $hookArray = [];
62
63
    /**
64
     * @var string
65
     */
66
    protected $formName = '';
67
68
    /**
69
     * @var IconFactory
70
     */
71
    protected $iconFactory;
72
73
    /**
74
     * @var array
75
     */
76
    protected $tableArray = [];
77
78
    /**
79
     * @var array Settings, usually from the controller
80
     */
81
    protected $settings = [];
82
83
    /**
84
     * @var array information on the menu of this module
85
     */
86
    protected $menuItems = [];
87
88
    /**
89
     * @var string
90
     */
91
    protected $moduleName;
92
93
    /**
94
     * @var array
95
     */
96
    protected $lang = [
97
        'OR' => 'or',
98
        'AND' => 'and',
99
        'comparison' => [
100
            // Type = text	offset = 0
101
            '0_' => 'contains',
102
            '1_' => 'does not contain',
103
            '2_' => 'starts with',
104
            '3_' => 'does not start with',
105
            '4_' => 'ends with',
106
            '5_' => 'does not end with',
107
            '6_' => 'equals',
108
            '7_' => 'does not equal',
109
            // Type = number , offset = 32
110
            '32_' => 'equals',
111
            '33_' => 'does not equal',
112
            '34_' => 'is greater than',
113
            '35_' => 'is less than',
114
            '36_' => 'is between',
115
            '37_' => 'is not between',
116
            '38_' => 'is in list',
117
            '39_' => 'is not in list',
118
            '40_' => 'binary AND equals',
119
            '41_' => 'binary AND does not equal',
120
            '42_' => 'binary OR equals',
121
            '43_' => 'binary OR does not equal',
122
            // Type = multiple, relation, offset = 64
123
            '64_' => 'equals',
124
            '65_' => 'does not equal',
125
            '66_' => 'contains',
126
            '67_' => 'does not contain',
127
            '68_' => 'is in list',
128
            '69_' => 'is not in list',
129
            '70_' => 'binary AND equals',
130
            '71_' => 'binary AND does not equal',
131
            '72_' => 'binary OR equals',
132
            '73_' => 'binary OR does not equal',
133
            // Type = date,time  offset = 96
134
            '96_' => 'equals',
135
            '97_' => 'does not equal',
136
            '98_' => 'is greater than',
137
            '99_' => 'is less than',
138
            '100_' => 'is between',
139
            '101_' => 'is not between',
140
            '102_' => 'binary AND equals',
141
            '103_' => 'binary AND does not equal',
142
            '104_' => 'binary OR equals',
143
            '105_' => 'binary OR does not equal',
144
            // Type = boolean,  offset = 128
145
            '128_' => 'is True',
146
            '129_' => 'is False',
147
            // Type = binary , offset = 160
148
            '160_' => 'equals',
149
            '161_' => 'does not equal',
150
            '162_' => 'contains',
151
            '163_' => 'does not contain',
152
        ],
153
    ];
154
155
    /**
156
     * @var array
157
     */
158
    protected $compSQL = [
159
        // Type = text	offset = 0
160
        '0' => '#FIELD# LIKE \'%#VALUE#%\'',
161
        '1' => '#FIELD# NOT LIKE \'%#VALUE#%\'',
162
        '2' => '#FIELD# LIKE \'#VALUE#%\'',
163
        '3' => '#FIELD# NOT LIKE \'#VALUE#%\'',
164
        '4' => '#FIELD# LIKE \'%#VALUE#\'',
165
        '5' => '#FIELD# NOT LIKE \'%#VALUE#\'',
166
        '6' => '#FIELD# = \'#VALUE#\'',
167
        '7' => '#FIELD# != \'#VALUE#\'',
168
        // Type = number, offset = 32
169
        '32' => '#FIELD# = \'#VALUE#\'',
170
        '33' => '#FIELD# != \'#VALUE#\'',
171
        '34' => '#FIELD# > #VALUE#',
172
        '35' => '#FIELD# < #VALUE#',
173
        '36' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
174
        '37' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
175
        '38' => '#FIELD# IN (#VALUE#)',
176
        '39' => '#FIELD# NOT IN (#VALUE#)',
177
        '40' => '(#FIELD# & #VALUE#)=#VALUE#',
178
        '41' => '(#FIELD# & #VALUE#)!=#VALUE#',
179
        '42' => '(#FIELD# | #VALUE#)=#VALUE#',
180
        '43' => '(#FIELD# | #VALUE#)!=#VALUE#',
181
        // Type = multiple, relation, offset = 64
182
        '64' => '#FIELD# = \'#VALUE#\'',
183
        '65' => '#FIELD# != \'#VALUE#\'',
184
        '66' => '#FIELD# LIKE \'%#VALUE#%\' AND #FIELD# LIKE \'%#VALUE1#%\'',
185
        '67' => '(#FIELD# NOT LIKE \'%#VALUE#%\' OR #FIELD# NOT LIKE \'%#VALUE1#%\')',
186
        '68' => '#FIELD# IN (#VALUE#)',
187
        '69' => '#FIELD# NOT IN (#VALUE#)',
188
        '70' => '(#FIELD# & #VALUE#)=#VALUE#',
189
        '71' => '(#FIELD# & #VALUE#)!=#VALUE#',
190
        '72' => '(#FIELD# | #VALUE#)=#VALUE#',
191
        '73' => '(#FIELD# | #VALUE#)!=#VALUE#',
192
        // Type = date, offset = 32
193
        '96' => '#FIELD# = \'#VALUE#\'',
194
        '97' => '#FIELD# != \'#VALUE#\'',
195
        '98' => '#FIELD# > #VALUE#',
196
        '99' => '#FIELD# < #VALUE#',
197
        '100' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
198
        '101' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
199
        '102' => '(#FIELD# & #VALUE#)=#VALUE#',
200
        '103' => '(#FIELD# & #VALUE#)!=#VALUE#',
201
        '104' => '(#FIELD# | #VALUE#)=#VALUE#',
202
        '105' => '(#FIELD# | #VALUE#)!=#VALUE#',
203
        // Type = boolean, offset = 128
204
        '128' => '#FIELD# = \'1\'',
205
        '129' => '#FIELD# != \'1\'',
206
        // Type = binary = 160
207
        '160' => '#FIELD# = \'#VALUE#\'',
208
        '161' => '#FIELD# != \'#VALUE#\'',
209
        '162' => '(#FIELD# & #VALUE#)=#VALUE#',
210
        '163' => '(#FIELD# & #VALUE#)=0',
211
    ];
212
213
    /**
214
     * @var array
215
     */
216
    protected $comp_offsets = [
217
        'text' => 0,
218
        'number' => 1,
219
        'multiple' => 2,
220
        'relation' => 2,
221
        'date' => 3,
222
        'time' => 3,
223
        'boolean' => 4,
224
        'binary' => 5,
225
    ];
226
227
    /**
228
     * Form data name prefix
229
     *
230
     * @var string
231
     */
232
    protected $name;
233
234
    /**
235
     * Table for the query
236
     *
237
     * @var string
238
     */
239
    protected $table;
240
241
    /**
242
     * Field list
243
     *
244
     * @var string
245
     */
246
    protected $fieldList;
247
248
    /**
249
     * Array of the fields possible
250
     *
251
     * @var array
252
     */
253
    protected $fields = [];
254
255
    /**
256
     * @var array
257
     */
258
    protected $extFieldLists = [];
259
260
    /**
261
     * The query config
262
     *
263
     * @var array
264
     */
265
    protected $queryConfig = [];
266
267
    /**
268
     * @var bool
269
     */
270
    protected $enablePrefix = false;
271
272
    /**
273
     * @var bool
274
     */
275
    protected $enableQueryParts = false;
276
277
    /**
278
     * @var string
279
     */
280
    protected $fieldName;
281
282
    /**
283
     * If the current user is an admin and $GLOBALS['TYPO3_CONF_VARS']['BE']['debug']
284
     * is set to true, the names of fields and tables are displayed.
285
     *
286
     * @var bool
287
     */
288
    protected $showFieldAndTableNames = false;
289
290
    public function __construct(array $settings, array $menuItems, string $moduleName)
291
    {
292
        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_t3lib_fullsearch.xlf');
293
        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
294
        $this->settings = $settings;
295
        $this->menuItems = $menuItems;
296
        $this->moduleName = $moduleName;
297
        $this->showFieldAndTableNames = ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] ?? false)
298
            && $this->getBackendUserAuthentication()->isAdmin();
299
    }
300
301
    /**
302
     * Get form
303
     *
304
     * @return string
305
     */
306
    public function form()
307
    {
308
        $markup = [];
309
        $markup[] = '<div class="form-group">';
310
        $markup[] = '<input placeholder="Search Word" class="form-control" type="search" name="SET[sword]" value="'
311
            . htmlspecialchars($this->settings['sword'] ?? '') . '">';
312
        $markup[] = '</div>';
313
        $markup[] = '<div class="form-group">';
314
        $markup[] = '<input class="btn btn-default" type="submit" name="submit" value="Search All Records">';
315
        $markup[] = '</div>';
316
        return implode(LF, $markup);
317
    }
318
319
    /**
320
     * Query marker
321
     *
322
     * @return string
323
     */
324
    public function queryMaker()
325
    {
326
        $output = '';
327
        $this->hookArray = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3lib_fullsearch'] ?? [];
328
        $msg = $this->procesStoreControl();
329
        $userTsConfig = $this->getBackendUserAuthentication()->getTSConfig();
330
        if (!($userTsConfig['mod.']['dbint.']['disableStoreControl'] ?? false)) {
331
            $output .= '<h2>Load/Save Query</h2>';
332
            $output .= '<div>' . $this->makeStoreControl() . '</div>';
333
            $output .= $msg;
334
        }
335
        // Query Maker:
336
        $this->init('queryConfig', $this->settings['queryTable'] ?? '', '', $this->settings);
337
        if ($this->formName) {
338
            $this->setFormName($this->formName);
339
        }
340
        $tmpCode = $this->makeSelectorTable($this->settings);
341
        $output .= '<div id="query"></div><h2>Make query</h2><div>' . $tmpCode . '</div>';
342
        $mQ = $this->settings['search_query_makeQuery'];
343
        // Make form elements:
344
        if ($this->table && is_array($GLOBALS['TCA'][$this->table])) {
345
            if ($mQ) {
346
                // Show query
347
                $this->enablePrefix = true;
348
                $queryString = $this->getQuery($this->queryConfig);
349
                $selectQueryString = $this->getSelectQuery($queryString);
350
                $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
351
352
                $isConnectionMysql = strpos($connection->getServerVersion(), 'MySQL') === 0;
353
                $fullQueryString = '';
354
                try {
355
                    if ($mQ === 'explain' && $isConnectionMysql) {
356
                        // EXPLAIN is no ANSI SQL, for now this is only executed on mysql
357
                        // @todo: Move away from getSelectQuery() or model differently
358
                        $fullQueryString = 'EXPLAIN ' . $selectQueryString;
359
                        $dataRows = $connection->executeQuery('EXPLAIN ' . $selectQueryString)->fetchAllAssociative();
360
                    } elseif ($mQ === 'count') {
361
                        $queryBuilder = $connection->createQueryBuilder();
362
                        $queryBuilder->getRestrictions()->removeAll();
363
                        if (empty($this->settings['show_deleted'])) {
364
                            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
365
                        }
366
                        $queryBuilder->count('*')
367
                            ->from($this->table)
368
                            ->where(QueryHelper::stripLogicalOperatorPrefix($queryString));
369
                        $fullQueryString = $queryBuilder->getSQL();
370
                        $dataRows = [$queryBuilder->execute()->fetchOne()];
371
                    } else {
372
                        $fullQueryString = $selectQueryString;
373
                        $dataRows = $connection->executeQuery($selectQueryString)->fetchAllAssociative();
374
                    }
375
                    if (!($userTsConfig['mod.']['dbint.']['disableShowSQLQuery'] ?? false)) {
376
                        $output .= '<h2>SQL query</h2><div><code>' . htmlspecialchars($fullQueryString) . '</code></div>';
377
                    }
378
                    $cPR = $this->getQueryResultCode($mQ, $dataRows, $this->table);
379
                    $output .= '<h2>' . ($cPR['header'] ?? '') . '</h2><div>' . $cPR['content'] . '</div>';
380
                } catch (DBALException $e) {
381
                    if (!($userTsConfig['mod.']['dbint.']['disableShowSQLQuery'] ?? false)) {
382
                        $output .= '<h2>SQL query</h2><div><code>' . htmlspecialchars($fullQueryString) . '</code></div>';
383
                    }
384
                    $out = '<p><strong>Error: <span class="text-danger">'
385
                        . htmlspecialchars($e->getMessage())
386
                        . '</span></strong></p>';
387
                    $output .= '<h2>SQL error</h2><div>' . $out . '</div>';
388
                }
389
            }
390
        }
391
        return '<div class="database-query-builder">' . $output . '</div>';
392
    }
393
394
    /**
395
     * Search
396
     *
397
     * @return string
398
     */
399
    public function search()
400
    {
401
        $swords = $this->settings['sword'] ?? '';
402
        $out = '';
403
        if ($swords) {
404
            foreach ($GLOBALS['TCA'] as $table => $value) {
405
                // Get fields list
406
                $conf = $GLOBALS['TCA'][$table];
407
                // Avoid querying tables with no columns
408
                if (empty($conf['columns'])) {
409
                    continue;
410
                }
411
                $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
412
                $tableColumns = $connection->getSchemaManager()->listTableColumns($table);
413
                $fieldsInDatabase = [];
414
                foreach ($tableColumns as $column) {
415
                    $fieldsInDatabase[] = $column->getName();
416
                }
417
                $fields = array_intersect(array_keys($conf['columns']), $fieldsInDatabase);
418
419
                $queryBuilder = $connection->createQueryBuilder();
420
                $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
421
                $queryBuilder->count('*')->from($table);
422
                $likes = [];
423
                $escapedLikeString = '%' . $queryBuilder->escapeLikeWildcards($swords) . '%';
424
                foreach ($fields as $field) {
425
                    $likes[] = $queryBuilder->expr()->like(
426
                        $field,
427
                        $queryBuilder->createNamedParameter($escapedLikeString, \PDO::PARAM_STR)
428
                    );
429
                }
430
                $count = $queryBuilder->orWhere(...$likes)->execute()->fetchOne();
431
432
                if ($count > 0) {
433
                    $queryBuilder = $connection->createQueryBuilder();
434
                    $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
435
                    $queryBuilder->select('uid', $conf['ctrl']['label'])
436
                        ->from($table)
437
                        ->setMaxResults(200);
438
                    $likes = [];
439
                    foreach ($fields as $field) {
440
                        $likes[] = $queryBuilder->expr()->like(
441
                            $field,
442
                            $queryBuilder->createNamedParameter($escapedLikeString, \PDO::PARAM_STR)
443
                        );
444
                    }
445
                    $statement = $queryBuilder->orWhere(...$likes)->execute();
446
                    $lastRow = null;
447
                    $rowArr = [];
448
                    while ($row = $statement->fetchAssociative()) {
449
                        $rowArr[] = $this->resultRowDisplay($row, $conf, $table);
450
                        $lastRow = $row;
451
                    }
452
                    $markup = [];
453
                    $markup[] = '<div class="panel panel-default">';
454
                    $markup[] = '  <div class="panel-heading">';
455
                    $markup[] = htmlspecialchars($this->getLanguageService()->sL($conf['ctrl']['title'])) . ' (' . $count . ')';
456
                    $markup[] = '  </div>';
457
                    $markup[] = '  <table class="table table-striped table-hover">';
458
                    $markup[] = $this->resultRowTitles($lastRow, $conf);
459
                    $markup[] = implode(LF, $rowArr);
460
                    $markup[] = '  </table>';
461
                    $markup[] = '</div>';
462
463
                    $out .= implode(LF, $markup);
464
                }
465
            }
466
        }
467
        return $out;
468
    }
469
470
    /**
471
     * Sets the current name of the input form.
472
     *
473
     * @param string $formName The name of the form.
474
     */
475
    public function setFormName($formName)
476
    {
477
        $this->formName = trim($formName);
478
    }
479
480
    /**
481
     * Make store control
482
     *
483
     * @return string
484
     */
485
    protected function makeStoreControl()
486
    {
487
        // Load/Save
488
        $storeArray = $this->initStoreArray();
489
490
        $opt = [];
491
        foreach ($storeArray as $k => $v) {
492
            $opt[] = '<option value="' . htmlspecialchars($k) . '">' . htmlspecialchars($v) . '</option>';
493
        }
494
        // Actions:
495
        if (ExtensionManagementUtility::isLoaded('sys_action') && $this->getBackendUserAuthentication()->isAdmin()) {
496
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_action');
497
            $queryBuilder->getRestrictions()->removeAll();
498
            $statement = $queryBuilder->select('uid', 'title')
499
                ->from('sys_action')
500
                ->where($queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter(2, \PDO::PARAM_INT)))
501
                ->orderBy('title')
502
                ->execute();
503
            $opt[] = '<option value="0">__Save to Action:__</option>';
504
            while ($row = $statement->fetchAssociative()) {
505
                $opt[] = '<option value="-' . (int)$row['uid'] . '">' . htmlspecialchars($row['title']
506
                        . ' [' . (int)$row['uid'] . ']') . '</option>';
507
            }
508
        }
509
        $markup = [];
510
        $markup[] = '<div class="load-queries">';
511
        $markup[] = '  <div class="row row-cols-auto">';
512
        $markup[] = '    <div class="col">';
513
        $markup[] = '      <select class="form-select" name="storeControl[STORE]" data-assign-store-control-title>' . implode(LF, $opt) . '</select>';
514
        $markup[] = '    </div>';
515
        $markup[] = '    <div class="col">';
516
        $markup[] = '      <input class="form-control" name="storeControl[title]" value="" type="text" max="80">';
517
        $markup[] = '    </div>';
518
        $markup[] = '    <div class="col">';
519
        $markup[] = '      <input class="btn btn-default" type="submit" name="storeControl[LOAD]" value="Load">';
520
        $markup[] = '    </div>';
521
        $markup[] = '    <div class="col">';
522
        $markup[] = '      <input class="btn btn-default" type="submit" name="storeControl[SAVE]" value="Save">';
523
        $markup[] = '    </div>';
524
        $markup[] = '    <div class="col">';
525
        $markup[] = '      <input class="btn btn-default" type="submit" name="storeControl[REMOVE]" value="Remove">';
526
        $markup[] = '    </div>';
527
        $markup[] = '  </div>';
528
        $markup[] = '</div>';
529
530
        return implode(LF, $markup);
531
    }
532
533
    /**
534
     * Init store array
535
     *
536
     * @return array
537
     */
538
    protected function initStoreArray()
539
    {
540
        $storeArray = [
541
            '0' => '[New]',
542
        ];
543
        $savedStoreArray = unserialize($this->settings['storeArray'] ?? '', ['allowed_classes' => false]);
544
        if (is_array($savedStoreArray)) {
545
            $storeArray = array_merge($storeArray, $savedStoreArray);
546
        }
547
        return $storeArray;
548
    }
549
550
    /**
551
     * Clean store query configs
552
     *
553
     * @param array $storeQueryConfigs
554
     * @param array $storeArray
555
     * @return array
556
     */
557
    protected function cleanStoreQueryConfigs($storeQueryConfigs, $storeArray)
558
    {
559
        if (is_array($storeQueryConfigs)) {
0 ignored issues
show
introduced by
The condition is_array($storeQueryConfigs) is always true.
Loading history...
560
            foreach ($storeQueryConfigs as $k => $v) {
561
                if (!isset($storeArray[$k])) {
562
                    unset($storeQueryConfigs[$k]);
563
                }
564
            }
565
        }
566
        return $storeQueryConfigs;
567
    }
568
569
    /**
570
     * Add to store query configs
571
     *
572
     * @param array $storeQueryConfigs
573
     * @param int $index
574
     * @return array
575
     */
576
    protected function addToStoreQueryConfigs($storeQueryConfigs, $index)
577
    {
578
        $keyArr = explode(',', $this->storeList);
579
        $storeQueryConfigs[$index] = [];
580
        foreach ($keyArr as $k) {
581
            $storeQueryConfigs[$index][$k] = $this->settings[$k] ?? null;
582
        }
583
        return $storeQueryConfigs;
584
    }
585
586
    /**
587
     * Save query in action
588
     *
589
     * @param int $uid
590
     * @return bool
591
     */
592
    protected function saveQueryInAction($uid)
593
    {
594
        if (ExtensionManagementUtility::isLoaded('sys_action')) {
595
            $keyArr = explode(',', $this->storeList);
596
            $saveArr = [];
597
            foreach ($keyArr as $k) {
598
                $saveArr[$k] = $this->settings[$k];
599
            }
600
            // Show query
601
            if ($saveArr['queryTable']) {
602
                $this->init('queryConfig', $saveArr['queryTable'], '', $this->settings);
603
                $this->makeSelectorTable($saveArr);
604
                $this->enablePrefix = true;
605
                $queryString = $this->getQuery($this->queryConfig);
606
607
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
608
                    ->getQueryBuilderForTable($this->table);
609
                $queryBuilder->getRestrictions()->removeAll()
610
                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
611
                $rowCount = $queryBuilder->count('*')
612
                    ->from($this->table)
613
                    ->where(QueryHelper::stripLogicalOperatorPrefix($queryString))
614
                    ->execute()->fetchOne();
615
616
                $t2DataValue = [
617
                    'qC' => $saveArr,
618
                    'qCount' => $rowCount,
619
                    'qSelect' => $this->getSelectQuery($queryString),
620
                    'qString' => $queryString,
621
                ];
622
                GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_action')
623
                    ->update(
624
                        'sys_action',
625
                        ['t2_data' => serialize($t2DataValue)],
626
                        ['uid' => (int)$uid],
627
                        ['t2_data' => Connection::PARAM_LOB]
628
                    );
629
            }
630
            return true;
631
        }
632
        return false;
633
    }
634
    /**
635
     * Load store query configs
636
     *
637
     * @param array $storeQueryConfigs
638
     * @param int $storeIndex
639
     * @param array $writeArray
640
     * @return array
641
     */
642
    protected function loadStoreQueryConfigs($storeQueryConfigs, $storeIndex, $writeArray)
643
    {
644
        if ($storeQueryConfigs[$storeIndex]) {
645
            $keyArr = explode(',', $this->storeList);
646
            foreach ($keyArr as $k) {
647
                $writeArray[$k] = $storeQueryConfigs[$storeIndex][$k];
648
            }
649
        }
650
        return $writeArray;
651
    }
652
653
    /**
654
     * Process store control
655
     *
656
     * @return string
657
     */
658
    protected function procesStoreControl()
659
    {
660
        $languageService = $this->getLanguageService();
661
        $flashMessage = null;
662
        $storeArray = $this->initStoreArray();
663
        $storeQueryConfigs = unserialize($this->settings['storeQueryConfigs'] ?? '', ['allowed_classes' => false]);
664
        $storeControl = GeneralUtility::_GP('storeControl');
665
        $storeIndex = (int)($storeControl['STORE'] ?? 0);
666
        $saveStoreArray = 0;
667
        $writeArray = [];
668
        $msg = '';
669
        if (is_array($storeControl)) {
0 ignored issues
show
introduced by
The condition is_array($storeControl) is always false.
Loading history...
670
            if ($storeControl['LOAD'] ?? false) {
671
                if ($storeIndex > 0) {
672
                    $writeArray = $this->loadStoreQueryConfigs($storeQueryConfigs, $storeIndex, $writeArray);
673
                    $saveStoreArray = 1;
674
                    $flashMessage = GeneralUtility::makeInstance(
675
                        FlashMessage::class,
676
                        sprintf($languageService->getLL('query_loaded'), $storeArray[$storeIndex])
677
                    );
678
                } elseif ($storeIndex < 0 && ExtensionManagementUtility::isLoaded('sys_action')) {
679
                    $actionRecord = BackendUtility::getRecord('sys_action', abs($storeIndex));
680
                    if (is_array($actionRecord)) {
681
                        $dA = unserialize($actionRecord['t2_data'], ['allowed_classes' => false]);
682
                        $dbSC = [];
683
                        if (is_array($dA['qC'])) {
684
                            $dbSC[0] = $dA['qC'];
685
                        }
686
                        $writeArray = $this->loadStoreQueryConfigs($dbSC, 0, $writeArray);
687
                        $saveStoreArray = 1;
688
                        $flashMessage = GeneralUtility::makeInstance(
689
                            FlashMessage::class,
690
                            sprintf($languageService->getLL('query_from_action_loaded'), $actionRecord['title'])
691
                        );
692
                    }
693
                }
694
            } elseif ($storeControl['SAVE'] ?? false) {
695
                if ($storeIndex < 0) {
696
                    $qOK = $this->saveQueryInAction(abs($storeIndex));
697
                    if ($qOK) {
698
                        $flashMessage = GeneralUtility::makeInstance(
699
                            FlashMessage::class,
700
                            $languageService->getLL('query_saved')
701
                        );
702
                    } else {
703
                        $flashMessage = GeneralUtility::makeInstance(
704
                            FlashMessage::class,
705
                            $languageService->getLL('query_notsaved'),
706
                            '',
707
                            FlashMessage::ERROR
708
                        );
709
                    }
710
                } else {
711
                    if (trim($storeControl['title'])) {
712
                        if ($storeIndex > 0) {
713
                            $storeArray[$storeIndex] = $storeControl['title'];
714
                        } else {
715
                            $storeArray[] = $storeControl['title'];
716
                            end($storeArray);
717
                            $storeIndex = key($storeArray);
718
                        }
719
                        $storeQueryConfigs = $this->addToStoreQueryConfigs($storeQueryConfigs, (int)$storeIndex);
720
                        $saveStoreArray = 1;
721
                        $flashMessage = GeneralUtility::makeInstance(
722
                            FlashMessage::class,
723
                            $languageService->getLL('query_saved')
724
                        );
725
                    }
726
                }
727
            } elseif ($storeControl['REMOVE'] ?? false) {
728
                if ($storeIndex > 0) {
729
                    $flashMessage = GeneralUtility::makeInstance(
730
                        FlashMessage::class,
731
                        sprintf($languageService->getLL('query_removed'), $storeArray[$storeControl['STORE']])
732
                    );
733
                    // Removing
734
                    unset($storeArray[$storeControl['STORE']]);
735
                    $saveStoreArray = 1;
736
                }
737
            }
738
            if (!empty($flashMessage)) {
739
                $msg = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)
740
                    ->resolve()
741
                    ->render([$flashMessage]);
742
            }
743
        }
744
        if ($saveStoreArray) {
745
            // Making sure, index 0 is not set!
746
            unset($storeArray[0]);
747
            $writeArray['storeArray'] = serialize($storeArray);
748
            $writeArray['storeQueryConfigs'] =
749
                serialize($this->cleanStoreQueryConfigs($storeQueryConfigs, $storeArray));
750
            $this->settings = BackendUtility::getModuleData(
751
                $this->menuItems,
752
                $writeArray,
753
                $this->moduleName,
754
                'ses'
755
            );
756
        }
757
        return $msg;
758
    }
759
760
    /**
761
     * Get query result code
762
     *
763
     * @param string $type
764
     * @param array $dataRows Rows to display
765
     * @param string $table
766
     * @return array HTML-code for "header" and "content"
767
     * @throws \TYPO3\CMS\Core\Exception
768
     */
769
    protected function getQueryResultCode($type, array $dataRows, $table)
770
    {
771
        $out = '';
772
        $cPR = [];
773
        switch ($type) {
774
            case 'count':
775
                $cPR['header'] = 'Count';
776
                $cPR['content'] = '<br><strong>' . (int)$dataRows[0] . '</strong> records selected.';
777
                break;
778
            case 'all':
779
                $rowArr = [];
780
                $dataRow = null;
781
                foreach ($dataRows as $dataRow) {
782
                    $rowArr[] = $this->resultRowDisplay($dataRow, $GLOBALS['TCA'][$table], $table);
783
                }
784
                if (is_array($this->hookArray['beforeResultTable'] ?? false)) {
785
                    foreach ($this->hookArray['beforeResultTable'] as $_funcRef) {
786
                        $out .= GeneralUtility::callUserFunction($_funcRef, $this->settings);
787
                    }
788
                }
789
                if (!empty($rowArr)) {
790
                    $cPR['header'] = 'Result';
791
                    $out .= '<table class="table table-striped table-hover">'
792
                        . $this->resultRowTitles($dataRow, $GLOBALS['TCA'][$table]) . implode(LF, $rowArr)
793
                        . '</table>';
794
                } else {
795
                    $this->renderNoResultsFoundMessage();
796
                }
797
798
                $cPR['content'] = $out;
799
                break;
800
            case 'csv':
801
                $rowArr = [];
802
                $first = 1;
803
                foreach ($dataRows as $dataRow) {
804
                    if ($first) {
805
                        $rowArr[] = $this->csvValues(array_keys($dataRow));
806
                        $first = 0;
807
                    }
808
                    $rowArr[] = $this->csvValues($dataRow, ',', '"', $GLOBALS['TCA'][$table], $table);
809
                }
810
                if (!empty($rowArr)) {
811
                    $cPR['header'] = 'Result';
812
                    $out .= '<textarea name="whatever" rows="20" class="text-monospace" style="width:100%">'
813
                        . htmlspecialchars(implode(LF, $rowArr))
814
                        . '</textarea>';
815
                    if (!$this->noDownloadB) {
816
                        $out .= '<br><input class="btn btn-default" type="submit" name="download_file" '
817
                            . 'value="Click to download file">';
818
                    }
819
                    // Downloads file:
820
                    // @todo: args. routing anyone?
821
                    if (GeneralUtility::_GP('download_file')) {
822
                        $filename = 'TYPO3_' . $table . '_export_' . date('dmy-Hi') . '.csv';
823
                        $mimeType = 'application/octet-stream';
824
                        header('Content-Type: ' . $mimeType);
825
                        header('Content-Disposition: attachment; filename=' . $filename);
826
                        echo implode(CRLF, $rowArr);
827
                        die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
828
                    }
829
                } else {
830
                    $this->renderNoResultsFoundMessage();
831
                }
832
                $cPR['content'] = $out;
833
                break;
834
            case 'explain':
835
            default:
836
                foreach ($dataRows as $dataRow) {
837
                    $out .= '<br />' . DebugUtility::viewArray($dataRow);
838
                }
839
                $cPR['header'] = 'Explain SQL query';
840
                $cPR['content'] = $out;
841
        }
842
        return $cPR;
843
    }
844
    /**
845
     * CSV values
846
     *
847
     * @param array $row
848
     * @param string $delim
849
     * @param string $quote
850
     * @param array $conf
851
     * @param string $table
852
     * @return string A single line of CSV
853
     */
854
    protected function csvValues($row, $delim = ',', $quote = '"', $conf = [], $table = '')
855
    {
856
        $valueArray = $row;
857
        if (($this->settings['search_result_labels'] ?? false) && $table) {
858
            foreach ($valueArray as $key => $val) {
859
                $valueArray[$key] = $this->getProcessedValueExtra($table, $key, $val, $conf, ';');
860
            }
861
        }
862
        return CsvUtility::csvValues($valueArray, $delim, $quote);
863
    }
864
865
    /**
866
     * Result row display
867
     *
868
     * @param array $row
869
     * @param array $conf
870
     * @param string $table
871
     * @return string
872
     */
873
    protected function resultRowDisplay($row, $conf, $table)
874
    {
875
        $languageService = $this->getLanguageService();
876
        $out = '<tr>';
877
        foreach ($row as $fieldName => $fieldValue) {
878
            if (GeneralUtility::inList($this->settings['queryFields'] ?? '', $fieldName)
879
                || !($this->settings['queryFields'] ?? false)
880
                && $fieldName !== 'pid'
881
                && $fieldName !== 'deleted'
882
            ) {
883
                if ($this->settings['search_result_labels'] ?? false) {
884
                    $fVnew = $this->getProcessedValueExtra($table, $fieldName, $fieldValue, $conf, '<br />');
885
                } else {
886
                    $fVnew = htmlspecialchars($fieldValue);
887
                }
888
                $out .= '<td>' . $fVnew . '</td>';
889
            }
890
        }
891
        $out .= '<td>';
892
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
893
894
        if (!($row['deleted'] ?? false)) {
895
            $out .= '<div class="btn-group" role="group">';
896
            $url = (string)$uriBuilder->buildUriFromRoute('record_edit', [
897
                'edit' => [
898
                    $table => [
899
                        $row['uid'] => 'edit',
900
                    ],
901
                ],
902
                'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri()
903
                    . HttpUtility::buildQueryString(['SET' => (array)GeneralUtility::_POST('SET')], '&'),
904
            ]);
905
            $out .= '<a class="btn btn-default" href="' . htmlspecialchars($url) . '">'
906
                . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
907
            $out .= '</div><div class="btn-group" role="group">';
908
            $out .= sprintf(
909
                '<a class="btn btn-default" href="#" data-dispatch-action="%s" data-dispatch-args-list="%s">%s</a>',
910
                'TYPO3.InfoWindow.showItem',
911
                htmlspecialchars($table . ',' . $row['uid']),
912
                $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render()
913
            );
914
            $out .= '</div>';
915
        } else {
916
            $out .= '<div class="btn-group" role="group">';
917
            $out .= '<a class="btn btn-default" href="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute('tce_db', [
918
                        'cmd' => [
919
                            $table => [
920
                                $row['uid'] => [
921
                                    'undelete' => 1,
922
                                ],
923
                            ],
924
                        ],
925
                        'redirect' => GeneralUtility::linkThisScript(),
926
                    ])) . '" title="' . htmlspecialchars($languageService->getLL('undelete_only')) . '">';
927
            $out .= $this->iconFactory->getIcon('actions-edit-restore', Icon::SIZE_SMALL)->render() . '</a>';
928
            $formEngineParameters = [
929
                'edit' => [
930
                    $table => [
931
                        $row['uid'] => 'edit',
932
                    ],
933
                ],
934
                'returnUrl' => GeneralUtility::linkThisScript(),
935
            ];
936
            $redirectUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', $formEngineParameters);
937
            $out .= '<a class="btn btn-default" href="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute('tce_db', [
938
                    'cmd' => [
939
                        $table => [
940
                            $row['uid'] => [
941
                                'undelete' => 1,
942
                            ],
943
                        ],
944
                    ],
945
                    'redirect' => $redirectUrl,
946
                ])) . '" title="' . htmlspecialchars($languageService->getLL('undelete_and_edit')) . '">';
947
            $out .= $this->iconFactory->getIcon('actions-delete-edit', Icon::SIZE_SMALL)->render() . '</a>';
948
            $out .= '</div>';
949
        }
950
        $_params = [$table => $row];
951
        if (is_array($this->hookArray['additionalButtons'] ?? false)) {
952
            foreach ($this->hookArray['additionalButtons'] as $_funcRef) {
953
                $out .= GeneralUtility::callUserFunction($_funcRef, $_params);
954
            }
955
        }
956
        $out .= '</td></tr>';
957
        return $out;
958
    }
959
960
    /**
961
     * Get processed value extra
962
     *
963
     * @param string $table
964
     * @param string $fieldName
965
     * @param string $fieldValue
966
     * @param array $conf Not used
967
     * @param string $splitString
968
     * @return string
969
     */
970
    protected function getProcessedValueExtra($table, $fieldName, $fieldValue, $conf, $splitString)
971
    {
972
        $out = '';
973
        $fields = [];
974
        // Analysing the fields in the table.
975
        if (is_array($GLOBALS['TCA'][$table] ?? null)) {
976
            $fC = $GLOBALS['TCA'][$table]['columns'][$fieldName] ?? null;
977
            $fields = $fC['config'] ?? [];
978
            $fields['exclude'] = $fC['exclude'] ?? '';
979
            if (is_array($fC) && $fC['label']) {
980
                $fields['label'] = preg_replace('/:$/', '', trim($this->getLanguageService()->sL($fC['label'])));
981
                switch ($fields['type']) {
982
                    case 'input':
983
                        if (preg_match('/int|year/i', $fields['eval'] ?? '')) {
984
                            $fields['type'] = 'number';
985
                        } elseif (preg_match('/time/i', $fields['eval'] ?? '')) {
986
                            $fields['type'] = 'time';
987
                        } elseif (preg_match('/date/i', $fields['eval'] ?? '')) {
988
                            $fields['type'] = 'date';
989
                        } else {
990
                            $fields['type'] = 'text';
991
                        }
992
                        break;
993
                    case 'check':
994
                        if (!$fields['items']) {
995
                            $fields['type'] = 'boolean';
996
                        } else {
997
                            $fields['type'] = 'binary';
998
                        }
999
                        break;
1000
                    case 'radio':
1001
                        $fields['type'] = 'multiple';
1002
                        break;
1003
                    case 'select':
1004
                    case 'category':
1005
                        $fields['type'] = 'multiple';
1006
                        if ($fields['foreign_table']) {
1007
                            $fields['type'] = 'relation';
1008
                        }
1009
                        if ($fields['special']) {
1010
                            $fields['type'] = 'text';
1011
                        }
1012
                        break;
1013
                    case 'group':
1014
                        if (($fields['internal_type'] ?? '') !== 'folder') {
1015
                            $fields['type'] = 'relation';
1016
                        }
1017
                        break;
1018
                    case 'user':
1019
                    case 'flex':
1020
                    case 'passthrough':
1021
                    case 'none':
1022
                    case 'text':
1023
                    default:
1024
                        $fields['type'] = 'text';
1025
                }
1026
            } else {
1027
                $fields['label'] = '[FIELD: ' . $fieldName . ']';
1028
                switch ($fieldName) {
1029
                    case 'pid':
1030
                        $fields['type'] = 'relation';
1031
                        $fields['allowed'] = 'pages';
1032
                        break;
1033
                    case 'cruser_id':
1034
                        $fields['type'] = 'relation';
1035
                        $fields['allowed'] = 'be_users';
1036
                        break;
1037
                    case 'tstamp':
1038
                    case 'crdate':
1039
                        $fields['type'] = 'time';
1040
                        break;
1041
                    default:
1042
                        $fields['type'] = 'number';
1043
                }
1044
            }
1045
        }
1046
        switch ($fields['type']) {
1047
            case 'date':
1048
                if ($fieldValue != -1) {
1049
                    // @todo Replace deprecated strftime in php 8.1. Suppress warning in v11.
1050
                    $out = (string)@strftime('%d-%m-%Y', (int)$fieldValue);
1051
                }
1052
                break;
1053
            case 'time':
1054
                if ($fieldValue != -1) {
1055
                    if ($splitString === '<br />') {
1056
                        // @todo Replace deprecated strftime in php 8.1. Suppress warning in v11.
1057
                        $out = (string)@strftime('%H:%M' . $splitString . '%d-%m-%Y', (int)$fieldValue);
1058
                    } else {
1059
                        // @todo Replace deprecated strftime in php 8.1. Suppress warning in v11.
1060
                        $out = (string)@strftime('%H:%M %d-%m-%Y', (int)$fieldValue);
1061
                    }
1062
                }
1063
                break;
1064
            case 'multiple':
1065
            case 'binary':
1066
            case 'relation':
1067
                $out = $this->makeValueList($fieldName, $fieldValue, $fields, $table, $splitString);
1068
                break;
1069
            case 'boolean':
1070
                $out = $fieldValue ? 'True' : 'False';
1071
                break;
1072
            default:
1073
                $out = htmlspecialchars($fieldValue);
1074
        }
1075
        return $out;
1076
    }
1077
1078
    /**
1079
     * Recursively fetch all descendants of a given page
1080
     *
1081
     * @param int $id uid of the page
1082
     * @param int $depth
1083
     * @param int $begin
1084
     * @param string $permsClause
1085
     * @return string comma separated list of descendant pages
1086
     */
1087
    protected function getTreeList($id, $depth, $begin = 0, $permsClause = '')
1088
    {
1089
        $depth = (int)$depth;
1090
        $begin = (int)$begin;
1091
        $id = (int)$id;
1092
        if ($id < 0) {
1093
            $id = abs($id);
1094
        }
1095
        if ($begin == 0) {
1096
            $theList = (string)$id;
1097
        } else {
1098
            $theList = '';
1099
        }
1100
        if ($id && $depth > 0) {
1101
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1102
            $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1103
            $statement = $queryBuilder->select('uid')
0 ignored issues
show
Unused Code introduced by
The assignment to $statement is dead and can be removed.
Loading history...
1104
                ->from('pages')
1105
                ->where(
1106
                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
1107
                    $queryBuilder->expr()->eq('sys_language_uid', 0)
1108
                )
1109
                ->orderBy('uid');
1110
            if ($permsClause !== '') {
1111
                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($permsClause));
1112
            }
1113
            $statement = $queryBuilder->execute();
1114
            while ($row = $statement->fetchAssociative()) {
1115
                if ($begin <= 0) {
1116
                    $theList .= ',' . $row['uid'];
1117
                }
1118
                if ($depth > 1) {
1119
                    $theSubList = $this->getTreeList($row['uid'], $depth - 1, $begin - 1, $permsClause);
1120
                    if (!empty($theList) && !empty($theSubList) && ($theSubList[0] !== ',')) {
1121
                        $theList .= ',';
1122
                    }
1123
                    $theList .= $theSubList;
1124
                }
1125
            }
1126
        }
1127
        return $theList;
1128
    }
1129
1130
    /**
1131
     * Make value list
1132
     *
1133
     * @param string $fieldName
1134
     * @param string $fieldValue
1135
     * @param array $conf
1136
     * @param string $table
1137
     * @param string $splitString
1138
     * @return string
1139
     */
1140
    protected function makeValueList($fieldName, $fieldValue, $conf, $table, $splitString)
1141
    {
1142
        $backendUserAuthentication = $this->getBackendUserAuthentication();
1143
        $languageService = $this->getLanguageService();
1144
        $from_table_Arr = [];
1145
        $fieldSetup = $conf;
1146
        $out = '';
1147
        if ($fieldSetup['type'] === 'multiple') {
1148
            foreach ($fieldSetup['items'] as $key => $val) {
1149
                if (strpos($val[0], 'LLL:') === 0) {
1150
                    $value = $languageService->sL($val[0]);
1151
                } else {
1152
                    $value = $val[0];
1153
                }
1154
                if (GeneralUtility::inList($fieldValue, $val[1]) || $fieldValue == $val[1]) {
1155
                    if ($out !== '') {
1156
                        $out .= $splitString;
1157
                    }
1158
                    $out .= htmlspecialchars($value);
1159
                }
1160
            }
1161
        }
1162
        if ($fieldSetup['type'] === 'binary') {
1163
            foreach ($fieldSetup['items'] as $Key => $val) {
1164
                if (strpos($val[0], 'LLL:') === 0) {
1165
                    $value = $languageService->sL($val[0]);
1166
                } else {
1167
                    $value = $val[0];
1168
                }
1169
                if ($out !== '') {
1170
                    $out .= $splitString;
1171
                }
1172
                $out .= htmlspecialchars($value);
1173
            }
1174
        }
1175
        if ($fieldSetup['type'] === 'relation') {
1176
            $dontPrefixFirstTable = 0;
1177
            $useTablePrefix = 0;
1178
            foreach (($fieldSetup['items'] ?? []) as $val) {
1179
                if (strpos($val[0], 'LLL:') === 0) {
1180
                    $value = $languageService->sL($val[0]);
1181
                } else {
1182
                    $value = $val[0];
1183
                }
1184
                if (GeneralUtility::inList($fieldValue, $value) || $fieldValue == $value) {
1185
                    if ($out !== '') {
1186
                        $out .= $splitString;
1187
                    }
1188
                    $out .= htmlspecialchars($value);
1189
                }
1190
            }
1191
            if (str_contains($fieldSetup['allowed'], ',')) {
1192
                $from_table_Arr = explode(',', $fieldSetup['allowed']);
1193
                $useTablePrefix = 1;
1194
                if (!$fieldSetup['prepend_tname']) {
1195
                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1196
                    $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1197
                    $statement = $queryBuilder->select($fieldName)->from($table)->execute();
1198
                    while ($row = $statement->fetchAssociative()) {
1199
                        if (str_contains($row[$fieldName], ',')) {
1200
                            $checkContent = explode(',', $row[$fieldName]);
1201
                            foreach ($checkContent as $singleValue) {
1202
                                if (!str_contains($singleValue, '_')) {
1203
                                    $dontPrefixFirstTable = 1;
1204
                                }
1205
                            }
1206
                        } else {
1207
                            $singleValue = $row[$fieldName];
1208
                            if ($singleValue !== '' && !str_contains($singleValue, '_')) {
1209
                                $dontPrefixFirstTable = 1;
1210
                            }
1211
                        }
1212
                    }
1213
                }
1214
            } else {
1215
                $from_table_Arr[0] = $fieldSetup['allowed'];
1216
            }
1217
            if (!empty($fieldSetup['prepend_tname'])) {
1218
                $useTablePrefix = 1;
1219
            }
1220
            if (!empty($fieldSetup['foreign_table'])) {
1221
                $from_table_Arr[0] = $fieldSetup['foreign_table'];
1222
            }
1223
            $counter = 0;
1224
            $useSelectLabels = 0;
1225
            $useAltSelectLabels = 0;
1226
            $tablePrefix = '';
1227
            $labelFieldSelect = [];
1228
            foreach ($from_table_Arr as $from_table) {
1229
                if ($useTablePrefix && !$dontPrefixFirstTable && $counter != 1 || $counter == 1) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($useTablePrefix && ! $d... != 1) || $counter == 1, Probably Intended Meaning: $useTablePrefix && ! $do... != 1 || $counter == 1)
Loading history...
1230
                    $tablePrefix = $from_table . '_';
1231
                }
1232
                $counter = 1;
1233
                if (is_array($GLOBALS['TCA'][$from_table] ?? null)) {
1234
                    $labelField = $GLOBALS['TCA'][$from_table]['ctrl']['label'] ?? '';
1235
                    $altLabelField = $GLOBALS['TCA'][$from_table]['ctrl']['label_alt'] ?? '';
1236
                    if (is_array($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] ?? false)) {
1237
                        $items = $GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'];
1238
                        foreach ($items as $labelArray) {
1239
                            if (str_starts_with($labelArray[0], 'LLL:')) {
1240
                                $labelFieldSelect[$labelArray[1]] = $languageService->sL($labelArray[0]);
1241
                            } else {
1242
                                $labelFieldSelect[$labelArray[1]] = $labelArray[0];
1243
                            }
1244
                        }
1245
                        $useSelectLabels = 1;
1246
                    }
1247
                    $altLabelFieldSelect = [];
1248
                    if (is_array($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] ?? false)) {
1249
                        $items = $GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'];
1250
                        foreach ($items as $altLabelArray) {
1251
                            if (str_starts_with($altLabelArray[0], 'LLL:')) {
1252
                                $altLabelFieldSelect[$altLabelArray[1]] = $languageService->sL($altLabelArray[0]);
1253
                            } else {
1254
                                $altLabelFieldSelect[$altLabelArray[1]] = $altLabelArray[0];
1255
                            }
1256
                        }
1257
                        $useAltSelectLabels = 1;
1258
                    }
1259
1260
                    if (empty($this->tableArray[$from_table])) {
1261
                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
1262
                        $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1263
                        $selectFields = ['uid', $labelField];
1264
                        if ($altLabelField) {
1265
                            $selectFields = array_merge($selectFields, GeneralUtility::trimExplode(',', $altLabelField, true));
1266
                        }
1267
                        $queryBuilder->select(...$selectFields)
1268
                            ->from($from_table)
1269
                            ->orderBy('uid');
1270
                        if (!$backendUserAuthentication->isAdmin()) {
1271
                            $webMounts = $backendUserAuthentication->returnWebmounts();
1272
                            $perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
1273
                            $webMountPageTree = '';
1274
                            $webMountPageTreePrefix = '';
1275
                            foreach ($webMounts as $webMount) {
1276
                                if ($webMountPageTree) {
1277
                                    $webMountPageTreePrefix = ',';
1278
                                }
1279
                                $webMountPageTree .= $webMountPageTreePrefix
1280
                                    . $this->getTreeList($webMount, 999, 0, $perms_clause);
1281
                            }
1282
                            if ($from_table === 'pages') {
1283
                                $queryBuilder->where(
1284
                                    QueryHelper::stripLogicalOperatorPrefix($perms_clause),
1285
                                    $queryBuilder->expr()->in(
1286
                                        'uid',
1287
                                        $queryBuilder->createNamedParameter(
1288
                                            GeneralUtility::intExplode(',', $webMountPageTree),
1289
                                            Connection::PARAM_INT_ARRAY
1290
                                        )
1291
                                    )
1292
                                );
1293
                            } else {
1294
                                $queryBuilder->where(
1295
                                    $queryBuilder->expr()->in(
1296
                                        'pid',
1297
                                        $queryBuilder->createNamedParameter(
1298
                                            GeneralUtility::intExplode(',', $webMountPageTree),
1299
                                            Connection::PARAM_INT_ARRAY
1300
                                        )
1301
                                    )
1302
                                );
1303
                            }
1304
                        }
1305
                        $statement = $queryBuilder->execute();
1306
                        $this->tableArray[$from_table] = [];
1307
                        while ($row = $statement->fetchAssociative()) {
1308
                            $this->tableArray[$from_table][] = $row;
1309
                        }
1310
                    }
1311
1312
                    foreach ($this->tableArray[$from_table] as $key => $val) {
1313
                        $this->settings['labels_noprefix'] =
1314
                            $this->settings['labels_noprefix'] == 1
1315
                                ? 'on'
1316
                                : $this->settings['labels_noprefix'];
1317
                        $prefixString =
1318
                            $this->settings['labels_noprefix'] === 'on'
1319
                                ? ''
1320
                                : ' [' . $tablePrefix . $val['uid'] . '] ';
1321
                        if ($out !== '') {
1322
                            $out .= $splitString;
1323
                        }
1324
                        if (GeneralUtility::inList($fieldValue, $tablePrefix . $val['uid'])
1325
                            || $fieldValue == $tablePrefix . $val['uid']) {
1326
                            if ($useSelectLabels) {
1327
                                $out .= htmlspecialchars($prefixString . $labelFieldSelect[$val[$labelField]]);
1328
                            } elseif ($val[$labelField]) {
1329
                                $out .= htmlspecialchars($prefixString . $val[$labelField]);
1330
                            } elseif ($useAltSelectLabels) {
1331
                                $out .= htmlspecialchars($prefixString . $altLabelFieldSelect[$val[$altLabelField]]);
1332
                            } else {
1333
                                $out .= htmlspecialchars($prefixString . $val[$altLabelField]);
1334
                            }
1335
                        }
1336
                    }
1337
                }
1338
            }
1339
        }
1340
        return $out;
1341
    }
1342
1343
    /**
1344
     * Render table header
1345
     *
1346
     * @param array $row Table columns
1347
     * @param array $conf Table TCA
1348
     * @return string HTML of table header
1349
     */
1350
    protected function resultRowTitles($row, $conf)
1351
    {
1352
        $languageService = $this->getLanguageService();
1353
        $tableHeader = [];
1354
        // Start header row
1355
        $tableHeader[] = '<thead><tr>';
1356
        // Iterate over given columns
1357
        foreach ($row as $fieldName => $fieldValue) {
1358
            if (GeneralUtility::inList($this->settings['queryFields'] ?? '', $fieldName)
1359
                || !($this->settings['queryFields'] ?? false)
1360
                && $fieldName !== 'pid'
1361
                && $fieldName !== 'deleted'
1362
            ) {
1363
                if ($this->settings['search_result_labels'] ?? false) {
1364
                    $title = $languageService->sL(($conf['columns'][$fieldName]['label'] ?? false) ?: $fieldName);
1365
                } else {
1366
                    $title = $languageService->sL($fieldName);
1367
                }
1368
                $tableHeader[] = '<th>' . htmlspecialchars($title) . '</th>';
1369
            }
1370
        }
1371
        // Add empty icon column
1372
        $tableHeader[] = '<th></th>';
1373
        // Close header row
1374
        $tableHeader[] = '</tr></thead>';
1375
        return implode(LF, $tableHeader);
1376
    }
1377
    /**
1378
     * @throws \InvalidArgumentException
1379
     * @throws \TYPO3\CMS\Core\Exception
1380
     */
1381
    private function renderNoResultsFoundMessage()
1382
    {
1383
        $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, 'No rows selected!', '', FlashMessage::INFO);
1384
        $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1385
        $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1386
        $defaultFlashMessageQueue->enqueue($flashMessage);
1387
    }
1388
1389
    /**
1390
     * Make a list of fields for current table
1391
     *
1392
     * @return string Separated list of fields
1393
     */
1394
    protected function makeFieldList()
1395
    {
1396
        $fieldListArr = [];
1397
        if (is_array($GLOBALS['TCA'][$this->table])) {
1398
            $fieldListArr = array_keys($GLOBALS['TCA'][$this->table]['columns'] ?? []);
1399
            $fieldListArr[] = 'uid';
1400
            $fieldListArr[] = 'pid';
1401
            $fieldListArr[] = 'deleted';
1402
            if ($GLOBALS['TCA'][$this->table]['ctrl']['tstamp'] ?? false) {
1403
                $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['tstamp'];
1404
            }
1405
            if ($GLOBALS['TCA'][$this->table]['ctrl']['crdate'] ?? false) {
1406
                $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['crdate'];
1407
            }
1408
            if ($GLOBALS['TCA'][$this->table]['ctrl']['cruser_id'] ?? false) {
1409
                $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['cruser_id'];
1410
            }
1411
            if ($GLOBALS['TCA'][$this->table]['ctrl']['sortby'] ?? false) {
1412
                $fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['sortby'];
1413
            }
1414
        }
1415
        return implode(',', $fieldListArr);
1416
    }
1417
1418
    /**
1419
     * Init function
1420
     *
1421
     * @param string $name The name
1422
     * @param string $table The table name
1423
     * @param string $fieldList The field list
1424
     * @param array $settings Module settings like checkboxes in the interface
1425
     */
1426
    protected function init($name, $table, $fieldList = '', array $settings = [])
1427
    {
1428
        // Analysing the fields in the table.
1429
        if (is_array($GLOBALS['TCA'][$table] ?? false)) {
1430
            $this->name = $name;
1431
            $this->table = $table;
1432
            $this->fieldList = $fieldList ?: $this->makeFieldList();
1433
            $this->settings = $settings;
1434
            $fieldArr = GeneralUtility::trimExplode(',', $this->fieldList, true);
1435
            foreach ($fieldArr as $fieldName) {
1436
                $fC = $GLOBALS['TCA'][$this->table]['columns'][$fieldName] ?? [];
1437
                $this->fields[$fieldName] = $fC['config'] ?? [];
1438
                $this->fields[$fieldName]['exclude'] = $fC['exclude'] ?? '';
1439
                if (($this->fields[$fieldName]['type'] ?? '') === 'user' && !isset($this->fields[$fieldName]['type']['userFunc'])
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($this->fields[$fieldNam...type'] ?? '' === 'none', Probably Intended Meaning: $this->fields[$fieldName...ype'] ?? '' === 'none')
Loading history...
1440
                    || ($this->fields[$fieldName]['type'] ?? '') === 'none'
1441
                ) {
1442
                    // Do not list type=none "virtual" fields or query them from db,
1443
                    // and if type is user without defined userFunc
1444
                    unset($this->fields[$fieldName]);
1445
                    continue;
1446
                }
1447
                if (is_array($fC) && ($fC['label'] ?? false)) {
1448
                    $this->fields[$fieldName]['label'] = rtrim(trim($this->getLanguageService()->sL($fC['label'])), ':');
1449
                    switch ($this->fields[$fieldName]['type']) {
1450
                        case 'input':
1451
                            if (preg_match('/int|year/i', ($this->fields[$fieldName]['eval'] ?? ''))) {
1452
                                $this->fields[$fieldName]['type'] = 'number';
1453
                            } elseif (preg_match('/time/i', ($this->fields[$fieldName]['eval'] ?? ''))) {
1454
                                $this->fields[$fieldName]['type'] = 'time';
1455
                            } elseif (preg_match('/date/i', ($this->fields[$fieldName]['eval'] ?? ''))) {
1456
                                $this->fields[$fieldName]['type'] = 'date';
1457
                            } else {
1458
                                $this->fields[$fieldName]['type'] = 'text';
1459
                            }
1460
                            break;
1461
                        case 'check':
1462
                            if (count($this->fields[$fieldName]['items'] ?? []) <= 1) {
1463
                                $this->fields[$fieldName]['type'] = 'boolean';
1464
                            } else {
1465
                                $this->fields[$fieldName]['type'] = 'binary';
1466
                            }
1467
                            break;
1468
                        case 'radio':
1469
                            $this->fields[$fieldName]['type'] = 'multiple';
1470
                            break;
1471
                        case 'select':
1472
                        case 'category':
1473
                            $this->fields[$fieldName]['type'] = 'multiple';
1474
                            if ($this->fields[$fieldName]['foreign_table'] ?? false) {
1475
                                $this->fields[$fieldName]['type'] = 'relation';
1476
                            }
1477
                            if ($this->fields[$fieldName]['special'] ?? false) {
1478
                                $this->fields[$fieldName]['type'] = 'text';
1479
                            }
1480
                            break;
1481
                        case 'group':
1482
                            if (($this->fields[$fieldName]['internal_type'] ?? '') !== 'folder') {
1483
                                $this->fields[$fieldName]['type'] = 'relation';
1484
                            }
1485
                            break;
1486
                        case 'user':
1487
                        case 'flex':
1488
                        case 'passthrough':
1489
                        case 'none':
1490
                        case 'text':
1491
                        default:
1492
                            $this->fields[$fieldName]['type'] = 'text';
1493
                    }
1494
                } else {
1495
                    $this->fields[$fieldName]['label'] = '[FIELD: ' . $fieldName . ']';
1496
                    switch ($fieldName) {
1497
                        case 'pid':
1498
                            $this->fields[$fieldName]['type'] = 'relation';
1499
                            $this->fields[$fieldName]['allowed'] = 'pages';
1500
                            break;
1501
                        case 'cruser_id':
1502
                            $this->fields[$fieldName]['type'] = 'relation';
1503
                            $this->fields[$fieldName]['allowed'] = 'be_users';
1504
                            break;
1505
                        case 'tstamp':
1506
                        case 'crdate':
1507
                            $this->fields[$fieldName]['type'] = 'time';
1508
                            break;
1509
                        case 'deleted':
1510
                            $this->fields[$fieldName]['type'] = 'boolean';
1511
                            break;
1512
                        default:
1513
                            $this->fields[$fieldName]['type'] = 'number';
1514
                    }
1515
                }
1516
            }
1517
        }
1518
        /*	// EXAMPLE:
1519
        $this->queryConfig = array(
1520
        array(
1521
        'operator' => 'AND',
1522
        'type' => 'FIELD_space_before_class',
1523
        ),
1524
        array(
1525
        'operator' => 'AND',
1526
        'type' => 'FIELD_records',
1527
        'negate' => 1,
1528
        'inputValue' => 'foo foo'
1529
        ),
1530
        array(
1531
        'type' => 'newlevel',
1532
        'nl' => array(
1533
        array(
1534
        'operator' => 'AND',
1535
        'type' => 'FIELD_space_before_class',
1536
        'negate' => 1,
1537
        'inputValue' => 'foo foo'
1538
        ),
1539
        array(
1540
        'operator' => 'AND',
1541
        'type' => 'FIELD_records',
1542
        'negate' => 1,
1543
        'inputValue' => 'foo foo'
1544
        )
1545
        )
1546
        ),
1547
        array(
1548
        'operator' => 'OR',
1549
        'type' => 'FIELD_maillist',
1550
        )
1551
        );
1552
         */
1553
    }
1554
1555
    /**
1556
     * Set and clean up external lists
1557
     *
1558
     * @param string $name The name
1559
     * @param string $list The list
1560
     * @param string $force
1561
     */
1562
    protected function setAndCleanUpExternalLists($name, $list, $force = '')
1563
    {
1564
        $fields = array_unique(GeneralUtility::trimExplode(',', $list . ',' . $force, true));
1565
        $reList = [];
1566
        foreach ($fields as $fieldName) {
1567
            if (isset($this->fields[$fieldName])) {
1568
                $reList[] = $fieldName;
1569
            }
1570
        }
1571
        $this->extFieldLists[$name] = implode(',', $reList);
1572
    }
1573
1574
    /**
1575
     * Process data
1576
     *
1577
     * @param array $qC Query config
1578
     */
1579
    protected function procesData($qC = [])
1580
    {
1581
        $this->queryConfig = $qC;
1582
        $POST = GeneralUtility::_POST();
1583
        // If delete...
1584
        if ($POST['qG_del'] ?? false) {
1585
            // Initialize array to work on, save special parameters
1586
            $ssArr = $this->getSubscript($POST['qG_del']);
1587
            $workArr = &$this->queryConfig;
1588
            $ssArrSize = count($ssArr) - 1;
1589
            $i = 0;
1590
            for (; $i < $ssArrSize; $i++) {
1591
                $workArr = &$workArr[$ssArr[$i]];
1592
            }
1593
            // Delete the entry and move the other entries
1594
            unset($workArr[$ssArr[$i]]);
1595
            $workArrSize = count((array)$workArr);
1596
            for ($j = $ssArr[$i]; $j < $workArrSize; $j++) {
1597
                $workArr[$j] = $workArr[$j + 1];
1598
                unset($workArr[$j + 1]);
1599
            }
1600
        }
1601
        // If insert...
1602
        if ($POST['qG_ins'] ?? false) {
1603
            // Initialize array to work on, save special parameters
1604
            $ssArr = $this->getSubscript($POST['qG_ins']);
1605
            $workArr = &$this->queryConfig;
1606
            $ssArrSize = count($ssArr) - 1;
1607
            $i = 0;
1608
            for (; $i < $ssArrSize; $i++) {
1609
                $workArr = &$workArr[$ssArr[$i]];
1610
            }
1611
            // Move all entries above position where new entry is to be inserted
1612
            $workArrSize = count((array)$workArr);
1613
            for ($j = $workArrSize; $j > $ssArr[$i]; $j--) {
1614
                $workArr[$j] = $workArr[$j - 1];
1615
            }
1616
            // Clear new entry position
1617
            unset($workArr[$ssArr[$i] + 1]);
1618
            $workArr[$ssArr[$i] + 1]['type'] = 'FIELD_';
1619
        }
1620
        // If move up...
1621
        if ($POST['qG_up'] ?? false) {
1622
            // Initialize array to work on
1623
            $ssArr = $this->getSubscript($POST['qG_up']);
1624
            $workArr = &$this->queryConfig;
1625
            $ssArrSize = count($ssArr) - 1;
1626
            $i = 0;
1627
            for (; $i < $ssArrSize; $i++) {
1628
                $workArr = &$workArr[$ssArr[$i]];
1629
            }
1630
            // Swap entries
1631
            $qG_tmp = $workArr[$ssArr[$i]];
1632
            $workArr[$ssArr[$i]] = $workArr[$ssArr[$i] - 1];
1633
            $workArr[$ssArr[$i] - 1] = $qG_tmp;
1634
        }
1635
        // If new level...
1636
        if ($POST['qG_nl'] ?? false) {
1637
            // Initialize array to work on
1638
            $ssArr = $this->getSubscript($POST['qG_nl']);
1639
            $workArr = &$this->queryConfig;
1640
            $ssArraySize = count($ssArr) - 1;
1641
            $i = 0;
1642
            for (; $i < $ssArraySize; $i++) {
1643
                $workArr = &$workArr[$ssArr[$i]];
1644
            }
1645
            // Do stuff:
1646
            $tempEl = $workArr[$ssArr[$i]];
1647
            if (is_array($tempEl)) {
1648
                if ($tempEl['type'] !== 'newlevel') {
1649
                    $workArr[$ssArr[$i]] = [
1650
                        'type' => 'newlevel',
1651
                        'operator' => $tempEl['operator'],
1652
                        'nl' => [$tempEl],
1653
                    ];
1654
                }
1655
            }
1656
        }
1657
        // If collapse level...
1658
        if ($POST['qG_remnl'] ?? false) {
1659
            // Initialize array to work on
1660
            $ssArr = $this->getSubscript($POST['qG_remnl']);
1661
            $workArr = &$this->queryConfig;
1662
            $ssArrSize = count($ssArr) - 1;
1663
            $i = 0;
1664
            for (; $i < $ssArrSize; $i++) {
1665
                $workArr = &$workArr[$ssArr[$i]];
1666
            }
1667
            // Do stuff:
1668
            $tempEl = $workArr[$ssArr[$i]];
1669
            if (is_array($tempEl)) {
1670
                if ($tempEl['type'] === 'newlevel' && is_array($workArr)) {
1671
                    $a1 = array_slice($workArr, 0, $ssArr[$i]);
1672
                    $a2 = array_slice($workArr, $ssArr[$i]);
1673
                    array_shift($a2);
1674
                    $a3 = $tempEl['nl'];
1675
                    $a3[0]['operator'] = $tempEl['operator'];
1676
                    $workArr = array_merge($a1, $a3, $a2);
1677
                }
1678
            }
1679
        }
1680
    }
1681
1682
    /**
1683
     * Clean up query config
1684
     *
1685
     * @param array $queryConfig Query config
1686
     * @return array
1687
     */
1688
    protected function cleanUpQueryConfig($queryConfig)
1689
    {
1690
        // Since we don't traverse the array using numeric keys in the upcoming while-loop make sure it's fresh and clean before displaying
1691
        if (!empty($queryConfig) && is_array($queryConfig)) {
1692
            ksort($queryConfig);
1693
        } else {
1694
            // queryConfig should never be empty!
1695
            if (!isset($queryConfig[0]) || empty($queryConfig[0]['type'])) {
1696
                // Make sure queryConfig is an array
1697
                $queryConfig = [];
1698
                $queryConfig[0] = ['type' => 'FIELD_'];
1699
            }
1700
        }
1701
        // Traverse:
1702
        foreach ($queryConfig as $key => $conf) {
1703
            $fieldName = '';
1704
            if (str_starts_with(($conf['type'] ?? ''), 'FIELD_')) {
1705
                $fieldName = substr($conf['type'], 6);
1706
                $fieldType = $this->fields[$fieldName]['type'] ?? '';
1707
            } elseif (($conf['type'] ?? '') === 'newlevel') {
1708
                $fieldType = $conf['type'];
1709
            } else {
1710
                $fieldType = 'ignore';
1711
            }
1712
            switch ($fieldType) {
1713
                case 'newlevel':
1714
                    if (!$queryConfig[$key]['nl']) {
1715
                        $queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
1716
                    }
1717
                    $queryConfig[$key]['nl'] = $this->cleanUpQueryConfig($queryConfig[$key]['nl']);
1718
                    break;
1719
                case 'userdef':
1720
                    break;
1721
                case 'ignore':
1722
                default:
1723
                    $verifiedName = $this->verifyType($fieldName);
1724
                    $queryConfig[$key]['type'] = 'FIELD_' . $this->verifyType($verifiedName);
1725
                    if ((int)($conf['comparison'] ?? 0) >> 5 !== (int)($this->comp_offsets[$fieldType] ?? 0)) {
1726
                        $conf['comparison'] = (int)($this->comp_offsets[$fieldType] ?? 0) << 5;
1727
                    }
1728
                    $queryConfig[$key]['comparison'] = $this->verifyComparison($conf['comparison'] ?? '0', ($conf['negate'] ?? null) ? 1 : 0);
1729
                    $queryConfig[$key]['inputValue'] = $this->cleanInputVal($queryConfig[$key]);
1730
                    $queryConfig[$key]['inputValue1'] = $this->cleanInputVal($queryConfig[$key], '1');
1731
            }
1732
        }
1733
        return $queryConfig;
1734
    }
1735
1736
    /**
1737
     * Get form elements
1738
     *
1739
     * @param int $subLevel
1740
     * @param string $queryConfig
1741
     * @param string $parent
1742
     * @return array
1743
     */
1744
    protected function getFormElements($subLevel = 0, $queryConfig = '', $parent = '')
1745
    {
1746
        $codeArr = [];
1747
        if (!is_array($queryConfig)) {
0 ignored issues
show
introduced by
The condition is_array($queryConfig) is always false.
Loading history...
1748
            $queryConfig = $this->queryConfig;
1749
        }
1750
        $c = 0;
1751
        $arrCount = 0;
1752
        $loopCount = 0;
1753
        foreach ($queryConfig as $key => $conf) {
1754
            $fieldName = '';
1755
            $subscript = $parent . '[' . $key . ']';
1756
            $lineHTML = [];
1757
            $lineHTML[] = $this->mkOperatorSelect($this->name . $subscript, ($conf['operator'] ?? ''), (bool)$c, ($conf['type'] ?? '') !== 'FIELD_');
1758
            if (str_starts_with(($conf['type'] ?? ''), 'FIELD_')) {
1759
                $fieldName = substr($conf['type'], 6);
1760
                $this->fieldName = $fieldName;
1761
                $fieldType = $this->fields[$fieldName]['type'] ?? '';
1762
                if ((int)($conf['comparison'] ?? 0) >> 5 !== (int)($this->comp_offsets[$fieldType] ?? 0)) {
1763
                    $conf['comparison'] = (int)($this->comp_offsets[$fieldType] ?? 0) << 5;
1764
                }
1765
                //nasty nasty...
1766
                //make sure queryConfig contains _actual_ comparevalue.
1767
                //mkCompSelect don't care, but getQuery does.
1768
                $queryConfig[$key]['comparison'] += isset($conf['negate']) - $conf['comparison'] % 2;
1769
            } elseif (($conf['type'] ?? '') === 'newlevel') {
1770
                $fieldType = $conf['type'];
1771
            } else {
1772
                $fieldType = 'ignore';
1773
            }
1774
            $fieldPrefix = htmlspecialchars($this->name . $subscript);
1775
            switch ($fieldType) {
1776
                case 'ignore':
1777
                    break;
1778
                case 'newlevel':
1779
                    if (!$queryConfig[$key]['nl']) {
1780
                        $queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
1781
                    }
1782
                    $lineHTML[] = '<input type="hidden" name="' . $fieldPrefix . '[type]" value="newlevel">';
1783
                    $codeArr[$arrCount]['sub'] = $this->getFormElements($subLevel + 1, $queryConfig[$key]['nl'], $subscript . '[nl]');
1784
                    break;
1785
                case 'userdef':
1786
                    $lineHTML[] = '';
1787
                    break;
1788
                case 'date':
1789
                    $lineHTML[] = '<div class="row row-cols-auto mb-2 mb-sm-0">';
1790
                    $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
1791
                    if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
1792
                        // between
1793
                        $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'date');
1794
                        $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue1]', $conf['inputValue1'], 'date');
1795
                    } else {
1796
                        $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'date');
1797
                    }
1798
                    $lineHTML[] = '</div>';
1799
                    break;
1800
                case 'time':
1801
                    $lineHTML[] = '<div class="row row-cols-auto mb-2 mb-sm-0">';
1802
                    $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
1803
                    if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
1804
                        // between:
1805
                        $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'datetime');
1806
                        $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue1]', $conf['inputValue1'], 'datetime');
1807
                    } else {
1808
                        $lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'datetime');
1809
                    }
1810
                    $lineHTML[] = '</div>';
1811
                    break;
1812
                case 'multiple':
1813
                case 'binary':
1814
                case 'relation':
1815
                    $lineHTML[] = '<div class="row row-cols-auto mb-2 mb-sm-0">';
1816
                    $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
1817
                    $lineHTML[] = '<div class="col mb-sm-2">';
1818
                    if ($conf['comparison'] === 68 || $conf['comparison'] === 69 || $conf['comparison'] === 162 || $conf['comparison'] === 163) {
1819
                        $lineHTML[] = '<select class="form-select" name="' . $fieldPrefix . '[inputValue][]" multiple="multiple">';
1820
                    } elseif ($conf['comparison'] === 66 || $conf['comparison'] === 67) {
1821
                        if (is_array($conf['inputValue'])) {
1822
                            $conf['inputValue'] = implode(',', $conf['inputValue']);
1823
                        }
1824
                        $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]">';
1825
                    } elseif ($conf['comparison'] === 64) {
1826
                        if (is_array($conf['inputValue'])) {
1827
                            $conf['inputValue'] = $conf['inputValue'][0];
1828
                        }
1829
                        $lineHTML[] = '<select class="form-select t3js-submit-change" name="' . $fieldPrefix . '[inputValue]">';
1830
                    } else {
1831
                        $lineHTML[] = '<select class="form-select t3js-submit-change" name="' . $fieldPrefix . '[inputValue]">';
1832
                    }
1833
                    if ($conf['comparison'] != 66 && $conf['comparison'] != 67) {
1834
                        $lineHTML[] = $this->makeOptionList($fieldName, $conf, $this->table);
1835
                        $lineHTML[] = '</select>';
1836
                    }
1837
                    $lineHTML[] = '</div>';
1838
                    $lineHTML[] = '</div>';
1839
                    break;
1840
                case 'boolean':
1841
                    $lineHTML[] = '<div class="row row-cols-auto mb-2 mb-sm-0">';
1842
                    $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
1843
                    $lineHTML[] = '<input type="hidden" value="1" name="' . $fieldPrefix . '[inputValue]">';
1844
                    $lineHTML[] = '</div>';
1845
                    break;
1846
                default:
1847
                    $lineHTML[] = '<div class="row row-cols-auto mb-2 mb-sm-0">';
1848
                    $lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
1849
                    $lineHTML[] = '<div class="col mb-sm-2">';
1850
                    if ($conf['comparison'] === 37 || $conf['comparison'] === 36) {
1851
                        // between:
1852
                        $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]">';
1853
                        $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue1']) . '" name="' . $fieldPrefix . '[inputValue1]">';
1854
                    } else {
1855
                        $lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]">';
1856
                    }
1857
                    $lineHTML[] = '</div>';
1858
                    $lineHTML[] = '</div>';
1859
            }
1860
            if ($fieldType !== 'ignore') {
1861
                $lineHTML[] = '<div class="row row-cols-auto mb-2">';
1862
                $lineHTML[] = '<div class="btn-group">';
1863
                $lineHTML[] = $this->updateIcon();
1864
                if ($loopCount) {
1865
                    $lineHTML[] = '<button class="btn btn-default" title="Remove condition" name="qG_del' . htmlspecialchars($subscript) . '"><i class="fa fa-trash fa-fw"></i></button>';
1866
                }
1867
                $lineHTML[] = '<button class="btn btn-default" title="Add condition" name="qG_ins' . htmlspecialchars($subscript) . '"><i class="fa fa-plus fa-fw"></i></button>';
1868
                if ($c != 0) {
1869
                    $lineHTML[] = '<button class="btn btn-default" title="Move up" name="qG_up' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-up fa-fw"></i></button>';
1870
                }
1871
                if ($c != 0 && $fieldType !== 'newlevel') {
1872
                    $lineHTML[] = '<button class="btn btn-default" title="New level" name="qG_nl' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-right fa-fw"></i></button>';
1873
                }
1874
                if ($fieldType === 'newlevel') {
1875
                    $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>';
1876
                }
1877
                $lineHTML[] = '</div>';
1878
                $lineHTML[] = '</div>';
1879
                $codeArr[$arrCount]['html'] = implode(LF, $lineHTML);
1880
                $codeArr[$arrCount]['query'] = $this->getQuerySingle($conf, $c === 0);
1881
                $arrCount++;
1882
                $c++;
1883
            }
1884
            $loopCount = 1;
1885
        }
1886
        $this->queryConfig = $queryConfig;
1887
        return $codeArr;
1888
    }
1889
1890
    /**
1891
     * @param string $subscript
1892
     * @param string $fieldName
1893
     * @param array $conf
1894
     *
1895
     * @return string
1896
     */
1897
    protected function makeComparisonSelector($subscript, $fieldName, $conf)
1898
    {
1899
        $fieldPrefix = $this->name . $subscript;
1900
        $lineHTML = [];
1901
        $lineHTML[] = '<div class="col mb-sm-2">';
1902
        $lineHTML[] =     $this->mkTypeSelect($fieldPrefix . '[type]', $fieldName);
1903
        $lineHTML[] = '</div>';
1904
        $lineHTML[] = '<div class="col mb-sm-2">';
1905
        $lineHTML[] = '	 <div class="input-group">';
1906
        $lineHTML[] =      $this->mkCompSelect($fieldPrefix . '[comparison]', $conf['comparison'], ($conf['negate'] ?? null) ? 1 : 0);
1907
        $lineHTML[] = '	   <span class="input-group-addon">';
1908
        $lineHTML[] = '		 <input type="checkbox" class="checkbox t3js-submit-click"' . (($conf['negate'] ?? null) ? ' checked' : '') . ' name="' . htmlspecialchars($fieldPrefix) . '[negate]">';
1909
        $lineHTML[] = '	   </span>';
1910
        $lineHTML[] = '  </div>';
1911
        $lineHTML[] = '	</div>';
1912
        return implode(LF, $lineHTML);
1913
    }
1914
1915
    /**
1916
     * Make option list
1917
     *
1918
     * @param string $fieldName
1919
     * @param array $conf
1920
     * @param string $table
1921
     * @return string
1922
     */
1923
    protected function makeOptionList($fieldName, $conf, $table)
1924
    {
1925
        $backendUserAuthentication = $this->getBackendUserAuthentication();
1926
        $from_table_Arr = [];
1927
        $out = [];
1928
        $fieldSetup = $this->fields[$fieldName];
1929
        $languageService = $this->getLanguageService();
1930
        if ($fieldSetup['type'] === 'multiple') {
1931
            $optGroupOpen = false;
1932
            foreach (($fieldSetup['items'] ?? []) as $val) {
1933
                if (strpos($val[0], 'LLL:') === 0) {
1934
                    $value = $languageService->sL($val[0]);
1935
                } else {
1936
                    $value = $val[0];
1937
                }
1938
                if ($val[1] === '--div--') {
1939
                    if ($optGroupOpen) {
1940
                        $out[] = '</optgroup>';
1941
                    }
1942
                    $optGroupOpen = true;
1943
                    $out[] = '<optgroup label="' . htmlspecialchars($value) . '">';
1944
                } elseif (GeneralUtility::inList($conf['inputValue'], $val[1])) {
1945
                    $out[] = '<option value="' . htmlspecialchars($val[1]) . '" selected>' . htmlspecialchars($value) . '</option>';
1946
                } else {
1947
                    $out[] = '<option value="' . htmlspecialchars($val[1]) . '">' . htmlspecialchars($value) . '</option>';
1948
                }
1949
            }
1950
            if ($optGroupOpen) {
1951
                $out[] = '</optgroup>';
1952
            }
1953
        }
1954
        if ($fieldSetup['type'] === 'binary') {
1955
            foreach ($fieldSetup['items'] as $key => $val) {
1956
                if (strpos($val[0], 'LLL:') === 0) {
1957
                    $value = $languageService->sL($val[0]);
1958
                } else {
1959
                    $value = $val[0];
1960
                }
1961
                if (GeneralUtility::inList($conf['inputValue'], (string)(2 ** $key))) {
1962
                    $out[] = '<option value="' . 2 ** $key . '" selected>' . htmlspecialchars($value) . '</option>';
1963
                } else {
1964
                    $out[] = '<option value="' . 2 ** $key . '">' . htmlspecialchars($value) . '</option>';
1965
                }
1966
            }
1967
        }
1968
        if ($fieldSetup['type'] === 'relation') {
1969
            $useTablePrefix = 0;
1970
            $dontPrefixFirstTable = 0;
1971
            foreach (($fieldSetup['items'] ?? []) as $val) {
1972
                if (strpos($val[0], 'LLL:') === 0) {
1973
                    $value = $languageService->sL($val[0]);
1974
                } else {
1975
                    $value = $val[0];
1976
                }
1977
                if (GeneralUtility::inList($conf['inputValue'], $val[1])) {
1978
                    $out[] = '<option value="' . htmlspecialchars($val[1]) . '" selected>' . htmlspecialchars($value) . '</option>';
1979
                } else {
1980
                    $out[] = '<option value="' . htmlspecialchars($val[1]) . '">' . htmlspecialchars($value) . '</option>';
1981
                }
1982
            }
1983
            $allowedFields = $fieldSetup['allowed'] ?? '';
1984
            if (str_contains($allowedFields, ',')) {
1985
                $from_table_Arr = explode(',', $allowedFields);
1986
                $useTablePrefix = 1;
1987
                if (!$fieldSetup['prepend_tname']) {
1988
                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1989
                    $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1990
                    $statement = $queryBuilder->select($fieldName)
1991
                        ->from($table)
1992
                        ->execute();
1993
                    while ($row = $statement->fetchAssociative()) {
1994
                        if (str_contains($row[$fieldName], ',')) {
1995
                            $checkContent = explode(',', $row[$fieldName]);
1996
                            foreach ($checkContent as $singleValue) {
1997
                                if (!str_contains($singleValue, '_')) {
1998
                                    $dontPrefixFirstTable = 1;
1999
                                }
2000
                            }
2001
                        } else {
2002
                            $singleValue = $row[$fieldName];
2003
                            if ($singleValue !== '' && !str_contains($singleValue, '_')) {
2004
                                $dontPrefixFirstTable = 1;
2005
                            }
2006
                        }
2007
                    }
2008
                }
2009
            } else {
2010
                $from_table_Arr[0] = $allowedFields;
2011
            }
2012
            if (!empty($fieldSetup['prepend_tname'])) {
2013
                $useTablePrefix = 1;
2014
            }
2015
            if (!empty($fieldSetup['foreign_table'])) {
2016
                $from_table_Arr[0] = $fieldSetup['foreign_table'];
2017
            }
2018
            $counter = 0;
2019
            $tablePrefix = '';
2020
            $outArray = [];
2021
            $labelFieldSelect = [];
2022
            foreach ($from_table_Arr as $from_table) {
2023
                $useSelectLabels = false;
2024
                $useAltSelectLabels = false;
2025
                if ($useTablePrefix && !$dontPrefixFirstTable && $counter != 1 || $counter === 1) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($useTablePrefix && ! $d...!= 1) || $counter === 1, Probably Intended Meaning: $useTablePrefix && ! $do...!= 1 || $counter === 1)
Loading history...
2026
                    $tablePrefix = $from_table . '_';
2027
                }
2028
                $counter = 1;
2029
                if (is_array($GLOBALS['TCA'][$from_table])) {
2030
                    $labelField = $GLOBALS['TCA'][$from_table]['ctrl']['label'] ?? '';
2031
                    $altLabelField = $GLOBALS['TCA'][$from_table]['ctrl']['label_alt'] ?? '';
2032
                    if ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] ?? false) {
2033
                        foreach ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] as $labelArray) {
2034
                            if (strpos($labelArray[0], 'LLL:') === 0) {
2035
                                $labelFieldSelect[$labelArray[1]] = $languageService->sL($labelArray[0]);
2036
                            } else {
2037
                                $labelFieldSelect[$labelArray[1]] = $labelArray[0];
2038
                            }
2039
                        }
2040
                        $useSelectLabels = true;
2041
                    }
2042
                    $altLabelFieldSelect = [];
2043
                    if ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] ?? false) {
2044
                        foreach ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] as $altLabelArray) {
2045
                            if (strpos($altLabelArray[0], 'LLL:') === 0) {
2046
                                $altLabelFieldSelect[$altLabelArray[1]] = $languageService->sL($altLabelArray[0]);
2047
                            } else {
2048
                                $altLabelFieldSelect[$altLabelArray[1]] = $altLabelArray[0];
2049
                            }
2050
                        }
2051
                        $useAltSelectLabels = true;
2052
                    }
2053
2054
                    if (!($this->tableArray[$from_table] ?? false)) {
2055
                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
2056
                        $queryBuilder->getRestrictions()->removeAll();
2057
                        if (empty($this->settings['show_deleted'])) {
2058
                            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2059
                        }
2060
                        $selectFields = ['uid', $labelField];
2061
                        if ($altLabelField) {
2062
                            $selectFields = array_merge($selectFields, GeneralUtility::trimExplode(',', $altLabelField, true));
2063
                        }
2064
                        $queryBuilder->select(...$selectFields)
2065
                            ->from($from_table)
2066
                            ->orderBy('uid');
2067
                        if (!$backendUserAuthentication->isAdmin()) {
2068
                            $webMounts = $backendUserAuthentication->returnWebmounts();
2069
                            $perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
2070
                            $webMountPageTree = '';
2071
                            $webMountPageTreePrefix = '';
2072
                            foreach ($webMounts as $webMount) {
2073
                                if ($webMountPageTree) {
2074
                                    $webMountPageTreePrefix = ',';
2075
                                }
2076
                                $webMountPageTree .= $webMountPageTreePrefix
2077
                                    . $this->getTreeList($webMount, 999, 0, $perms_clause);
2078
                            }
2079
                            if ($from_table === 'pages') {
2080
                                $queryBuilder->where(
2081
                                    QueryHelper::stripLogicalOperatorPrefix($perms_clause),
2082
                                    $queryBuilder->expr()->in(
2083
                                        'uid',
2084
                                        $queryBuilder->createNamedParameter(
2085
                                            GeneralUtility::intExplode(',', $webMountPageTree),
2086
                                            Connection::PARAM_INT_ARRAY
2087
                                        )
2088
                                    )
2089
                                );
2090
                            } else {
2091
                                $queryBuilder->where(
2092
                                    $queryBuilder->expr()->in(
2093
                                        'pid',
2094
                                        $queryBuilder->createNamedParameter(
2095
                                            GeneralUtility::intExplode(',', $webMountPageTree),
2096
                                            Connection::PARAM_INT_ARRAY
2097
                                        )
2098
                                    )
2099
                                );
2100
                            }
2101
                        }
2102
                        $statement = $queryBuilder->execute();
2103
                        $this->tableArray[$from_table] = $statement->fetchAllAssociative();
2104
                    }
2105
2106
                    foreach (($this->tableArray[$from_table] ?? []) as $val) {
2107
                        if ($useSelectLabels) {
2108
                            $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($labelFieldSelect[$val[$labelField]]);
2109
                        } elseif ($val[$labelField]) {
2110
                            $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$labelField]);
2111
                        } elseif ($useAltSelectLabels) {
2112
                            $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($altLabelFieldSelect[$val[$altLabelField]]);
2113
                        } else {
2114
                            $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$altLabelField]);
2115
                        }
2116
                    }
2117
                    if (isset($this->settings['options_sortlabel']) && $this->settings['options_sortlabel'] && is_array($outArray)) {
2118
                        natcasesort($outArray);
2119
                    }
2120
                }
2121
            }
2122
            foreach ($outArray as $key2 => $val2) {
2123
                if (GeneralUtility::inList($conf['inputValue'], $key2)) {
2124
                    $out[] = '<option value="' . htmlspecialchars($key2) . '" selected>[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
2125
                } else {
2126
                    $out[] = '<option value="' . htmlspecialchars($key2) . '">[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
2127
                }
2128
            }
2129
        }
2130
        return implode(LF, $out);
2131
    }
2132
2133
    /**
2134
     * Print code array
2135
     *
2136
     * @param array $codeArr
2137
     * @param int $recursionLevel
2138
     * @return string
2139
     */
2140
    protected function printCodeArray($codeArr, $recursionLevel = 0)
2141
    {
2142
        $out = [];
2143
        foreach (array_values($codeArr) as $queryComponent) {
2144
            $out[] = '<div class="card">';
2145
            $out[] =     '<div class="card-body pb-2">';
2146
            $out[] =         $queryComponent['html'];
2147
2148
            if ($this->enableQueryParts) {
2149
                $out[] = '<div class="row row-cols-auto mb-2">';
2150
                $out[] =     '<div class="col">';
2151
                $out[] =         '<code class="m-0">';
2152
                $out[] =             htmlspecialchars($queryComponent['query']);
2153
                $out[] =         '</code>';
2154
                $out[] =     '</div>';
2155
                $out[] = '</div>';
2156
            }
2157
            if (is_array($queryComponent['sub'] ?? null)) {
2158
                $out[] = '<div class="mb-2">';
2159
                $out[] =     $this->printCodeArray($queryComponent['sub'], $recursionLevel + 1);
2160
                $out[] = '</div>';
2161
            }
2162
            $out[] =     '</div>';
2163
            $out[] = '</div>';
2164
        }
2165
        return implode(LF, $out);
2166
    }
2167
2168
    /**
2169
     * Make operator select
2170
     *
2171
     * @param string $name
2172
     * @param string $op
2173
     * @param bool $draw
2174
     * @param bool $submit
2175
     * @return string
2176
     */
2177
    protected function mkOperatorSelect($name, $op, $draw, $submit)
2178
    {
2179
        $out = [];
2180
        if ($draw) {
2181
            $out[] = '<div class="row row-cols-auto mb-2">';
2182
            $out[] = '	<div class="col">';
2183
            $out[] = '    <select class="form-select' . ($submit ? ' t3js-submit-change' : '') . '" name="' . htmlspecialchars($name) . '[operator]">';
2184
            $out[] = '	    <option value="AND"' . (!$op || $op === 'AND' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['AND']) . '</option>';
2185
            $out[] = '	    <option value="OR"' . ($op === 'OR' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['OR']) . '</option>';
2186
            $out[] = '    </select>';
2187
            $out[] = '	</div>';
2188
            $out[] = '</div>';
2189
        } else {
2190
            $out[] = '<input type="hidden" value="' . htmlspecialchars($op) . '" name="' . htmlspecialchars($name) . '[operator]">';
2191
        }
2192
        return implode(LF, $out);
2193
    }
2194
2195
    /**
2196
     * Make type select
2197
     *
2198
     * @param string $name
2199
     * @param string $fieldName
2200
     * @param string $prepend
2201
     * @return string
2202
     */
2203
    protected function mkTypeSelect($name, $fieldName, $prepend = 'FIELD_')
2204
    {
2205
        $out = [];
2206
        $out[] = '<select class="form-select t3js-submit-change" name="' . htmlspecialchars($name) . '">';
2207
        $out[] = '<option value=""></option>';
2208
        foreach ($this->fields as $key => $value) {
2209
            if (!($value['exclude'] ?? false) || $this->getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
2210
                $label = $this->fields[$key]['label'];
2211
                if ($this->showFieldAndTableNames) {
2212
                    $label .= ' [' . $key . ']';
2213
                }
2214
                $out[] = '<option value="' . htmlspecialchars($prepend . $key) . '"' . ($key === $fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
2215
            }
2216
        }
2217
        $out[] = '</select>';
2218
        return implode(LF, $out);
2219
    }
2220
2221
    /**
2222
     * Verify type
2223
     *
2224
     * @param string $fieldName
2225
     * @return string
2226
     */
2227
    protected function verifyType($fieldName)
2228
    {
2229
        $first = '';
2230
        foreach ($this->fields as $key => $value) {
2231
            if (!$first) {
2232
                $first = $key;
2233
            }
2234
            if ($key === $fieldName) {
2235
                return $key;
2236
            }
2237
        }
2238
        return $first;
2239
    }
2240
2241
    /**
2242
     * Verify comparison
2243
     *
2244
     * @param string $comparison
2245
     * @param int $neg
2246
     * @return int
2247
     */
2248
    protected function verifyComparison($comparison, $neg)
2249
    {
2250
        $compOffSet = $comparison >> 5;
2251
        $first = -1;
2252
        for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
2253
            if ($first === -1) {
2254
                $first = $i;
2255
            }
2256
            if ($i >> 1 === $comparison >> 1) {
2257
                return $i;
2258
            }
2259
        }
2260
        return $first;
2261
    }
2262
2263
    /**
2264
     * Make field to input select
2265
     *
2266
     * @param string $name
2267
     * @param string $fieldName
2268
     * @return string
2269
     */
2270
    protected function mkFieldToInputSelect($name, $fieldName)
2271
    {
2272
        $out = [];
2273
        $out[] = '<div class="input-group mb-2">';
2274
        $out[] = '	<span class="input-group-btn">';
2275
        $out[] = $this->updateIcon();
2276
        $out[] = ' 	</span>';
2277
        $out[] = '	<input type="text" class="form-control t3js-clearable" value="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($name) . '">';
2278
        $out[] = '</div>';
2279
2280
        $out[] = '<select class="form-select t3js-addfield" name="_fieldListDummy" size="5" data-field="' . htmlspecialchars($name) . '">';
2281
        foreach ($this->fields as $key => $value) {
2282
            if (!$value['exclude'] || $this->getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
2283
                $label = $this->fields[$key]['label'];
2284
                if ($this->showFieldAndTableNames) {
2285
                    $label .= ' [' . $key . ']';
2286
                }
2287
                $out[] = '<option value="' . htmlspecialchars($key) . '"' . ($key === $fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
2288
            }
2289
        }
2290
        $out[] = '</select>';
2291
        return implode(LF, $out);
2292
    }
2293
2294
    /**
2295
     * Make table select
2296
     *
2297
     * @param string $name
2298
     * @param string $cur
2299
     * @return string
2300
     */
2301
    protected function mkTableSelect($name, $cur)
2302
    {
2303
        $out = [];
2304
        $out[] = '<select class="form-select t3js-submit-change" name="' . $name . '">';
2305
        $out[] = '<option value=""></option>';
2306
        foreach ($GLOBALS['TCA'] as $tN => $value) {
2307
            if ($this->getBackendUserAuthentication()->check('tables_select', $tN)) {
2308
                $label = $this->getLanguageService()->sL($GLOBALS['TCA'][$tN]['ctrl']['title']);
2309
                if ($this->showFieldAndTableNames) {
2310
                    $label .= ' [' . $tN . ']';
2311
                }
2312
                $out[] = '<option value="' . htmlspecialchars($tN) . '"' . ($tN === $cur ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
2313
            }
2314
        }
2315
        $out[] = '</select>';
2316
        return implode(LF, $out);
2317
    }
2318
2319
    /**
2320
     * Make comparison select
2321
     *
2322
     * @param string $name
2323
     * @param string $comparison
2324
     * @param int $neg
2325
     * @return string
2326
     */
2327
    protected function mkCompSelect($name, $comparison, $neg)
2328
    {
2329
        $compOffSet = $comparison >> 5;
2330
        $out = [];
2331
        $out[] = '<select class="form-select t3js-submit-change" name="' . $name . '">';
2332
        for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
2333
            if ($this->lang['comparison'][$i . '_'] ?? false) {
2334
                $out[] = '<option value="' . $i . '"' . ($i >> 1 === $comparison >> 1 ? ' selected' : '') . '>' . htmlspecialchars($this->lang['comparison'][$i . '_']) . '</option>';
2335
            }
2336
        }
2337
        $out[] = '</select>';
2338
        return implode(LF, $out);
2339
    }
2340
2341
    /**
2342
     * Get subscript
2343
     *
2344
     * @param array $arr
2345
     * @return array
2346
     */
2347
    protected function getSubscript($arr): array
2348
    {
2349
        $retArr = [];
2350
        while (\is_array($arr)) {
2351
            reset($arr);
2352
            $key = key($arr);
2353
            $retArr[] = $key;
2354
            if (isset($arr[$key])) {
2355
                $arr = $arr[$key];
2356
            } else {
2357
                break;
2358
            }
2359
        }
2360
        return $retArr;
2361
    }
2362
2363
    /**
2364
     * Get query
2365
     *
2366
     * @param array $queryConfig
2367
     * @param string $pad
2368
     * @return string
2369
     */
2370
    protected function getQuery($queryConfig, $pad = '')
2371
    {
2372
        $qs = '';
2373
        // Since we don't traverse the array using numeric keys in the upcoming whileloop make sure it's fresh and clean
2374
        ksort($queryConfig);
2375
        $first = true;
2376
        foreach ($queryConfig as $key => $conf) {
2377
            $conf = $this->convertIso8601DatetimeStringToUnixTimestamp($conf);
2378
            switch ($conf['type']) {
2379
                case 'newlevel':
2380
                    $qs .= LF . $pad . trim($conf['operator']) . ' (' . $this->getQuery(
2381
                        $queryConfig[$key]['nl'],
2382
                        $pad . '   '
2383
                    ) . LF . $pad . ')';
2384
                    break;
2385
                default:
2386
                    $qs .= LF . $pad . $this->getQuerySingle($conf, $first);
2387
            }
2388
            $first = false;
2389
        }
2390
        return $qs;
2391
    }
2392
2393
    /**
2394
     * Convert ISO-8601 timestamp (string) into unix timestamp (int)
2395
     *
2396
     * @param array $conf
2397
     * @return array
2398
     */
2399
    protected function convertIso8601DatetimeStringToUnixTimestamp(array $conf): array
2400
    {
2401
        if ($this->isDateOfIso8601Format($conf['inputValue'] ?? '')) {
2402
            $conf['inputValue'] = strtotime($conf['inputValue']);
2403
            if ($this->isDateOfIso8601Format($conf['inputValue1'] ?? '')) {
2404
                $conf['inputValue1'] = strtotime($conf['inputValue1']);
2405
            }
2406
        }
2407
2408
        return $conf;
2409
    }
2410
2411
    /**
2412
     * Checks if the given value is of the ISO 8601 format.
2413
     *
2414
     * @param mixed $date
2415
     * @return bool
2416
     */
2417
    protected function isDateOfIso8601Format($date): bool
2418
    {
2419
        if (!is_int($date) && !is_string($date)) {
2420
            return false;
2421
        }
2422
        $format = 'Y-m-d\\TH:i:s\\Z';
2423
        $formattedDate = \DateTime::createFromFormat($format, (string)$date);
2424
        return $formattedDate && $formattedDate->format($format) === $date;
2425
    }
2426
2427
    /**
2428
     * Get single query
2429
     *
2430
     * @param array $conf
2431
     * @param bool $first
2432
     * @return string
2433
     */
2434
    protected function getQuerySingle($conf, $first)
2435
    {
2436
        $comparison = (int)($conf['comparison'] ?? 0);
2437
        $qs = '';
2438
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
2439
        $prefix = $this->enablePrefix ? $this->table . '.' : '';
2440
        if (!$first) {
2441
            // Is it OK to insert the AND operator if none is set?
2442
            $operator = strtoupper(trim($conf['operator'] ?? ''));
2443
            if (!in_array($operator, ['AND', 'OR'], true)) {
2444
                $operator = 'AND';
2445
            }
2446
            $qs .= $operator . ' ';
2447
        }
2448
        $qsTmp = str_replace('#FIELD#', $prefix . trim(substr($conf['type'], 6)), $this->compSQL[$comparison] ?? '');
2449
        $inputVal = $this->cleanInputVal($conf);
2450
        if ($comparison === 68 || $comparison === 69) {
2451
            $inputVal = explode(',', (string)$inputVal);
2452
            foreach ($inputVal as $key => $fileName) {
2453
                $inputVal[$key] = $queryBuilder->quote($fileName);
2454
            }
2455
            $inputVal = implode(',', $inputVal);
2456
            $qsTmp = str_replace('#VALUE#', $inputVal, $qsTmp);
2457
        } elseif ($comparison === 162 || $comparison === 163) {
2458
            $inputValArray = explode(',', (string)$inputVal);
2459
            $inputVal = 0;
2460
            foreach ($inputValArray as $fileName) {
2461
                $inputVal += (int)$fileName;
2462
            }
2463
            $qsTmp = str_replace('#VALUE#', (string)$inputVal, $qsTmp);
2464
        } else {
2465
            if (is_array($inputVal)) {
0 ignored issues
show
introduced by
The condition is_array($inputVal) is always false.
Loading history...
2466
                $inputVal = $inputVal[0];
2467
            }
2468
            // @todo This is weired, as it seems that it quotes the value as string and remove
2469
            //       quotings using the trim() method. Should be investagated/refactored.
2470
            $qsTmp = str_replace('#VALUE#', trim($queryBuilder->quote((string)$inputVal), '\''), $qsTmp);
2471
        }
2472
        if ($comparison === 37 || $comparison === 36 || $comparison === 66 || $comparison === 67 || $comparison === 100 || $comparison === 101) {
2473
            // between:
2474
            $inputVal = $this->cleanInputVal($conf, '1');
2475
            // @todo This is weired, as it seems that it quotes the value as string and remove
2476
            //       quotings using the trim() method. Should be investagated/refactored.
2477
            $qsTmp = str_replace('#VALUE1#', trim($queryBuilder->quote((string)$inputVal), '\''), $qsTmp);
2478
        }
2479
        $qs .= trim((string)$qsTmp);
2480
        return $qs;
2481
    }
2482
2483
    /**
2484
     * Clean input value
2485
     *
2486
     * @param array $conf
2487
     * @param string $suffix
2488
     * @return string|int|float|null
2489
     */
2490
    protected function cleanInputVal($conf, $suffix = '')
2491
    {
2492
        $comparison = (int)($conf['comparison'] ?? 0);
2493
        if ($comparison >> 5 === 0 || ($comparison === 32 || $comparison === 33 || $comparison === 64 || $comparison === 65 || $comparison === 66 || $comparison === 67 || $comparison === 96 || $comparison === 97)) {
2494
            $inputVal = $conf['inputValue' . $suffix] ?? null;
2495
        } elseif ($comparison === 39 || $comparison === 38) {
2496
            // in list:
2497
            $inputVal = implode(',', GeneralUtility::intExplode(',', ($conf['inputValue' . $suffix] ?? '')));
2498
        } elseif ($comparison === 68 || $comparison === 69 || $comparison === 162 || $comparison === 163) {
2499
            // in list:
2500
            if (is_array($conf['inputValue' . $suffix] ?? false)) {
2501
                $inputVal = implode(',', $conf['inputValue' . $suffix]);
2502
            } elseif ($conf['inputValue' . $suffix] ?? false) {
2503
                $inputVal = $conf['inputValue' . $suffix];
2504
            } else {
2505
                $inputVal = 0;
2506
            }
2507
        } elseif (!is_array($conf['inputValue' . $suffix] ?? null) && strtotime($conf['inputValue' . $suffix] ?? '')) {
2508
            $inputVal = $conf['inputValue' . $suffix];
2509
        } elseif (!is_array($conf['inputValue' . $suffix] ?? null) && MathUtility::canBeInterpretedAsInteger($conf['inputValue' . $suffix] ?? null)) {
2510
            $inputVal = (int)$conf['inputValue' . $suffix];
2511
        } else {
2512
            // TODO: Six eyes looked at this code and nobody understood completely what is going on here and why we
2513
            // fallback to float casting, the whole class smells like it needs a refactoring.
2514
            $inputVal = (float)($conf['inputValue' . $suffix] ?? 0.0);
2515
        }
2516
        return $inputVal;
2517
    }
2518
2519
    /**
2520
     * Update icon
2521
     *
2522
     * @return string
2523
     */
2524
    protected function updateIcon()
2525
    {
2526
        return '<button class="btn btn-default" title="Update" name="just_update"><i class="fa fa-refresh fa-fw"></i></button>';
2527
    }
2528
2529
    /**
2530
     * Get label column
2531
     *
2532
     * @return string
2533
     */
2534
    protected function getLabelCol()
2535
    {
2536
        return $GLOBALS['TCA'][$this->table]['ctrl']['label'];
2537
    }
2538
2539
    /**
2540
     * Make selector table
2541
     *
2542
     * @param array $modSettings
2543
     * @param string $enableList
2544
     * @return string
2545
     */
2546
    protected function makeSelectorTable($modSettings, $enableList = 'table,fields,query,group,order,limit')
2547
    {
2548
        $out = [];
2549
        $enableArr = explode(',', $enableList);
2550
        $userTsConfig = $this->getBackendUserAuthentication()->getTSConfig();
2551
2552
        // Make output
2553
        if (in_array('table', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableSelectATable'] ?? false)) {
2554
            $out[] = '<div class="form-group">';
2555
            $out[] =     '<label for="SET[queryTable]">Select a table:</label>';
2556
            $out[] =     '<div class="row row-cols-auto">';
2557
            $out[] =         '<div class="col">';
2558
            $out[] =             $this->mkTableSelect('SET[queryTable]', $this->table);
2559
            $out[] =         '</div>';
2560
            $out[] =     '</div>';
2561
            $out[] = '</div>';
2562
        }
2563
        if ($this->table) {
2564
            // Init fields:
2565
            $this->setAndCleanUpExternalLists('queryFields', $modSettings['queryFields'] ?? '', 'uid,' . $this->getLabelCol());
2566
            $this->setAndCleanUpExternalLists('queryGroup', $modSettings['queryGroup'] ?? '');
2567
            $this->setAndCleanUpExternalLists('queryOrder', ($modSettings['queryOrder'] ?? '') . ',' . ($modSettings['queryOrder2'] ?? ''));
2568
            // Limit:
2569
            $this->extFieldLists['queryLimit'] = $modSettings['queryLimit'] ?? '';
2570
            if (!$this->extFieldLists['queryLimit']) {
2571
                $this->extFieldLists['queryLimit'] = 100;
2572
            }
2573
            $parts = GeneralUtility::intExplode(',', $this->extFieldLists['queryLimit']);
2574
            $limitBegin = 0;
2575
            $limitLength = (int)($this->extFieldLists['queryLimit'] ?? 0);
2576
            if ($parts[1] ?? null) {
2577
                $limitBegin = (int)$parts[0];
2578
                $limitLength = (int)$parts[1];
2579
            }
2580
            $this->extFieldLists['queryLimit'] = implode(',', array_slice($parts, 0, 2));
2581
            // Insert Descending parts
2582
            if ($this->extFieldLists['queryOrder']) {
2583
                $descParts = explode(',', ($modSettings['queryOrderDesc'] ?? '') . ',' . ($modSettings['queryOrder2Desc'] ?? ''));
2584
                $orderParts = explode(',', $this->extFieldLists['queryOrder']);
2585
                $reList = [];
2586
                foreach ($orderParts as $kk => $vv) {
2587
                    $reList[] = $vv . ($descParts[$kk] ? ' DESC' : '');
2588
                }
2589
                $this->extFieldLists['queryOrder_SQL'] = implode(',', $reList);
2590
            }
2591
            // Query Generator:
2592
            $this->procesData(($modSettings['queryConfig'] ?? false) ? unserialize($modSettings['queryConfig'] ?? '', ['allowed_classes' => false]) : []);
2593
            $this->queryConfig = $this->cleanUpQueryConfig($this->queryConfig);
2594
            $this->enableQueryParts = (bool)($modSettings['search_query_smallparts'] ?? false);
2595
            $codeArr = $this->getFormElements();
2596
            $queryCode = $this->printCodeArray($codeArr);
2597
            if (in_array('fields', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableSelectFields'] ?? false)) {
2598
                $out[] = '<div class="form-group form-group-with-button-addon">';
2599
                $out[] = '	<label for="SET[queryFields]">Select fields:</label>';
2600
                $out[] =    $this->mkFieldToInputSelect('SET[queryFields]', $this->extFieldLists['queryFields']);
2601
                $out[] = '</div>';
2602
            }
2603
            if (in_array('query', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableMakeQuery'] ?? false)) {
2604
                $out[] = '<div class="form-group">';
2605
                $out[] = '	<label>Make Query:</label>';
2606
                $out[] =    $queryCode;
2607
                $out[] = '</div>';
2608
            }
2609
            if (in_array('group', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableGroupBy'] ?? false)) {
2610
                $out[] = '<div class="form-group">';
2611
                $out[] =    '<label for="SET[queryGroup]">Group By:</label>';
2612
                $out[] =     '<div class="row row-cols-auto">';
2613
                $out[] =         '<div class="col">';
2614
                $out[] =             $this->mkTypeSelect('SET[queryGroup]', $this->extFieldLists['queryGroup'], '');
2615
                $out[] =         '</div>';
2616
                $out[] =     '</div>';
2617
                $out[] = '</div>';
2618
            }
2619
            if (in_array('order', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableOrderBy'] ?? false)) {
2620
                $orderByArr = explode(',', $this->extFieldLists['queryOrder']);
2621
                $orderBy = [];
2622
                $orderBy[] = '<div class="row row-cols-auto align-items-center">';
2623
                $orderBy[] =     '<div class="col">';
2624
                $orderBy[] =         $this->mkTypeSelect('SET[queryOrder]', $orderByArr[0], '');
2625
                $orderBy[] =     '</div>';
2626
                $orderBy[] =     '<div class="col mt-2">';
2627
                $orderBy[] =         '<div class="form-check">';
2628
                $orderBy[] =              BackendUtility::getFuncCheck(0, 'SET[queryOrderDesc]', $modSettings['queryOrderDesc'] ?? '', '', '', 'id="checkQueryOrderDesc"');
2629
                $orderBy[] =              '<label class="form-check-label" for="checkQueryOrderDesc">Descending</label>';
2630
                $orderBy[] =         '</div>';
2631
                $orderBy[] =     '</div>';
2632
                $orderBy[] = '</div>';
2633
2634
                if ($orderByArr[0]) {
2635
                    $orderBy[] = '<div class="row row-cols-auto align-items-center mt-2">';
2636
                    $orderBy[] =     '<div class="col">';
2637
                    $orderBy[] =         '<div class="input-group">';
2638
                    $orderBy[] =             $this->mkTypeSelect('SET[queryOrder2]', $orderByArr[1] ?? '', '');
2639
                    $orderBy[] =         '</div>';
2640
                    $orderBy[] =     '</div>';
2641
                    $orderBy[] =     '<div class="col mt-2">';
2642
                    $orderBy[] =         '<div class="form-check">';
2643
                    $orderBy[] =             BackendUtility::getFuncCheck(0, 'SET[queryOrder2Desc]', $modSettings['queryOrder2Desc'] ?? false, '', '', 'id="checkQueryOrder2Desc"');
2644
                    $orderBy[] =             '<label class="form-check-label" for="checkQueryOrder2Desc">Descending</label>';
2645
                    $orderBy[] =         '</div>';
2646
                    $orderBy[] =     '</div>';
2647
                    $orderBy[] = '</div>';
2648
                }
2649
                $out[] = '<div class="form-group">';
2650
                $out[] = '	<label>Order By:</label>';
2651
                $out[] =     implode(LF, $orderBy);
2652
                $out[] = '</div>';
2653
            }
2654
            if (in_array('limit', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableLimit'] ?? false)) {
2655
                $limit = [];
2656
                $limit[] = '<div class="input-group">';
2657
                $limit[] = '	<span class="input-group-btn">';
2658
                $limit[] = $this->updateIcon();
2659
                $limit[] = '	</span>';
2660
                $limit[] = '	<input type="text" class="form-control" value="' . htmlspecialchars($this->extFieldLists['queryLimit']) . '" name="SET[queryLimit]" id="queryLimit">';
2661
                $limit[] = '</div>';
2662
2663
                $prevLimit = $limitBegin - $limitLength < 0 ? 0 : $limitBegin - $limitLength;
2664
                $prevButton = '';
2665
                $nextButton = '';
2666
2667
                if ($limitBegin) {
2668
                    $prevButton = '<input type="button" class="btn btn-default" value="previous ' . htmlspecialchars((string)$limitLength) . '" data-value="' . htmlspecialchars($prevLimit . ',' . $limitLength) . '">';
2669
                }
2670
                if (!$limitLength) {
2671
                    $limitLength = 100;
2672
                }
2673
2674
                $nextLimit = $limitBegin + $limitLength;
2675
                if ($nextLimit < 0) {
2676
                    $nextLimit = 0;
2677
                }
2678
                if ($nextLimit) {
2679
                    $nextButton = '<input type="button" class="btn btn-default" value="next ' . htmlspecialchars((string)$limitLength) . '" data-value="' . htmlspecialchars($nextLimit . ',' . $limitLength) . '">';
2680
                }
2681
2682
                $out[] = '<div class="form-group">';
2683
                $out[] = '	<label>Limit:</label>';
2684
                $out[] = '	<div class="row row-cols-auto">';
2685
                $out[] = '   <div class="col">';
2686
                $out[] =        implode(LF, $limit);
2687
                $out[] = '   </div>';
2688
                $out[] = '   <div class="col">';
2689
                $out[] = '		<div class="btn-group t3js-limit-submit">';
2690
                $out[] =            $prevButton;
2691
                $out[] =            $nextButton;
2692
                $out[] = '		</div>';
2693
                $out[] = '   </div>';
2694
                $out[] = '   <div class="col">';
2695
                $out[] = '		<div class="btn-group t3js-limit-submit">';
2696
                $out[] = '			<input type="button" class="btn btn-default" data-value="10" value="10">';
2697
                $out[] = '			<input type="button" class="btn btn-default" data-value="20" value="20">';
2698
                $out[] = '			<input type="button" class="btn btn-default" data-value="50" value="50">';
2699
                $out[] = '			<input type="button" class="btn btn-default" data-value="100" value="100">';
2700
                $out[] = '		</div>';
2701
                $out[] = '   </div>';
2702
                $out[] = '	</div>';
2703
                $out[] = '</div>';
2704
            }
2705
        }
2706
        return implode(LF, $out);
2707
    }
2708
2709
    /**
2710
     * Get select query
2711
     *
2712
     * @param string $qString
2713
     * @return string
2714
     */
2715
    protected function getSelectQuery($qString = ''): string
2716
    {
2717
        $backendUserAuthentication = $this->getBackendUserAuthentication();
2718
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
2719
        $queryBuilder->getRestrictions()->removeAll();
2720
        if (empty($this->settings['show_deleted'])) {
2721
            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2722
        }
2723
        $deleteField = $GLOBALS['TCA'][$this->table]['ctrl']['delete'] ?? '';
2724
        $fieldList = GeneralUtility::trimExplode(
2725
            ',',
2726
            $this->extFieldLists['queryFields']
2727
            . ',pid'
2728
            . ($deleteField ? ',' . $deleteField : '')
2729
        );
2730
        $queryBuilder->select(...$fieldList)
2731
            ->from($this->table);
2732
2733
        if ($this->extFieldLists['queryGroup']) {
2734
            $queryBuilder->groupBy(...QueryHelper::parseGroupBy($this->extFieldLists['queryGroup']));
2735
        }
2736
        if ($this->extFieldLists['queryOrder']) {
2737
            foreach (QueryHelper::parseOrderBy($this->extFieldLists['queryOrder_SQL']) as $orderPair) {
2738
                [$fieldName, $order] = $orderPair;
2739
                $queryBuilder->addOrderBy($fieldName, $order);
2740
            }
2741
        }
2742
        if ($this->extFieldLists['queryLimit']) {
2743
            // Explode queryLimit to fetch the limit and a possible offset
2744
            $parts = GeneralUtility::intExplode(',', $this->extFieldLists['queryLimit']);
2745
            if ($parts[1] ?? null) {
2746
                // Offset and limit are given
2747
                $queryBuilder->setFirstResult($parts[0]);
2748
                $queryBuilder->setMaxResults($parts[1]);
2749
            } else {
2750
                // Only the limit is given
2751
                $queryBuilder->setMaxResults($parts[0]);
2752
            }
2753
        }
2754
2755
        if (!$backendUserAuthentication->isAdmin()) {
2756
            $webMounts = $backendUserAuthentication->returnWebmounts();
2757
            $perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
2758
            $webMountPageTree = '';
2759
            $webMountPageTreePrefix = '';
2760
            foreach ($webMounts as $webMount) {
2761
                if ($webMountPageTree) {
2762
                    $webMountPageTreePrefix = ',';
2763
                }
2764
                $webMountPageTree .= $webMountPageTreePrefix
2765
                    . $this->getTreeList($webMount, 999, 0, $perms_clause);
2766
            }
2767
            // createNamedParameter() is not used here because the SQL fragment will only include
2768
            // the :dcValueX placeholder when the query is returned as a string. The value for the
2769
            // placeholder would be lost in the process.
2770
            if ($this->table === 'pages') {
2771
                $queryBuilder->where(
2772
                    QueryHelper::stripLogicalOperatorPrefix($perms_clause),
2773
                    $queryBuilder->expr()->in(
2774
                        'uid',
2775
                        GeneralUtility::intExplode(',', $webMountPageTree)
2776
                    )
2777
                );
2778
            } else {
2779
                $queryBuilder->where(
2780
                    $queryBuilder->expr()->in(
2781
                        'pid',
2782
                        GeneralUtility::intExplode(',', $webMountPageTree)
2783
                    )
2784
                );
2785
            }
2786
        }
2787
        if (!$qString) {
2788
            $qString = $this->getQuery($this->queryConfig);
2789
        }
2790
        $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($qString));
2791
2792
        return $queryBuilder->getSQL();
2793
    }
2794
2795
    /**
2796
     * @param string $name the field name
2797
     * @param string $timestamp ISO-8601 timestamp
2798
     * @param string $type [datetime, date, time, timesec, year]
2799
     *
2800
     * @return string
2801
     */
2802
    protected function getDateTimePickerField($name, $timestamp, $type)
2803
    {
2804
        $value = strtotime($timestamp) ? date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)strtotime($timestamp)) : '';
2805
        $id = StringUtility::getUniqueId('dt_');
2806
        $html = [];
2807
        $html[] = '<div class="col mb-sm-2">';
2808
        $html[] = '  <div class="input-group" id="' . $id . '-wrapper">';
2809
        $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 . '">';
2810
        $html[] = '	   <input name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($timestamp) . '" type="hidden">';
2811
        $html[] = '	   <span class="input-group-btn">';
2812
        $html[] = '	     <label class="btn btn-default" for="' . $id . '">';
2813
        $html[] = '		   <span class="fa fa-calendar"></span>';
2814
        $html[] = '		 </label>';
2815
        $html[] = '    </span>';
2816
        $html[] = '  </div>';
2817
        $html[] = '</div>';
2818
        return implode(LF, $html);
2819
    }
2820
2821
    protected function getBackendUserAuthentication(): BackendUserAuthentication
2822
    {
2823
        return $GLOBALS['BE_USER'];
2824
    }
2825
2826
    protected function getLanguageService(): LanguageService
2827
    {
2828
        return $GLOBALS['LANG'];
2829
    }
2830
}
2831