‪TYPO3CMS  ‪main
DatabaseIntegrityController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Doctrine\DBAL\Exception as DBALException;
21 use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
22 use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
23 use Doctrine\DBAL\Platforms\PostgreSQLPlatform as DoctrinePostgreSQLPlatform;
24 use Doctrine\DBAL\Types\Type;
25 use Doctrine\DBAL\Types\Types;
26 use Psr\Http\Message\ResponseInterface;
27 use Psr\Http\Message\ServerRequestInterface;
33 use TYPO3\CMS\Backend\Utility\BackendUtility;
42 use TYPO3\CMS\Core\Imaging\IconSize;
61 
67 #[AsController]
69 {
73  protected array ‪$MOD_MENU = [];
74 
78  protected array ‪$MOD_SETTINGS = [];
79 
80  protected string ‪$formName = '';
81  protected string ‪$moduleName = 'system_dbint';
82 
87  protected bool ‪$showFieldAndTableNames = false;
88  protected string ‪$table = '';
89  protected bool ‪$enablePrefix = false;
90  protected int ‪$noDownloadB = 0;
91  protected array ‪$tableArray = [];
92  protected array ‪$queryConfig = [];
93  protected array ‪$extFieldLists = [];
94  protected array ‪$fields = [];
95  protected string ‪$storeList = 'search_query_smallparts,search_result_labels,labels_noprefix,show_deleted,queryConfig,queryTable,queryFields,queryLimit,queryOrder,queryOrderDesc,queryOrder2,queryOrder2Desc,queryGroup,search_query_makeQuery';
96  protected bool ‪$enableQueryParts = false;
97  protected array ‪$lang = [
98  'OR' => 'or',
99  'AND' => 'and',
100  'comparison' => [
101  // Type = text offset = 0
102  '0_' => 'contains',
103  '1_' => 'does not contain',
104  '2_' => 'starts with',
105  '3_' => 'does not start with',
106  '4_' => 'ends with',
107  '5_' => 'does not end with',
108  '6_' => 'equals',
109  '7_' => 'does not equal',
110  // Type = number , offset = 32
111  '32_' => 'equals',
112  '33_' => 'does not equal',
113  '34_' => 'is greater than',
114  '35_' => 'is less than',
115  '36_' => 'is between',
116  '37_' => 'is not between',
117  '38_' => 'is in list',
118  '39_' => 'is not in list',
119  '40_' => 'binary AND equals',
120  '41_' => 'binary AND does not equal',
121  '42_' => 'binary OR equals',
122  '43_' => 'binary OR does not equal',
123  // Type = multiple, relation, offset = 64
124  '64_' => 'equals',
125  '65_' => 'does not equal',
126  '66_' => 'contains',
127  '67_' => 'does not contain',
128  '68_' => 'is in list',
129  '69_' => 'is not in list',
130  '70_' => 'binary AND equals',
131  '71_' => 'binary AND does not equal',
132  '72_' => 'binary OR equals',
133  '73_' => 'binary OR does not equal',
134  // Type = date,time offset = 96
135  '96_' => 'equals',
136  '97_' => 'does not equal',
137  '98_' => 'is greater than',
138  '99_' => 'is less than',
139  '100_' => 'is between',
140  '101_' => 'is not between',
141  '102_' => 'binary AND equals',
142  '103_' => 'binary AND does not equal',
143  '104_' => 'binary OR equals',
144  '105_' => 'binary OR does not equal',
145  // Type = boolean, offset = 128
146  '128_' => 'is True',
147  '129_' => 'is False',
148  // Type = binary , offset = 160
149  '160_' => 'equals',
150  '161_' => 'does not equal',
151  '162_' => 'contains',
152  '163_' => 'does not contain',
153  ],
154  ];
155  protected string ‪$fieldName = '';
156  protected string ‪$name = '';
157  protected string ‪$fieldList;
158  protected array ‪$comp_offsets = [
159  'text' => 0,
160  'number' => 1,
161  'multiple' => 2,
162  'relation' => 2,
163  'date' => 3,
164  'time' => 3,
165  'boolean' => 4,
166  'binary' => 5,
167  ];
168  protected array ‪$compSQL = [
169  // Type = text offset = 0
170  '0' => '#FIELD# LIKE \'%#VALUE#%\'',
171  '1' => '#FIELD# NOT LIKE \'%#VALUE#%\'',
172  '2' => '#FIELD# LIKE \'#VALUE#%\'',
173  '3' => '#FIELD# NOT LIKE \'#VALUE#%\'',
174  '4' => '#FIELD# LIKE \'%#VALUE#\'',
175  '5' => '#FIELD# NOT LIKE \'%#VALUE#\'',
176  '6' => '#FIELD# = \'#VALUE#\'',
177  '7' => '#FIELD# != \'#VALUE#\'',
178  // Type = number, offset = 32
179  '32' => '#FIELD# = \'#VALUE#\'',
180  '33' => '#FIELD# != \'#VALUE#\'',
181  '34' => '#FIELD# > #VALUE#',
182  '35' => '#FIELD# < #VALUE#',
183  '36' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
184  '37' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
185  '38' => '#FIELD# IN (#VALUE#)',
186  '39' => '#FIELD# NOT IN (#VALUE#)',
187  '40' => '(#FIELD# & #VALUE#)=#VALUE#',
188  '41' => '(#FIELD# & #VALUE#)!=#VALUE#',
189  '42' => '(#FIELD# | #VALUE#)=#VALUE#',
190  '43' => '(#FIELD# | #VALUE#)!=#VALUE#',
191  // Type = multiple, relation, offset = 64
192  '64' => '#FIELD# = \'#VALUE#\'',
193  '65' => '#FIELD# != \'#VALUE#\'',
194  '66' => '#FIELD# LIKE \'%#VALUE#%\' AND #FIELD# LIKE \'%#VALUE1#%\'',
195  '67' => '(#FIELD# NOT LIKE \'%#VALUE#%\' OR #FIELD# NOT LIKE \'%#VALUE1#%\')',
196  '68' => '#FIELD# IN (#VALUE#)',
197  '69' => '#FIELD# NOT IN (#VALUE#)',
198  '70' => '(#FIELD# & #VALUE#)=#VALUE#',
199  '71' => '(#FIELD# & #VALUE#)!=#VALUE#',
200  '72' => '(#FIELD# | #VALUE#)=#VALUE#',
201  '73' => '(#FIELD# | #VALUE#)!=#VALUE#',
202  // Type = date, offset = 32
203  '96' => '#FIELD# = \'#VALUE#\'',
204  '97' => '#FIELD# != \'#VALUE#\'',
205  '98' => '#FIELD# > #VALUE#',
206  '99' => '#FIELD# < #VALUE#',
207  '100' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
208  '101' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
209  '102' => '(#FIELD# & #VALUE#)=#VALUE#',
210  '103' => '(#FIELD# & #VALUE#)!=#VALUE#',
211  '104' => '(#FIELD# | #VALUE#)=#VALUE#',
212  '105' => '(#FIELD# | #VALUE#)!=#VALUE#',
213  // Type = boolean, offset = 128
214  '128' => '#FIELD# = \'1\'',
215  '129' => '#FIELD# != \'1\'',
216  // Type = binary = 160
217  '160' => '#FIELD# = \'#VALUE#\'',
218  '161' => '#FIELD# != \'#VALUE#\'',
219  '162' => '(#FIELD# & #VALUE#)=#VALUE#',
220  '163' => '(#FIELD# & #VALUE#)=0',
221  ];
222 
223  public function ‪__construct(
224  protected ‪IconFactory $iconFactory,
225  protected readonly ‪UriBuilder $uriBuilder,
226  protected readonly ‪ModuleTemplateFactory $moduleTemplateFactory,
227  protected readonly ‪PlatformHelper $platformHelper,
228  ) {}
229 
230  public function ‪handleRequest(ServerRequestInterface $request): ResponseInterface
231  {
232  $languageService = $this->‪getLanguageService();
233 
234  $this->‪menuConfig($request);
235  $moduleTemplate = $this->moduleTemplateFactory->create($request);
236  $this->‪setUpDocHeader($moduleTemplate);
237 
238  $title = $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:module.dbint.title');
239  switch ($this->MOD_SETTINGS['function']) {
240  case 'search':
241  $moduleTemplate->setTitle($title, $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch'));
242  return $this->‪searchAction($moduleTemplate, $request);
243  case 'records':
244  $moduleTemplate->setTitle($title, $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:recordStatistics'));
245  return $this->‪recordStatisticsAction($moduleTemplate, $request);
246  case 'relations':
247  $moduleTemplate->setTitle($title, $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:databaseRelations'));
248  return $this->‪relationsAction($moduleTemplate);
249  default:
250  $moduleTemplate->setTitle($title, $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:manageRefIndex'));
251  return $this->‪referenceIndexAction($moduleTemplate, $request);
252  }
253  }
254 
258  protected function ‪menuConfig(ServerRequestInterface $request): void
259  {
260  ‪$lang = $this->‪getLanguageService();
261  $parsedBody = $request->getParsedBody();
262  $queryParams = $request->getQueryParams();
263 
264  // MENU-ITEMS:
265  // If array, then it's a selector box menu
266  // If empty string it's just a variable, that'll be saved.
267  // Values NOT in this array will not be saved in the settings-array for the module.
268  $this->MOD_MENU = [
269  'function' => [
270  'refindex' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:manageRefIndex')),
271  'records' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:recordStatistics')),
272  'relations' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:databaseRelations')),
273  'search' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch')),
274  ],
275  'search' => [
276  'raw' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:rawSearch')),
277  'query' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:advancedQuery')),
278  ],
279  'search_query_smallparts' => '',
280  'search_result_labels' => '',
281  'labels_noprefix' => '',
282  'options_sortlabel' => '',
283  'show_deleted' => '',
284  'queryConfig' => '',
285  // Current query
286  'queryTable' => '',
287  // Current table
288  'queryFields' => '',
289  // Current tableFields
290  'queryLimit' => '',
291  // Current limit
292  'queryOrder' => '',
293  // Current Order field
294  'queryOrderDesc' => '',
295  // Current Order field descending flag
296  'queryOrder2' => '',
297  // Current Order2 field
298  'queryOrder2Desc' => '',
299  // Current Order2 field descending flag
300  'queryGroup' => '',
301  // Current Group field
302  'storeArray' => '',
303  // Used to store the available Query config memory banks
304  'storeQueryConfigs' => '',
305  // Used to store the available Query configs in memory
306  'search_query_makeQuery' => [
307  'all' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:selectRecords')),
308  'count' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:countResults')),
309  'explain' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:explainQuery')),
310  'csv' => htmlspecialchars(‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:csvExport')),
311  ],
312  'sword' => '',
313  ];
314 
315  // EXPLAIN is no ANSI SQL, for now this is only executed on mysql
316  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionByName(‪ConnectionPool::DEFAULT_CONNECTION_NAME);
317  $platform = $connection->getDatabasePlatform();
318  if (!($platform instanceof DoctrineMariaDBPlatform || $platform instanceof DoctrineMySQLPlatform)) {
319  unset($this->MOD_MENU['search_query_makeQuery']['explain']);
320  }
321 
322  // CLEAN SETTINGS
323  $OLD_MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, [], $this->moduleName, 'ses');
324  $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $parsedBody['SET'] ?? $queryParams['SET'] ?? [], $this->moduleName, 'ses');
325  ‪$queryConfig = $parsedBody['queryConfig'] ?? $queryParams['queryConfig'] ?? false;
326  if (‪$queryConfig) {
327  $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, ['queryConfig' => serialize(‪$queryConfig)], $this->moduleName, 'ses');
328  }
329  $setLimitToStart = false;
330  foreach ($OLD_MOD_SETTINGS as $key => $val) {
331  if (str_starts_with($key, 'query') && $this->MOD_SETTINGS[$key] != $val && $key !== 'queryLimit' && $key !== 'use_listview') {
332  $setLimitToStart = true;
333  $addConditionCheck = (bool)($parsedBody['qG_ins'] ?? $queryParams['qG_ins'] ?? false);
334  if ($key === 'queryTable' && !$addConditionCheck) {
335  $this->MOD_SETTINGS['queryConfig'] = '';
336  }
337  }
338  if ($key === 'queryTable' && $this->MOD_SETTINGS[$key] != $val) {
339  $this->MOD_SETTINGS['queryFields'] = '';
340  }
341  }
342  if ($setLimitToStart) {
343  $currentLimit = explode(',', $this->MOD_SETTINGS['queryLimit'] ?? '');
344  if (!empty($currentLimit[1] ?? 0)) {
345  $this->MOD_SETTINGS['queryLimit'] = '0,' . $currentLimit[1];
346  } else {
347  $this->MOD_SETTINGS['queryLimit'] = '0';
348  }
349  $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $this->MOD_SETTINGS, $this->moduleName, 'ses');
350  }
351  }
352 
356  protected function ‪setUpDocHeader(‪ModuleTemplate $moduleTemplate): void
357  {
358  $buttonBar = $moduleTemplate->‪getDocHeaderComponent()->getButtonBar();
359  $shortCutButton = $buttonBar->makeShortcutButton()
360  ->setRouteIdentifier($this->moduleName)
361  ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']])
362  ->setArguments([
363  'SET' => [
364  'function' => $this->MOD_SETTINGS['function'] ?? '',
365  'search' => $this->MOD_SETTINGS['search'] ?? 'raw',
366  'search_query_makeQuery' => $this->MOD_SETTINGS['search_query_makeQuery'] ?? '',
367  ],
368  ]);
369  $buttonBar->addButton($shortCutButton, ‪ButtonBar::BUTTON_POSITION_RIGHT, 2);
370 
371  $menu = $moduleTemplate->‪getDocHeaderComponent()->getMenuRegistry()->makeMenu();
372  $menu->setIdentifier('DatabaseJumpMenu');
373  $menu->setLabel(
374  $this->‪getLanguageService()->sL(
375  'LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:module.dbint.docheader.viewmode'
376  )
377  );
378  foreach ($this->MOD_MENU['function'] as $controller => $title) {
379  $item = $menu
380  ->makeMenuItem()
381  ->setHref(
382  (string)$this->uriBuilder->buildUriFromRoute(
383  $this->moduleName,
384  [
385  'id' => 0,
386  'SET' => [
387  'function' => $controller,
388  ],
389  ]
390  )
391  )
392  ->setTitle($title);
393  if ($controller === $this->MOD_SETTINGS['function']) {
394  $item->setActive(true);
395  }
396  $menu->addMenuItem($item);
397  }
398  $moduleTemplate->‪getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
399  }
400 
404  protected function ‪referenceIndexAction(‪ModuleTemplate $view, ServerRequestInterface $request): ResponseInterface
405  {
406  $isUpdate = $request->getParsedBody()['update'] ?? false;
407  $isCheckOnly = (bool)($request->getParsedBody()['checkOnly'] ?? false);
408  $referenceIndexResult = [];
409  if ($isUpdate || $isCheckOnly) {
410  $referenceIndexResult = GeneralUtility::makeInstance(ReferenceIndex::class)->updateIndex($isCheckOnly);
411  }
412  $readmeLocation = ‪ExtensionManagementUtility::extPath('lowlevel', 'README.rst');
413  $view->‪assignMultiple([
414  'ReadmeLink' => ‪PathUtility::getAbsoluteWebPath($readmeLocation),
415  'ReadmeLocation' => $readmeLocation,
416  'binaryPath' => ‪ExtensionManagementUtility::extPath('core', 'bin/typo3'),
417  'referenceIndexResult' => $referenceIndexResult,
418  ]);
419 
420  return $view->‪renderResponse('ReferenceIndex');
421  }
422 
426  protected function ‪searchAction(‪ModuleTemplate $view, ServerRequestInterface $request): ResponseInterface
427  {
428  ‪$lang = $this->‪getLanguageService();
429  $this->showFieldAndTableNames = $this->‪getBackendUserAuthentication()->shallDisplayDebugInformation();
430  $searchMode = $this->MOD_SETTINGS['search'];
431 
432  $searchTypeSelect = '';
433  $searchTypeSelect .= '<div class="form-group">';
434  $searchTypeSelect .= '<label for="search" class="form-label">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.searchType.label') . '</label>';
435  $searchTypeSelect .= '<div class="input-group">' . $this->‪getDropdownMenu('SET[search]', $searchMode, $this->MOD_MENU['search'], $request) . '</div>';
436  $searchTypeSelect .= '</div>';
437 
438  $queryTypeSelect = '';
439  $queryOptions = '';
440  if ($searchMode === 'query') {
441  $queryTypeSelect .= '<div class="form-group">';
442  $queryTypeSelect .= '<label for="search-search-query-make-query" class="form-label">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.makeQuery.label') . '</label>';
443  $queryTypeSelect .= '<div class="input-group">' . $this->‪getDropdownMenu('SET[search_query_makeQuery]', $this->MOD_SETTINGS['search_query_makeQuery'], $this->MOD_MENU['search_query_makeQuery'], $request) . '</div>';
444  $queryTypeSelect .= '</div>';
445 
446  $queryOptions .= '<div class="form-row">';
447  $queryOptions .= '<div class="form-group">';
448  $queryOptions .= '<fieldset>';
449  $queryOptions .= '<legend class="form-legend">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.section.queryOptions') . '</legend>';
450  $queryOptions .= '<div class="form-check form-switch form-check-size-input">' . $this->‪getFuncCheck('SET[search_query_smallparts]', $this->MOD_SETTINGS['search_query_smallparts'] ?? '', $request, 'id="checkSearch_query_smallparts"')
451  . '<label class="form-check-label" for="checkSearch_query_smallparts">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:showSQL') . '</label></div>';
452  $queryOptions .= '<div class="form-check form-switch form-check-size-input">' . $this->‪getFuncCheck('SET[search_result_labels]', $this->MOD_SETTINGS['search_result_labels'] ?? '', $request, 'id="checkSearch_result_labels"')
453  . '<label class="form-check-label" for="checkSearch_result_labels">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:useFormattedStrings') . '</label></div>';
454  $queryOptions .= '<div class="form-check form-switch form-check-size-input">' . $this->‪getFuncCheck('SET[labels_noprefix]', $this->MOD_SETTINGS['labels_noprefix'] ?? '', $request, 'id="checkLabels_noprefix"')
455  . '<label class="form-check-label" for="checkLabels_noprefix">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:dontUseOrigValues') . '</label></div>';
456  $queryOptions .= '<div class="form-check form-switch form-check-size-input">' . $this->‪getFuncCheck('SET[options_sortlabel]', $this->MOD_SETTINGS['options_sortlabel'] ?? '', $request, 'id="checkOptions_sortlabel"')
457  . '<label class="form-check-label" for="checkOptions_sortlabel">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:sortOptions') . '</label></div>';
458  $queryOptions .= '<div class="form-check form-switch form-check-size-input">' . $this->‪getFuncCheck('SET[show_deleted]', $this->MOD_SETTINGS['show_deleted'] ?? 0, $request, 'id="checkShow_deleted"')
459  . '<label class="form-check-label" for="checkShow_deleted">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:showDeleted') . '</label></div>';
460  $queryOptions .= '</fieldset>';
461  $queryOptions .= '</div>';
462  $queryOptions .= '</div>';
463  }
464 
465  $view->‪assignMultiple([
466  'queryOptions' => $queryOptions,
467  'queryTypeSelect' => $queryTypeSelect,
468  'searchMode' => $searchMode,
469  'searchTypeSelect' => $searchTypeSelect,
470  ]);
471 
472  switch ($searchMode) {
473  case 'query':
474  $view->‪assign('queryMaker', $this->‪queryMaker($request));
475  break;
476  case 'raw':
477  default:
478  $view->‪assign('sword', (string)($this->MOD_SETTINGS['sword'] ?? ''));
479  $view->‪assign('results', $this->‪search($request));
480  $view->‪assign('isSearching', $request->getMethod() === 'POST');
481  }
482 
483  return $view->‪renderResponse('CustomSearch');
484  }
485 
486  protected function ‪queryMaker(ServerRequestInterface $request): string
487  {
488  ‪$lang = $this->‪getLanguageService();
489  ‪$output = '';
490  $msg = $this->‪procesStoreControl($request);
491  $userTsConfig = $this->‪getBackendUserAuthentication()->getTSConfig();
492  if (!($userTsConfig['mod.']['dbint.']['disableStoreControl'] ?? false)) {
493  ‪$output .= '<div class="card">';
494  ‪$output .= '<div class="card-body">';
495  ‪$output .= '<h2 class="card-title">' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.section.queryStorage') . '</h2>';
496  ‪$output .= $this->‪makeStoreControl();
497  ‪$output .= '<div class="card-text">' . $msg . '</div>';
498  ‪$output .= '</div>';
499  ‪$output .= '</div>';
500  }
501 
502  // Query Maker:
503  $this->‪init('queryConfig', $this->MOD_SETTINGS['queryTable'] ?? '', '', $this->MOD_SETTINGS);
504 
505  ‪$output .= '<h2>' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.section.querySettings') . '</h2>';
506  ‪$output .= '<div class="task-form">';
507  ‪$output .= '<fieldset class="form-section">';
508  ‪$output .= $this->‪makeSelectorTable($this->MOD_SETTINGS, $request);
509  ‪$output .= '</fieldset>';
510  ‪$output .= '</div>';
511  $mQ = $this->MOD_SETTINGS['search_query_makeQuery'] ?? '';
512 
513  // Make form elements:
514  if ($this->table && is_array(‪$GLOBALS['TCA'][$this->table])) {
515  if ($mQ) {
516  // Show query
517  $this->enablePrefix = true;
518  $queryString = $this->‪getQuery($this->queryConfig);
519  $selectQueryString = $this->‪getSelectQuery($queryString);
520  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
521  $platform = $connection->getDatabasePlatform();
522 
523  $isConnectionMysql = ($platform instanceof DoctrineMariaDBPlatform || $platform instanceof DoctrineMySQLPlatform);
524  $fullQueryString = '';
525  try {
526  if ($mQ === 'explain' && $isConnectionMysql) {
527  // EXPLAIN is no ANSI SQL, for now this is only executed on mysql
528  // @todo: Move away from getSelectQuery() or model differently
529  $fullQueryString = 'EXPLAIN ' . $selectQueryString;
530  $dataRows = $connection->executeQuery('EXPLAIN ' . $selectQueryString)->fetchAllAssociative();
531  } elseif ($mQ === 'count') {
532  $queryBuilder = $connection->createQueryBuilder();
533  $queryBuilder->getRestrictions()->removeAll();
534  if (empty($this->MOD_SETTINGS['show_deleted'])) {
535  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
536  }
537  $queryBuilder->count('*')
538  ->from($this->table)
539  ->where(‪QueryHelper::stripLogicalOperatorPrefix($queryString));
540  $fullQueryString = $queryBuilder->getSQL();
541  $dataRows = [$queryBuilder->executeQuery()->fetchOne()];
542  } else {
543  $fullQueryString = $selectQueryString;
544  $dataRows = $connection->executeQuery($selectQueryString)->fetchAllAssociative();
545  }
546  if (!($userTsConfig['mod.']['dbint.']['disableShowSQLQuery'] ?? false)) {
547  ‪$output .= '<h2>' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.section.querySQL') . '</h2>';
548  ‪$output .= '<pre class="language-sql">';
549  ‪$output .= '<code class="language-sql">';
550  ‪$output .= htmlspecialchars($fullQueryString);
551  ‪$output .= '</code>';
552  ‪$output .= '</pre>';
553  }
554  $cPR = $this->‪getQueryResultCode($mQ, $dataRows, $this->table, $request);
555  if ($cPR['header'] ?? null) {
556  ‪$output .= '<h2>' . $cPR['header'] . '</h2>';
557  }
558  if ($cPR['content'] ?? null) {
559  ‪$output .= $cPR['content'];
560  }
561  } catch (DBALException $e) {
562  if (!($userTsConfig['mod.']['dbint.']['disableShowSQLQuery'] ?? false)) {
563  ‪$output .= '<h2>' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.section.querySQL') . '</h2>';
564  ‪$output .= '<pre class="language-sql">';
565  ‪$output .= '<code class="language-sql">';
566  ‪$output .= htmlspecialchars($fullQueryString);
567  ‪$output .= '</code>';
568  ‪$output .= '</pre>';
569  }
570  ‪$output .= '<h2>' . ‪$lang->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.section.querySQL.error') . '</h2>';
571  ‪$output .= '<div class="alert alert-danger">';
572  ‪$output .= '<p class="alert-message"><strong>Error:</strong> ' . htmlspecialchars($e->getMessage()) . '</p>';
573  ‪$output .= '</div>';
574  }
575  }
576  }
577 
578  return ‪$output;
579  }
580 
581  protected function ‪getSelectQuery(string $qString = ''): string
582  {
583  $backendUserAuthentication = $this->‪getBackendUserAuthentication();
584  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
585  $queryBuilder->getRestrictions()->removeAll();
586  if (empty($this->MOD_SETTINGS['show_deleted'])) {
587  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
588  }
589  $deleteField = ‪$GLOBALS['TCA'][‪$this->table]['ctrl']['delete'] ?? '';
591  ',',
592  $this->extFieldLists['queryFields']
593  . ',pid'
594  . ($deleteField ? ',' . $deleteField : '')
595  );
596  $queryBuilder->select(...‪$fieldList)
597  ->from($this->table);
598 
599  if ($this->extFieldLists['queryGroup']) {
600  $queryBuilder->groupBy(...‪QueryHelper::parseGroupBy($this->extFieldLists['queryGroup']));
601  }
602  if ($this->extFieldLists['queryOrder']) {
603  foreach (‪QueryHelper::parseOrderBy($this->extFieldLists['queryOrder_SQL']) as $orderPair) {
604  [‪$fieldName, $order] = $orderPair;
605  $queryBuilder->addOrderBy(‪$fieldName, $order);
606  }
607  }
608  $queryLimit = (string)($this->extFieldLists['queryLimit'] ?? '');
609  if ($queryLimit) {
610  // Explode queryLimit to fetch the limit and a possible offset
611  $parts = ‪GeneralUtility::intExplode(',', $queryLimit);
612  if ($parts[1] ?? null) {
613  // Offset and limit are given
614  $queryBuilder->setFirstResult($parts[0]);
615  $queryBuilder->setMaxResults($parts[1]);
616  } else {
617  // Only the limit is given
618  $queryBuilder->setMaxResults($parts[0]);
619  }
620  }
621 
622  if (!$backendUserAuthentication->isAdmin()) {
623  $webMounts = $backendUserAuthentication->returnWebmounts();
624  $perms_clause = $backendUserAuthentication->getPagePermsClause(‪Permission::PAGE_SHOW);
625  $webMountPageTree = '';
626  $webMountPageTreePrefix = '';
627  foreach ($webMounts as $webMount) {
628  if ($webMountPageTree) {
629  $webMountPageTreePrefix = ',';
630  }
631  $webMountPageTree .= $webMountPageTreePrefix
632  . $this->‪getTreeList($webMount, 999, 0, $perms_clause);
633  }
634  // createNamedParameter() is not used here because the SQL fragment will only include
635  // the :dcValueX placeholder when the query is returned as a string. The value for the
636  // placeholder would be lost in the process.
637  if ($this->table === 'pages') {
638  $queryBuilder->where(
640  $queryBuilder->expr()->in(
641  'uid',
642  ‪GeneralUtility::intExplode(',', $webMountPageTree)
643  )
644  );
645  } else {
646  $queryBuilder->where(
647  $queryBuilder->expr()->in(
648  'pid',
649  ‪GeneralUtility::intExplode(',', $webMountPageTree)
650  )
651  );
652  }
653  }
654  if (!$qString) {
655  $qString = $this->‪getQuery($this->queryConfig);
656  }
657  $queryBuilder->andWhere(‪QueryHelper::stripLogicalOperatorPrefix($qString));
658 
659  return $queryBuilder->getSQL();
660  }
661 
667  protected function ‪getTreeList(int $id, int $depth, int $begin = 0, string $permsClause = ''): string
668  {
669  if ($id < 0) {
670  $id = abs($id);
671  }
672  if ($begin === 0) {
673  $theList = (string)$id;
674  } else {
675  $theList = '';
676  }
677  if ($id && $depth > 0) {
678  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
679  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
680  $statement = $queryBuilder->select('uid')
681  ->from('pages')
682  ->where(
683  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, ‪Connection::PARAM_INT)),
684  $queryBuilder->expr()->eq('sys_language_uid', 0)
685  )
686  ->orderBy('uid');
687  if ($permsClause !== '') {
688  $queryBuilder->andWhere(‪QueryHelper::stripLogicalOperatorPrefix($permsClause));
689  }
690  $statement = $queryBuilder->executeQuery();
691  while ($row = $statement->fetchAssociative()) {
692  if ($begin <= 0) {
693  $theList .= ',' . $row['uid'];
694  }
695  if ($depth > 1) {
696  $theSubList = $this->‪getTreeList($row['uid'], $depth - 1, $begin - 1, $permsClause);
697  if (!empty($theList) && !empty($theSubList) && ($theSubList[0] !== ',')) {
698  $theList .= ',';
699  }
700  $theList .= $theSubList;
701  }
702  }
703  }
704 
705  return $theList;
706  }
707 
712  protected function ‪getQueryResultCode(string $type, array $dataRows, string ‪$table, ServerRequestInterface $request): array
713  {
714  $languageService = $this->‪getLanguageService();
715  $out = '';
716  $cPR = [];
717  $cPR['header'] = $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.section.result');
718  switch ($type) {
719  case 'count':
720  $cPR['content'] = '<p><strong>' . (int)$dataRows[0] . '</strong> ' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.type.count.resultsFound') . '</p>';
721  break;
722  case 'all':
723  $rowArr = [];
724  $dataRow = null;
725  foreach ($dataRows as $dataRow) {
726  $rowArr[] = $this->‪resultRowDisplay($dataRow, ‪$GLOBALS['TCA'][‪$table], ‪$table, $request);
727  }
728  if (!empty($rowArr)) {
729  $out .= '<div class="table-fit">';
730  $out .= '<table class="table table-striped table-hover">';
731  $out .= $this->‪resultRowTitles((array)$dataRow, ‪$GLOBALS['TCA'][‪$table]) . implode(LF, $rowArr);
732  $out .= '</table>';
733  $out .= '</div>';
734  } else {
735  $out .= '<p>' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.type.all.noResultsFound') . '</p>';
737  }
738 
739  $cPR['content'] = $out;
740  break;
741  case 'csv':
742  $rowArr = [];
743  $first = 1;
744  foreach ($dataRows as $dataRow) {
745  if ($first) {
746  $rowArr[] = $this->‪csvValues(array_keys($dataRow));
747  $first = 0;
748  }
749  $rowArr[] = $this->‪csvValues($dataRow, ',', '"', ‪$GLOBALS['TCA'][‪$table], ‪$table);
750  }
751  if (!empty($rowArr)) {
752  $out .= '<div class="form-group">';
753  $out .= '<textarea class="form-control" name="whatever" rows="20" class="font-monospace" style="width:100%">';
754  $out .= htmlspecialchars(implode(LF, $rowArr));
755  $out .= '</textarea>';
756  $out .= '</div>';
757  if (!$this->noDownloadB) {
758  $out .= '<button class="btn btn-default" type="submit" name="download_file" value="Click to download file">';
759  $out .= $this->iconFactory->getIcon('actions-file-csv-download', IconSize::SMALL)->render();
760  $out .= ' Click to download file';
761  $out .= '</button>';
762  }
763  // Downloads file:
764  // @todo: args. routing anyone?
765  if ($request->getParsedBody()['download_file'] ?? false) {
766  $filename = 'TYPO3_' . ‪$table . '_export_' . date('dmy-Hi') . '.csv';
767  $mimeType = 'application/octet-stream';
768  header('Content-Type: ' . $mimeType);
769  header('Content-Disposition: attachment; filename=' . $filename);
770  echo implode(CRLF, $rowArr);
771  die;
772  }
773  } else {
774  $out .= '<p>' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.type.all.noResultsFound') . '</p>';
776  }
777  $cPR['content'] = $out;
778  break;
779  case 'explain':
780  default:
781  foreach ($dataRows as $dataRow) {
782  $out .= ‪DebugUtility::viewArray($dataRow);
783  }
784  $cPR['content'] = $out;
785  }
786 
787  return $cPR;
788  }
789 
790  protected function ‪csvValues(array $row, string $delim = ',', string $quote = '"', array $conf = [], string ‪$table = ''): string
791  {
792  $valueArray = $row;
793  if (($this->MOD_SETTINGS['search_result_labels'] ?? false) && ‪$table) {
794  foreach ($valueArray as $key => $val) {
795  $valueArray[$key] = $this->‪getProcessedValueExtra($table, $key, (string)$val, $conf, ';');
796  }
797  }
798 
799  return ‪CsvUtility::csvValues($valueArray, $delim, $quote);
800  }
801 
805  protected function ‪resultRowTitles(?array $row, array $conf): string
806  {
807  $languageService = $this->‪getLanguageService();
808  $tableHeader = [];
809  // Start header row
810  $tableHeader[] = '<thead><tr>';
811  // Iterate over given columns
812  foreach ($row ?? [] as ‪$fieldName => $fieldValue) {
813  if (‪GeneralUtility::inList($this->MOD_SETTINGS['queryFields'] ?? '', ‪$fieldName)
814  || !($this->MOD_SETTINGS['queryFields'] ?? false)
815  && ‪$fieldName !== 'pid'
816  && ‪$fieldName !== 'deleted'
817  ) {
818  if ($this->MOD_SETTINGS['search_result_labels'] ?? false) {
819  $title = $languageService->sL(($conf['columns'][‪$fieldName]['label'] ?? false) ?: ‪$fieldName);
820  } else {
821  $title = $languageService->sL(‪$fieldName);
822  }
823  $tableHeader[] = '<th>' . htmlspecialchars($title) . '</th>';
824  }
825  }
826  // Add empty icon column
827  $tableHeader[] = '<th></th>';
828  // Close header row
829  $tableHeader[] = '</tr></thead>';
830 
831  return implode(LF, $tableHeader);
832  }
833 
834  protected function ‪resultRowDisplay(array $row, array $conf, string ‪$table, ServerRequestInterface $request): string
835  {
836  $languageService = $this->‪getLanguageService();
837  $out = '<tr>';
838  foreach ($row as ‪$fieldName => $fieldValue) {
839  if (‪GeneralUtility::inList($this->MOD_SETTINGS['queryFields'] ?? '', ‪$fieldName)
840  || !($this->MOD_SETTINGS['queryFields'] ?? false)
841  && ‪$fieldName !== 'pid'
842  && ‪$fieldName !== 'deleted'
843  ) {
844  if ($this->MOD_SETTINGS['search_result_labels'] ?? false) {
845  $fVnew = $this->‪getProcessedValueExtra($table, ‪$fieldName, (string)$fieldValue, $conf, '<br>');
846  } else {
847  $fVnew = htmlspecialchars((string)$fieldValue);
848  }
849  $out .= '<td>' . $fVnew . '</td>';
850  }
851  }
852  $out .= '<td class="col-control">';
853 
854  if (!($row['deleted'] ?? false)) {
855  // "Edit"
856  $editActionUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [
857  'edit' => [
858  ‪$table => [
859  $row['uid'] => 'edit',
860  ],
861  ],
862  'returnUrl' => $request->getAttribute('normalizedParams')->getRequestUri()
863  . ‪HttpUtility::buildQueryString(['SET' => $request->getParsedBody()['SET'] ?? []], '&'),
864  ]);
865  $editAction = '<a class="btn btn-default" href="' . htmlspecialchars($editActionUrl) . '"'
866  . ' title="' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:edit')) . '">'
867  . $this->iconFactory->getIcon('actions-open', IconSize::SMALL)->render()
868  . '</a>';
869  $out .= '<div class="btn-group" role="group">' . $editAction . '</div>';
870 
871  // "Info"
872  $infoActionTitle = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:showInfo'));
873  $infoAction = sprintf(
874  '<a class="btn btn-default" href="#" title="' . $infoActionTitle . '" data-dispatch-action="%s" data-dispatch-args-list="%s">%s</a>',
875  'TYPO3.InfoWindow.showItem',
876  htmlspecialchars(‪$table . ',' . $row['uid']),
877  $this->iconFactory->getIcon('actions-document-info', IconSize::SMALL)->render()
878  );
879  $out .= '<div class="btn-group" role="group">' . $infoAction . '</div>';
880  } else {
881  $undeleteActionUrl = (string)$this->uriBuilder->buildUriFromRoute('tce_db', [
882  'cmd' => [
883  ‪$table => [
884  $row['uid'] => [
885  'undelete' => 1,
886  ],
887  ],
888  ],
889  'redirect' => (string)$this->uriBuilder->buildUriFromRoute($this->moduleName),
890  ]);
891  $undeleteAction = '<a class="btn btn-default" href="' . htmlspecialchars($undeleteActionUrl) . '"'
892  . ' title="' . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_t3lib_fullsearch.xlf:undelete_only')) . '">'
893  . $this->iconFactory->getIcon('actions-edit-restore', IconSize::SMALL)->render()
894  . '</a>';
895  $out .= '<div class="btn-group" role="group">' . $undeleteAction . '</div>';
896  }
897  $out .= '</td></tr>';
898 
899  return $out;
900  }
901 
902  protected function ‪getProcessedValueExtra(string ‪$table, string ‪$fieldName, string $fieldValue, array $conf, string $splitString): string
903  {
904  $out = '';
905  ‪$fields = [];
906  $user = $this->‪getBackendUserAuthentication();
907  if ($user->user['lang'] ?? false) {
908  $locale = GeneralUtility::makeInstance(Locales::class)->createLocale($user->user['lang']);
909  } else {
910  $locale = new ‪Locale();
911  }
912  // Analysing the fields in the table.
913  if (is_array(‪$GLOBALS['TCA'][‪$table] ?? null)) {
914  $fC = ‪$GLOBALS['TCA'][‪$table]['columns'][‪$fieldName] ?? null;
915  ‪$fields = $fC['config'] ?? [];
916  ‪$fields['exclude'] = $fC['exclude'] ?? '';
917  if (is_array($fC) && ($fC['label'] ?? false)) {
918  ‪$fields['label'] = preg_replace('/:$/', '', trim($this->‪getLanguageService()->sL($fC['label'])));
919  switch (‪$fields['type']) {
920  case 'input':
921  if (‪GeneralUtility::inList(‪$fields['eval'] ?? '', 'year')) {
922  ‪$fields['type'] = 'number';
923  } else {
924  ‪$fields['type'] = 'text';
925  }
926  break;
927  case 'number':
928  // Empty on purpose, we have to keep the type "number".
929  // Falling back to the "default" case would set the type to "text"
930  break;
931  case 'datetime':
932  if (!in_array(‪$fields['dbType'] ?? '', ‪QueryHelper::getDateTimeTypes(), true)) {
933  ‪$fields['type'] = 'number';
934  } elseif (‪$fields['dbType'] === 'time') {
935  ‪$fields['type'] = 'time';
936  } else {
937  ‪$fields['type'] = 'date';
938  }
939  break;
940  case 'check':
941  if (!(‪$fields['items'] ?? false)) {
942  ‪$fields['type'] = 'boolean';
943  } else {
944  ‪$fields['type'] = 'binary';
945  }
946  break;
947  case 'radio':
948  ‪$fields['type'] = 'multiple';
949  break;
950  case 'select':
951  case 'category':
952  ‪$fields['type'] = 'multiple';
953  if (‪$fields['foreign_table'] ?? false) {
954  ‪$fields['type'] = 'relation';
955  }
956  if (‪$fields['special'] ?? false) {
957  ‪$fields['type'] = 'text';
958  }
959  break;
960  case 'group':
961  ‪$fields['type'] = 'relation';
962  break;
963  case 'user':
964  case 'flex':
965  case 'passthrough':
966  case 'none':
967  case 'text':
968  case 'email':
969  case 'link':
970  case 'password':
971  case 'color':
972  case 'json':
973  case 'uuid':
974  default:
975  ‪$fields['type'] = 'text';
976  }
977  } else {
978  ‪$fields['label'] = '[FIELD: ' . ‪$fieldName . ']';
979  switch (‪$fieldName) {
980  case 'pid':
981  ‪$fields['type'] = 'relation';
982  ‪$fields['allowed'] = 'pages';
983  break;
984  case 'tstamp':
985  case 'crdate':
986  ‪$fields['type'] = 'time';
987  break;
988  default:
989  ‪$fields['type'] = 'number';
990  }
991  }
992  }
993  switch (‪$fields['type']) {
994  case 'date':
995  if ($fieldValue != -1) {
996  $formatter = new ‪DateFormatter();
997  $out = $formatter->format((int)$fieldValue, 'SHORTDATE', $locale);
998  }
999  break;
1000  case 'time':
1001  if ($fieldValue != -1) {
1002  $formatter = new ‪DateFormatter();
1003  if ($splitString === '<br>') {
1004  $out = $formatter->format((int)$fieldValue, 'HH:mm\'' . $splitString . '\'dd-MM-yyyy', $locale);
1005  } else {
1006  $out = $formatter->format((int)$fieldValue, 'HH:mm dd-MM-yyyy', $locale);
1007  }
1008  }
1009  break;
1010  case 'multiple':
1011  case 'binary':
1012  case 'relation':
1013  $out = $this->makeValueList($fieldName, $fieldValue, $fields, $table, $splitString);
1014  break;
1015  case 'boolean':
1016  $out = $fieldValue ? 'True' : 'False';
1017  break;
1018  default:
1019  $out = htmlspecialchars($fieldValue);
1020  }
1021 
1022  return $out;
1023  }
1024 
1025  protected function makeValueList(string $fieldName, string $fieldValue, array $conf, string $table, string $splitString): string
1026  {
1027  $backendUserAuthentication = $this->getBackendUserAuthentication();
1028  $languageService = $this->getLanguageService();
1029  $from_table_Arr = [];
1030  $fieldSetup = $conf;
1031  $out = '';
1032  if ($fieldSetup['type'] === 'multiple') {
1033  foreach (($fieldSetup['items'] ?? []) as $val) {
1034  $value = $languageService->sL($val['label']);
1035  if (GeneralUtility::inList($fieldValue, $val['value']) || $fieldValue == $val['value']) {
1036  if ($out !== '') {
1037  $out .= $splitString;
1038  }
1039  $out .= htmlspecialchars($value);
1040  }
1041  }
1042  }
1043  if ($fieldSetup['type'] === 'binary') {
1044  foreach ($fieldSetup['items'] as $val) {
1045  $value = $languageService->sL($val['label']);
1046  if ($out !== '') {
1047  $out .= $splitString;
1048  }
1049  $out .= htmlspecialchars($value);
1050  }
1051  }
1052  if ($fieldSetup['type'] === 'relation') {
1053  $dontPrefixFirstTable = 0;
1054  $useTablePrefix = 0;
1055  foreach (($fieldSetup['items'] ?? []) as $val) {
1056  if (str_starts_with($val['label'], 'LLL:')) {
1057  $value = $languageService->sL($val['label']);
1058  } else {
1059  $value = $val['label'];
1060  }
1061  if (GeneralUtility::inList($fieldValue, $value) || $fieldValue == $value) {
1062  if ($out !== '') {
1063  $out .= $splitString;
1064  }
1065  $out .= htmlspecialchars($value);
1066  }
1067  }
1068  if (str_contains($fieldSetup['allowed'] ?? '', ',')) {
1069  $from_table_Arr = explode(',', $fieldSetup['allowed']);
1070  $useTablePrefix = 1;
1071  if (!$fieldSetup['prepend_tname']) {
1072  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1073  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1074  $statement = $queryBuilder->select($fieldName)->from($table)->executeQuery();
1075  while ($row = $statement->fetchAssociative()) {
1076  if (str_contains($row[$fieldName], ',')) {
1077  $checkContent = explode(',', $row[$fieldName]);
1078  foreach ($checkContent as $singleValue) {
1079  if (!str_contains($singleValue, '_')) {
1080  $dontPrefixFirstTable = 1;
1081  }
1082  }
1083  } else {
1084  $singleValue = $row[$fieldName];
1085  if ($singleValue !== '' && !str_contains($singleValue, '_')) {
1086  $dontPrefixFirstTable = 1;
1087  }
1088  }
1089  }
1090  }
1091  } else {
1092  $from_table_Arr[0] = $fieldSetup['allowed'] ?? null;
1093  }
1094  if (!empty($fieldSetup['prepend_tname'])) {
1095  $useTablePrefix = 1;
1096  }
1097  if (!empty($fieldSetup['foreign_table'])) {
1098  $from_table_Arr[0] = $fieldSetup['foreign_table'];
1099  }
1100  $counter = 0;
1101  $useSelectLabels = 0;
1102  $useAltSelectLabels = 0;
1103  $tablePrefix = '';
1104  $labelFieldSelect = [];
1105  foreach ($from_table_Arr as $from_table) {
1106  if ($useTablePrefix && !$dontPrefixFirstTable && $counter !== 1 || $counter === 1) {
1107  $tablePrefix = $from_table . '_';
1108  }
1109  $counter = 1;
1110  if (is_array($GLOBALS['TCA'][$from_table] ?? null)) {
1111  $labelField = $GLOBALS['TCA'][$from_table]['ctrl']['label'] ?? '';
1112  $altLabelField = $GLOBALS['TCA'][$from_table]['ctrl']['label_alt'] ?? '';
1113  if (is_array($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] ?? false)) {
1114  $items = $GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'];
1115  foreach ($items as $labelArray) {
1116  $labelFieldSelect[$labelArray['value']] = $languageService->sL($labelArray['label']);
1117  }
1118  $useSelectLabels = 1;
1119  }
1120  $altLabelFieldSelect = [];
1121  if (is_array($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] ?? false)) {
1122  $items = $GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'];
1123  foreach ($items as $altLabelArray) {
1124  $altLabelFieldSelect[$altLabelArray['value']] = $languageService->sL($altLabelArray['label']);
1125  }
1126  $useAltSelectLabels = 1;
1127  }
1128 
1129  if (empty($this->tableArray[$from_table])) {
1130  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
1131  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1132  $selectFields = ['uid', $labelField];
1133  if ($altLabelField) {
1134  $selectFields = array_merge($selectFields, GeneralUtility::trimExplode(',', $altLabelField, true));
1135  }
1136  $queryBuilder->select(...$selectFields)
1137  ->from($from_table)
1138  ->orderBy('uid');
1139  if (!$backendUserAuthentication->isAdmin()) {
1140  $webMounts = $backendUserAuthentication->returnWebmounts();
1141  $perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
1142  $webMountPageTree = '';
1143  $webMountPageTreePrefix = '';
1144  foreach ($webMounts as $webMount) {
1145  if ($webMountPageTree) {
1146  $webMountPageTreePrefix = ',';
1147  }
1148  $webMountPageTree .= $webMountPageTreePrefix
1149  . $this->getTreeList($webMount, 999, 0, $perms_clause);
1150  }
1151  if ($from_table === 'pages') {
1152  $queryBuilder->where(
1153  QueryHelper::stripLogicalOperatorPrefix($perms_clause),
1154  $queryBuilder->expr()->in(
1155  'uid',
1156  $queryBuilder->createNamedParameter(
1157  GeneralUtility::intExplode(',', $webMountPageTree),
1158  Connection::PARAM_INT_ARRAY
1159  )
1160  )
1161  );
1162  } else {
1163  $queryBuilder->where(
1164  $queryBuilder->expr()->in(
1165  'pid',
1166  $queryBuilder->createNamedParameter(
1167  GeneralUtility::intExplode(',', $webMountPageTree),
1168  Connection::PARAM_INT_ARRAY
1169  )
1170  )
1171  );
1172  }
1173  }
1174  $statement = $queryBuilder->executeQuery();
1175  $this->tableArray[$from_table] = [];
1176  while ($row = $statement->fetchAssociative()) {
1177  $this->tableArray[$from_table][] = $row;
1178  }
1179  }
1180 
1181  foreach ($this->tableArray[$from_table] as $key => $val) {
1182  $this->MOD_SETTINGS['labels_noprefix'] =
1183  ($this->MOD_SETTINGS['labels_noprefix'] ?? '') == 1
1184  ? 'on'
1185  : $this->MOD_SETTINGS['labels_noprefix'];
1186  $prefixString =
1187  $this->MOD_SETTINGS['labels_noprefix'] === 'on'
1188  ? ''
1189  : ' [' . $tablePrefix . $val['uid'] . '] ';
1190  if ($out !== '') {
1191  $out .= $splitString;
1192  }
1193  if (GeneralUtility::inList($fieldValue, $tablePrefix . $val['uid'])
1194  || $fieldValue == $tablePrefix . $val['uid']) {
1195  if ($useSelectLabels) {
1196  $out .= htmlspecialchars($prefixString . $labelFieldSelect[$val[$labelField]]);
1197  } elseif ($val[$labelField]) {
1198  $out .= htmlspecialchars($prefixString . $val[$labelField]);
1199  } elseif ($useAltSelectLabels) {
1200  $out .= htmlspecialchars($prefixString . $altLabelFieldSelect[$val[$altLabelField]]);
1201  } else {
1202  $out .= htmlspecialchars($prefixString . $val[$altLabelField]);
1203  }
1204  }
1205  }
1206  }
1207  }
1208  }
1209 
1210  return $out;
1211  }
1212 
1217  private function renderNoResultsFoundMessage(): void
1218  {
1219  $languageService = $this->getLanguageService();
1220  $flashMessageText = $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.flashMessage.noResultsFoundMessage');
1221  $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $flashMessageText, '', ContextualFeedbackSeverity::INFO);
1222  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1223  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1224  $defaultFlashMessageQueue->enqueue($flashMessage);
1225  }
1226 
1227  protected function getQuery(array $queryConfig, string $pad = ''): string
1228  {
1229  $qs = '';
1230  // Since we don't traverse the array using numeric keys in the upcoming whileloop make sure it's fresh and clean
1231  ksort($queryConfig);
1232  $first = true;
1233  foreach ($queryConfig as $key => $conf) {
1234  $conf = $this->convertIso8601DatetimeStringToUnixTimestamp($conf);
1235  switch ($conf['type']) {
1236  case 'newlevel':
1237  $qs .= LF . $pad . trim($conf['operator']) . ' (' . $this->getQuery(
1238  $queryConfig[$key]['nl'],
1239  $pad . ' '
1240  ) . LF . $pad . ')';
1241  break;
1242  default:
1243  $qs .= LF . $pad . $this->getQuerySingle($conf, $first);
1244  }
1245  $first = false;
1246  }
1247 
1248  return $qs;
1249  }
1250 
1251  protected function getQuerySingle(array $conf, bool $first): string
1252  {
1253  $comparison = (int)($conf['comparison'] ?? 0);
1254  $qs = '';
1255  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
1256  $prefix = $this->enablePrefix ? $this->table . '.' : '';
1257  if (!$first) {
1258  // Is it OK to insert the AND operator if none is set?
1259  $operator = strtoupper(trim($conf['operator'] ?? ''));
1260  if (!in_array($operator, ['AND', 'OR'], true)) {
1261  $operator = 'AND';
1262  }
1263  $qs .= $operator . ' ';
1264  }
1265  $qsTmp = str_replace('#FIELD#', $prefix . trim(substr($conf['type'], 6)), $this->compSQL[$comparison] ?? '');
1266  $inputVal = $this->cleanInputVal($conf);
1267  if ($comparison === 68 || $comparison === 69) {
1268  $inputVal = explode(',', (string)$inputVal);
1269  foreach ($inputVal as $key => $fileName) {
1270  $inputVal[$key] = $queryBuilder->quote($fileName);
1271  }
1272  $inputVal = implode(',', $inputVal);
1273  $qsTmp = str_replace('#VALUE#', $inputVal, $qsTmp);
1274  } elseif ($comparison === 162 || $comparison === 163) {
1275  $inputValArray = explode(',', (string)$inputVal);
1276  $inputVal = 0;
1277  foreach ($inputValArray as $fileName) {
1278  $inputVal += (int)$fileName;
1279  }
1280  $qsTmp = str_replace('#VALUE#', (string)$inputVal, $qsTmp);
1281  } else {
1282  if (is_array($inputVal)) {
1283  $inputVal = $inputVal[0];
1284  }
1285  // @todo This is weired, as it seems that it quotes the value as string and remove
1286  // quotings using the trim() method. Should be investagated/refactored.
1287  $qsTmp = str_replace('#VALUE#', trim($queryBuilder->quote((string)$inputVal), '\''), $qsTmp);
1288  }
1289  if ($comparison === 37 || $comparison === 36 || $comparison === 66 || $comparison === 67 || $comparison === 100 || $comparison === 101) {
1290  // between:
1291  $inputVal = $this->‪cleanInputVal($conf, '1');
1292  // @todo This is weired, as it seems that it quotes the value as string and remove
1293  // quotings using the trim() method. Should be investagated/refactored.
1294  $qsTmp = str_replace('#VALUE1#', trim($queryBuilder->quote((string)$inputVal), '\''), $qsTmp);
1295  }
1296  $qs .= trim((string)$qsTmp);
1297 
1298  return $qs;
1299  }
1300 
1304  protected function ‪cleanInputVal(array $conf, string $suffix = '')
1305  {
1306  $comparison = (int)($conf['comparison'] ?? 0);
1307  $var = $conf['inputValue' . $suffix] ?? '';
1308  if ($comparison >> 5 === 0 || ($comparison === 32 || $comparison === 33 || $comparison === 64 || $comparison === 65 || $comparison === 66 || $comparison === 67 || $comparison === 96 || $comparison === 97)) {
1309  $inputVal = $var;
1310  } elseif ($comparison === 39 || $comparison === 38) {
1311  // in list:
1312  $inputVal = implode(',', ‪GeneralUtility::intExplode(',', (string)$var));
1313  } elseif ($comparison === 68 || $comparison === 69 || $comparison === 162 || $comparison === 163) {
1314  // in list:
1315  if (is_array($var)) {
1316  $inputVal = implode(',', $var);
1317  } elseif ($var) {
1318  $inputVal = $var;
1319  } else {
1320  $inputVal = 0;
1321  }
1322  } elseif (!is_array($var) && strtotime((string)$var)) {
1323  $inputVal = $var;
1324  } elseif (!is_array($var) && ‪MathUtility::canBeInterpretedAsInteger($var)) {
1325  $inputVal = (int)$var;
1326  } else {
1327  // TODO: Six eyes looked at this code and nobody understood completely what is going on here and why we
1328  // fallback to float casting, the whole class smells like it needs a refactoring.
1329  $inputVal = (float)$var;
1330  }
1331 
1332  return $inputVal;
1333  }
1334 
1335  protected function ‪convertIso8601DatetimeStringToUnixTimestamp(array $conf): array
1336  {
1337  if ($this->‪isDateOfIso8601Format($conf['inputValue'] ?? '')) {
1338  $conf['inputValue'] = strtotime($conf['inputValue']);
1339  if ($this->‪isDateOfIso8601Format($conf['inputValue1'] ?? '')) {
1340  $conf['inputValue1'] = strtotime($conf['inputValue1']);
1341  }
1342  }
1343 
1344  return $conf;
1345  }
1346 
1350  protected function ‪isDateOfIso8601Format(mixed $date): bool
1351  {
1352  if (!is_int($date) && !is_string($date)) {
1353  return false;
1354  }
1355  $format = 'Y-m-d\\TH:i:s\\Z';
1356  $formattedDate = \DateTime::createFromFormat($format, (string)$date);
1357 
1358  return $formattedDate && $formattedDate->format($format) === $date;
1359  }
1360 
1361  protected function ‪makeSelectorTable(array $modSettings, ServerRequestInterface $request, string $enableList = 'table,fields,query,group,order,limit'): string
1362  {
1363  $languageService = $this->‪getLanguageService();
1364  $out = [];
1365  $enableArr = explode(',', $enableList);
1366  $userTsConfig = $this->‪getBackendUserAuthentication()->getTSConfig();
1367 
1368  // Make output
1369  if (in_array('table', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableSelectATable'] ?? false)) {
1370  $out[] = '<div class="row">';
1371  $out[] = '<div class="form-group">';
1372  $out[] = '<label class="form-label" for="select-table">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.queryTable.label') . '</label>';
1373  $out[] = $this->‪mkTableSelect('SET[queryTable]', $this->table);
1374  $out[] = '</div>';
1375  $out[] = '</div>';
1376  }
1377  if ($this->table) {
1378  // Init fields:
1379  $this->‪setAndCleanUpExternalLists('queryFields', $modSettings['queryFields'] ?? '', 'uid,' . $this->‪getLabelCol());
1380  $this->‪setAndCleanUpExternalLists('queryGroup', $modSettings['queryGroup'] ?? '');
1381  $this->‪setAndCleanUpExternalLists('queryOrder', ($modSettings['queryOrder'] ?? '') . ',' . ($modSettings['queryOrder2'] ?? ''));
1382  // Limit:
1383  $this->extFieldLists['queryLimit'] = $modSettings['queryLimit'] ?? '';
1384  if (!$this->extFieldLists['queryLimit']) {
1385  $this->extFieldLists['queryLimit'] = 100;
1386  }
1387  $parts = ‪GeneralUtility::intExplode(',', (string)$this->extFieldLists['queryLimit']);
1388  $limitBegin = 0;
1389  $limitLength = (int)($this->extFieldLists['queryLimit']);
1390  if ($parts[1] ?? null) {
1391  $limitBegin = (int)$parts[0];
1392  $limitLength = (int)$parts[1];
1393  }
1394  $this->extFieldLists['queryLimit'] = implode(',', array_slice($parts, 0, 2));
1395  // Insert Descending parts
1396  if ($this->extFieldLists['queryOrder']) {
1397  $descParts = explode(',', ($modSettings['queryOrderDesc'] ?? '') . ',' . ($modSettings['queryOrder2Desc'] ?? ''));
1398  $orderParts = explode(',', $this->extFieldLists['queryOrder']);
1399  $reList = [];
1400  foreach ($orderParts as $kk => $vv) {
1401  $reList[] = $vv . ($descParts[$kk] ? ' DESC' : '');
1402  }
1403  $this->extFieldLists['queryOrder_SQL'] = implode(',', $reList);
1404  }
1405  // Query Generator:
1406  $this->‪procesData($request, ($modSettings['queryConfig'] ?? '') ? unserialize((string)$modSettings['queryConfig'], ['allowed_classes' => false]) : []);
1407  $this->queryConfig = $this->‪cleanUpQueryConfig($this->queryConfig);
1408  $this->enableQueryParts = (bool)($modSettings['search_query_smallparts'] ?? false);
1409  $codeArr = $this->‪getFormElements();
1410  $queryCode = $this->‪printCodeArray($codeArr);
1411  if (in_array('fields', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableSelectFields'] ?? false)) {
1412  $out[] = '<div class="row">';
1413  $out[] = '<div class="form-group">';
1414  $out[] = '<label class="form-label" for="select-queryFields">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.queryFields.label') . '</label>';
1415  $out[] = $this->‪mkFieldToInputSelect('SET[queryFields]', $this->extFieldLists['queryFields']);
1416  $out[] = '</div>';
1417  $out[] = '</div>';
1418  }
1419  if (in_array('query', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableMakeQuery'] ?? false)) {
1420  $out[] = '<div class="row">';
1421  $out[] = '<div class="form-group">';
1422  $out[] = '<label class="form-label">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.query.label') . '</label>';
1423  $out[] = $queryCode;
1424  $out[] = '</div>';
1425  $out[] = '</div>';
1426  }
1427 
1428  // 'Order by' and 'Group by':
1429  if (in_array('group', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableGroupBy'] ?? false) && in_array('order', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableOrderBy'] ?? false)) {
1430  $out[] = '<div class="row">';
1431  }
1432  if (in_array('group', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableGroupBy'] ?? false)) {
1433  $out[] = '<div class="form-group col-sm-6">';
1434  $out[] = '<label class="form-label" for="SET[queryGroup]">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.groupBy.label') . '</label>';
1435  $out[] = $this->‪mkTypeSelect('SET[queryGroup]', $this->extFieldLists['queryGroup'], '');
1436  $out[] = '</div>';
1437  }
1438  if (in_array('order', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableOrderBy'] ?? false)) {
1439  $orderByArr = explode(',', $this->extFieldLists['queryOrder']);
1440  $orderBy = [];
1441  $orderBy[] = '<div class="form-group">';
1442  $orderBy[] = '<div class="input-group">';
1443  $orderBy[] = $this->‪mkTypeSelect('SET[queryOrder]', $orderByArr[0], '');
1444  $orderBy[] = '<div class="input-group-text">';
1445  $orderBy[] = '<div class="form-check form-check-type-toggle">';
1446  $orderBy[] = $this->‪getFuncCheck('SET[queryOrderDesc]', $modSettings['queryOrderDesc'] ?? '', $request, 'id="checkQueryOrderDesc"');
1447  $orderBy[] = '<label class="form-check-label" for="checkQueryOrderDesc">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.orderBy.descending') . '</label>';
1448  $orderBy[] = '</div>';
1449  $orderBy[] = '</div>';
1450  $orderBy[] = '</div>';
1451  $orderBy[] = '</div>';
1452 
1453  if ($orderByArr[0]) {
1454  $orderBy[] = '<div class="form-group">';
1455  $orderBy[] = '<div class="input-group">';
1456  $orderBy[] = $this->‪mkTypeSelect('SET[queryOrder2]', $orderByArr[1] ?? '', '');
1457  $orderBy[] = '<div class="input-group-text">';
1458  $orderBy[] = '<div class="form-check form-check-type-toggle">';
1459  $orderBy[] = $this->‪getFuncCheck('SET[queryOrder2Desc]', $modSettings['queryOrder2Desc'] ?? false, $request, 'id="checkQueryOrder2Desc"');
1460  $orderBy[] = '<label class="form-check-label" for="checkQueryOrder2Desc">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.orderBy.descending') . '</label>';
1461  $orderBy[] = '</div>';
1462  $orderBy[] = '</div>';
1463  $orderBy[] = '</div>';
1464  $orderBy[] = '</div>';
1465  }
1466 
1467  $out[] = '<div class="form-group col-sm-6">';
1468  $out[] = '<label class="form-label">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.orderBy.label') . '</label>';
1469  $out[] = implode(LF, $orderBy);
1470  $out[] = '</div>';
1471  }
1472  if (in_array('group', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableGroupBy'] ?? false) && in_array('order', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableOrderBy'] ?? false)) {
1473  $out[] = '</div>';
1474  }
1475 
1476  if (in_array('limit', $enableArr) && !($userTsConfig['mod.']['dbint.']['disableLimit'] ?? false)) {
1477  $limit = [];
1478  $limit[] = '<div class="input-group">';
1479  $limit[] = $this->‪updateIcon();
1480  $limit[] = '<input type="text" class="form-control" value="' . htmlspecialchars($this->extFieldLists['queryLimit']) . '" name="SET[queryLimit]" id="queryLimit">';
1481  $limit[] = '</div>';
1482 
1483  $prevLimit = $limitBegin - $limitLength < 0 ? 0 : $limitBegin - $limitLength;
1484  $prevButton = '';
1485  $nextButton = '';
1486 
1487  if ($limitBegin) {
1488  $prevButton = '<input type="button" class="btn btn-default" value="previous ' . htmlspecialchars((string)$limitLength) . '" data-value="' . htmlspecialchars($prevLimit . ',' . $limitLength) . '">';
1489  }
1490  if (!$limitLength) {
1491  $limitLength = 100;
1492  }
1493 
1494  $nextLimit = $limitBegin + $limitLength;
1495  if ($nextLimit < 0) {
1496  $nextLimit = 0;
1497  }
1498  if ($nextLimit) {
1499  $nextButton = '<input type="button" class="btn btn-default" value="next ' . htmlspecialchars((string)$limitLength) . '" data-value="' . htmlspecialchars($nextLimit . ',' . $limitLength) . '">';
1500  }
1501 
1502  $out[] = '<div class="row">';
1503  $out[] = ' <div class="form-group">';
1504  $out[] = ' <label for="queryLimit" class="form-label">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.limit.label') . '</label>';
1505  $out[] = ' <div class="form-row">';
1506  $out[] = ' <div class="form-group">';
1507  $out[] = implode(LF, $limit);
1508  $out[] = ' </div>';
1509  $out[] = ' <div class="form-group">';
1510  $out[] = ' <div class="btn-group t3js-limit-submit">';
1511  $out[] = $prevButton;
1512  $out[] = $nextButton;
1513  $out[] = ' </div>';
1514  $out[] = ' </div>';
1515  $out[] = ' <div class="form-group">';
1516  $out[] = ' <div class="btn-group t3js-limit-submit">';
1517  $out[] = ' <input type="button" class="btn btn-default" data-value="10" value="10">';
1518  $out[] = ' <input type="button" class="btn btn-default" data-value="20" value="20">';
1519  $out[] = ' <input type="button" class="btn btn-default" data-value="50" value="50">';
1520  $out[] = ' <input type="button" class="btn btn-default" data-value="100" value="100">';
1521  $out[] = ' </div>';
1522  $out[] = ' </div>';
1523  $out[] = ' </div>';
1524  $out[] = ' </div>';
1525  $out[] = '</div>';
1526  }
1527  }
1528 
1529  return implode(LF, $out);
1530  }
1531 
1532  protected function ‪cleanUpQueryConfig(array ‪$queryConfig): array
1533  {
1534  // Since we don't traverse the array using numeric keys in the upcoming while-loop make sure it's fresh and clean before displaying
1535  if (!empty(‪$queryConfig)) {
1536  ksort(‪$queryConfig);
1537  } else {
1538  ‪$queryConfig = [['type' => 'FIELD_']];
1539  }
1540  // Traverse:
1541  foreach (‪$queryConfig as $key => $conf) {
1542  ‪$fieldName = '';
1543  if (str_starts_with(($conf['type'] ?? ''), 'FIELD_')) {
1544  ‪$fieldName = substr($conf['type'], 6);
1545  $fieldType = $this->fields[‪$fieldName]['type'] ?? '';
1546  } elseif (($conf['type'] ?? '') === 'newlevel') {
1547  $fieldType = $conf['type'];
1548  } else {
1549  $fieldType = 'ignore';
1550  }
1551  switch ($fieldType) {
1552  case 'newlevel':
1553  if (!isset($conf['nl'])) {
1554  ‪$queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
1555  }
1556  ‪$queryConfig[$key]['nl'] = $this->‪cleanUpQueryConfig($queryConfig[$key]['nl']);
1557  break;
1558  case 'userdef':
1559  break;
1560  case 'ignore':
1561  default:
1562  $verifiedName = $this->‪verifyType($fieldName);
1563  ‪$queryConfig[$key]['type'] = 'FIELD_' . $this->‪verifyType($verifiedName);
1564  if ((int)($conf['comparison'] ?? 0) >> 5 !== (int)($this->comp_offsets[$fieldType] ?? 0)) {
1565  $conf['comparison'] = (int)($this->comp_offsets[$fieldType] ?? 0) << 5;
1566  }
1567  ‪$queryConfig[$key]['comparison'] = $this->‪verifyComparison($conf['comparison'] ?? 0 ? (int)$conf['comparison'] : 0, (bool)($conf['negate'] ?? null));
1568  ‪$queryConfig[$key]['inputValue'] = $this->‪cleanInputVal($queryConfig[$key]);
1569  ‪$queryConfig[$key]['inputValue1'] = $this->‪cleanInputVal($queryConfig[$key], '1');
1570  }
1571  }
1572 
1573  return ‪$queryConfig;
1574  }
1575 
1576  protected function ‪verifyType(string ‪$fieldName): string
1577  {
1578  $first = '';
1579  foreach ($this->fields as $key => $value) {
1580  if (!$first) {
1581  $first = $key;
1582  }
1583  if ($key === ‪$fieldName) {
1584  return $key;
1585  }
1586  }
1587 
1588  return $first;
1589  }
1590 
1591  protected function ‪verifyComparison(int $comparison, bool $neg): int
1592  {
1593  $compOffSet = $comparison >> 5;
1594  $first = -1;
1595  for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
1596  if ($first === -1) {
1597  $first = $i;
1598  }
1599  if ($i >> 1 === $comparison >> 1) {
1600  return $i;
1601  }
1602  }
1603 
1604  return $first;
1605  }
1606 
1607  protected function ‪getFormElements(int $subLevel = 0, string|array ‪$queryConfig = null, string $parent = ''): array
1608  {
1609  $codeArr = [];
1610  if (!is_array(‪$queryConfig)) {
1612  }
1613  $c = 0;
1614  $arrCount = 0;
1615  $loopCount = 0;
1616  foreach (‪$queryConfig as $key => $conf) {
1617  ‪$fieldName = '';
1618  $subscript = $parent . '[' . $key . ']';
1619  $lineHTML = [];
1620  $lineHTML[] = $this->‪mkOperatorSelect($this->name . $subscript, ($conf['operator'] ?? ''), (bool)$c, ($conf['type'] ?? '') !== 'FIELD_');
1621  if (str_starts_with(($conf['type'] ?? ''), 'FIELD_')) {
1622  ‪$fieldName = substr($conf['type'], 6);
1623  $this->fieldName = ‪$fieldName;
1624  $fieldType = $this->fields[‪$fieldName]['type'] ?? '';
1625  if ((int)($conf['comparison'] ?? 0) >> 5 !== (int)($this->comp_offsets[$fieldType] ?? 0)) {
1626  $conf['comparison'] = (int)($this->comp_offsets[$fieldType] ?? 0) << 5;
1627  }
1628  //nasty nasty...
1629  //make sure queryConfig contains _actual_ comparevalue.
1630  //mkCompSelect don't care, but getQuery does.
1631  ‪$queryConfig[$key]['comparison'] += isset($conf['negate']) - $conf['comparison'] % 2;
1632  } elseif (($conf['type'] ?? '') === 'newlevel') {
1633  $fieldType = $conf['type'];
1634  } else {
1635  $fieldType = 'ignore';
1636  }
1637  $fieldPrefix = htmlspecialchars($this->name . $subscript);
1638  switch ($fieldType) {
1639  case 'ignore':
1640  break;
1641  case 'newlevel':
1642  if (!is_array(‪$queryConfig[$key]['nl'] ?? null)) {
1643  ‪$queryConfig[$key]['nl'] = [];
1644  ‪$queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
1645  }
1646  $lineHTML[] = '<input type="hidden" name="' . $fieldPrefix . '[type]" value="newlevel">';
1647  $codeArr[$arrCount]['sub'] = $this->‪getFormElements($subLevel + 1, ‪$queryConfig[$key]['nl'], $subscript . '[nl]');
1648  break;
1649  case 'userdef':
1650  $lineHTML[] = '';
1651  break;
1652  case 'date':
1653  $lineHTML[] = '<div class="form-row">';
1654  $lineHTML[] = $this->‪makeComparisonSelector($subscript, ‪$fieldName, $conf);
1655  if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
1656  // between
1657  $lineHTML[] = $this->‪getDateTimePickerField($fieldPrefix . '[inputValue]', (string)$conf['inputValue'], 'date');
1658  $lineHTML[] = $this->‪getDateTimePickerField($fieldPrefix . '[inputValue1]', (string)$conf['inputValue1'], 'date');
1659  } else {
1660  $lineHTML[] = $this->‪getDateTimePickerField($fieldPrefix . '[inputValue]', (string)$conf['inputValue'], 'date');
1661  }
1662  $lineHTML[] = '</div>';
1663  break;
1664  case 'time':
1665  $lineHTML[] = '<div class="form-row">';
1666  $lineHTML[] = $this->‪makeComparisonSelector($subscript, ‪$fieldName, $conf);
1667  if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
1668  // between:
1669  $lineHTML[] = $this->‪getDateTimePickerField($fieldPrefix . '[inputValue]', (string)$conf['inputValue'], 'datetime');
1670  $lineHTML[] = $this->‪getDateTimePickerField($fieldPrefix . '[inputValue1]', (string)$conf['inputValue1'], 'datetime');
1671  } else {
1672  $lineHTML[] = $this->‪getDateTimePickerField($fieldPrefix . '[inputValue]', (string)$conf['inputValue'], 'datetime');
1673  }
1674  $lineHTML[] = '</div>';
1675  break;
1676  case 'multiple':
1677  case 'binary':
1678  case 'relation':
1679  $lineHTML[] = '<div class="form-row">';
1680  $lineHTML[] = $this->‪makeComparisonSelector($subscript, ‪$fieldName, $conf);
1681  $lineHTML[] = '<div class="form-group">';
1682  if ($conf['comparison'] === 68 || $conf['comparison'] === 69 || $conf['comparison'] === 162 || $conf['comparison'] === 163) {
1683  $lineHTML[] = '<select class="form-select" name="' . $fieldPrefix . '[inputValue][]" multiple="multiple">';
1684  } elseif ($conf['comparison'] === 66 || $conf['comparison'] === 67) {
1685  if (is_array($conf['inputValue'])) {
1686  $conf['inputValue'] = implode(',', $conf['inputValue']);
1687  }
1688  $lineHTML[] = '<input class="form-control form-control-clearable t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue'] ?? '') . '" name="' . $fieldPrefix . '[inputValue]">';
1689  } elseif ($conf['comparison'] === 64) {
1690  if (is_array($conf['inputValue'])) {
1691  $conf['inputValue'] = $conf['inputValue'][0];
1692  }
1693  $lineHTML[] = '<select class="form-select t3js-submit-change" name="' . $fieldPrefix . '[inputValue]">';
1694  } else {
1695  $lineHTML[] = '<select class="form-select t3js-submit-change" name="' . $fieldPrefix . '[inputValue]">';
1696  }
1697  if ($conf['comparison'] != 66 && $conf['comparison'] != 67) {
1698  $lineHTML[] = $this->‪makeOptionList($fieldName, $conf, $this->table);
1699  $lineHTML[] = '</select>';
1700  }
1701  $lineHTML[] = '</div>';
1702  $lineHTML[] = '</div>';
1703  break;
1704  case 'boolean':
1705  $lineHTML[] = '<div class="form-row">';
1706  $lineHTML[] = $this->‪makeComparisonSelector($subscript, ‪$fieldName, $conf);
1707  $lineHTML[] = '<input type="hidden" value="1" name="' . $fieldPrefix . '[inputValue]">';
1708  $lineHTML[] = '</div>';
1709  break;
1710  default:
1711  $lineHTML[] = '<div class="form-row">';
1712  $lineHTML[] = $this->‪makeComparisonSelector($subscript, ‪$fieldName, $conf);
1713  if ($conf['comparison'] === 37 || $conf['comparison'] === 36) {
1714  // between:
1715  $lineHTML[] = '<div class="form-group">';
1716  $lineHTML[] = ' <input class="form-control form-control-clearable t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue'] ?? '') . '" name="' . $fieldPrefix . '[inputValue]">';
1717  $lineHTML[] = '</div>';
1718  $lineHTML[] = '<div class="form-group">';
1719  $lineHTML[] = ' <input class="form-control form-control-clearable t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue1'] ?? '') . '" name="' . $fieldPrefix . '[inputValue1]">';
1720  $lineHTML[] = '</div>';
1721  } else {
1722  $lineHTML[] = '<div class="form-group">';
1723  $lineHTML[] = ' <input class="form-control form-control-clearable t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue'] ?? '') . '" name="' . $fieldPrefix . '[inputValue]">';
1724  $lineHTML[] = '</div>';
1725  }
1726  $lineHTML[] = '</div>';
1727  }
1728  if ($fieldType !== 'ignore') {
1729  $lineHTML[] = '<div class="form-row">';
1730  $lineHTML[] = '<div class="btn-group">';
1731  $lineHTML[] = $this->‪updateIcon();
1732  if ($loopCount) {
1733  $lineHTML[] = ''
1734  . '<button class="btn btn-default" title="Remove condition" name="qG_del' . htmlspecialchars($subscript) . '">'
1735  . $this->iconFactory->getIcon('actions-delete', IconSize::SMALL)->render()
1736  . '</button>';
1737  }
1738  $lineHTML[] = ''
1739  . '<button class="btn btn-default" title="Add condition" name="qG_ins' . htmlspecialchars($subscript) . '">'
1740  . $this->iconFactory->getIcon('actions-plus', IconSize::SMALL)->render()
1741  . '</button>';
1742  if ($c != 0) {
1743  $lineHTML[] = ''
1744  . '<button class="btn btn-default" title="Move up" name="qG_up' . htmlspecialchars($subscript) . '">'
1745  . $this->iconFactory->getIcon('actions-chevron-up', IconSize::SMALL)->render()
1746  . '</button>';
1747  }
1748  if ($c != 0 && $fieldType !== 'newlevel') {
1749  $lineHTML[] = ''
1750  . '<button class="btn btn-default" title="New level" name="qG_nl' . htmlspecialchars($subscript) . '">'
1751  . $this->iconFactory->getIcon('actions-chevron-right', IconSize::SMALL)->render()
1752  . '</button>';
1753  }
1754  if ($fieldType === 'newlevel') {
1755  $lineHTML[] = ''
1756  . '<button class="btn btn-default" title="Collapse new level" name="qG_remnl' . htmlspecialchars($subscript) . '">'
1757  . $this->iconFactory->getIcon('actions-chevron-left', IconSize::SMALL)->render()
1758  . '</button>';
1759  }
1760  $lineHTML[] = '</div>';
1761  $lineHTML[] = '</div>';
1762  $codeArr[$arrCount]['html'] = implode(LF, $lineHTML);
1763  $codeArr[$arrCount]['query'] = $this->‪getQuerySingle($conf, $c === 0);
1764  $arrCount++;
1765  $c++;
1766  }
1767  $loopCount = 1;
1768  }
1769  $this->queryConfig = ‪$queryConfig;
1770 
1771  return $codeArr;
1772  }
1773 
1774  protected function ‪getDateTimePickerField(string ‪$name, string $timestamp, string $type): string
1775  {
1776  $value = strtotime($timestamp) ? date(‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' ' . ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)strtotime($timestamp)) : '';
1777  $id = ‪StringUtility::getUniqueId('dt_');
1778  $html = [];
1779  $html[] = '<div class="form-group">';
1780  $html[] = ' <div class="input-group" id="' . $id . '-wrapper">';
1781  $html[] = ' <input data-formengine-input-name="' . htmlspecialchars(‪$name) . '" value="' . $value . '" class="form-control form-control-clearable t3js-datetimepicker t3js-clearable" data-date-type="' . htmlspecialchars($type) . '" type="text" id="' . $id . '">';
1782  $html[] = ' <input name="' . htmlspecialchars(‪$name) . '" value="' . htmlspecialchars($timestamp) . '" type="hidden">';
1783  $html[] = ' <button class="btn btn-default" type="button" data-global-event="click" data-action-focus="#' . $id . '">';
1784  $html[] = $this->iconFactory->getIcon('actions-calendar-alternative', IconSize::SMALL)->render();
1785  $html[] = ' </button>';
1786  $html[] = ' </div>';
1787  $html[] = '</div>';
1788 
1789  return implode(LF, $html);
1790  }
1791 
1792  protected function ‪makeOptionList(string ‪$fieldName, array $conf, string ‪$table): string
1793  {
1794  $backendUserAuthentication = $this->‪getBackendUserAuthentication();
1795  $from_table_Arr = [];
1796  $out = [];
1797  $fieldSetup = $this->fields[‪$fieldName];
1798  $languageService = $this->‪getLanguageService();
1799  if ($fieldSetup['type'] === 'multiple') {
1800  $optGroupOpen = false;
1801  foreach (($fieldSetup['items'] ?? []) as $val) {
1802  $value = $languageService->sL($val['label']);
1803  if ($val['value'] === '--div--') {
1804  if ($optGroupOpen) {
1805  $out[] = '</optgroup>';
1806  }
1807  $optGroupOpen = true;
1808  $out[] = '<optgroup label="' . htmlspecialchars($value) . '">';
1809  } elseif (‪GeneralUtility::inList($conf['inputValue'], (string)$val['value'])) {
1810  $out[] = '<option value="' . htmlspecialchars((string)$val['value']) . '" selected>' . htmlspecialchars($value) . '</option>';
1811  } else {
1812  $out[] = '<option value="' . htmlspecialchars((string)$val['value']) . '">' . htmlspecialchars($value) . '</option>';
1813  }
1814  }
1815  if ($optGroupOpen) {
1816  $out[] = '</optgroup>';
1817  }
1818  }
1819  if ($fieldSetup['type'] === 'binary') {
1820  foreach ($fieldSetup['items'] as $key => $val) {
1821  $value = $languageService->sL($val['label']);
1822  if (‪GeneralUtility::inList($conf['inputValue'], (string)(2 ** $key))) {
1823  $out[] = '<option value="' . 2 ** $key . '" selected>' . htmlspecialchars($value) . '</option>';
1824  } else {
1825  $out[] = '<option value="' . 2 ** $key . '">' . htmlspecialchars($value) . '</option>';
1826  }
1827  }
1828  }
1829  if ($fieldSetup['type'] === 'relation') {
1830  $useTablePrefix = 0;
1831  $dontPrefixFirstTable = 0;
1832  foreach (($fieldSetup['items'] ?? []) as $val) {
1833  $value = $languageService->sL($val['label']);
1834  if (‪GeneralUtility::inList($conf['inputValue'], (string)$val['value'])) {
1835  $out[] = '<option value="' . htmlspecialchars((string)$val['value']) . '" selected>' . htmlspecialchars($value) . '</option>';
1836  } else {
1837  $out[] = '<option value="' . htmlspecialchars((string)$val['value']) . '">' . htmlspecialchars($value) . '</option>';
1838  }
1839  }
1840  $allowedFields = $fieldSetup['allowed'] ?? '';
1841  if (str_contains($allowedFields, ',')) {
1842  $from_table_Arr = explode(',', $allowedFields);
1843  $useTablePrefix = 1;
1844  if (!$fieldSetup['prepend_tname']) {
1845  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(‪$table);
1846  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1847  $statement = $queryBuilder->select(‪$fieldName)
1848  ->from(‪$table)
1849  ->executeQuery();
1850  while ($row = $statement->fetchAssociative()) {
1851  if (str_contains($row[‪$fieldName], ',')) {
1852  $checkContent = explode(',', $row[‪$fieldName]);
1853  foreach ($checkContent as $singleValue) {
1854  if (!str_contains($singleValue, '_')) {
1855  $dontPrefixFirstTable = 1;
1856  }
1857  }
1858  } else {
1859  $singleValue = $row[‪$fieldName];
1860  if ($singleValue !== '' && !str_contains($singleValue, '_')) {
1861  $dontPrefixFirstTable = 1;
1862  }
1863  }
1864  }
1865  }
1866  } else {
1867  $from_table_Arr[0] = $allowedFields;
1868  }
1869  if (!empty($fieldSetup['prepend_tname'])) {
1870  $useTablePrefix = 1;
1871  }
1872  if (!empty($fieldSetup['foreign_table'])) {
1873  $from_table_Arr[0] = $fieldSetup['foreign_table'];
1874  }
1875  $counter = 0;
1876  $tablePrefix = '';
1877  $outArray = [];
1878  $labelFieldSelect = [];
1879  foreach ($from_table_Arr as $from_table) {
1880  $useSelectLabels = false;
1881  $useAltSelectLabels = false;
1882  if ($useTablePrefix && !$dontPrefixFirstTable && $counter !== 1 || $counter === 1) {
1883  $tablePrefix = $from_table . '_';
1884  }
1885  $counter = 1;
1886  if (is_array(‪$GLOBALS['TCA'][$from_table])) {
1887  $labelField = ‪$GLOBALS['TCA'][$from_table]['ctrl']['label'] ?? '';
1888  $altLabelField = ‪$GLOBALS['TCA'][$from_table]['ctrl']['label_alt'] ?? '';
1889  if (‪$GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] ?? false) {
1890  foreach (‪$GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] as $labelArray) {
1891  $labelFieldSelect[$labelArray[1]] = $languageService->sL($labelArray[0]);
1892  }
1893  $useSelectLabels = true;
1894  }
1895  $altLabelFieldSelect = [];
1896  if (‪$GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] ?? false) {
1897  foreach (‪$GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] as $altLabelArray) {
1898  $altLabelFieldSelect[$altLabelArray[1]] = $languageService->sL($altLabelArray[0]);
1899  }
1900  $useAltSelectLabels = true;
1901  }
1902 
1903  if (!($this->tableArray[$from_table] ?? false)) {
1904  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
1905  $queryBuilder->getRestrictions()->removeAll();
1906  if (empty($this->MOD_SETTINGS['show_deleted'])) {
1907  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1908  }
1909  $selectFields = ['uid', $labelField];
1910  if ($altLabelField) {
1911  $selectFields = array_merge($selectFields, ‪GeneralUtility::trimExplode(',', $altLabelField, true));
1912  }
1913  $queryBuilder->select(...$selectFields)
1914  ->from($from_table)
1915  ->orderBy('uid');
1916  if (!$backendUserAuthentication->isAdmin()) {
1917  $webMounts = $backendUserAuthentication->returnWebmounts();
1918  $perms_clause = $backendUserAuthentication->getPagePermsClause(‪Permission::PAGE_SHOW);
1919  $webMountPageTree = '';
1920  $webMountPageTreePrefix = '';
1921  foreach ($webMounts as $webMount) {
1922  if ($webMountPageTree) {
1923  $webMountPageTreePrefix = ',';
1924  }
1925  $webMountPageTree .= $webMountPageTreePrefix
1926  . $this->‪getTreeList($webMount, 999, 0, $perms_clause);
1927  }
1928  if ($from_table === 'pages') {
1929  $queryBuilder->where(
1931  $queryBuilder->expr()->in(
1932  'uid',
1933  $queryBuilder->createNamedParameter(
1934  ‪GeneralUtility::intExplode(',', $webMountPageTree),
1936  )
1937  )
1938  );
1939  } else {
1940  $queryBuilder->where(
1941  $queryBuilder->expr()->in(
1942  'pid',
1943  $queryBuilder->createNamedParameter(
1944  ‪GeneralUtility::intExplode(',', $webMountPageTree),
1946  )
1947  )
1948  );
1949  }
1950  }
1951  $statement = $queryBuilder->executeQuery();
1952  $this->tableArray[$from_table] = $statement->fetchAllAssociative();
1953  }
1954 
1955  foreach (($this->tableArray[$from_table] ?? []) as $val) {
1956  if ($useSelectLabels) {
1957  $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($labelFieldSelect[$val[$labelField]]);
1958  } elseif ($val[$labelField]) {
1959  $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$labelField]);
1960  } elseif ($useAltSelectLabels) {
1961  $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($altLabelFieldSelect[$val[$altLabelField]]);
1962  } else {
1963  $outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$altLabelField]);
1964  }
1965  }
1966  if (isset($this->MOD_SETTINGS['options_sortlabel']) && $this->MOD_SETTINGS['options_sortlabel'] && is_array($outArray)) {
1967  natcasesort($outArray);
1968  }
1969  }
1970  }
1971  foreach ($outArray as $key2 => $val2) {
1972  $key2 = (string)$key2;
1973  $val2 = (string)$val2;
1974  if (‪GeneralUtility::inList($conf['inputValue'], $key2)) {
1975  $out[] = '<option value="' . htmlspecialchars($key2) . '" selected>[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
1976  } else {
1977  $out[] = '<option value="' . htmlspecialchars($key2) . '">[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
1978  }
1979  }
1980  }
1981 
1982  return implode(LF, $out);
1983  }
1984 
1985  protected function ‪mkOperatorSelect(string ‪$name, string $op, bool $draw, bool $submit): string
1986  {
1987  $out = [];
1988  if ($draw) {
1989  $out[] = '<div class="form-group">';
1990  $out[] = ' <select class="form-select' . ($submit ? ' t3js-submit-change' : '') . '" name="' . htmlspecialchars(‪$name) . '[operator]">';
1991  $out[] = ' <option value="AND"' . (!$op || $op === 'AND' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['AND']) . '</option>';
1992  $out[] = ' <option value="OR"' . ($op === 'OR' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['OR']) . '</option>';
1993  $out[] = ' </select>';
1994  $out[] = '</div>';
1995  } else {
1996  $out[] = '<input type="hidden" value="' . htmlspecialchars($op) . '" name="' . htmlspecialchars(‪$name) . '[operator]">';
1997  }
1998 
1999  return implode(LF, $out);
2000  }
2001 
2002  protected function ‪makeComparisonSelector(string $subscript, string ‪$fieldName, array $conf): string
2003  {
2004  $fieldPrefix = $this->name . $subscript;
2005  $lineHTML = [];
2006  $lineHTML[] = '<div class="form-group">';
2007  $lineHTML[] = $this->‪mkTypeSelect($fieldPrefix . '[type]', ‪$fieldName);
2008  $lineHTML[] = '</div>';
2009  $lineHTML[] = '<div class="form-group">';
2010  $lineHTML[] = ' <div class="input-group">';
2011  $lineHTML[] = $this->‪mkCompSelect($fieldPrefix . '[comparison]', (int)$conf['comparison'], ($conf['negate'] ?? null) ? 1 : 0);
2012  $lineHTML[] = ' <span class="input-group-text">';
2013  $lineHTML[] = ' <div class="form-check form-check-type-toggle">';
2014  $lineHTML[] = ' <input type="checkbox" class="form-check-input t3js-submit-click"' . (($conf['negate'] ?? null) ? ' checked' : '') . ' name="' . htmlspecialchars($fieldPrefix) . '[negate]">';
2015  $lineHTML[] = ' </div>';
2016  $lineHTML[] = ' </span>';
2017  $lineHTML[] = ' </div>';
2018  $lineHTML[] = '</div>';
2019 
2020  return implode(LF, $lineHTML);
2021  }
2022 
2023  protected function ‪mkCompSelect(string ‪$name, int $comparison, int $neg): string
2024  {
2025  $compOffSet = $comparison >> 5;
2026  $out = [];
2027  $out[] = '<select class="form-select t3js-submit-change" name="' . ‪$name . '">';
2028  for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
2029  if ($this->lang['comparison'][$i . '_'] ?? false) {
2030  $out[] = '<option value="' . $i . '"' . ($i >> 1 === $comparison >> 1 ? ' selected' : '') . '>' . htmlspecialchars($this->lang['comparison'][$i . '_']) . '</option>';
2031  }
2032  }
2033  $out[] = '</select>';
2034 
2035  return implode(LF, $out);
2036  }
2037 
2038  protected function ‪printCodeArray(array $codeArr, int $recursionLevel = 0): string
2039  {
2040  $out = [];
2041  foreach (array_values($codeArr) as $queryComponent) {
2042  $out[] = '<div class="card">';
2043  $out[] = '<div class="card-body">';
2044  $out[] = $queryComponent['html'];
2045 
2046  if ($this->enableQueryParts) {
2047  $out[] = '<pre class="language-sql">';
2048  $out[] = '<code class="language-sql">' . htmlspecialchars($queryComponent['query']) . '</code>';
2049  $out[] = '</pre>';
2050  }
2051  if (is_array($queryComponent['sub'] ?? null)) {
2052  $out[] = $this->‪printCodeArray($queryComponent['sub'], $recursionLevel + 1);
2053  }
2054  $out[] = '</div>';
2055  $out[] = '</div>';
2056  }
2057 
2058  return implode(LF, $out);
2059  }
2060 
2061  protected function ‪mkFieldToInputSelect(string ‪$name, string ‪$fieldName): string
2062  {
2063  $out = [];
2064  $out[] = '<div class="input-group mb-1">';
2065  $out[] = $this->‪updateIcon();
2066  $out[] = '<input type="text" class="form-control form-control-clearable t3js-clearable" value="' . htmlspecialchars(‪$fieldName) . '" name="' . htmlspecialchars(‪$name) . '" id="select-queryFields">';
2067  $out[] = '</div>';
2068  $out[] = '<select class="form-select t3js-addfield" name="_fieldListDummy" size="5" data-field="' . htmlspecialchars(‪$name) . '">';
2069  foreach ($this->fields as $key => $value) {
2070  if (!$value['exclude'] || $this->‪getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
2071  $label = $this->fields[$key]['label'];
2072  if ($this->showFieldAndTableNames) {
2073  $label .= ' [' . $key . ']';
2074  }
2075  $out[] = '<option value="' . htmlspecialchars($key) . '"' . ($key === ‪$fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
2076  }
2077  }
2078  $out[] = '</select>';
2079 
2080  return implode(LF, $out);
2081  }
2082 
2083  protected function ‪procesData(ServerRequestInterface $request, array $qC = []): void
2084  {
2085  $this->queryConfig = $qC;
2086  $POST = $request->getParsedBody();
2087  // If delete...
2088  if ($POST['qG_del'] ?? false) {
2089  // Initialize array to work on, save special parameters
2090  $ssArr = $this->‪getSubscript($POST['qG_del']);
2091  $workArr = &‪$this->queryConfig;
2092  $ssArrSize = count($ssArr) - 1;
2093  $i = 0;
2094  for (; $i < $ssArrSize; $i++) {
2095  $workArr = &$workArr[$ssArr[$i]];
2096  }
2097  // Delete the entry and move the other entries
2098  unset($workArr[$ssArr[$i]]);
2099  $workArrSize = count((array)$workArr);
2100  for ($j = $ssArr[$i]; $j < $workArrSize; $j++) {
2101  $workArr[$j] = $workArr[$j + 1];
2102  unset($workArr[$j + 1]);
2103  }
2104  }
2105  // If insert...
2106  if ($POST['qG_ins'] ?? false) {
2107  // Initialize array to work on, save special parameters
2108  $ssArr = $this->‪getSubscript($POST['qG_ins']);
2109  $workArr = &‪$this->queryConfig;
2110  $ssArrSize = count($ssArr) - 1;
2111  $i = 0;
2112  for (; $i < $ssArrSize; $i++) {
2113  $workArr = &$workArr[$ssArr[$i]];
2114  }
2115  // Move all entries above position where new entry is to be inserted
2116  $workArrSize = count((array)$workArr);
2117  for ($j = $workArrSize; $j > $ssArr[$i]; $j--) {
2118  $workArr[$j] = $workArr[$j - 1];
2119  }
2120  // Clear new entry position
2121  unset($workArr[$ssArr[$i] + 1]);
2122  $workArr[$ssArr[$i] + 1]['type'] = 'FIELD_';
2123  }
2124  // If move up...
2125  if ($POST['qG_up'] ?? false) {
2126  // Initialize array to work on
2127  $ssArr = $this->‪getSubscript($POST['qG_up']);
2128  $workArr = &‪$this->queryConfig;
2129  $ssArrSize = count($ssArr) - 1;
2130  $i = 0;
2131  for (; $i < $ssArrSize; $i++) {
2132  $workArr = &$workArr[$ssArr[$i]];
2133  }
2134  // Swap entries
2135  $qG_tmp = $workArr[$ssArr[$i]];
2136  $workArr[$ssArr[$i]] = $workArr[$ssArr[$i] - 1];
2137  $workArr[$ssArr[$i] - 1] = $qG_tmp;
2138  }
2139  // If new level...
2140  if ($POST['qG_nl'] ?? false) {
2141  // Initialize array to work on
2142  $ssArr = $this->‪getSubscript($POST['qG_nl']);
2143  $workArr = &‪$this->queryConfig;
2144  $ssArraySize = count($ssArr) - 1;
2145  $i = 0;
2146  for (; $i < $ssArraySize; $i++) {
2147  $workArr = &$workArr[$ssArr[$i]];
2148  }
2149  // Do stuff:
2150  $tempEl = $workArr[$ssArr[$i]];
2151  if (is_array($tempEl)) {
2152  if ($tempEl['type'] !== 'newlevel') {
2153  $workArr[$ssArr[$i]] = [
2154  'type' => 'newlevel',
2155  'operator' => $tempEl['operator'],
2156  'nl' => [$tempEl],
2157  ];
2158  }
2159  }
2160  }
2161  // If collapse level...
2162  if ($POST['qG_remnl'] ?? false) {
2163  // Initialize array to work on
2164  $ssArr = $this->‪getSubscript($POST['qG_remnl']);
2165  $workArr = &‪$this->queryConfig;
2166  $ssArrSize = count($ssArr) - 1;
2167  $i = 0;
2168  for (; $i < $ssArrSize; $i++) {
2169  $workArr = &$workArr[$ssArr[$i]];
2170  }
2171  // Do stuff:
2172  $tempEl = $workArr[$ssArr[$i]];
2173  if (is_array($tempEl)) {
2174  if ($tempEl['type'] === 'newlevel' && is_array($workArr)) {
2175  $a1 = array_slice($workArr, 0, $ssArr[$i]);
2176  $a2 = array_slice($workArr, $ssArr[$i]);
2177  array_shift($a2);
2178  $a3 = $tempEl['nl'];
2179  $a3[0]['operator'] = $tempEl['operator'];
2180  $workArr = array_merge($a1, $a3, $a2);
2181  }
2182  }
2183  }
2184  }
2185 
2186  protected function ‪getSubscript($arr): array
2187  {
2188  $retArr = [];
2189  while (\is_array($arr)) {
2190  reset($arr);
2191  $key = key($arr);
2192  $retArr[] = $key;
2193  if (isset($arr[$key])) {
2194  $arr = $arr[$key];
2195  } else {
2196  break;
2197  }
2198  }
2199 
2200  return $retArr;
2201  }
2202 
2203  protected function ‪getLabelCol(): string
2204  {
2205  return ‪$GLOBALS['TCA'][‪$this->table]['ctrl']['label'];
2206  }
2207 
2208  protected function ‪mkTypeSelect(string ‪$name, string ‪$fieldName, string $prepend = 'FIELD_'): string
2209  {
2210  $out = [];
2211  $out[] = '<select class="form-select t3js-submit-change" name="' . htmlspecialchars(‪$name) . '" id="' . htmlspecialchars(‪$name) . '">';
2212  $out[] = '<option value=""></option>';
2213  foreach ($this->fields as $key => $value) {
2214  if (!($value['exclude'] ?? false) || $this->‪getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
2215  $label = $this->fields[$key]['label'];
2216  if ($this->showFieldAndTableNames) {
2217  $label .= ' [' . $key . ']';
2218  }
2219  $out[] = '<option value="' . htmlspecialchars($prepend . $key) . '"' . ($key === ‪$fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
2220  }
2221  }
2222  $out[] = '</select>';
2223 
2224  return implode(LF, $out);
2225  }
2226 
2227  protected function ‪updateIcon(): string
2228  {
2229  return '<button class="btn btn-default" title="Update" name="just_update">' . $this->iconFactory->getIcon('actions-refresh', IconSize::SMALL)->render() . '</button>';
2230  }
2231 
2232  protected function ‪setAndCleanUpExternalLists(string ‪$name, string $list, string $force = ''): void
2233  {
2234  ‪$fields = array_unique(‪GeneralUtility::trimExplode(',', $list . ',' . $force, true));
2235  $reList = [];
2236  foreach (‪$fields as ‪$fieldName) {
2237  if (isset($this->fields[‪$fieldName])) {
2238  $reList[] = ‪$fieldName;
2239  }
2240  }
2241  $this->extFieldLists[‪$name] = implode(',', $reList);
2242  }
2243 
2244  protected function ‪mkTableSelect(string ‪$name, string $cur): string
2245  {
2246  $tables = [];
2247  foreach (‪$GLOBALS['TCA'] as $tableName => $value) {
2248  $tableTitle = $this->‪getLanguageService()->sL(‪$GLOBALS['TCA'][$tableName]['ctrl']['title'] ?? '');
2249  if (!$tableTitle || $this->showFieldAndTableNames) {
2250  $tableTitle .= ' [' . $tableName . ']';
2251  }
2252  $tables[$tableName] = trim($tableTitle);
2253  }
2254  asort($tables);
2255 
2256  $out = [];
2257  $out[] = '<select class="form-select t3js-submit-change" name="' . ‪$name . '" id="select-table">';
2258  $out[] = '<option value=""></option>';
2259  foreach ($tables as $tableName => $label) {
2260  if ($this->‪getBackendUserAuthentication()->check('tables_select', $tableName)) {
2261  $out[] = '<option value="' . htmlspecialchars($tableName) . '"' . ($tableName === $cur ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
2262  }
2263  }
2264  $out[] = '</select>';
2265 
2266  return implode(LF, $out);
2267  }
2268 
2272  protected function ‪init(string ‪$name, string ‪$table, string ‪$fieldList = '', array $settings = []): void
2273  {
2274  // Analysing the fields in the table.
2275  if (is_array(‪$GLOBALS['TCA'][‪$table] ?? false)) {
2276  $this->name = ‪$name;
2277  $this->table = ‪$table;
2278  $this->fieldList = ‪$fieldList ?: $this->‪makeFieldList();
2279  $this->MOD_SETTINGS = $settings;
2280  $fieldArr = ‪GeneralUtility::trimExplode(',', $this->fieldList, true);
2281  foreach ($fieldArr as ‪$fieldName) {
2282  $fieldConfig = ‪$GLOBALS['TCA'][‪$this->table]['columns'][‪$fieldName] ?? [];
2283  $this->fields[‪$fieldName] = $fieldConfig['config'] ?? [];
2284  $this->fields[‪$fieldName]['exclude'] = $fieldConfig['exclude'] ?? '';
2285  if (((($this->fields[‪$fieldName]['type'] ?? '') === 'user') && (!isset($this->fields[‪$fieldName]['renderType'])))
2286  || ($this->fields[‪$fieldName]['type'] ?? '') === 'none'
2287  ) {
2288  // Do not list type=none "virtual" fields or query them from db,
2289  // and if type=user without defined renderType
2290  unset($this->fields[‪$fieldName]);
2291  continue;
2292  }
2293  if (is_array($fieldConfig) && ($fieldConfig['label'] ?? false)) {
2294  $this->fields[‪$fieldName]['label'] = rtrim(trim($this->‪getLanguageService()->sL($fieldConfig['label'])), ':');
2295  switch ($this->fields[‪$fieldName]['type']) {
2296  case 'input':
2297  if (preg_match('/int|year/i', ($this->fields[‪$fieldName]['eval'] ?? ''))) {
2298  $this->fields[‪$fieldName]['type'] = 'number';
2299  } else {
2300  $this->fields[‪$fieldName]['type'] = 'text';
2301  }
2302  break;
2303  case 'number':
2304  // Empty on purpose, we have to keep the type "number".
2305  // Falling back to the "default" case would set the type to "text"
2306  break;
2307  case 'datetime':
2308  if (!in_array($this->fields[‪$fieldName]['dbType'] ?? '', ‪QueryHelper::getDateTimeTypes(), true)) {
2309  $this->fields[‪$fieldName]['type'] = 'number';
2310  } elseif ($this->fields[‪$fieldName]['dbType'] === 'time') {
2311  $this->fields[‪$fieldName]['type'] = 'time';
2312  } else {
2313  $this->fields[‪$fieldName]['type'] = 'date';
2314  }
2315  break;
2316  case 'check':
2317  if (count($this->fields[‪$fieldName]['items'] ?? []) <= 1) {
2318  $this->fields[‪$fieldName]['type'] = 'boolean';
2319  } else {
2320  $this->fields[‪$fieldName]['type'] = 'binary';
2321  }
2322  break;
2323  case 'radio':
2324  $this->fields[‪$fieldName]['type'] = 'multiple';
2325  break;
2326  case 'select':
2327  case 'category':
2328  $this->fields[‪$fieldName]['type'] = 'multiple';
2329  if ($this->fields[‪$fieldName]['foreign_table'] ?? false) {
2330  $this->fields[‪$fieldName]['type'] = 'relation';
2331  }
2332  if ($this->fields[‪$fieldName]['special'] ?? false) {
2333  $this->fields[‪$fieldName]['type'] = 'text';
2334  }
2335  break;
2336  case 'group':
2337  $this->fields[‪$fieldName]['type'] = 'relation';
2338  break;
2339  case 'user':
2340  case 'flex':
2341  case 'passthrough':
2342  case 'none':
2343  case 'text':
2344  case 'email':
2345  case 'link':
2346  case 'password':
2347  case 'color':
2348  case 'json':
2349  case 'uuid':
2350  default:
2351  $this->fields[‪$fieldName]['type'] = 'text';
2352  }
2353  } else {
2354  $this->fields[‪$fieldName]['label'] = '[FIELD: ' . ‪$fieldName . ']';
2355  switch (‪$fieldName) {
2356  case 'pid':
2357  $this->fields[‪$fieldName]['type'] = 'relation';
2358  $this->fields[‪$fieldName]['allowed'] = 'pages';
2359  break;
2360  case 'tstamp':
2361  case 'crdate':
2362  $this->fields[‪$fieldName]['type'] = 'time';
2363  break;
2364  case 'deleted':
2365  $this->fields[‪$fieldName]['type'] = 'boolean';
2366  break;
2367  default:
2368  $this->fields[‪$fieldName]['type'] = 'number';
2369  }
2370  }
2371 
2372  uasort($this->fields, static fn($fieldA, $fieldB) => strcmp($fieldA['label'], $fieldB['label']));
2373  }
2374  }
2375  /* // EXAMPLE:
2376  $this->queryConfig = array(
2377  array(
2378  'operator' => 'AND',
2379  'type' => 'FIELD_space_before_class',
2380  ),
2381  array(
2382  'operator' => 'AND',
2383  'type' => 'FIELD_records',
2384  'negate' => 1,
2385  'inputValue' => 'foo foo'
2386  ),
2387  array(
2388  'type' => 'newlevel',
2389  'nl' => array(
2390  array(
2391  'operator' => 'AND',
2392  'type' => 'FIELD_space_before_class',
2393  'negate' => 1,
2394  'inputValue' => 'foo foo'
2395  ),
2396  array(
2397  'operator' => 'AND',
2398  'type' => 'FIELD_records',
2399  'negate' => 1,
2400  'inputValue' => 'foo foo'
2401  )
2402  )
2403  ),
2404  array(
2405  'operator' => 'OR',
2406  'type' => 'FIELD_maillist',
2407  )
2408  );
2409  */
2410  }
2411 
2412  protected function ‪makeFieldList(): string
2413  {
2414  $fieldListArr = [];
2415  if (is_array(‪$GLOBALS['TCA'][$this->table])) {
2416  $fieldListArr = array_keys(‪$GLOBALS['TCA'][$this->table]['columns'] ?? []);
2417  $fieldListArr[] = 'uid';
2418  $fieldListArr[] = 'pid';
2419  $fieldListArr[] = 'deleted';
2420  if (‪$GLOBALS['TCA'][$this->table]['ctrl']['tstamp'] ?? false) {
2421  $fieldListArr[] = ‪$GLOBALS['TCA'][‪$this->table]['ctrl']['tstamp'];
2422  }
2423  if (‪$GLOBALS['TCA'][$this->table]['ctrl']['crdate'] ?? false) {
2424  $fieldListArr[] = ‪$GLOBALS['TCA'][‪$this->table]['ctrl']['crdate'];
2425  }
2426  if (‪$GLOBALS['TCA'][$this->table]['ctrl']['sortby'] ?? false) {
2427  $fieldListArr[] = ‪$GLOBALS['TCA'][‪$this->table]['ctrl']['sortby'];
2428  }
2429  }
2430 
2431  return implode(',', $fieldListArr);
2432  }
2433 
2434  protected function ‪makeStoreControl(): string
2435  {
2436  $languageService = $this->‪getLanguageService();
2437 
2438  // Load/Save
2439  $storeArray = $this->‪initStoreArray();
2440 
2441  $opt = [];
2442  foreach ($storeArray as $k => $v) {
2443  $opt[] = '<option value="' . htmlspecialchars((string)$k) . '">' . htmlspecialchars((string)$v) . '</option>';
2444  }
2445 
2446  $markup = [];
2447  $markup[] = '<div class="form-row align-items-sm-end">';
2448  $markup[] = ' <div class="form-group">';
2449  $markup[] = ' <label for="query-store" class="form-label">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.queryStore.storage.label') . '</label>';
2450  $markup[] = ' <div class="input-group">';
2451  $markup[] = ' <select class="form-select" name="storeControl[STORE]" id="query-store" data-assign-store-control-title>' . implode(LF, $opt) . '</select>';
2452  $markup[] = ' </div>';
2453  $markup[] = ' </div>';
2454  $markup[] = ' <div class="form-group">';
2455  $markup[] = ' <label for="query-title" class="form-label">' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.queryStore.title.label') . '</label>';
2456  $markup[] = ' <input class="form-control" name="storeControl[title]" id="query-title" value="" type="text" max="80">';
2457  $markup[] = ' </div>';
2458  $markup[] = ' <div class="form-group">';
2459  $markup[] = ' <button class="btn btn-default" type="submit" name="storeControl[LOAD]" value="' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.btn.load.label') . '">';
2460  $markup[] = $this->iconFactory->getIcon('actions-upload', IconSize::SMALL)->render();
2461  $markup[] = $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.btn.load.label');
2462  $markup[] = ' </button>';
2463  $markup[] = ' <button class="btn btn-default" type="submit" name="storeControl[SAVE]" value="' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.btn.save.label') . '">';
2464  $markup[] = $this->iconFactory->getIcon('actions-save', IconSize::SMALL)->render();
2465  $markup[] = $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.btn.save.label');
2466  $markup[] = ' </button>';
2467  $markup[] = ' <button class="btn btn-default" type="submit" name="storeControl[REMOVE]" value="' . $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.btn.delete.label') . '">';
2468  $markup[] = $this->iconFactory->getIcon('actions-delete', IconSize::SMALL)->render();
2469  $markup[] = $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.btn.delete.label');
2470  $markup[] = ' </button>';
2471  $markup[] = ' </div>';
2472  $markup[] = '</div>';
2473 
2474  return implode(LF, $markup);
2475  }
2476 
2477  protected function ‪procesStoreControl(ServerRequestInterface $request): string
2478  {
2479  $languageService = $this->‪getLanguageService();
2480  $flashMessage = null;
2481  $storeArray = $this->‪initStoreArray();
2482  $storeQueryConfigs = (array)(unserialize($this->MOD_SETTINGS['storeQueryConfigs'] ?? '', ['allowed_classes' => false]));
2483  $storeControl = $request->getParsedBody()['storeControl'] ?? [];
2484  $storeIndex = (int)($storeControl['STORE'] ?? 0);
2485  $saveStoreArray = 0;
2486  $writeArray = [];
2487  $msg = '';
2488  if (is_array($storeControl)) {
2489  if ($storeControl['LOAD'] ?? false) {
2490  if ($storeIndex > 0) {
2491  $writeArray = $this->‪loadStoreQueryConfigs($storeQueryConfigs, $storeIndex, $writeArray);
2492  $saveStoreArray = 1;
2493  $flashMessage = GeneralUtility::makeInstance(
2494  FlashMessage::class,
2495  sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_t3lib_fullsearch.xlf:query_loaded'), $storeArray[$storeIndex])
2496  );
2497  }
2498  } elseif ($storeControl['SAVE'] ?? false) {
2499  if (trim($storeControl['title'])) {
2500  if ($storeIndex > 0) {
2501  $storeArray[$storeIndex] = $storeControl['title'];
2502  } else {
2503  $storeArray[] = $storeControl['title'];
2504  end($storeArray);
2505  $storeIndex = key($storeArray);
2506  }
2507  $storeQueryConfigs = $this->‪addToStoreQueryConfigs($storeQueryConfigs, (int)$storeIndex);
2508  $saveStoreArray = 1;
2509  $flashMessage = GeneralUtility::makeInstance(
2510  FlashMessage::class,
2511  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_t3lib_fullsearch.xlf:query_saved')
2512  );
2513  }
2514  } elseif ($storeControl['REMOVE'] ?? false) {
2515  if ($storeIndex > 0) {
2516  $flashMessage = GeneralUtility::makeInstance(
2517  FlashMessage::class,
2518  sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_t3lib_fullsearch.xlf:query_removed'), $storeArray[$storeControl['STORE']])
2519  );
2520  // Removing
2521  unset($storeArray[$storeControl['STORE']]);
2522  $saveStoreArray = 1;
2523  }
2524  }
2525  if (!empty($flashMessage)) {
2526  $msg = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)
2527  ->resolve()
2528  ->render([$flashMessage]);
2529  }
2530  }
2531  if ($saveStoreArray) {
2532  // Making sure, index 0 is not set!
2533  unset($storeArray[0]);
2534  $writeArray['storeArray'] = serialize($storeArray);
2535  $writeArray['storeQueryConfigs'] =
2536  serialize($this->‪cleanStoreQueryConfigs($storeQueryConfigs, $storeArray));
2537  $this->MOD_SETTINGS = BackendUtility::getModuleData(
2538  $this->MOD_MENU,
2539  $writeArray,
2540  $this->moduleName,
2541  'ses'
2542  );
2543  }
2544 
2545  return $msg;
2546  }
2547 
2548  protected function ‪cleanStoreQueryConfigs(array $storeQueryConfigs, array $storeArray): array
2549  {
2550  if (is_array($storeQueryConfigs)) {
2551  foreach ($storeQueryConfigs as $k => $v) {
2552  if (!isset($storeArray[$k])) {
2553  unset($storeQueryConfigs[$k]);
2554  }
2555  }
2556  }
2557 
2558  return $storeQueryConfigs;
2559  }
2560 
2561  protected function ‪addToStoreQueryConfigs(array $storeQueryConfigs, int $index): array
2562  {
2563  $keyArr = explode(',', $this->storeList);
2564  $storeQueryConfigs[$index] = [];
2565  foreach ($keyArr as $k) {
2566  $storeQueryConfigs[$index][$k] = $this->MOD_SETTINGS[$k] ?? null;
2567  }
2568 
2569  return $storeQueryConfigs;
2570  }
2571 
2572  protected function ‪loadStoreQueryConfigs(array $storeQueryConfigs, int $storeIndex, array $writeArray): array
2573  {
2574  if ($storeQueryConfigs[$storeIndex]) {
2575  $keyArr = explode(',', $this->storeList);
2576  foreach ($keyArr as $k) {
2577  $writeArray[$k] = $storeQueryConfigs[$storeIndex][$k];
2578  }
2579  }
2580 
2581  return $writeArray;
2582  }
2583 
2584  protected function ‪initStoreArray(): array
2585  {
2586  $languageService = $this->‪getLanguageService();
2587  $storeArray = [
2588  '0' => $languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fullSearch.form.field.queryStore.storage.0'),
2589  ];
2590  $savedStoreArray = unserialize($this->MOD_SETTINGS['storeArray'] ?? '', ['allowed_classes' => false]);
2591  if (is_array($savedStoreArray)) {
2592  $storeArray = array_merge($storeArray, $savedStoreArray);
2593  }
2594 
2595  return $storeArray;
2596  }
2597 
2598  protected function ‪search(ServerRequestInterface $request): string
2599  {
2600  $swords = $this->MOD_SETTINGS['sword'] ?? '';
2601  $out = '';
2602  if ($swords) {
2603  foreach (‪$GLOBALS['TCA'] as ‪$table => $value) {
2604  // Get fields list
2605  $conf = ‪$GLOBALS['TCA'][‪$table];
2606  // Avoid querying tables with no columns
2607  if (empty($conf['columns'])) {
2608  continue;
2609  }
2610  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(‪$table);
2611  $identifierQuoteCharacter = $this->platformHelper->getIdentifierQuoteCharacter($connection->getDatabasePlatform());
2612  $tableColumns = $connection->createSchemaManager()->listTableColumns(‪$table);
2613  $normalizedTableColumns = [];
2614  $fieldsInDatabase = [];
2615  foreach ($tableColumns as $column) {
2616  $fieldsInDatabase[] = $column->getName();
2617  $normalizedTableColumns[trim($column->getName(), $identifierQuoteCharacter)] = $column;
2618  }
2619  ‪$fields = array_intersect(array_keys($conf['columns']), $fieldsInDatabase);
2620 
2621  $queryBuilder = $connection->createQueryBuilder();
2622  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2623  $queryBuilder->count('*')->from(‪$table);
2624  $likes = [];
2625  $escapedLikeString = '%' . $queryBuilder->escapeLikeWildcards($swords) . '%';
2626  foreach (‪$fields as $field) {
2627  $field = trim($field, $identifierQuoteCharacter);
2628  $quotedField = $queryBuilder->quoteIdentifier($field);
2629  $column = $normalizedTableColumns[$field] ?? $normalizedTableColumns[$quotedField] ?? null;
2630  if ($column !== null
2631  && $connection->getDatabasePlatform() instanceof DoctrinePostgreSQLPlatform
2632  && !in_array(Type::getTypeRegistry()->lookupName($column->getType()), [Types::STRING, Types::ASCII_STRING, Types::JSON], true)
2633  ) {
2634  if (Type::getTypeRegistry()->lookupName($column->getType()) === Types::SMALLINT) {
2635  // we need to cast smallint to int first, otherwise text case below won't work
2636  $quotedField .= '::int';
2637  }
2638  $quotedField .= '::text';
2639  }
2640  $likes[] = $queryBuilder->expr()->comparison(
2641  $quotedField,
2642  'LIKE',
2643  $queryBuilder->createNamedParameter($escapedLikeString)
2644  );
2645  }
2646  $queryBuilder->orWhere(...$likes);
2647  $count = $queryBuilder->executeQuery()->fetchOne();
2648 
2649  if ($count > 0) {
2650  $queryBuilder = $connection->createQueryBuilder();
2651  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
2652  $queryBuilder->select('uid', $conf['ctrl']['label'])
2653  ->from(‪$table)
2654  ->setMaxResults(200);
2655  $likes = [];
2656  foreach (‪$fields as $field) {
2657  $field = trim($field, $identifierQuoteCharacter);
2658  $quotedField = $queryBuilder->quoteIdentifier($field);
2659  $column = $normalizedTableColumns[$field] ?? $normalizedTableColumns[$quotedField] ?? null;
2660  if ($column !== null
2661  && $connection->getDatabasePlatform() instanceof DoctrinePostgreSQLPlatform
2662  && !in_array(Type::getTypeRegistry()->lookupName($column->getType()), [Types::STRING, Types::ASCII_STRING, Types::JSON], true)
2663  ) {
2664  if (Type::getTypeRegistry()->lookupName($column->getType()) === Types::SMALLINT) {
2665  // we need to cast smallint to int first, otherwise text case below won't work
2666  $quotedField .= '::int';
2667  }
2668  $quotedField .= '::text';
2669  }
2670  $likes[] = $queryBuilder->expr()->comparison(
2671  $quotedField,
2672  'LIKE',
2673  $queryBuilder->createNamedParameter($escapedLikeString)
2674  );
2675  }
2676  $statement = $queryBuilder->orWhere(...$likes)->executeQuery();
2677  $lastRow = null;
2678  $rowArr = [];
2679  while ($row = $statement->fetchAssociative()) {
2680  $rowArr[] = $this->‪resultRowDisplay($row, $conf, ‪$table, $request);
2681  $lastRow = $row;
2682  }
2683  $markup = [];
2684  $markup[] = '<div class="panel panel-default">';
2685  $markup[] = ' <div class="panel-heading">';
2686  $markup[] = htmlspecialchars($this->‪getLanguageService()->sL($conf['ctrl']['title'])) . ' (' . $count . ')';
2687  $markup[] = ' </div>';
2688  $markup[] = ' <div class="table-fit">';
2689  $markup[] = ' <table class="table table-striped table-hover">';
2690  $markup[] = $this->‪resultRowTitles((array)$lastRow, $conf);
2691  $markup[] = implode(LF, $rowArr);
2692  $markup[] = ' </table>';
2693  $markup[] = ' </div>';
2694  $markup[] = '</div>';
2695 
2696  $out .= implode(LF, $markup);
2697  }
2698  }
2699  }
2700 
2701  return $out;
2702  }
2703 
2707  protected function ‪recordStatisticsAction(‪ModuleTemplate $view, ServerRequestInterface $request): ResponseInterface
2708  {
2709  $languageService = $this->‪getLanguageService();
2710  $databaseIntegrityCheck = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
2711  $databaseIntegrityCheck->genTree(0);
2712 
2713  // Page stats
2714  $pageStatistic = [
2715  'total_pages' => [
2716  'icon' => $this->iconFactory->getIconForRecord('pages', [], IconSize::SMALL)->render(),
2717  'count' => count($databaseIntegrityCheck->getPageIdArray()),
2718  ],
2719  'translated_pages' => [
2720  'icon' => $this->iconFactory->getIconForRecord('pages', [], IconSize::SMALL)->render(),
2721  'count' => count($databaseIntegrityCheck->getPageTranslatedPageIDArray()),
2722  ],
2723  'hidden_pages' => [
2724  'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], IconSize::SMALL)->render(),
2725  'count' => $databaseIntegrityCheck->getRecStats()['hidden'] ?? 0,
2726  ],
2727  'deleted_pages' => [
2728  'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], IconSize::SMALL)->render(),
2729  'count' => isset($databaseIntegrityCheck->getRecStats()['deleted']['pages']) ? count($databaseIntegrityCheck->getRecStats()['deleted']['pages']) : 0,
2730  ],
2731  ];
2732 
2733  // doktypes stats
2734  $doktypes = [];
2735  $doktype = ‪$GLOBALS['TCA']['pages']['columns']['doktype']['config']['items'];
2736  if (is_array($doktype)) {
2737  foreach ($doktype as $setup) {
2738  if ($setup['value'] !== '--div--') {
2739  $doktypes[] = [
2740  'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $setup['value']], IconSize::SMALL)->render(),
2741  'title' => $languageService->sL($setup['label']) . ' (' . $setup['value'] . ')',
2742  'count' => (int)($databaseIntegrityCheck->getRecStats()['doktype'][$setup['value']] ?? 0),
2743  ];
2744  }
2745  }
2746  }
2747 
2748  // Tables and lost records
2749  $id_list = '-1,0,' . implode(',', array_keys($databaseIntegrityCheck->getPageIdArray()));
2750  $id_list = rtrim($id_list, ',');
2751  $databaseIntegrityCheck->lostRecords($id_list);
2752 
2753  // Fix a lost record if requested
2754  $fixSingleLostRecordTableName = (string)($request->getQueryParams()['fixLostRecords_table'] ?? '');
2755  $fixSingleLostRecordUid = (int)($request->getQueryParams()['fixLostRecords_uid'] ?? 0);
2756  if (!empty($fixSingleLostRecordTableName) && $fixSingleLostRecordUid
2757  && $databaseIntegrityCheck->fixLostRecord($fixSingleLostRecordTableName, $fixSingleLostRecordUid)
2758  ) {
2759  $databaseIntegrityCheck = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
2760  $databaseIntegrityCheck->genTree(0);
2761  $id_list = '-1,0,' . implode(',', array_keys($databaseIntegrityCheck->getPageIdArray()));
2762  $id_list = rtrim($id_list, ',');
2763  $databaseIntegrityCheck->lostRecords($id_list);
2764  }
2765 
2766  $tableStatistic = [];
2767  $countArr = $databaseIntegrityCheck->countRecords($id_list);
2768  if (is_array(‪$GLOBALS['TCA'])) {
2769  foreach (‪$GLOBALS['TCA'] as $t => $value) {
2770  if (‪$GLOBALS['TCA'][$t]['ctrl']['hideTable'] ?? false) {
2771  continue;
2772  }
2773  if ($t === 'pages' && $databaseIntegrityCheck->getLostPagesList() !== '') {
2774  $lostRecordCount = count(explode(',', $databaseIntegrityCheck->getLostPagesList()));
2775  } else {
2776  $lostRecordCount = isset($databaseIntegrityCheck->getLRecords()[$t]) ? count($databaseIntegrityCheck->getLRecords()[$t]) : 0;
2777  }
2778  $recordCount = 0;
2779  if ($countArr['all'][$t] ?? false) {
2780  $recordCount = (int)($countArr['non_deleted'][$t] ?? 0) . '/' . $lostRecordCount;
2781  }
2782  $lostRecordList = [];
2783  if (is_array($databaseIntegrityCheck->getLRecords()[$t] ?? false)) {
2784  foreach ($databaseIntegrityCheck->getLRecords()[$t] as $data) {
2785  if (!‪GeneralUtility::inList($databaseIntegrityCheck->getLostPagesList(), $data['pid'])) {
2786  $fixLink = (string)$this->uriBuilder->buildUriFromRoute(
2787  $this->moduleName,
2788  ['SET' => ['function' => 'records'], 'fixLostRecords_table' => $t, 'fixLostRecords_uid' => $data['uid']]
2789  );
2790  $lostRecordList[] =
2791  '<div class="record">' .
2792  '<a href="' . htmlspecialchars($fixLink) . '" title="' . htmlspecialchars($languageService->sL('LLL:EXT:lowlevel/Resources/Private/Language/locallang.xlf:fixLostRecord')) . '">' .
2793  $this->iconFactory->getIcon('status-dialog-error', IconSize::SMALL)->render() .
2794  '</a>uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(‪GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) .
2795  '</div>';
2796  } else {
2797  $lostRecordList[] =
2798  '<div class="record-noicon">' .
2799  'uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(‪GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20)) .
2800  '</div>';
2801  }
2802  }
2803  }
2804  $tableStatistic[$t] = [
2805  'icon' => $this->iconFactory->getIconForRecord($t, [], IconSize::SMALL)->render(),
2806  'title' => $languageService->sL(‪$GLOBALS['TCA'][$t]['ctrl']['title']),
2807  'count' => $recordCount,
2808  'lostRecords' => implode(LF, $lostRecordList),
2809  ];
2810  }
2811  }
2812 
2813  $view->‪assignMultiple([
2814  'pages' => $pageStatistic,
2815  'doktypes' => $doktypes,
2816  'tables' => $tableStatistic,
2817  ]);
2818 
2819  return $view->‪renderResponse('RecordStatistics');
2820  }
2821 
2825  protected function ‪relationsAction(‪ModuleTemplate $view): ResponseInterface
2826  {
2827  $databaseIntegrityCheck = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
2828  $databaseIntegrityCheck->selectNonEmptyRecordsWithFkeys();
2829  $view->‪assignMultiple([
2830  'select_db' => $databaseIntegrityCheck->testDBRefs($databaseIntegrityCheck->getCheckSelectDBRefs()),
2831  'group_db' => $databaseIntegrityCheck->testDBRefs($databaseIntegrityCheck->getCheckGroupDBRefs()),
2832  ]);
2833 
2834  return $view->‪renderResponse('Relations');
2835  }
2836 
2838  {
2839  return ‪$GLOBALS['BE_USER'];
2840  }
2841 
2843  {
2844  return ‪$GLOBALS['LANG'];
2845  }
2846 
2847  //################################
2848  // copied over from BackendUtility to enable deprecation of the original method
2849  // @todo finish fluidification of template and remove HTML generation from controller
2850  //################################
2851 
2860  protected function ‪getDropdownMenu(
2861  string $elementName,
2862  string|int $currentValue,
2863  mixed $menuItems,
2864  ServerRequestInterface $request
2865  ) {
2866  if (!is_array($menuItems) || count($menuItems) <= 1) {
2867  return '';
2868  }
2869  $scriptUrl = $this->uriBuilder->buildUriFromRequest($request);
2870  $options = [];
2871  foreach ($menuItems as $value => $label) {
2872  $options[] = '<option value="'
2873  . htmlspecialchars($value) . '"'
2874  . ((string)$currentValue === (string)$value ? ' selected="selected"' : '') . '>'
2875  . htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false) . '</option>';
2876  }
2877  $dataMenuIdentifier = str_replace(['SET[', ']'], '', $elementName);
2878  $dataMenuIdentifier = ‪GeneralUtility::camelCaseToLowerCaseUnderscored($dataMenuIdentifier);
2879  $dataMenuIdentifier = str_replace('_', '-', $dataMenuIdentifier);
2880  // relies on module 'TYPO3/CMS/Backend/ActionDispatcher'
2881  $attributes = GeneralUtility::implodeAttributes([
2882  'name' => $elementName,
2883  'id' => $dataMenuIdentifier,
2884  'class' => 'form-select',
2885  'data-menu-identifier' => $dataMenuIdentifier,
2886  'data-global-event' => 'change',
2887  'data-action-navigate' => '$data=~s/$value/',
2888  'data-navigate-value' => $scriptUrl . '&' . $elementName . '=${value}',
2889  ], true);
2890 
2891  return '
2892  <select class="form-select" ' . $attributes . '>
2893  ' . implode(LF, $options) . '
2894  </select>';
2895  }
2896 
2905  protected function ‪getFuncCheck(
2906  string $elementName,
2907  string|bool|int $currentValue,
2908  ServerRequestInterface $request,
2909  string $tagParams = ''
2910  ) {
2911  // relies on module 'TYPO3/CMS/Backend/ActionDispatcher'
2912  $scriptUrl = $this->uriBuilder->buildUriFromRequest($request);
2913  $attributes = GeneralUtility::implodeAttributes([
2914  'type' => 'checkbox',
2915  'class' => 'form-check-input',
2916  'name' => $elementName,
2917  'value' => '1',
2918  'data-global-event' => 'change',
2919  'data-action-navigate' => '$data=~s/$value/',
2920  'data-navigate-value' => sprintf('%s&%s=${value}', $scriptUrl, $elementName),
2921  'data-empty-value' => '0',
2922  ], true);
2923 
2924  return
2925  '<input ' . $attributes .
2926  ($currentValue ? ' checked="checked"' : '') .
2927  ($tagParams ? ' ' . $tagParams : '') .
2928  ' />';
2929  }
2930 }
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:44
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$formName
‪string $formName
Definition: DatabaseIntegrityController.php:80
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\search
‪search(ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:2598
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\procesStoreControl
‪procesStoreControl(ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:2477
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Backend\Template\Components\ButtonBar
Definition: ButtonBar.php:33
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixed_lgd_cs
‪static string fixed_lgd_cs(string $string, int $chars, string $appendString='...')
Definition: GeneralUtility.php:92
‪TYPO3\CMS\Backend\Template\ModuleTemplate\assignMultiple
‪assignMultiple(array $values)
Definition: ModuleTemplate.php:103
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getFormElements
‪getFormElements(int $subLevel=0, string|array $queryConfig=null, string $parent='')
Definition: DatabaseIntegrityController.php:1607
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\resultRowTitles
‪resultRowTitles(?array $row, array $conf)
Definition: DatabaseIntegrityController.php:805
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getQuerySingle
‪getQuerySingle(array $conf, bool $first)
Definition: DatabaseIntegrityController.php:1251
‪TYPO3\CMS\Backend\Template\ModuleTemplateFactory
Definition: ModuleTemplateFactory.php:33
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\makeFieldList
‪makeFieldList()
Definition: DatabaseIntegrityController.php:2412
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\handleRequest
‪handleRequest(ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:230
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$tableArray
‪array $tableArray
Definition: DatabaseIntegrityController.php:91
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\makeStoreControl
‪makeStoreControl()
Definition: DatabaseIntegrityController.php:2434
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\isDateOfIso8601Format
‪isDateOfIso8601Format(mixed $date)
Definition: DatabaseIntegrityController.php:1350
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\verifyType
‪verifyType(string $fieldName)
Definition: DatabaseIntegrityController.php:1576
‪TYPO3\CMS\Core\Database\ReferenceIndex
Definition: ReferenceIndex.php:40
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$lang
‪array $lang
Definition: DatabaseIntegrityController.php:97
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\setAndCleanUpExternalLists
‪setAndCleanUpExternalLists(string $name, string $list, string $force='')
Definition: DatabaseIntegrityController.php:2232
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseGroupBy
‪static array string[] parseGroupBy(string $input)
Definition: QueryHelper.php:102
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$fieldName
‪string $fieldName
Definition: DatabaseIntegrityController.php:155
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\mkTableSelect
‪mkTableSelect(string $name, string $cur)
Definition: DatabaseIntegrityController.php:2244
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\mkTypeSelect
‪mkTypeSelect(string $name, string $fieldName, string $prepend='FIELD_')
Definition: DatabaseIntegrityController.php:2208
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$queryConfig
‪array $queryConfig
Definition: DatabaseIntegrityController.php:92
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\relationsAction
‪relationsAction(ModuleTemplate $view)
Definition: DatabaseIntegrityController.php:2825
‪TYPO3\CMS\Backend\Template\ModuleTemplate\renderResponse
‪renderResponse(string $templateFileName='')
Definition: ModuleTemplate.php:122
‪TYPO3\CMS\Core\Utility\DebugUtility\viewArray
‪static string viewArray(mixed $array_in)
Definition: DebugUtility.php:113
‪TYPO3\CMS\Core\Localization\Locales
Definition: Locales.php:36
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$MOD_SETTINGS
‪array $MOD_SETTINGS
Definition: DatabaseIntegrityController.php:78
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$enableQueryParts
‪bool $enableQueryParts
Definition: DatabaseIntegrityController.php:96
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$storeList
‪string $storeList
Definition: DatabaseIntegrityController.php:95
‪TYPO3\CMS\Core\Utility\GeneralUtility\camelCaseToLowerCaseUnderscored
‪static string camelCaseToLowerCaseUnderscored($string)
Definition: GeneralUtility.php:683
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$moduleName
‪string $moduleName
Definition: DatabaseIntegrityController.php:81
‪TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver
Definition: FlashMessageRendererResolver.php:33
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\cleanUpQueryConfig
‪cleanUpQueryConfig(array $queryConfig)
Definition: DatabaseIntegrityController.php:1532
‪TYPO3\CMS\Backend\Template\ModuleTemplate
Definition: ModuleTemplate.php:46
‪TYPO3\CMS\Core\Database\ConnectionPool\DEFAULT_CONNECTION_NAME
‪const DEFAULT_CONNECTION_NAME
Definition: ConnectionPool.php:50
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\makeSelectorTable
‪makeSelectorTable(array $modSettings, ServerRequestInterface $request, string $enableList='table, fields, query, group, order, limit')
Definition: DatabaseIntegrityController.php:1361
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\searchAction
‪searchAction(ModuleTemplate $view, ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:426
‪TYPO3\CMS\Core\Localization\DateFormatter
Definition: DateFormatter.php:27
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:32
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$enablePrefix
‪bool $enablePrefix
Definition: DatabaseIntegrityController.php:89
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getBackendUserAuthentication
‪getBackendUserAuthentication()
Definition: DatabaseIntegrityController.php:2837
‪TYPO3\CMS\Core\Utility\PathUtility\getAbsoluteWebPath
‪static string getAbsoluteWebPath(string $targetPath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:52
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\convertIso8601DatetimeStringToUnixTimestamp
‪convertIso8601DatetimeStringToUnixTimestamp(array $conf)
Definition: DatabaseIntegrityController.php:1335
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$noDownloadB
‪int $noDownloadB
Definition: DatabaseIntegrityController.php:90
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static extPath(string $key, string $script='')
Definition: ExtensionManagementUtility.php:82
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getTreeList
‪string getTreeList(int $id, int $depth, int $begin=0, string $permsClause='')
Definition: DatabaseIntegrityController.php:667
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\resultRowDisplay
‪resultRowDisplay(array $row, array $conf, string $table, ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:834
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\verifyComparison
‪verifyComparison(int $comparison, bool $neg)
Definition: DatabaseIntegrityController.php:1591
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\cleanInputVal
‪mixed cleanInputVal(array $conf, string $suffix='')
Definition: DatabaseIntegrityController.php:1304
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getLabelCol
‪getLabelCol()
Definition: DatabaseIntegrityController.php:2203
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$fieldList
‪string $fieldList
Definition: DatabaseIntegrityController.php:157
‪TYPO3\CMS\Core\Database\Query\QueryHelper\getDateTimeTypes
‪static array getDateTimeTypes()
Definition: QueryHelper.php:211
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Core\Utility\HttpUtility\buildQueryString
‪static string buildQueryString(array $parameters, string $prependCharacter='', bool $skipEmptyParameters=false)
Definition: HttpUtility.php:124
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\addToStoreQueryConfigs
‪addToStoreQueryConfigs(array $storeQueryConfigs, int $index)
Definition: DatabaseIntegrityController.php:2561
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\procesData
‪procesData(ServerRequestInterface $request, array $qC=[])
Definition: DatabaseIntegrityController.php:2083
‪TYPO3\CMS\Core\Utility\CsvUtility
Definition: CsvUtility.php:26
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\mkFieldToInputSelect
‪mkFieldToInputSelect(string $name, string $fieldName)
Definition: DatabaseIntegrityController.php:2061
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\csvValues
‪csvValues(array $row, string $delim=',', string $quote='"', array $conf = [], string $table = '')
Definition: DatabaseIntegrityController.php:790
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getDateTimePickerField
‪getDateTimePickerField(string $name, string $timestamp, string $type)
Definition: DatabaseIntegrityController.php:1774
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$fields
‪array $fields
Definition: DatabaseIntegrityController.php:94
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\referenceIndexAction
‪referenceIndexAction(ModuleTemplate $view, ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:404
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$extFieldLists
‪array $extFieldLists
Definition: DatabaseIntegrityController.php:93
‪TYPO3\CMS\Core\Utility\DebugUtility
Definition: DebugUtility.php:27
‪TYPO3\CMS\Lowlevel\Controller
Definition: ConfigurationController.php:18
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getFuncCheck
‪string getFuncCheck(string $elementName, string|bool|int $currentValue, ServerRequestInterface $request, string $tagParams='')
Definition: DatabaseIntegrityController.php:2905
‪$output
‪$output
Definition: annotationChecker.php:114
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\init
‪init(string $name, string $table, string $fieldList='', array $settings=[])
Definition: DatabaseIntegrityController.php:2272
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\initStoreArray
‪initStoreArray()
Definition: DatabaseIntegrityController.php:2584
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\updateIcon
‪updateIcon()
Definition: DatabaseIntegrityController.php:2227
‪TYPO3\CMS\Backend\Template\ModuleTemplate\getDocHeaderComponent
‪getDocHeaderComponent()
Definition: ModuleTemplate.php:181
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\makeComparisonSelector
‪makeComparisonSelector(string $subscript, string $fieldName, array $conf)
Definition: DatabaseIntegrityController.php:2002
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\cleanStoreQueryConfigs
‪cleanStoreQueryConfigs(array $storeQueryConfigs, array $storeArray)
Definition: DatabaseIntegrityController.php:2548
‪TYPO3\CMS\Lowlevel\Integrity\DatabaseIntegrityCheck
Definition: DatabaseIntegrityCheck.php:43
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$showFieldAndTableNames
‪bool $showFieldAndTableNames
Definition: DatabaseIntegrityController.php:87
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getDropdownMenu
‪string getDropdownMenu(string $elementName, string|int $currentValue, mixed $menuItems, ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:2860
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:171
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\mkOperatorSelect
‪mkOperatorSelect(string $name, string $op, bool $draw, bool $submit)
Definition: DatabaseIntegrityController.php:1985
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$comp_offsets
‪array $comp_offsets
Definition: DatabaseIntegrityController.php:158
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$table
‪string $table
Definition: DatabaseIntegrityController.php:88
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\makeOptionList
‪makeOptionList(string $fieldName, array $conf, string $table)
Definition: DatabaseIntegrityController.php:1792
‪TYPO3\CMS\Core\Localization\Locale
Definition: Locale.php:30
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Backend\Template\ModuleTemplate\assign
‪assign(string $key, mixed $value)
Definition: ModuleTemplate.php:94
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$name
‪string $name
Definition: DatabaseIntegrityController.php:156
‪TYPO3\CMS\Core\Utility\GeneralUtility\inList
‪static bool inList($list, $item)
Definition: GeneralUtility.php:422
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\setUpDocHeader
‪setUpDocHeader(ModuleTemplate $moduleTemplate)
Definition: DatabaseIntegrityController.php:356
‪TYPO3\CMS\Backend\Attribute\AsController
Definition: AsController.php:25
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\mkCompSelect
‪mkCompSelect(string $name, int $comparison, int $neg)
Definition: DatabaseIntegrityController.php:2023
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:24
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\recordStatisticsAction
‪recordStatisticsAction(ModuleTemplate $view, ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:2707
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getQueryResultCode
‪array getQueryResultCode(string $type, array $dataRows, string $table, ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:712
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$MOD_MENU
‪array $MOD_MENU
Definition: DatabaseIntegrityController.php:73
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getSubscript
‪getSubscript($arr)
Definition: DatabaseIntegrityController.php:2186
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getProcessedValueExtra
‪getProcessedValueExtra(string $table, string $fieldName, string $fieldValue, array $conf, string $splitString)
Definition: DatabaseIntegrityController.php:902
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\renderNoResultsFoundMessage
‪renderNoResultsFoundMessage()
Definition: DatabaseIntegrityController.php:1217
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\queryMaker
‪queryMaker(ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:486
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\BUTTON_POSITION_RIGHT
‪const BUTTON_POSITION_RIGHT
Definition: ButtonBar.php:42
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\menuConfig
‪menuConfig(ServerRequestInterface $request)
Definition: DatabaseIntegrityController.php:258
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController
Definition: DatabaseIntegrityController.php:69
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getSelectQuery
‪getSelectQuery(string $qString='')
Definition: DatabaseIntegrityController.php:581
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getQuery
‪getQuery(array $queryConfig, string $pad='')
Definition: DatabaseIntegrityController.php:1227
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static list< int > intExplode(string $delimiter, string $string, bool $removeEmptyValues=false)
Definition: GeneralUtility.php:756
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\__construct
‪__construct(protected IconFactory $iconFactory, protected readonly UriBuilder $uriBuilder, protected readonly ModuleTemplateFactory $moduleTemplateFactory, protected readonly PlatformHelper $platformHelper,)
Definition: DatabaseIntegrityController.php:223
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\$compSQL
‪array $compSQL
Definition: DatabaseIntegrityController.php:168
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\loadStoreQueryConfigs
‪loadStoreQueryConfigs(array $storeQueryConfigs, int $storeIndex, array $writeArray)
Definition: DatabaseIntegrityController.php:2572
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\printCodeArray
‪printCodeArray(array $codeArr, int $recursionLevel=0)
Definition: DatabaseIntegrityController.php:2038
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:57
‪TYPO3\CMS\Core\Utility\CsvUtility\csvValues
‪static string csvValues(array $row, string $delim=',', string $quote='"', int $type = self::TYPE_REMOVE_CONTROLS)
Definition: CsvUtility.php:100
‪TYPO3\CMS\Core\Database\Platform\PlatformHelper
Definition: PlatformHelper.php:26
‪TYPO3\CMS\Lowlevel\Controller\DatabaseIntegrityController\getLanguageService
‪getLanguageService()
Definition: DatabaseIntegrityController.php:2842