TYPO3 CMS  TYPO3_8-7
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 
31 
36 {
40  const PAGE_JUMP_TABLE = 'pages';
41 
46 
51 
56 
60  private $queryString = '';
61 
65  private $startCount = 0;
66 
70  private $limitCount = 5;
71 
75  protected $userPermissions = '';
76 
80  protected $queryParser = null;
81 
85  public function __construct()
86  {
87  $this->userPermissions = $GLOBALS['BE_USER']->getPagePermsClause(1);
88  $this->queryParser = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Search\LiveSearch\QueryParser::class);
89  }
90 
97  public function find($searchQuery)
98  {
99  $recordArray = [];
100  $pageList = [];
101  $mounts = $GLOBALS['BE_USER']->returnWebmounts();
102  foreach ($mounts as $pageId) {
103  $pageList[] = $this->getAvailablePageIds($pageId, self::RECURSIVE_PAGE_LEVEL);
104  }
105  $pageIdList = array_unique(explode(',', implode(',', $pageList)));
106  unset($pageList);
107  if ($this->queryParser->isValidCommand($searchQuery)) {
108  $this->setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
109  $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
110  if ($tableName) {
111  $recordArray[] = $this->findByTable($tableName, $pageIdList, $this->startCount, $this->limitCount);
112  }
113  } else {
114  $this->setQueryString($searchQuery);
115  $recordArray = $this->findByGlobalTableList($pageIdList);
116  }
117  return $recordArray;
118  }
119 
126  protected function findPageById($id)
127  {
128  $pageRecord = [];
129  $row = BackendUtility::getRecord(self::PAGE_JUMP_TABLE, $id);
130  if (is_array($row)) {
131  $pageRecord = $row;
132  }
133  return $pageRecord;
134  }
135 
142  protected function findByGlobalTableList($pageIdList)
143  {
144  $limit = $this->limitCount;
145  $getRecordArray = [];
146  foreach ($GLOBALS['TCA'] as $tableName => $value) {
147  // if no access for the table (read or write) or table is hidden, skip this table
148  if (
149  (
150  !$GLOBALS['BE_USER']->check('tables_select', $tableName) &&
151  !$GLOBALS['BE_USER']->check('tables_modify', $tableName)
152  ) ||
153  (isset($value['ctrl']['hideTable']) && $value['ctrl']['hideTable'])
154  ) {
155  continue;
156  }
157  $recordArray = $this->findByTable($tableName, $pageIdList, 0, $limit);
158  $recordCount = count($recordArray);
159  if ($recordCount) {
160  $limit = $limit - $recordCount;
161  $getRecordArray[] = $recordArray;
162  if ($limit <= 0) {
163  break;
164  }
165  }
166  }
167  return $getRecordArray;
168  }
169 
182  protected function findByTable($tableName, $pageIdList, $firstResult, $maxResults)
183  {
184  $fieldsToSearchWithin = $this->extractSearchableFieldsFromTable($tableName);
185  $getRecordArray = [];
186  if (!empty($fieldsToSearchWithin)) {
187  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
188  ->getQueryBuilderForTable($tableName);
189  $queryBuilder->getRestrictions()
190  ->removeByType(HiddenRestriction::class)
191  ->removeByType(StartTimeRestriction::class)
192  ->removeByType(EndTimeRestriction::class);
193 
194  $queryBuilder
195  ->select('*')
196  ->from($tableName)
197  ->where(
198  $queryBuilder->expr()->in(
199  'pid',
200  $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
201  ),
202  $this->makeQuerySearchByTable($queryBuilder, $tableName, $fieldsToSearchWithin)
203  )
204  ->setFirstResult($firstResult)
205  ->setMaxResults($maxResults);
206 
207  if ($tableName === 'pages' && $this->userPermissions) {
208  $queryBuilder->andWhere($this->userPermissions);
209  }
210 
211  $orderBy = $GLOBALS['TCA'][$tableName]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$tableName]['ctrl']['default_sortby'];
212  foreach (QueryHelper::parseOrderBy((string)$orderBy) as $orderPair) {
213  list($fieldName, $order) = $orderPair;
214  $queryBuilder->addOrderBy($fieldName, $order);
215  }
216 
217  $getRecordArray = $this->getRecordArray($queryBuilder, $tableName);
218  }
219 
220  return $getRecordArray;
221  }
222 
232  protected function getRecordArray($queryBuilder, $tableName)
233  {
234  $collect = [];
235  $result = $queryBuilder->execute();
236  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
237  while ($row = $result->fetch()) {
238  $title = 'id=' . $row['uid'] . ', pid=' . $row['pid'];
239  $collect[] = [
240  'id' => $tableName . ':' . $row['uid'],
241  'pageId' => $tableName === 'pages' ? $row['uid'] : $row['pid'],
242  'typeLabel' => htmlspecialchars($this->getTitleOfCurrentRecordType($tableName)),
243  'iconHTML' => '<span title="' . htmlspecialchars($title) . '">' . $iconFactory->getIconForRecord($tableName, $row, Icon::SIZE_SMALL)->render() . '</span>',
244  'title' => htmlspecialchars(BackendUtility::getRecordTitle($tableName, $row)),
245  'editLink' => htmlspecialchars($this->getEditLink($tableName, $row))
246  ];
247  }
248  return $collect;
249  }
250 
259  protected function getEditLink($tableName, $row)
260  {
261  $pageInfo = BackendUtility::readPageAccess($row['pid'], $this->userPermissions);
262  $calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo);
263  $editLink = '';
264  if ($tableName === 'pages') {
265  $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
266  $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
267  } else {
268  $permsEdit = $calcPerms & Permission::CONTENT_EDIT;
269  }
270  // "Edit" link - Only if permissions to edit the page-record of the content of the parent page ($this->id)
271  if ($permsEdit) {
272  $returnUrl = BackendUtility::getModuleUrl('web_list', ['id' => $row['pid']]);
273  $editLink = BackendUtility::getModuleUrl('record_edit', [
274  'edit[' . $tableName . '][' . $row['uid'] . ']' => 'edit',
275  'returnUrl' => $returnUrl
276  ]);
277  }
278  return $editLink;
279  }
280 
287  protected function getTitleOfCurrentRecordType($tableName)
288  {
289  return $GLOBALS['LANG']->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']);
290  }
291 
301  public function getRecordTitlePrep($title, $titleLength = 0)
302  {
303  // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
304  if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
305  $titleLength = $GLOBALS['BE_USER']->uc['titleLen'];
306  }
307  return htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
308  }
309 
318  protected function makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
319  {
320  $constraints = [];
321 
322  // If the search string is a simple integer, assemble an equality comparison
323  if (MathUtility::canBeInterpretedAsInteger($this->queryString)) {
324  foreach ($fieldsToSearchWithin as $fieldName) {
325  if ($fieldName !== 'uid'
326  && $fieldName !== 'pid'
327  && !isset($GLOBALS['TCA'][$tableName]['columns'][$fieldName])
328  ) {
329  continue;
330  }
331  $fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
332  $fieldType = $fieldConfig['type'];
333  $evalRules = $fieldConfig['eval'] ?: '';
334 
335  // Assemble the search condition only if the field is an integer, or is uid or pid
336  if ($fieldName === 'uid'
337  || $fieldName === 'pid'
338  || ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int'))
339  ) {
340  $constraints[] = $queryBuilder->expr()->eq(
341  $fieldName,
342  $queryBuilder->createNamedParameter($this->queryString, \PDO::PARAM_INT)
343  );
344  } elseif ($fieldType === 'text'
345  || $fieldType === 'flex'
346  || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
347  ) {
348  // Otherwise and if the field makes sense to be searched, assemble a like condition
349  $constraints[] = $constraints[] = $queryBuilder->expr()->like(
350  $fieldName,
351  $queryBuilder->createNamedParameter(
352  '%' . $queryBuilder->escapeLikeWildcards((int)$this->queryString) . '%',
353  \PDO::PARAM_STR
354  )
355  );
356  }
357  }
358  } else {
359  $like = '%' . $queryBuilder->escapeLikeWildcards($this->queryString) . '%';
360  foreach ($fieldsToSearchWithin as $fieldName) {
361  if (!isset($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
362  continue;
363  }
364  $fieldConfig = &$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
365  $fieldType = $fieldConfig['type'];
366  $evalRules = $fieldConfig['eval'] ?: '';
367 
368  // Check whether search should be case-sensitive or not
369  $searchConstraint = $queryBuilder->expr()->andX(
370  $queryBuilder->expr()->comparison(
371  'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
372  'LIKE',
373  $queryBuilder->createNamedParameter(mb_strtolower($like), \PDO::PARAM_STR)
374  )
375  );
376 
377  if (is_array($fieldConfig['search'])) {
378  if (in_array('case', $fieldConfig['search'], true)) {
379  // Replace case insensitive default constraint
380  $searchConstraint = $queryBuilder->expr()->andX(
381  $queryBuilder->expr()->like(
382  $fieldName,
383  $queryBuilder->createNamedParameter($like, \PDO::PARAM_STR)
384  )
385  );
386  }
387  // Apply additional condition, if any
388  if ($fieldConfig['search']['andWhere']) {
389  $searchConstraint->add(
390  QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
391  );
392  }
393  }
394  // Assemble the search condition only if the field makes sense to be searched
395  if ($fieldType === 'text'
396  || $fieldType === 'flex'
397  || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
398  ) {
399  if ($searchConstraint->count() !== 0) {
400  $constraints[] = $searchConstraint;
401  }
402  }
403  }
404  }
405 
406  // If no search field conditions have been build ensure no results are returned
407  if (empty($constraints)) {
408  return '0=1';
409  }
410 
411  return $queryBuilder->expr()->orX(...$constraints);
412  }
413 
420  protected function extractSearchableFieldsFromTable($tableName)
421  {
422  // Get the list of fields to search in from the TCA, if any
423  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
424  $fieldListArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
425  } else {
426  $fieldListArray = [];
427  }
428  // Add special fields
429  if ($GLOBALS['BE_USER']->isAdmin()) {
430  $fieldListArray[] = 'uid';
431  $fieldListArray[] = 'pid';
432  }
433  return $fieldListArray;
434  }
435 
441  public function setLimitCount($limitCount)
442  {
444  if ($limit > 0) {
445  $this->limitCount = $limit;
446  }
447  }
448 
454  public function setStartCount($startCount)
455  {
457  }
458 
464  public function setQueryString($queryString)
465  {
466  $this->queryString = $queryString;
467  }
468 
477  protected function getAvailablePageIds($id, $depth)
478  {
479  $tree = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\View\PageTreeView::class);
480  $tree->init('AND ' . $this->userPermissions);
481  $tree->makeHTML = 0;
482  $tree->fieldArray = ['uid', 'php_tree_stop'];
483  if ($depth) {
484  $tree->getTree($id, $depth, '');
485  }
486  $tree->ids[] = $id;
487  // add workspace pid - workspace permissions are taken into account by where clause later
488  $tree->ids[] = -1;
489  $idList = implode(',', $tree->ids);
490  return $idList;
491  }
492 }
static readPageAccess($id, $perms_clause)
createNamedParameter($value, int $type=\PDO::PARAM_STR, string $placeHolder=null)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
findByTable($tableName, $pageIdList, $firstResult, $maxResults)
Definition: LiveSearch.php:182
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
Definition: LiveSearch.php:318
static stripLogicalOperatorPrefix(string $constraint)
static fixed_lgd_cs($string, $chars, $appendString='...')
static convertToPositiveInteger($theInt)
Definition: MathUtility.php:55
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
getRecordArray($queryBuilder, $tableName)
Definition: LiveSearch.php:232