‪TYPO3CMS  10.4
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 
36 
42 {
47 
51  private ‪$queryString = '';
52 
56  private ‪$startCount = 0;
57 
61  private ‪$limitCount = 5;
62 
66  protected ‪$userPermissions = '';
67 
71  protected ‪$queryParser;
72 
76  public function ‪__construct()
77  {
78  $this->userPermissions = $this->‪getBackendUser()->getPagePermsClause(‪Permission::PAGE_SHOW);
79  $this->queryParser = GeneralUtility::makeInstance(QueryParser::class);
80  }
81 
88  public function ‪find($searchQuery)
89  {
90  $recordArray = [];
91  $pageList = [];
92  $mounts = $this->‪getBackendUser()->returnWebmounts();
93  foreach ($mounts as $pageId) {
94  $pageList[] = $this->‪getAvailablePageIds($pageId, self::RECURSIVE_PAGE_LEVEL);
95  }
96  $pageIdList = array_unique(explode(',', implode(',', $pageList)));
97  unset($pageList);
98  if ($this->queryParser->isValidCommand($searchQuery)) {
99  $this->‪setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
100  $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
101  if ($tableName) {
102  $recordArray[] = $this->‪findByTable($tableName, $pageIdList, $this->startCount, $this->limitCount);
103  }
104  } else {
105  $this->‪setQueryString($searchQuery);
106  $recordArray = $this->‪findByGlobalTableList($pageIdList);
107  }
108  return $recordArray;
109  }
110 
117  protected function ‪findByGlobalTableList($pageIdList)
118  {
119  $limit = ‪$this->limitCount;
120  $getRecordArray = [];
121  foreach (‪$GLOBALS['TCA'] as $tableName => $value) {
122  // if no access for the table (read or write) or table is hidden, skip this table
123  if (
124  (isset($value['ctrl']['hideTable']) && $value['ctrl']['hideTable'])
125  ||
126  (
127  !$this->‪getBackendUser()->check('tables_select', $tableName) &&
128  !$this->‪getBackendUser()->check('tables_modify', $tableName)
129  )
130  ) {
131  continue;
132  }
133  $recordArray = $this->‪findByTable($tableName, $pageIdList, 0, $limit);
134  $recordCount = count($recordArray);
135  if ($recordCount) {
136  $limit -= $recordCount;
137  $getRecordArray[] = $recordArray;
138  if ($limit <= 0) {
139  break;
140  }
141  }
142  }
143  return $getRecordArray;
144  }
145 
158  protected function ‪findByTable($tableName, $pageIdList, $firstResult, $maxResults)
159  {
160  $fieldsToSearchWithin = $this->‪extractSearchableFieldsFromTable($tableName);
161  $getRecordArray = [];
162  if (!empty($fieldsToSearchWithin)) {
163  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
164  ->getQueryBuilderForTable($tableName);
165  $queryBuilder->getRestrictions()
166  ->removeByType(HiddenRestriction::class)
167  ->removeByType(StartTimeRestriction::class)
168  ->removeByType(EndTimeRestriction::class);
169 
170  $queryBuilder
171  ->select('*')
172  ->from($tableName)
173  ->where(
174  $queryBuilder->expr()->in(
175  'pid',
176  $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
177  ),
178  $this->makeQuerySearchByTable($queryBuilder, $tableName, $fieldsToSearchWithin)
179  )
180  ->setFirstResult($firstResult)
181  ->setMaxResults($maxResults);
182 
183  if ($tableName === 'pages' && $this->userPermissions) {
184  $queryBuilder->andWhere($this->userPermissions);
185  }
186 
187  $orderBy = ‪$GLOBALS['TCA'][$tableName]['ctrl']['sortby'] ?: ‪$GLOBALS['TCA'][$tableName]['ctrl']['default_sortby'];
188  foreach (‪QueryHelper::parseOrderBy((string)$orderBy) as $orderPair) {
189  [$fieldName, $order] = $orderPair;
190  $queryBuilder->addOrderBy($fieldName, $order);
191  }
192 
193  $getRecordArray = $this->‪getRecordArray($queryBuilder, $tableName);
194  }
195 
196  return $getRecordArray;
197  }
198 
208  protected function ‪getRecordArray($queryBuilder, $tableName)
209  {
210  $collect = [];
211  $result = $queryBuilder->execute();
212  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
213  while ($row = $result->fetch()) {
214  ‪BackendUtility::workspaceOL($tableName, $row);
215  if (!is_array($row)) {
216  continue;
217  }
218  $onlineUid = $row['t3ver_oid'] ?: $row['uid'];
219  $title = 'id=' . $row['uid'] . ', pid=' . $row['pid'];
220  $collect[$onlineUid] = [
221  'id' => $tableName . ':' . $row['uid'],
222  'pageId' => $tableName === 'pages' ? $row['uid'] : $row['pid'],
223  'typeLabel' => htmlspecialchars($this->‪getTitleOfCurrentRecordType($tableName)),
224  'iconHTML' => '<span title="' . htmlspecialchars($title) . '">' . $iconFactory->getIconForRecord($tableName, $row, ‪Icon::SIZE_SMALL)->render() . '</span>',
225  'title' => htmlspecialchars(‪BackendUtility::getRecordTitle($tableName, $row)),
226  'editLink' => htmlspecialchars($this->‪getEditLink($tableName, $row))
227  ];
228  }
229  return $collect;
230  }
231 
240  protected function ‪getEditLink($tableName, $row)
241  {
242  $backendUser = $this->‪getBackendUser();
243  $editLink = '';
244  if ($tableName === 'pages') {
245  $permsEdit = $backendUser->calcPerms(‪BackendUtility::getRecord('pages', $row['uid']) ?? []) & ‪Permission::PAGE_EDIT;
246  } else {
247  $permsEdit = $backendUser->calcPerms(‪BackendUtility::readPageAccess($row['pid'], $this->userPermissions) ?: []) & ‪Permission::CONTENT_EDIT;
248  }
249  // "Edit" link - Only with proper edit permissions
250  if (!(‪$GLOBALS['TCA'][$tableName]['ctrl']['readOnly'] ?? false)
251  && (
252  $backendUser->isAdmin()
253  || (
254  $permsEdit
255  && !(‪$GLOBALS['TCA'][$tableName]['ctrl']['adminOnly'] ?? false)
256  && $backendUser->check('tables_modify', $tableName)
257  && $backendUser->recordEditAccessInternals($tableName, $row)
258  )
259  )
260  ) {
261  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
262  $returnUrl = (string)$uriBuilder->buildUriFromRoute('web_list', ['id' => $row['pid']]);
263  $editLink = (string)$uriBuilder->buildUriFromRoute('record_edit', [
264  'edit[' . $tableName . '][' . $row['uid'] . ']' => 'edit',
265  'returnUrl' => $returnUrl
266  ]);
267  }
268  return $editLink;
269  }
270 
277  protected function ‪getTitleOfCurrentRecordType($tableName)
278  {
279  return $this->‪getLanguageService()->‪sL(‪$GLOBALS['TCA'][$tableName]['ctrl']['title']);
280  }
281 
290  protected function ‪makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
291  {
292  $constraints = [];
293 
294  // If the search string is a simple integer, assemble an equality comparison
295  if (‪MathUtility::canBeInterpretedAsInteger($this->queryString)) {
296  foreach ($fieldsToSearchWithin as $fieldName) {
297  if ($fieldName !== 'uid'
298  && $fieldName !== 'pid'
299  && !isset(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])
300  ) {
301  continue;
302  }
303  $fieldConfig = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
304  $fieldType = $fieldConfig['type'];
305  $evalRules = $fieldConfig['eval'] ?: '';
306 
307  // Assemble the search condition only if the field is an integer, or is uid or pid
308  if ($fieldName === 'uid'
309  || $fieldName === 'pid'
310  || ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int'))
311  ) {
312  $constraints[] = $queryBuilder->expr()->eq(
313  $fieldName,
314  $queryBuilder->createNamedParameter($this->queryString, \PDO::PARAM_INT)
315  );
316  } elseif ($fieldType === 'text'
317  || $fieldType === 'flex'
318  || $fieldType === 'slug'
319  || ($fieldType === 'input' && (!$evalRules || !preg_match('/\b(?:date|time|int)\b/', $evalRules)))
320  ) {
321  // Otherwise and if the field makes sense to be searched, assemble a like condition
322  $constraints[] = $constraints[] = $queryBuilder->expr()->like(
323  $fieldName,
324  $queryBuilder->createNamedParameter(
325  '%' . $queryBuilder->escapeLikeWildcards((int)$this->queryString) . '%',
326  \PDO::PARAM_STR
327  )
328  );
329  }
330  }
331  } else {
332  $like = '%' . $queryBuilder->escapeLikeWildcards($this->queryString) . '%';
333  foreach ($fieldsToSearchWithin as $fieldName) {
334  if (!isset(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
335  continue;
336  }
337  $fieldConfig = &‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
338  $fieldType = $fieldConfig['type'];
339  $evalRules = $fieldConfig['eval'] ?: '';
340 
341  // Check whether search should be case-sensitive or not
342  $searchConstraint = $queryBuilder->expr()->andX(
343  $queryBuilder->expr()->comparison(
344  'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
345  'LIKE',
346  $queryBuilder->createNamedParameter(mb_strtolower($like), \PDO::PARAM_STR)
347  )
348  );
349 
350  if (is_array($fieldConfig['search'])) {
351  if (in_array('case', $fieldConfig['search'], true)) {
352  // Replace case insensitive default constraint
353  $searchConstraint = $queryBuilder->expr()->andX(
354  $queryBuilder->expr()->like(
355  $fieldName,
356  $queryBuilder->createNamedParameter($like, \PDO::PARAM_STR)
357  )
358  );
359  }
360  // Apply additional condition, if any
361  if ($fieldConfig['search']['andWhere']) {
362  $searchConstraint->add(
363  ‪QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
364  );
365  }
366  }
367  // Assemble the search condition only if the field makes sense to be searched
368  if ($fieldType === 'text'
369  || $fieldType === 'flex'
370  || $fieldType === 'slug'
371  || ($fieldType === 'input' && (!$evalRules || !preg_match('/\b(?:date|time|int)\b/', $evalRules)))
372  ) {
373  if ($searchConstraint->count() !== 0) {
374  $constraints[] = $searchConstraint;
375  }
376  }
377  }
378  }
379 
380  // If no search field conditions have been build ensure no results are returned
381  if (empty($constraints)) {
382  return '0=1';
383  }
384 
385  return $queryBuilder->expr()->orX(...$constraints);
386  }
387 
394  protected function ‪extractSearchableFieldsFromTable($tableName)
395  {
396  // Get the list of fields to search in from the TCA, if any
397  if (isset(‪$GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
398  $fieldListArray = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
399  } else {
400  $fieldListArray = [];
401  }
402  // Add special fields
403  if ($this->‪getBackendUser()->isAdmin()) {
404  $fieldListArray[] = 'uid';
405  $fieldListArray[] = 'pid';
406  }
407  return $fieldListArray;
408  }
409 
415  public function ‪setLimitCount(‪$limitCount)
416  {
418  if ($limit > 0) {
419  $this->limitCount = $limit;
420  }
421  }
422 
428  public function ‪setStartCount(‪$startCount)
429  {
431  }
432 
438  public function ‪setQueryString(‪$queryString)
439  {
440  $this->queryString = ‪$queryString;
441  }
442 
451  protected function ‪getAvailablePageIds($id, $depth)
452  {
453  $tree = GeneralUtility::makeInstance(PageTreeView::class);
454  $tree->init('AND ' . $this->userPermissions);
455  $tree->makeHTML = 0;
456  $tree->fieldArray = ['uid', 'php_tree_stop'];
457  if ($depth) {
458  $tree->getTree($id, $depth, '');
459  }
460  $tree->ids[] = $id;
461  // add workspace pid - workspace permissions are taken into account by where clause later
462  $tree->ids[] = -1;
463  return implode(',', $tree->ids);
464  }
465 
466  protected function ‪getBackendUser(): ‪BackendUserAuthentication
467  {
468  return ‪$GLOBALS['BE_USER'];
469  }
470 
474  protected function ‪getLanguageService(): ?‪LanguageService
475  {
476  return ‪$GLOBALS['LANG'] ?? null;
477  }
478 }
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\quoteIdentifier
‪string quoteIdentifier(string $identifier)
Definition: QueryBuilder.php:999
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:30
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:44
‪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:153
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\like
‪string like(string $fieldName, $value)
Definition: ExpressionBuilder.php:217
‪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:71
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\extractSearchableFieldsFromTable
‪array extractSearchableFieldsFromTable($tableName)
Definition: LiveSearch.php:389
‪TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction
Definition: EndTimeRestriction.php:27
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\eq
‪string eq(string $fieldName, $value)
Definition: ExpressionBuilder.php:109
‪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:446
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch
Definition: LiveSearch.php:42
‪TYPO3\CMS\Backend\Search\LiveSearch\QueryParser
Definition: QueryParser.php:24
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$limitCount
‪int $limitCount
Definition: LiveSearch.php:58
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\RECURSIVE_PAGE_LEVEL
‪const RECURSIVE_PAGE_LEVEL
Definition: LiveSearch.php:46
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\createNamedParameter
‪string createNamedParameter($value, int $type=\PDO::PARAM_STR, string $placeHolder=null)
Definition: QueryBuilder.php:941
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\escapeLikeWildcards
‪string escapeLikeWildcards(string $value)
Definition: QueryBuilder.php:971
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getEditLink
‪string getEditLink($tableName, $row)
Definition: LiveSearch.php:235
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\makeQuerySearchByTable
‪CompositeExpression makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
Definition: LiveSearch.php:285
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$startCount
‪int $startCount
Definition: LiveSearch.php:54
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:33
‪TYPO3\CMS\Core\Localization\LanguageService\sL
‪string sL($input)
Definition: LanguageService.php:194
‪TYPO3\CMS\Backend\Tree\View\PageTreeView
Definition: PageTreeView.php:24
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:24
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getLanguageService
‪LanguageService null getLanguageService()
Definition: LiveSearch.php:469
‪TYPO3\CMS\Core\Database\Query\QueryBuilder
Definition: QueryBuilder.php:52
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setStartCount
‪setStartCount($startCount)
Definition: LiveSearch.php:423
‪TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression
Definition: CompositeExpression.php:25
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\andX
‪CompositeExpression andX(... $expressions)
Definition: ExpressionBuilder.php:70
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Backend\Search\LiveSearch
Definition: LiveSearch.php:16
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:38
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\findByGlobalTableList
‪array findByGlobalTableList($pageIdList)
Definition: LiveSearch.php:112
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\find
‪array find($searchQuery)
Definition: LiveSearch.php:83
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitle
‪static string getRecordTitle($table, $row, $prep=false, $forceResult=true)
Definition: BackendUtility.php:1541
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\orX
‪CompositeExpression orX(... $expressions)
Definition: ExpressionBuilder.php:82
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$queryParser
‪QueryParser $queryParser
Definition: LiveSearch.php:66
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:33
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getBackendUser
‪getBackendUser()
Definition: LiveSearch.php:461
‪TYPO3\CMS\Core\Utility\MathUtility\convertToPositiveInteger
‪static int convertToPositiveInteger($theInt)
Definition: MathUtility.php:56
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:95
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪TYPO3\CMS\Backend\Utility\BackendUtility\readPageAccess
‪static array false readPageAccess($id, $perms_clause)
Definition: BackendUtility.php:597
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:36
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getTitleOfCurrentRecordType
‪string getTitleOfCurrentRecordType($tableName)
Definition: LiveSearch.php:272
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:53
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:165
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setQueryString
‪setQueryString($queryString)
Definition: LiveSearch.php:433
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$queryString
‪string $queryString
Definition: LiveSearch.php:50
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Backend\Utility\BackendUtility\workspaceOL
‪static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
Definition: BackendUtility.php:3586
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$userPermissions
‪string $userPermissions
Definition: LiveSearch.php:62
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_EDIT
‪const PAGE_EDIT
Definition: Permission.php:38
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getRecordArray
‪array getRecordArray($queryBuilder, $tableName)
Definition: LiveSearch.php:203
‪TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression\add
‪Doctrine DBAL Query Expression CompositeExpression add($part)
Definition: CompositeExpression.php:48
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setLimitCount
‪setLimitCount($limitCount)
Definition: LiveSearch.php:410
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\expr
‪ExpressionBuilder expr()
Definition: QueryBuilder.php:151
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\comparison
‪string comparison($leftExpression, string $operator, $rightExpression)
Definition: ExpressionBuilder.php:96