‪TYPO3CMS  9.5
LiveSearch.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
32 
38 {
42  const ‪PAGE_JUMP_TABLE = 'pages';
43 
48 
53 
58 
62  private ‪$queryString = '';
63 
67  private ‪$startCount = 0;
68 
72  private ‪$limitCount = 5;
73 
77  protected ‪$userPermissions = '';
78 
82  protected ‪$queryParser;
83 
87  public function ‪__construct()
88  {
89  $this->userPermissions = ‪$GLOBALS['BE_USER']->getPagePermsClause(‪Permission::PAGE_SHOW);
90  $this->queryParser = GeneralUtility::makeInstance(QueryParser::class);
91  }
92 
99  public function ‪find($searchQuery)
100  {
101  $recordArray = [];
102  $pageList = [];
103  $mounts = ‪$GLOBALS['BE_USER']->returnWebmounts();
104  foreach ($mounts as $pageId) {
105  $pageList[] = $this->‪getAvailablePageIds($pageId, self::RECURSIVE_PAGE_LEVEL);
106  }
107  $pageIdList = array_unique(explode(',', implode(',', $pageList)));
108  unset($pageList);
109  if ($this->queryParser->isValidCommand($searchQuery)) {
110  $this->‪setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
111  $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
112  if ($tableName) {
113  $recordArray[] = $this->‪findByTable($tableName, $pageIdList, $this->startCount, $this->limitCount);
114  }
115  } else {
116  $this->‪setQueryString($searchQuery);
117  $recordArray = $this->‪findByGlobalTableList($pageIdList);
118  }
119  return $recordArray;
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  !‪$GLOBALS['BE_USER']->check('tables_select', $tableName) &&
139  !‪$GLOBALS['BE_USER']->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  $queryBuilder->expr()->in(
186  'pid',
187  $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
188  ),
189  $this->makeQuerySearchByTable($queryBuilder, $tableName, $fieldsToSearchWithin)
190  )
191  ->setFirstResult($firstResult)
192  ->setMaxResults($maxResults);
193 
194  if ($tableName === 'pages' && $this->userPermissions) {
195  $queryBuilder->andWhere($this->userPermissions);
196  }
197 
198  $orderBy = ‪$GLOBALS['TCA'][$tableName]['ctrl']['sortby'] ?: ‪$GLOBALS['TCA'][$tableName]['ctrl']['default_sortby'];
199  foreach (‪QueryHelper::parseOrderBy((string)$orderBy) as $orderPair) {
200  list($fieldName, $order) = $orderPair;
201  $queryBuilder->addOrderBy($fieldName, $order);
202  }
203 
204  $getRecordArray = $this->‪getRecordArray($queryBuilder, $tableName);
205  }
206 
207  return $getRecordArray;
208  }
209 
219  protected function ‪getRecordArray($queryBuilder, $tableName)
220  {
221  $collect = [];
222  $result = $queryBuilder->execute();
223  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
224  while ($row = $result->fetch()) {
225  ‪BackendUtility::workspaceOL($tableName, $row);
226  if (!is_array($row)) {
227  continue;
228  }
229  $onlineUid = $row['t3ver_oid'] ?: $row['uid'];
230  $title = 'id=' . $row['uid'] . ', pid=' . $row['pid'];
231  $collect[$onlineUid] = [
232  'id' => $tableName . ':' . $row['uid'],
233  'pageId' => $tableName === 'pages' ? $row['uid'] : $row['pid'],
234  'typeLabel' => htmlspecialchars($this->‪getTitleOfCurrentRecordType($tableName)),
235  'iconHTML' => '<span title="' . htmlspecialchars($title) . '">' . $iconFactory->getIconForRecord($tableName, $row, ‪Icon::SIZE_SMALL)->render() . '</span>',
236  'title' => htmlspecialchars(‪BackendUtility::getRecordTitle($tableName, $row)),
237  'editLink' => htmlspecialchars($this->‪getEditLink($tableName, $row))
238  ];
239  }
240  return $collect;
241  }
242 
251  protected function ‪getEditLink($tableName, $row)
252  {
253  $pageInfo = ‪BackendUtility::readPageAccess($row['pid'], $this->userPermissions);
254  $calcPerms = ‪$GLOBALS['BE_USER']->calcPerms($pageInfo);
255  $editLink = '';
256  if ($tableName === 'pages') {
257  $localCalcPerms = ‪$GLOBALS['BE_USER']->calcPerms(‪BackendUtility::getRecord('pages', $row['uid']));
258  $permsEdit = $localCalcPerms & ‪Permission::PAGE_EDIT;
259  } else {
260  $permsEdit = $calcPerms & ‪Permission::CONTENT_EDIT;
261  }
262  // "Edit" link - Only if permissions to edit the page-record of the content of the parent page ($this->id)
263  if ($permsEdit) {
264  $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
265  $returnUrl = (string)$uriBuilder->buildUriFromRoute('web_list', ['id' => $row['pid']]);
266  $editLink = (string)$uriBuilder->buildUriFromRoute('record_edit', [
267  'edit[' . $tableName . '][' . $row['uid'] . ']' => 'edit',
268  'returnUrl' => $returnUrl
269  ]);
270  }
271  return $editLink;
272  }
273 
280  protected function ‪getTitleOfCurrentRecordType($tableName)
281  {
282  return ‪$GLOBALS['LANG']->sL(‪$GLOBALS['TCA'][$tableName]['ctrl']['title']);
283  }
284 
294  public function ‪getRecordTitlePrep($title, $titleLength = 0)
295  {
296  // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
297  if (!$titleLength || !‪MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
298  $titleLength = ‪$GLOBALS['BE_USER']->uc['titleLen'];
299  }
300  return htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
301  }
302 
311  protected function ‪makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
312  {
313  $constraints = [];
314 
315  // If the search string is a simple integer, assemble an equality comparison
316  if (‪MathUtility::canBeInterpretedAsInteger($this->queryString)) {
317  foreach ($fieldsToSearchWithin as $fieldName) {
318  if ($fieldName !== 'uid'
319  && $fieldName !== 'pid'
320  && !isset(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])
321  ) {
322  continue;
323  }
324  $fieldConfig = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
325  $fieldType = $fieldConfig['type'];
326  $evalRules = $fieldConfig['eval'] ?: '';
327 
328  // Assemble the search condition only if the field is an integer, or is uid or pid
329  if ($fieldName === 'uid'
330  || $fieldName === 'pid'
331  || ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int'))
332  ) {
333  $constraints[] = $queryBuilder->expr()->eq(
334  $fieldName,
335  $queryBuilder->createNamedParameter($this->queryString, \PDO::PARAM_INT)
336  );
337  } elseif ($fieldType === 'text'
338  || $fieldType === 'flex'
339  || ($fieldType === 'input' && (!$evalRules || !preg_match('/\b(?:date|time|int)\b/', $evalRules)))
340  ) {
341  // Otherwise and if the field makes sense to be searched, assemble a like condition
342  $constraints[] = $constraints[] = $queryBuilder->expr()->like(
343  $fieldName,
344  $queryBuilder->createNamedParameter(
345  '%' . $queryBuilder->escapeLikeWildcards((int)$this->queryString) . '%',
346  \PDO::PARAM_STR
347  )
348  );
349  }
350  }
351  } else {
352  $like = '%' . $queryBuilder->escapeLikeWildcards($this->queryString) . '%';
353  foreach ($fieldsToSearchWithin as $fieldName) {
354  if (!isset(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
355  continue;
356  }
357  $fieldConfig = &‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
358  $fieldType = $fieldConfig['type'];
359  $evalRules = $fieldConfig['eval'] ?: '';
360 
361  // Check whether search should be case-sensitive or not
362  $searchConstraint = $queryBuilder->expr()->andX(
363  $queryBuilder->expr()->comparison(
364  'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
365  'LIKE',
366  $queryBuilder->createNamedParameter(mb_strtolower($like), \PDO::PARAM_STR)
367  )
368  );
369 
370  if (is_array($fieldConfig['search'])) {
371  if (in_array('case', $fieldConfig['search'], true)) {
372  // Replace case insensitive default constraint
373  $searchConstraint = $queryBuilder->expr()->andX(
374  $queryBuilder->expr()->like(
375  $fieldName,
376  $queryBuilder->createNamedParameter($like, \PDO::PARAM_STR)
377  )
378  );
379  }
380  // Apply additional condition, if any
381  if ($fieldConfig['search']['andWhere']) {
382  $searchConstraint->add(
383  ‪QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
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 === 'input' && (!$evalRules || !preg_match('/\b(?:date|time|int)\b/', $evalRules)))
391  ) {
392  if ($searchConstraint->count() !== 0) {
393  $constraints[] = $searchConstraint;
394  }
395  }
396  }
397  }
398 
399  // If no search field conditions have been build ensure no results are returned
400  if (empty($constraints)) {
401  return '0=1';
402  }
403 
404  return $queryBuilder->expr()->orX(...$constraints);
405  }
406 
413  protected function ‪extractSearchableFieldsFromTable($tableName)
414  {
415  // Get the list of fields to search in from the TCA, if any
416  if (isset(‪$GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
417  $fieldListArray = GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
418  } else {
419  $fieldListArray = [];
420  }
421  // Add special fields
422  if (‪$GLOBALS['BE_USER']->isAdmin()) {
423  $fieldListArray[] = 'uid';
424  $fieldListArray[] = 'pid';
425  }
426  return $fieldListArray;
427  }
428 
434  public function ‪setLimitCount(‪$limitCount)
435  {
437  if ($limit > 0) {
438  $this->limitCount = $limit;
439  }
440  }
441 
447  public function ‪setStartCount(‪$startCount)
448  {
450  }
451 
457  public function ‪setQueryString(‪$queryString)
458  {
459  $this->queryString = ‪$queryString;
460  }
461 
470  protected function ‪getAvailablePageIds($id, $depth)
471  {
472  $tree = GeneralUtility::makeInstance(\‪TYPO3\CMS\Backend\Tree\View\PageTreeView::class);
473  $tree->init('AND ' . $this->userPermissions);
474  $tree->makeHTML = 0;
475  $tree->fieldArray = ['uid', 'php_tree_stop'];
476  if ($depth) {
477  $tree->getTree($id, $depth, '');
478  }
479  $tree->ids[] = $id;
480  // add workspace pid - workspace permissions are taken into account by where clause later
481  $tree->ids[] = -1;
482  return implode(',', $tree->ids);
483  }
484 }
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\quoteIdentifier
‪string quoteIdentifier(string $identifier)
Definition: QueryBuilder.php:952
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:29
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:42
‪TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction
Definition: HiddenRestriction.php:25
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\findByTable
‪array findByTable($tableName, $pageIdList, $firstResult, $maxResults)
Definition: LiveSearch.php:164
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\like
‪string like(string $fieldName, $value)
Definition: ExpressionBuilder.php:215
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:73
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\__construct
‪__construct()
Definition: LiveSearch.php:82
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\extractSearchableFieldsFromTable
‪array extractSearchableFieldsFromTable($tableName)
Definition: LiveSearch.php:408
‪TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction
Definition: EndTimeRestriction.php:25
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\eq
‪string eq(string $fieldName, $value)
Definition: ExpressionBuilder.php:107
‪TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction
Definition: StartTimeRestriction.php:25
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:25
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getAvailablePageIds
‪string getAvailablePageIds($id, $depth)
Definition: LiveSearch.php:465
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch
Definition: LiveSearch.php:38
‪TYPO3\CMS\Backend\Search\LiveSearch\QueryParser
Definition: QueryParser.php:23
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$limitCount
‪int $limitCount
Definition: LiveSearch.php:69
‪TYPO3
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\RECURSIVE_PAGE_LEVEL
‪const RECURSIVE_PAGE_LEVEL
Definition: LiveSearch.php:47
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\createNamedParameter
‪string createNamedParameter($value, int $type=\PDO::PARAM_STR, string $placeHolder=null)
Definition: QueryBuilder.php:894
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\escapeLikeWildcards
‪string escapeLikeWildcards(string $value)
Definition: QueryBuilder.php:924
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getEditLink
‪string getEditLink($tableName, $row)
Definition: LiveSearch.php:246
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\makeQuerySearchByTable
‪CompositeExpression makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
Definition: LiveSearch.php:306
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$startCount
‪int $startCount
Definition: LiveSearch.php:65
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:31
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:23
‪TYPO3\CMS\Core\Database\Query\QueryBuilder
Definition: QueryBuilder.php:47
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setStartCount
‪setStartCount($startCount)
Definition: LiveSearch.php:442
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\PAGE_JUMP_TABLE
‪const PAGE_JUMP_TABLE
Definition: LiveSearch.php:42
‪TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression
Definition: CompositeExpression.php:23
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\andX
‪CompositeExpression andX(... $expressions)
Definition: ExpressionBuilder.php:68
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:30
‪TYPO3\CMS\Backend\Search\LiveSearch
Definition: LiveSearch.php:2
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:35
‪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:94
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordTitle
‪static string getRecordTitle($table, $row, $prep=false, $forceResult=true)
Definition: BackendUtility.php:1811
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\orX
‪CompositeExpression orX(... $expressions)
Definition: ExpressionBuilder.php:80
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$queryParser
‪QueryParser $queryParser
Definition: LiveSearch.php:77
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\RECORD_TITLE_MAX_LENGTH
‪const RECORD_TITLE_MAX_LENGTH
Definition: LiveSearch.php:57
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:32
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Core\Utility\MathUtility\convertToPositiveInteger
‪static int convertToPositiveInteger($theInt)
Definition: MathUtility.php:55
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:31
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getRecordTitlePrep
‪string getRecordTitlePrep($title, $titleLength=0)
Definition: LiveSearch.php:289
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getTitleOfCurrentRecordType
‪string getTitleOfCurrentRecordType($tableName)
Definition: LiveSearch.php:275
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:52
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:163
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setQueryString
‪setQueryString($queryString)
Definition: LiveSearch.php:452
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$queryString
‪string $queryString
Definition: LiveSearch.php:61
‪$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:4048
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\$userPermissions
‪string $userPermissions
Definition: LiveSearch.php:73
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_EDIT
‪const PAGE_EDIT
Definition: Permission.php:37
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\getRecordArray
‪array getRecordArray($queryBuilder, $tableName)
Definition: LiveSearch.php:214
‪TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression\add
‪Doctrine DBAL Query Expression CompositeExpression add($part)
Definition: CompositeExpression.php:46
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\setLimitCount
‪setLimitCount($limitCount)
Definition: LiveSearch.php:429
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\expr
‪ExpressionBuilder expr()
Definition: QueryBuilder.php:125
‪TYPO3\CMS\Backend\Search\LiveSearch\LiveSearch\GROUP_TITLE_MAX_LENGTH
‪const GROUP_TITLE_MAX_LENGTH
Definition: LiveSearch.php:52
‪TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder\comparison
‪string comparison($leftExpression, string $operator, $rightExpression)
Definition: ExpressionBuilder.php:94
‪TYPO3\CMS\Backend\Utility\BackendUtility\readPageAccess
‪static array bool readPageAccess($id, $perms_clause)
Definition: BackendUtility.php:635