[BUGFIX] Using datetime field with datepicker the time information gets lost
[Packages/TYPO3.CMS.git] / t3lib / search / class.t3lib_search_livesearch.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009-2011 Michael Klapper <michael.klapper@aoemedia.de>
6 * (c) 2010-2011 Jeff Segars <jeff@webempoweredchurch.org>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * This copyright notice MUST APPEAR in all copies of the script!
27 ***************************************************************/
28
29 /**
30 * Class for handling backend live search.
31 *
32 * @author Michael Klapper <michael.klapper@aoemedia.de>
33 * @author Jeff Segars <jeff@webempoweredchurch.org>
34 * @package TYPO3
35 * @subpackage t3lib
36 */
37 class t3lib_search_livesearch {
38
39 /**
40 * @var string
41 */
42 const PAGE_JUMP_TABLE = 'pages';
43
44 /**
45 * @var integer
46 */
47 const RECURSIVE_PAGE_LEVEL = 99;
48
49 /**
50 * @var integer
51 */
52 const GROUP_TITLE_MAX_LENGTH = 15;
53
54 /**
55 * @var integer
56 */
57 const RECORD_TITLE_MAX_LENGTH = 28;
58
59 /**
60 * @var string
61 */
62 private $queryString = '';
63
64 /**
65 * @var integer
66 */
67 private $startCount = 0;
68
69 /**
70 * @var integer
71 */
72 private $limitCount = 5;
73
74 /**
75 * @var string
76 */
77 protected $userPermissions = '';
78
79 /**
80 * @var t3lib_search_livesearch_queryParser
81 */
82 protected $queryParser = NULL;
83
84 /**
85 * Initialize access settings.
86 *
87 * @return void
88 */
89 public function __construct() {
90 $this->userPermissions = $GLOBALS['BE_USER']->getPagePermsClause(1);
91 $this->queryParser = t3lib_div::makeInstance('t3lib_search_livesearch_queryParser');
92 }
93
94 /**
95 * Find records from database based on the given $searchQuery.
96 *
97 * @param string $searchQuery
98 * @return string Edit link to an page record if exists. Otherwise an empty string will returned
99 */
100 public function findPage($searchQuery) {
101 $link = '';
102 $pageId = $this->queryParser->getId($searchQuery);
103 $pageRecord = $this->findPageById($pageId);
104
105 if (!empty($pageRecord)) {
106 $link = $this->getEditLink(self::PAGE_JUMP_TABLE, $this->findPageById($pageId));
107 }
108
109 return $link;
110 }
111
112 /**
113 * Find records from database based on the given $searchQuery.
114 *
115 * @param string $searchQuery
116 * @return array Result list of database search.
117 */
118 public function find($searchQuery) {
119 $recordArray = array();
120 $pageIdList = $this->getAvailablePageIds(
121 implode(',', $GLOBALS['BE_USER']->returnWebmounts()),
122 self::RECURSIVE_PAGE_LEVEL
123 );
124 $limit = $this->startCount . ',' . $this->limitCount;
125
126 if ($this->queryParser->isValidCommand($searchQuery)) {
127 $this->setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
128 $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
129 if ($tableName) {
130 $recordArray[] = $this->findByTable($tableName, $pageIdList, $limit);
131 }
132 } else {
133 $this->setQueryString($searchQuery);
134 $recordArray = $this->findByGlobalTableList($pageIdList);
135 }
136
137 return $recordArray;
138 }
139
140 /**
141 * Retrieve the page record from given $id.
142 *
143 * @param integer $id
144 * @return array
145 */
146 protected function findPageById($id) {
147 $pageRecord = array();
148 $row = t3lib_BEfunc::getRecord(self::PAGE_JUMP_TABLE, $id);
149
150 if (is_array($row)) {
151 $pageRecord = $row;
152 }
153
154 return $pageRecord;
155 }
156
157 /**
158 * Find records from all registered TCA table & column values.
159 *
160 * @param string $pageIdList Comma seperated list of page IDs
161 * @return array Records found in the database matching the searchQuery
162 */
163 protected function findByGlobalTableList($pageIdList) {
164 $limit = $this->limitCount;
165 $getRecordArray = array();
166 foreach ($GLOBALS['TCA'] as $tableName => $value) {
167 $recordArray = $this->findByTable($tableName, $pageIdList, '0,' . $limit);
168 $recordCount = count($recordArray);
169 if ($recordCount) {
170 $limit = $limit - $recordCount;
171 $getRecordArray[] = $recordArray;
172
173 if ($limit <= 0) {
174 break;
175 }
176 }
177 }
178
179 return $getRecordArray;
180 }
181
182 /**
183 * Find records by given table name.
184 *
185 * @param string $tableName Database table name
186 * @param string $pageIdList Comma seperated list of page IDs
187 * @param string $limit MySql Limit notation
188 * @return array Records found in the database matching the searchQuery
189 *
190 * @see getRecordArray()
191 * @see makeOrderByTable()
192 * @see makeQuerySearchByTable()
193 * @see extractSearchableFieldsFromTable()
194 */
195 protected function findByTable($tableName, $pageIdList, $limit) {
196 $fieldsToSearchWithin = $this->extractSearchableFieldsFromTable($tableName);
197
198 $getRecordArray = array();
199 if (count($fieldsToSearchWithin) > 0) {
200 $pageBasedPermission = ($tableName == 'pages' && $this->userPermissions) ? $this->userPermissions : '1=1 ';
201 $where = 'pid IN (' . $pageIdList . ') AND ' . $pageBasedPermission . $this->makeQuerySearchByTable($tableName, $fieldsToSearchWithin);
202 $orderBy = $this->makeOrderByTable($tableName);
203 $getRecordArray = $this->getRecordArray(
204 $tableName,
205 $where,
206 $this->makeOrderByTable($tableName),
207 $limit
208 );
209 }
210
211 return $getRecordArray;
212 }
213
214 /**
215 * Process the Database operation to get the search result.
216 *
217 * @param string $tableName Database table name
218 * @param string $where
219 * @param string $orderBy
220 * @param string $limit MySql Limit notation
221 * @return array
222 *
223 * @see t3lib_db::exec_SELECT_queryArray()
224 * @see t3lib_db::sql_num_rows()
225 * @see t3lib_db::sql_fetch_assoc()
226 * @see t3lib_iconWorks::getSpriteIconForRecord()
227 * @see getTitleFromCurrentRow()
228 * @see getEditLink()
229 */
230 protected function getRecordArray($tableName, $where, $orderBy, $limit) {
231 $collect = array();
232 $isFirst = TRUE;
233 $queryParts = array(
234 'SELECT' => '*',
235 'FROM' => $tableName,
236 'WHERE' => $where,
237 'ORDERBY' => $orderBy,
238 'LIMIT' => $limit
239 );
240 $result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryParts);
241 $dbCount = $GLOBALS['TYPO3_DB']->sql_num_rows($result);
242
243 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result)) {
244 $collect[] = array(
245 'id' => $tableName . ':' . $row['uid'],
246 'recordTitle' => ($isFirst) ? $this->getRecordTitlePrep($this->getTitleOfCurrentRecordType($tableName), self::GROUP_TITLE_MAX_LENGTH) : '',
247 'iconHTML' => t3lib_iconWorks::getSpriteIconForRecord($tableName, $row),
248 'title' => $this->getRecordTitlePrep($this->getTitleFromCurrentRow($tableName, $row), self::RECORD_TITLE_MAX_LENGTH),
249 'editLink' => $this->getEditLink($tableName, $row),
250 );
251 $isFirst = FALSE;
252 }
253
254 return $collect;
255 }
256
257 /**
258 * Build a backend edit link based on given record.
259 *
260 * @param string $tableName Record table name
261 * @param array $row Current record row from database.
262 * @return string Link to open an edit window for record.
263 *
264 * @see t3lib_BEfunc::readPageAccess()
265 */
266 protected function getEditLink($tableName, $row) {
267 $pageInfo = t3lib_BEfunc::readPageAccess($row['pid'], $this->userPermissions);
268 $calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo);
269 $editLink = '';
270
271 if ($tableName == 'pages') {
272 $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages', $row['uid']));
273 $permsEdit = $localCalcPerms & 2;
274 } else {
275 $permsEdit = $calcPerms & 16;
276 }
277
278 // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
279 // @todo Is there an existing function to generate this link?
280 if ($permsEdit) {
281 $editLink = 'alt_doc.php?' . '&edit[' . $tableName . '][' . $row['uid'] . ']=edit';
282 }
283
284 return $editLink;
285 }
286
287 /**
288 * Retrieve the record name
289 *
290 * @param string $tableName Record table name
291 * @return string
292 */
293 protected function getTitleOfCurrentRecordType($tableName) {
294 return $GLOBALS['LANG']->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']);
295 }
296
297 /**
298 * Crops a title string to a limited lenght and if it really was cropped, wrap it in a <span title="...">|</span>,
299 * which offers a tooltip with the original title when moving mouse over it.
300 *
301 * @param string $title: The title string to be cropped
302 * @param integer $titleLength: Crop title after this length - if not set, BE_USER->uc['titleLen'] is used
303 * @return string The processed title string, wrapped in <span title="...">|</span> if cropped
304 */
305 public function getRecordTitlePrep($title, $titleLength = 0) {
306 // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
307 if (!$titleLength || !t3lib_div::testInt($titleLength) || $titleLength < 0) {
308 $titleLength = $GLOBALS['BE_USER']->uc['titleLen'];
309 }
310
311 return htmlspecialchars(t3lib_div::fixed_lgd_cs($title, $titleLength));
312 ;
313 }
314
315 /**
316 * Retrieve the column name which contains the title value
317 *
318 * @param string $tableName Record table name
319 * @param array $row Current record row from database.
320 * @return string
321 *
322 * @todo Use the backend function to get the calculated label instead.
323 */
324 protected function getTitleFromCurrentRow($tableName, $row) {
325 $titleColumnName = $GLOBALS['TCA'][$tableName]['ctrl']['label'];
326 return $row[$titleColumnName];
327 }
328
329 /**
330 * Build the MySql where clause by table.
331 *
332 * @param string $tableName Record table name
333 * @param array $fieldsToSearchWithin User right based visible fields where we can search within.
334 * @return string
335 */
336 protected function makeQuerySearchByTable($tableName, array $fieldsToSearchWithin) {
337 // free text search
338 $queryLikeStatement = ' LIKE \'%' . $this->getQueryString($tableName) . '%\'';
339 $integerFieldsToSearchWithin = array();
340 $queryEqualStatement = '';
341
342 if (is_numeric($this->getQueryString($tableName))) {
343 $queryEqualStatement = ' = \'' . $this->getQueryString($tableName) . '\'';
344 }
345 $uidPos = array_search('uid', $fieldsToSearchWithin);
346 if ($uidPos) {
347 $integerFieldsToSearchWithin[] = 'uid';
348 unset($fieldsToSearchWithin[$uidPos]);
349 }
350 $pidPos = array_search('pid', $fieldsToSearchWithin);
351 if ($pidPos) {
352 $integerFieldsToSearchWithin[] = 'pid';
353 unset($fieldsToSearchWithin[$pidPos]);
354 }
355
356 $queryPart = ' AND (';
357 if (count($integerFieldsToSearchWithin) && $queryEqualStatement !== '') {
358 $queryPart .= implode($queryEqualStatement . ' OR ', $integerFieldsToSearchWithin) . $queryEqualStatement . ' OR ';
359 }
360 $queryPart .= implode($queryLikeStatement . ' OR ', $fieldsToSearchWithin) . $queryLikeStatement . ')';
361 $queryPart .= t3lib_BEfunc::deleteClause($tableName);
362 $queryPart .= t3lib_BEfunc::versioningPlaceholderClause($tableName);
363
364 return $queryPart;
365 }
366
367 /**
368 * Build the MySql ORDER BY statement.
369 *
370 *
371 * @param string $tableName Record table name
372 * @return string
373 * @see t3lib_db::stripOrderBy()
374 */
375 protected function makeOrderByTable($tableName) {
376 $orderBy = '';
377
378 if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('sortby', $GLOBALS['TCA'][$tableName]['ctrl'])) {
379 $orderBy = 'ORDER BY ' . $GLOBALS['TCA'][$tableName]['ctrl']['sortby'];
380 } else {
381 $orderBy = $GLOBALS['TCA'][$tableName]['ctrl']['default_sortby'];
382 }
383
384 return $GLOBALS['TYPO3_DB']->stripOrderBy($orderBy);
385 }
386
387 /**
388 * Get all fields from given table where we can search for.
389 *
390 * @param string $tableName
391 * @return array
392 */
393 protected function extractSearchableFieldsFromTable($tableName) {
394 $fieldListArray = array();
395
396 // Traverse configured columns and add them to field array, if available for user.
397 foreach ((array) $GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $fieldValue) {
398 // @todo Reformat
399 if (
400 (!$fieldValue['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields', $tableName . ':' . $fieldName)) // does current user have access to the field
401 &&
402 ($fieldValue['config']['type'] != 'passthrough') // field type is not searchable
403 &&
404 (!preg_match('/date|time|int/', $fieldValue['config']['eval'])) // field can't be of type date, time, int
405 &&
406 (
407 ($fieldValue['config']['type'] == 'text')
408 ||
409 ($fieldValue['config']['type'] == 'input')
410 )
411 ) {
412 $fieldListArray[] = $fieldName;
413 }
414 }
415
416 // Add special fields:
417 if ($GLOBALS['BE_USER']->isAdmin()) {
418 $fieldListArray[] = 'uid';
419 $fieldListArray[] = 'pid';
420 }
421
422 return $fieldListArray;
423 }
424
425 /**
426 * Safely retrieve the queryString.
427 *
428 * @param string $tableName
429 * @return string
430 * @see t3lib_db::quoteStr()
431 */
432 public function getQueryString($tableName = '') {
433 return $GLOBALS['TYPO3_DB']->quoteStr($this->queryString, $tableName);
434 }
435
436 /**
437 * Setter for limit value.
438 *
439 * @param integer $limitCount
440 * @return void
441 */
442 public function setLimitCount($limitCount) {
443 $limit = t3lib_div::intval_positive($limitCount);
444 if ($limit > 0) {
445 $this->limitCount = $limit;
446 }
447 }
448
449 /**
450 * Setter for start count value.
451 *
452 * @param integer $startCount
453 * @return void
454 */
455 public function setStartCount($startCount) {
456 $this->startCount = t3lib_div::intval_positive($startCount);
457 }
458
459 /**
460 * Setter for the search query string.
461 *
462 * @param string $queryString
463 * @return void
464 * @see t3lib_div::removeXSS()
465 */
466 public function setQueryString($queryString) {
467 $this->queryString = t3lib_div::removeXSS($queryString);
468 }
469
470 /**
471 * Creates an instance of t3lib_pageTree which will select a page tree to
472 * $depth and return the object. In that object we will find the ids of the tree.
473 *
474 * @param integer Page id.
475 * @param integer Depth to go down.
476 *
477 * @return string coma separated list of uids
478 */
479 protected function getAvailablePageIds($id, $depth) {
480 $idList = '';
481 $tree = t3lib_div::makeInstance('t3lib_pageTree');
482 $tree->init('AND ' . $this->userPermissions);
483 $tree->makeHTML = 0;
484 $tree->fieldArray = array('uid', 'php_tree_stop');
485 if ($depth) {
486 $tree->getTree($id, $depth, '');
487 }
488 $tree->ids[] = $id;
489 $idList = implode(',', $tree->ids);
490 return $idList;
491 }
492 }
493
494 ?>