‪TYPO3CMS  11.5
LiveSearch.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
26 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
37 
43 {
47  public const ‪RECURSIVE_PAGE_LEVEL = 99;
48 
52  private ‪$queryString = '';
53 
57  private ‪$startCount = 0;
58 
62  private ‪$limitCount = 5;
63 
67  protected ‪$userPermissions = '';
68 
72  protected ‪$queryParser;
73 
77  public function ‪__construct()
78  {
79  $this->userPermissions = $this->‪getBackendUser()->getPagePermsClause(‪Permission::PAGE_SHOW);
80  $this->queryParser = GeneralUtility::makeInstance(QueryParser::class);
81  }
82 
89  public function ‪find($searchQuery)
90  {
91  $recordArray = [];
92  $pageIdList = $this->‪getPageIdList();
93  if ($this->queryParser->isValidCommand($searchQuery)) {
94  $this->‪setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
95  $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
96  if ($tableName) {
97  $recordArray[] = $this->‪findByTable($tableName, $pageIdList, $this->startCount, $this->limitCount);
98  }
99  } else {
100  $this->‪setQueryString($searchQuery);
101  $recordArray = $this->‪findByGlobalTableList($pageIdList);
102  }
103  return $recordArray;
104  }
105 
109  protected function ‪getPageIdList(): array
110  {
111  $pageList = [];
112  if ($this->‪getBackendUser()->isAdmin()) {
113  return $pageList;
114  }
115  $mounts = $this->‪getBackendUser()->returnWebmounts();
116  foreach ($mounts as $pageId) {
117  $pageList[] = $this->‪getAvailablePageIds($pageId, self::RECURSIVE_PAGE_LEVEL);
118  }
119  return array_unique(explode(',', implode(',', $pageList)));
120  }
121 
128  protected function ‪findByGlobalTableList($pageIdList)
129  {
130  $limit = ‪$this->limitCount;
131  $getRecordArray = [];
132  foreach (‪$GLOBALS['TCA'] as $tableName => $value) {
133  // if no access for the table (read or write) or table is hidden, skip this table
134  if (
135  (isset($value['ctrl']['hideTable']) && $value['ctrl']['hideTable'])
136  ||
137  (
138  !$this->‪getBackendUser()->check('tables_select', $tableName) &&
139  !$this->‪getBackendUser()->check('tables_modify', $tableName)
140  )
141  ) {
142  continue;
143  }
144  $recordArray = $this->‪findByTable($tableName, $pageIdList, 0, $limit);
145  $recordCount = count($recordArray);
146  if ($recordCount) {
147  $limit -= $recordCount;
148  $getRecordArray[] = $recordArray;
149  if ($limit <= 0) {
150  break;
151  }
152  }
153  }
154  return $getRecordArray;
155  }
156 
169  protected function ‪findByTable($tableName, $pageIdList, $firstResult, $maxResults)
170  {
171  $fieldsToSearchWithin = $this->‪extractSearchableFieldsFromTable($tableName);
172  $getRecordArray = [];
173  if (!empty($fieldsToSearchWithin)) {
174  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
175  ->getQueryBuilderForTable($tableName);
176  $queryBuilder->getRestrictions()
177  ->removeByType(HiddenRestriction::class)
178  ->removeByType(StartTimeRestriction::class)
179  ->removeByType(EndTimeRestriction::class);
180 
181  $queryBuilder
182  ->select('*')
183  ->from($tableName)
184  ->where(
185  $this->‪makeQuerySearchByTable($queryBuilder, $tableName, $fieldsToSearchWithin)
186  )
187  ->setFirstResult($firstResult)
188  ->setMaxResults($maxResults);
189 
190  if ($pageIdList !== []) {
191  $queryBuilder->andWhere(
192  $queryBuilder->expr()->in(
193  'pid',
194  $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
195  )
196  );
197  }
198 
199  if ($tableName === 'pages' && $this->userPermissions) {
200  $queryBuilder->andWhere($this->userPermissions);
201  }
202 
203  $queryBuilder->addOrderBy('uid', 'DESC');
204 
205  $getRecordArray = $this->‪getRecordArray($queryBuilder, $tableName);
206  }
207 
208  return $getRecordArray;
209  }
210 
220  protected function ‪getRecordArray($queryBuilder, $tableName)
221  {
222  $collect = [];
223  $result = $queryBuilder->executeQuery();
224  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
225  while ($row = $result->fetchAssociative()) {
226  BackendUtility::workspaceOL($tableName, $row);
227  if (!is_array($row)) {
228  continue;
229  }
230  $onlineUid = ($row['t3ver_oid'] ?? false) ?: $row['uid'];
231  $title = 'id=' . $row['uid'] . ', pid=' . $row['pid'];
232  $collect[$onlineUid] = [
233  'id' => $tableName . ':' . $row['uid'],
234  'pageId' => $tableName === 'pages' ? $row['uid'] : $row['pid'],
235  'typeLabel' => $this->‪getTitleOfCurrentRecordType($tableName),
236  'iconHTML' => '<span title="' . htmlspecialchars($title) . '">' . $iconFactory->getIconForRecord($tableName, $row, ‪Icon::SIZE_SMALL)->render() . '</span>',
237  'title' => BackendUtility::getRecordTitle($tableName, $row),
238  'editLink' => $this->‪getEditLink($tableName, $row),
239  ];
240  }
241  return $collect;
242  }
243 
252  protected function ‪getEditLink($tableName, $row)
253  {
254  $backendUser = $this->‪getBackendUser();
255  $editLink = '';
256  if ($tableName === 'pages') {
257  $localCalcPerms = new Permission($backendUser->calcPerms(BackendUtility::getRecord('pages', $row['uid']) ?? []));
258  $permsEdit = $localCalcPerms->editPagePermissionIsGranted();
259  } else {
260  $calcPerms = new Permission($backendUser->calcPerms(BackendUtility::readPageAccess($row['pid'], $this->userPermissions) ?: []));
261  $permsEdit = $calcPerms->editContentPermissionIsGranted();
262  }
263  // "Edit" link - Only with proper edit permissions
264  if (!(‪$GLOBALS['TCA'][$tableName]['ctrl']['readOnly'] ?? false)
265  && (
266  $backendUser->isAdmin()
267  || (
268  $permsEdit
269  && !(‪$GLOBALS['TCA'][$tableName]['ctrl']['adminOnly'] ?? false)
270  && $backendUser->check('tables_modify', $tableName)
271  && $backendUser->recordEditAccessInternals($tableName, $row)
272  )
273  )
274  ) {
275  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
276  $returnUrl = (string)$uriBuilder->buildUriFromRoute('web_list', ['id' => $row['pid']]);
277  $editLink = (string)$uriBuilder->buildUriFromRoute('record_edit', [
278  'edit[' . $tableName . '][' . $row['uid'] . ']' => 'edit',
279  'returnUrl' => $returnUrl,
280  ]);
281  }
282  return $editLink;
283  }
284 
291  protected function ‪getTitleOfCurrentRecordType($tableName)
292  {
293  return $this->‪getLanguageService()->‪sL(‪$GLOBALS['TCA'][$tableName]['ctrl']['title']);
294  }
295 
304  protected function ‪makeQuerySearchByTable(QueryBuilder $queryBuilder, $tableName, array $fieldsToSearchWithin)
305  {
306  $constraints = [];
307 
308  // If the search string is a simple integer, assemble an equality comparison
309  if (‪MathUtility::canBeInterpretedAsInteger($this->queryString)) {
310  foreach ($fieldsToSearchWithin as $fieldName) {
311  if ($fieldName !== 'uid'
312  && $fieldName !== 'pid'
313  && !isset(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])
314  ) {
315  continue;
316  }
317  $fieldConfig = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'] ?? [];
318  $fieldType = $fieldConfig['type'] ?? '';
319  $evalRules = $fieldConfig['eval'] ?? '';
320 
321  // Assemble the search condition only if the field is an integer, or is uid or pid
322  if ($fieldName === 'uid'
323  || $fieldName === 'pid'
324  || ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int'))
325  ) {
326  $constraints[] = $queryBuilder->expr()->eq(
327  $fieldName,
328  $queryBuilder->createNamedParameter($this->queryString, ‪Connection::PARAM_INT)
329  );
330  } elseif ($fieldType === 'text'
331  || $fieldType === 'flex'
332  || $fieldType === 'slug'
333  || ($fieldType === 'input' && (!$evalRules || !preg_match('/\b(?:date|time|int)\b/', $evalRules)))
334  ) {
335  // Otherwise and if the field makes sense to be searched, assemble a like condition
336  $constraints[] = $queryBuilder->expr()->like(
337  $fieldName,
338  $queryBuilder->createNamedParameter(
339  '%' . $queryBuilder->escapeLikeWildcards($this->queryString) . '%',
341  )
342  );
343  }
344  }
345  } else {
346  $like = '%' . $queryBuilder->escapeLikeWildcards($this->queryString) . '%';
347  foreach ($fieldsToSearchWithin as $fieldName) {
348  if (!isset(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
349  continue;
350  }
351  $fieldConfig = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'] ?? [];
352  $fieldType = $fieldConfig['type'] ?? '';
353  $evalRules = $fieldConfig['eval'] ?? '';
354 
355  // Check whether search should be case-sensitive or not
356  $searchConstraint = $queryBuilder->expr()->andX(
357  $queryBuilder->expr()->comparison(
358  'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
359  'LIKE',
360  $queryBuilder->createNamedParameter(mb_strtolower($like), ‪Connection::PARAM_STR)
361  )
362  );
363 
364  if (is_array($fieldConfig['search'] ?? false)) {
365  if (in_array('case', $fieldConfig['search'], true)) {
366  // Replace case insensitive default constraint
367  $searchConstraint = $queryBuilder->expr()->andX(
368  $queryBuilder->expr()->like(
369  $fieldName,
370  $queryBuilder->createNamedParameter($like, ‪Connection::PARAM_STR)
371  )
372  );
373  }
374  // Apply additional condition, if any
375  if ($fieldConfig['search']['andWhere'] ?? false) {
376  if (GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('runtimeDbQuotingOfTcaConfiguration')) {
377  $searchConstraint->add(
378  ‪QueryHelper::stripLogicalOperatorPrefix(‪QueryHelper::quoteDatabaseIdentifiers($queryBuilder->getConnection(), $fieldConfig['search']['andWhere']))
379  );
380  } else {
381  $searchConstraint->add(
382  ‪QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
383  );
384  }
385  }
386  }
387  // Assemble the search condition only if the field makes sense to be searched
388  if ($fieldType === 'text'
389  || $fieldType === 'flex'
390  || $fieldType === 'slug'
391  || ($fieldType === 'input' && (!$evalRules || !preg_match('/\b(?:date|time|int)\b/', $evalRules)))
392  ) {
393  if ($searchConstraint->count() !== 0) {
394  $constraints[] = $searchConstraint;
395  }
396  }
397  }
398  }
399 
400  // If no search field conditions have been build ensure no results are returned
401  if (empty($constraints)) {
402  return '0=1';
403  }
404 
405  return $queryBuilder->expr()->orX(...$constraints);
406  }
407 
414  protected function ‪extractSearchableFieldsFromTable($tableName)
415  {
416  // Get the list of fields to search in from the TCA, if any
417  if (isset(‪$GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
418  $fieldListArray = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
419  } else {
420  $fieldListArray = [];
421  }
422  // Add special fields
423  if ($this->‪getBackendUser()->isAdmin()) {
424  $fieldListArray[] = 'uid';
425  $fieldListArray[] = 'pid';
426  }
427  return $fieldListArray;
428  }
429 
435  public function ‪setLimitCount(‪$limitCount)
436  {
438  if ($limit > 0) {
439  $this->limitCount = $limit;
440  }
441  }
442 
448  public function ‪setStartCount(‪$startCount)
449  {
451  }
452 
458  public function ‪setQueryString(‪$queryString)
459  {
460  $this->queryString = ‪$queryString;
461  }
462 
471  protected function ‪getAvailablePageIds($id, $depth)
472  {
473  $tree = GeneralUtility::makeInstance(PageTreeView::class);
474  $tree->init('AND ' . $this->userPermissions);
475  $tree->makeHTML = 0;
476  $tree->fieldArray = ['uid', 'php_tree_stop'];
477  if ($depth) {
478  $tree->getTree($id, $depth);
479  }
480  $tree->ids[] = $id;
481  // add workspace pid - workspace permissions are taken into account by where clause later
482  $tree->ids[] = -1;
483  return implode(',', $tree->ids);
484  }
485 
486  protected function ‪getBackendUser(): ‪BackendUserAuthentication
487  {
488  return ‪$GLOBALS['BE_USER'];
489  }
490 
494  protected function ‪getLanguageService(): ?‪LanguageService
495  {
496  return ‪$GLOBALS['LANG'] ?? null;
497  }
498 }
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:30
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:999
‪TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction
Definition: HiddenRestriction.php:27
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\findByTable
‪array findByTable($tableName, $pageIdList, $firstResult, $maxResults)
Definition: LiveSearch.php:164
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\__construct
‪__construct()
Definition: LiveSearch.php:72
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\extractSearchableFieldsFromTable
‪array extractSearchableFieldsFromTable($tableName)
Definition: LiveSearch.php:409
‪TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction
Definition: EndTimeRestriction.php:27
‪TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction
Definition: StartTimeRestriction.php:27
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getAvailablePageIds
‪string getAvailablePageIds($id, $depth)
Definition: LiveSearch.php:466
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch
Definition: LiveSearch.php:43
‪TYPO3\CMS\Backend\Search\LiveSearch\QueryParser
Definition: QueryParser.php:24
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$limitCount
‪int $limitCount
Definition: LiveSearch.php:59
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\RECURSIVE_PAGE_LEVEL
‪const RECURSIVE_PAGE_LEVEL
Definition: LiveSearch.php:47
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getEditLink
‪string getEditLink($tableName, $row)
Definition: LiveSearch.php:247
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$startCount
‪int $startCount
Definition: LiveSearch.php:55
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:161
‪TYPO3\CMS\Core\Database\Query\QueryHelper\quoteDatabaseIdentifiers
‪static string quoteDatabaseIdentifiers(Connection $connection, string $sql)
Definition: QueryHelper.php:228
‪TYPO3\CMS\Backend\Tree\View\PageTreeView
Definition: PageTreeView.php:24
‪TYPO3\CMS\Core\Database\Connection\PARAM_STR
‪const PARAM_STR
Definition: Connection.php:54
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getLanguageService
‪LanguageService null getLanguageService()
Definition: LiveSearch.php:489
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setStartCount
‪setStartCount($startCount)
Definition: LiveSearch.php:443
‪TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression
Definition: CompositeExpression.php:25
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Backend\Search\LiveSearch
Definition: LiveSearch.php:16
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\makeQuerySearchByTable
‪CompositeExpression makeQuerySearchByTable(QueryBuilder $queryBuilder, $tableName, array $fieldsToSearchWithin)
Definition: LiveSearch.php:299
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:40
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\findByGlobalTableList
‪array findByGlobalTableList($pageIdList)
Definition: LiveSearch.php:123
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\find
‪array find($searchQuery)
Definition: LiveSearch.php:84
‪TYPO3\CMS\Core\Configuration\Features
Definition: Features.php:56
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$queryParser
‪QueryParser $queryParser
Definition: LiveSearch.php:67
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getBackendUser
‪getBackendUser()
Definition: LiveSearch.php:481
‪TYPO3\CMS\Core\Utility\MathUtility\convertToPositiveInteger
‪static int convertToPositiveInteger($theInt)
Definition: MathUtility.php:56
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getTitleOfCurrentRecordType
‪string getTitleOfCurrentRecordType($tableName)
Definition: LiveSearch.php:286
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:171
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setQueryString
‪setQueryString($queryString)
Definition: LiveSearch.php:453
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$queryString
‪string $queryString
Definition: LiveSearch.php:51
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$userPermissions
‪string $userPermissions
Definition: LiveSearch.php:63
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getRecordArray
‪array getRecordArray($queryBuilder, $tableName)
Definition: LiveSearch.php:215
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setLimitCount
‪setLimitCount($limitCount)
Definition: LiveSearch.php:430
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getPageIdList
‪getPageIdList()
Definition: LiveSearch.php:104
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50