TYPO3  7.6
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 
23 
28 {
32  const PAGE_JUMP_TABLE = 'pages';
33 
38 
43 
48 
52  private $queryString = '';
53 
57  private $startCount = 0;
58 
62  private $limitCount = 5;
63 
67  protected $userPermissions = '';
68 
72  protected $queryParser = null;
73 
77  public function __construct()
78  {
79  $this->userPermissions = $GLOBALS['BE_USER']->getPagePermsClause(1);
80  $this->queryParser = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Search\LiveSearch\QueryParser::class);
81  }
82 
89  public function findPage($searchQuery)
90  {
91  $link = '';
92  $pageId = $this->queryParser->getId($searchQuery);
93  $pageRecord = $this->findPageById($pageId);
94  if (!empty($pageRecord)) {
95  $link = $this->getEditLink(self::PAGE_JUMP_TABLE, $this->findPageById($pageId));
96  }
97  return $link;
98  }
99 
106  public function find($searchQuery)
107  {
108  $recordArray = array();
109  $pageList = array();
110  $mounts = $GLOBALS['BE_USER']->returnWebmounts();
111  foreach ($mounts as $pageId) {
112  $pageList[] = $this->getAvailablePageIds($pageId, self::RECURSIVE_PAGE_LEVEL);
113  }
114  $pageIdList = implode(',', array_unique(explode(',', implode(',', $pageList))));
115  unset($pageList);
116  $limit = $this->startCount . ',' . $this->limitCount;
117  if ($this->queryParser->isValidCommand($searchQuery)) {
118  $this->setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
119  $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
120  if ($tableName) {
121  $recordArray[] = $this->findByTable($tableName, $pageIdList, $limit);
122  }
123  } else {
124  $this->setQueryString($searchQuery);
125  $recordArray = $this->findByGlobalTableList($pageIdList);
126  }
127  return $recordArray;
128  }
129 
136  protected function findPageById($id)
137  {
138  $pageRecord = array();
139  $row = BackendUtility::getRecord(self::PAGE_JUMP_TABLE, $id);
140  if (is_array($row)) {
141  $pageRecord = $row;
142  }
143  return $pageRecord;
144  }
145 
152  protected function findByGlobalTableList($pageIdList)
153  {
154  $limit = $this->limitCount;
155  $getRecordArray = array();
156  foreach ($GLOBALS['TCA'] as $tableName => $value) {
157  // if no access for the table (read or write), skip this table
158  if (!$GLOBALS['BE_USER']->check('tables_select', $tableName) && !$GLOBALS['BE_USER']->check('tables_modify', $tableName)) {
159  continue;
160  }
161  $recordArray = $this->findByTable($tableName, $pageIdList, '0,' . $limit);
162  $recordCount = count($recordArray);
163  if ($recordCount) {
164  $limit = $limit - $recordCount;
165  $getRecordArray[] = $recordArray;
166  if ($limit <= 0) {
167  break;
168  }
169  }
170  }
171  return $getRecordArray;
172  }
173 
186  protected function findByTable($tableName, $pageIdList, $limit)
187  {
188  $fieldsToSearchWithin = $this->extractSearchableFieldsFromTable($tableName);
189  $getRecordArray = array();
190  if (!empty($fieldsToSearchWithin)) {
191  $pageBasedPermission = $tableName == 'pages' && $this->userPermissions ? $this->userPermissions : '1=1 ';
192  $where = 'pid IN (' . $pageIdList . ') AND ' . $pageBasedPermission . $this->makeQuerySearchByTable($tableName, $fieldsToSearchWithin);
193  $getRecordArray = $this->getRecordArray($tableName, $where, $this->makeOrderByTable($tableName), $limit);
194  }
195  return $getRecordArray;
196  }
197 
209  protected function getRecordArray($tableName, $where, $orderBy, $limit)
210  {
211  $collect = array();
212  $isFirst = true;
213  $queryParts = array(
214  'SELECT' => '*',
215  'FROM' => $tableName,
216  'WHERE' => $where,
217  'ORDERBY' => $orderBy,
218  'LIMIT' => $limit
219  );
220  $result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryParts);
221  $dbCount = $GLOBALS['TYPO3_DB']->sql_num_rows($result);
222  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
223  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result)) {
224  $title = 'id=' . $row['uid'] . ', pid=' . $row['pid'];
225  $collect[] = array(
226  'id' => $tableName . ':' . $row['uid'],
227  'pageId' => $tableName === 'pages' ? $row['uid'] : $row['pid'],
228  'typeLabel' => htmlspecialchars($this->getTitleOfCurrentRecordType($tableName)),
229  'iconHTML' => '<span title="' . htmlspecialchars($title) . '">' . $iconFactory->getIconForRecord($tableName, $row, Icon::SIZE_SMALL)->render() . '</span>',
230  'title' => htmlspecialchars(BackendUtility::getRecordTitle($tableName, $row)),
231  'editLink' => htmlspecialchars($this->getEditLink($tableName, $row))
232  );
233  $isFirst = false;
234  }
235  $GLOBALS['TYPO3_DB']->sql_free_result($result);
236  return $collect;
237  }
238 
247  protected function getEditLink($tableName, $row)
248  {
249  $pageInfo = BackendUtility::readPageAccess($row['pid'], $this->userPermissions);
250  $calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo);
251  $editLink = '';
252  if ($tableName == 'pages') {
253  $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(BackendUtility::getRecord('pages', $row['uid']));
254  $permsEdit = $localCalcPerms & Permission::PAGE_EDIT;
255  } else {
256  $permsEdit = $calcPerms & Permission::CONTENT_EDIT;
257  }
258  // "Edit" link - Only if permissions to edit the page-record of the content of the parent page ($this->id)
259  if ($permsEdit) {
260  $returnUrl = BackendUtility::getModuleUrl('web_list', array('id' => $row['pid']));
261  $editLink = BackendUtility::getModuleUrl('record_edit', array(
262  'edit[' . $tableName . '][' . $row['uid'] . ']' => 'edit',
263  'returnUrl' => $returnUrl
264  ));
265  }
266  return $editLink;
267  }
268 
275  protected function getTitleOfCurrentRecordType($tableName)
276  {
277  return $GLOBALS['LANG']->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']);
278  }
279 
289  public function getRecordTitlePrep($title, $titleLength = 0)
290  {
291  // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
292  if (!$titleLength || !MathUtility::canBeInterpretedAsInteger($titleLength) || $titleLength < 0) {
293  $titleLength = $GLOBALS['BE_USER']->uc['titleLen'];
294  }
295  return htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, $titleLength));
296  }
297 
305  protected function makeQuerySearchByTable($tableName, array $fieldsToSearchWithin)
306  {
307  $queryPart = '';
308  $whereParts = array();
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' || $fieldName == 'pid' || isset($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
313  $fieldConfig = &$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
314  // Assemble the search condition only if the field is an integer, or is uid or pid
315  if ($fieldName == 'uid' || $fieldName == 'pid' || $fieldConfig['type'] == 'input' && $fieldConfig['eval'] && GeneralUtility::inList($fieldConfig['eval'], 'int')) {
316  $whereParts[] = $fieldName . '=' . $this->queryString;
317  } elseif (
318  $fieldConfig['type'] == 'text' ||
319  $fieldConfig['type'] == 'flex' ||
320  ($fieldConfig['type'] == 'input' && (!$fieldConfig['eval'] ||
321  !preg_match('/date|time|int/', $fieldConfig['eval'])))) {
322  // Otherwise and if the field makes sense to be searched, assemble a like condition
323  $whereParts[] = $fieldName . ' LIKE \'%' . $this->queryString . '%\'';
324  }
325  }
326  }
327  } else {
328  $like = '\'%' . $GLOBALS['TYPO3_DB']->escapeStrForLike($GLOBALS['TYPO3_DB']->quoteStr($this->queryString, $tableName), $tableName) . '%\'';
329  foreach ($fieldsToSearchWithin as $fieldName) {
330  if (isset($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
331  $fieldConfig = &$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
332  // Check whether search should be case-sensitive or not
333  $format = 'LOWER(%s) LIKE LOWER(%s)';
334  if (is_array($fieldConfig['search'])) {
335  if (in_array('case', $fieldConfig['search'])) {
336  $format = '%s LIKE %s';
337  }
338  // Apply additional condition, if any
339  if ($fieldConfig['search']['andWhere']) {
340  $format = '((' . $fieldConfig['search']['andWhere'] . ') AND (' . $format . '))';
341  }
342  }
343  // Assemble the search condition only if the field makes sense to be searched
344  if ($fieldConfig['type'] == 'text' || $fieldConfig['type'] == 'flex' || $fieldConfig['type'] == 'input' && (!$fieldConfig['eval'] || !preg_match('/date|time|int/', $fieldConfig['eval']))) {
345  $whereParts[] = sprintf($format, $fieldName, $like);
346  }
347  }
348  }
349  }
350  // If at least one condition was defined, create the search query
351  if (!empty($whereParts)) {
352  $queryPart = ' AND (' . implode(' OR ', $whereParts) . ')';
353  // And the relevant conditions for deleted and versioned records
354  $queryPart .= BackendUtility::deleteClause($tableName);
355  $queryPart .= BackendUtility::versioningPlaceholderClause($tableName);
356  } else {
357  $queryPart = ' AND 0 = 1';
358  }
359  return $queryPart;
360  }
361 
368  protected function makeOrderByTable($tableName)
369  {
370  $orderBy = '';
371  if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('sortby', $GLOBALS['TCA'][$tableName]['ctrl'])) {
372  $sortBy = trim($GLOBALS['TCA'][$tableName]['ctrl']['sortby']);
373  if (!empty($sortBy)) {
374  $orderBy = 'ORDER BY ' . $sortBy;
375  }
376  } else {
377  $orderBy = $GLOBALS['TCA'][$tableName]['ctrl']['default_sortby'];
378  }
379  return $GLOBALS['TYPO3_DB']->stripOrderBy($orderBy);
380  }
381 
388  protected function extractSearchableFieldsFromTable($tableName)
389  {
390  // Get the list of fields to search in from the TCA, if any
391  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['searchFields'])) {
392  $fieldListArray = GeneralUtility::trimExplode(',', $GLOBALS['TCA'][$tableName]['ctrl']['searchFields'], true);
393  } else {
394  $fieldListArray = array();
395  }
396  // Add special fields
397  if ($GLOBALS['BE_USER']->isAdmin()) {
398  $fieldListArray[] = 'uid';
399  $fieldListArray[] = 'pid';
400  }
401  return $fieldListArray;
402  }
403 
410  public function getQueryString($tableName = '')
411  {
412  return $GLOBALS['TYPO3_DB']->quoteStr($this->queryString, $tableName);
413  }
414 
421  public function setLimitCount($limitCount)
422  {
424  if ($limit > 0) {
425  $this->limitCount = $limit;
426  }
427  }
428 
435  public function setStartCount($startCount)
436  {
438  }
439 
447  public function setQueryString($queryString)
448  {
449  $this->queryString = GeneralUtility::removeXSS($queryString);
450  }
451 
460  protected function getAvailablePageIds($id, $depth)
461  {
462  $tree = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Tree\View\PageTreeView::class);
463  $tree->init('AND ' . $this->userPermissions);
464  $tree->makeHTML = 0;
465  $tree->fieldArray = array('uid', 'php_tree_stop');
466  if ($depth) {
467  $tree->getTree($id, $depth, '');
468  }
469  $tree->ids[] = $id;
470  $idList = implode(',', $tree->ids);
471  return $idList;
472  }
473 }