TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
LiveSearch.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Search\LiveSearch;
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 
28 
33 {
37  const PAGE_JUMP_TABLE = 'pages';
38 
43 
48 
53 
57  private $queryString = '';
58 
62  private $startCount = 0;
63 
67  private $limitCount = 5;
68 
72  protected $userPermissions = '';
73 
77  protected $queryParser = null;
78 
82  public function __construct()
83  {
84  $this->userPermissions = $GLOBALS['BE_USER']->getPagePermsClause(1);
85  $this->queryParser = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Search\LiveSearch\QueryParser::class);
86  }
87 
94  public function find($searchQuery)
95  {
96  $recordArray = [];
97  $pageList = [];
98  $mounts = $GLOBALS['BE_USER']->returnWebmounts();
99  foreach ($mounts as $pageId) {
100  $pageList[] = $this->getAvailablePageIds($pageId, self::RECURSIVE_PAGE_LEVEL);
101  }
102  $pageIdList = array_unique(explode(',', implode(',', $pageList)));
103  unset($pageList);
104  if ($this->queryParser->isValidCommand($searchQuery)) {
105  $this->setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
106  $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
107  if ($tableName) {
108  $recordArray[] = $this->findByTable($tableName, $pageIdList, $this->startCount, $this->limitCount);
109  }
110  } else {
111  $this->setQueryString($searchQuery);
112  $recordArray = $this->findByGlobalTableList($pageIdList);
113  }
114  return $recordArray;
115  }
116 
123  protected function findPageById($id)
124  {
125  $pageRecord = [];
126  $row = BackendUtility::getRecord(self::PAGE_JUMP_TABLE, $id);
127  if (is_array($row)) {
128  $pageRecord = $row;
129  }
130  return $pageRecord;
131  }
132 
139  protected function findByGlobalTableList($pageIdList)
140  {
141  $limit = $this->limitCount;
142  $getRecordArray = [];
143  foreach ($GLOBALS['TCA'] as $tableName => $value) {
144  // if no access for the table (read or write), skip this table
145  if (!$GLOBALS['BE_USER']->check('tables_select', $tableName) && !$GLOBALS['BE_USER']->check('tables_modify', $tableName)) {
146  continue;
147  }
148  $recordArray = $this->findByTable($tableName, $pageIdList, 0, $limit);
149  $recordCount = count($recordArray);
150  if ($recordCount) {
151  $limit = $limit - $recordCount;
152  $getRecordArray[] = $recordArray;
153  if ($limit <= 0) {
154  break;
155  }
156  }
157  }
158  return $getRecordArray;
159  }
160 
173  protected function findByTable($tableName, $pageIdList, $firstResult, $maxResults)
174  {
175  $fieldsToSearchWithin = $this->extractSearchableFieldsFromTable($tableName);
176  $getRecordArray = [];
177  if (!empty($fieldsToSearchWithin)) {
178  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
179  ->getQueryBuilderForTable($tableName);
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  $title = 'id=' . $row['uid'] . ', pid=' . $row['pid'];
226  $collect[] = [
227  'id' => $tableName . ':' . $row['uid'],
228  'pageId' => $tableName === 'pages' ? $row['uid'] : $row['pid'],
229  'typeLabel' => htmlspecialchars($this->getTitleOfCurrentRecordType($tableName)),
230  'iconHTML' => '<span title="' . htmlspecialchars($title) . '">' . $iconFactory->getIconForRecord($tableName, $row, Icon::SIZE_SMALL)->render() . '</span>',
231  'title' => htmlspecialchars(BackendUtility::getRecordTitle($tableName, $row)),
232  'editLink' => htmlspecialchars($this->getEditLink($tableName, $row))
233  ];
234  }
235  return $collect;
236  }
237 
246  protected function getEditLink($tableName, $row)
247  {
248  $pageInfo = BackendUtility::readPageAccess($row['pid'], $this->userPermissions);
249  $calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo);
250  $editLink = '';
251  if ($tableName === 'pages') {
252  $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
253  $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
254  } else {
255  $permsEdit = $calcPerms & Permission::CONTENT_EDIT;
256  }
257  // "Edit" link - Only if permissions to edit the page-record of the content of the parent page ($this->id)
258  if ($permsEdit) {
259  $returnUrl = BackendUtility::getModuleUrl('web_list', ['id' => $row['pid']]);
260  $editLink = BackendUtility::getModuleUrl('record_edit', [
261  'edit[' . $tableName . '][' . $row['uid'] . ']' => 'edit',
262  'returnUrl' => $returnUrl
263  ]);
264  }
265  return $editLink;
266  }
267 
274  protected function getTitleOfCurrentRecordType($tableName)
275  {
276  return $GLOBALS['LANG']->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']);
277  }
278 
288  public function getRecordTitlePrep($title, $titleLength = 0)
289  {
290  // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
291  if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
292  $titleLength = $GLOBALS['BE_USER']->uc['titleLen'];
293  }
294  return htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
295  }
296 
305  protected function makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
306  {
307  $constraints = [];
308 
309  // If the search string is a simple integer, assemble an equality comparison
310  if (MathUtility::canBeInterpretedAsInteger($this->queryString)) {
311  foreach ($fieldsToSearchWithin as $fieldName) {
312  if ($fieldName !== 'uid'
313  && $fieldName !== 'pid'
314  && !isset($GLOBALS['TCA'][$tableName]['columns'][$fieldName])
315  ) {
316  continue;
317  }
318  $fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
319  $fieldType = $fieldConfig['type'];
320  $evalRules = $fieldConfig['eval'] ?: '';
321 
322  // Assemble the search condition only if the field is an integer, or is uid or pid
323  if ($fieldName === 'uid'
324  || $fieldName === 'pid'
325  || ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int'))
326  ) {
327  $constraints[] = $queryBuilder->expr()->eq(
328  $fieldName,
329  $queryBuilder->createNamedParameter($this->queryString, \PDO::PARAM_INT)
330  );
331  } elseif ($fieldType === 'text'
332  || $fieldType === 'flex'
333  || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
334  ) {
335  // Otherwise and if the field makes sense to be searched, assemble a like condition
336  $constraints[] = $constraints[] = $queryBuilder->expr()->like(
337  $fieldName,
338  $queryBuilder->createNamedParameter(
339  '%' . $queryBuilder->escapeLikeWildcards((int)$this->queryString) . '%',
340  \PDO::PARAM_STR
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(strtolower($like), \PDO::PARAM_STR)
361  )
362  );
363 
364  if (is_array($fieldConfig['search'])) {
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, \PDO::PARAM_STR)
371  )
372  );
373  }
374  // Apply additional condition, if any
375  if ($fieldConfig['search']['andWhere']) {
376  $searchConstraint->add(
377  QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
378  );
379  }
380  }
381  // Assemble the search condition only if the field makes sense to be searched
382  if ($fieldType === 'text'
383  || $fieldType === 'flex'
384  || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
385  ) {
386  if ($searchConstraint->count() !== 0) {
387  $constraints[] = $searchConstraint;
388  }
389  }
390  }
391  }
392 
393  // If no search field conditions have been build ensure no results are returned
394  if (empty($constraints)) {
395  return '0=1';
396  }
397 
398  return $queryBuilder->expr()->orX(...$constraints);
399  }
400 
407  protected function extractSearchableFieldsFromTable($tableName)
408  {
409  // Get the list of fields to search in from the TCA, if any
410  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
411  $fieldListArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
412  } else {
413  $fieldListArray = [];
414  }
415  // Add special fields
416  if ($GLOBALS['BE_USER']->isAdmin()) {
417  $fieldListArray[] = 'uid';
418  $fieldListArray[] = 'pid';
419  }
420  return $fieldListArray;
421  }
422 
429  public function setLimitCount($limitCount)
430  {
432  if ($limit > 0) {
433  $this->limitCount = $limit;
434  }
435  }
436 
443  public function setStartCount($startCount)
444  {
446  }
447 
454  public function setQueryString($queryString)
455  {
456  $this->queryString = $queryString;
457  }
458 
467  protected function getAvailablePageIds($id, $depth)
468  {
469  $tree = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\View\PageTreeView::class);
470  $tree->init('AND ' . $this->userPermissions);
471  $tree->makeHTML = 0;
472  $tree->fieldArray = ['uid', 'php_tree_stop'];
473  if ($depth) {
474  $tree->getTree($id, $depth, '');
475  }
476  $tree->ids[] = $id;
477  // add workspace pid - workspace permissions are taken into account by where clause later
478  $tree->ids[] = -1;
479  $idList = implode(',', $tree->ids);
480  return $idList;
481  }
482 }
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
findByTable($tableName, $pageIdList, $firstResult, $maxResults)
Definition: LiveSearch.php:173
makeQuerySearchByTable(QueryBuilder &$queryBuilder, $tableName, array $fieldsToSearchWithin)
Definition: LiveSearch.php:305
createNamedParameter($value, int $type=\PDO::PARAM_STR, string $placeHolder=null)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
getRecordArray($queryBuilder, $tableName)
Definition: LiveSearch.php:219
static stripLogicalOperatorPrefix(string $constraint)
static convertToPositiveInteger($theInt)
Definition: MathUtility.php:55