TYPO3 API  SVNRelease
class.t3lib_search_livesearch.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2009-2011 Michael Klapper <michael.klapper@aoemedia.de>
00006  *  (c) 2010-2011 Jeff Segars <jeff@webempoweredchurch.org>
00007  *  All rights reserved
00008  *
00009  *  This script is part of the TYPO3 project. The TYPO3 project is
00010  *  free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  The GNU General Public License can be found at
00016  *  http://www.gnu.org/copyleft/gpl.html.
00017  *  A copy is found in the textfile GPL.txt and important notices to the license
00018  *  from the author is found in LICENSE.txt distributed with these scripts.
00019  *
00020  *
00021  *  This script is distributed in the hope that it will be useful,
00022  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00023  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00024  *  GNU General Public License for more details.
00025  *
00026  *  This copyright notice MUST APPEAR in all copies of the script!
00027  ***************************************************************/
00028 
00029 /**
00030  * Class for handling backend live search.
00031  *
00032  * @author Michael Klapper <michael.klapper@aoemedia.de>
00033  * @author Jeff Segars <jeff@webempoweredchurch.org>
00034  * @package TYPO3
00035  * @subpackage t3lib
00036  */
00037 class t3lib_search_livesearch {
00038 
00039     /**
00040      * @var string
00041      */
00042     const PAGE_JUMP_TABLE = 'pages';
00043 
00044     /**
00045      * @var integer
00046      */
00047     const RECURSIVE_PAGE_LEVEL = 99;
00048 
00049     /**
00050      * @var integer
00051      */
00052     const GROUP_TITLE_MAX_LENGTH = 15;
00053 
00054     /**
00055      * @var integer
00056      */
00057     const RECORD_TITLE_MAX_LENGTH = 28;
00058 
00059     /**
00060      * @var string
00061      */
00062     private $queryString = '';
00063 
00064     /**
00065      * @var integer
00066      */
00067     private $startCount = 0;
00068 
00069     /**
00070      * @var integer
00071      */
00072     private $limitCount = 5;
00073 
00074     /**
00075      * @var string
00076      */
00077     protected $userPermissions = '';
00078 
00079     /**
00080      * @var t3lib_search_livesearch_queryParser
00081      */
00082     protected $queryParser = NULL;
00083 
00084     /**
00085      * Initialize access settings.
00086      *
00087      * @return void
00088      */
00089     public function __construct() {
00090         $this->userPermissions = $GLOBALS['BE_USER']->getPagePermsClause(1);
00091         $this->queryParser = t3lib_div::makeInstance('t3lib_search_livesearch_queryParser');
00092     }
00093 
00094     /**
00095      * Find records from database based on the given $searchQuery.
00096      *
00097      * @param string $searchQuery
00098      * @return string Edit link to an page record if exists. Otherwise an empty string will returned
00099      */
00100     public function findPage($searchQuery) {
00101         $link = '';
00102         $pageId = $this->queryParser->getId($searchQuery);
00103         $pageRecord = $this->findPageById($pageId);
00104 
00105         if (!empty($pageRecord)) {
00106             $link = $this->getEditLink(self::PAGE_JUMP_TABLE, $this->findPageById($pageId));
00107         }
00108 
00109         return $link;
00110     }
00111 
00112     /**
00113      * Find records from database based on the given $searchQuery.
00114      *
00115      * @param string $searchQuery
00116      * @return array Result list of database search.
00117      */
00118     public function find($searchQuery) {
00119         $recordArray = array();
00120         $pageIdList = $this->getAvailablePageIds(
00121             implode(',', $GLOBALS['BE_USER']->returnWebmounts()),
00122             self::RECURSIVE_PAGE_LEVEL
00123         );
00124         $limit = $this->startCount . ',' . $this->limitCount;
00125 
00126         if ($this->queryParser->isValidCommand($searchQuery)) {
00127             $this->setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
00128             $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
00129             if ($tableName) {
00130                 $recordArray[] = $this->findByTable($tableName, $pageIdList, $limit);
00131             }
00132         } else {
00133             $this->setQueryString($searchQuery);
00134             $recordArray = $this->findByGlobalTableList($pageIdList);
00135         }
00136 
00137         return $recordArray;
00138     }
00139 
00140     /**
00141      * Retrieve the page record from given $id.
00142      *
00143      * @param integer $id
00144      * @return array
00145      */
00146     protected function findPageById($id) {
00147         $pageRecord = array();
00148         $row = t3lib_BEfunc::getRecord(self::PAGE_JUMP_TABLE, $id);
00149 
00150         if (is_array($row)) {
00151             $pageRecord = $row;
00152         }
00153 
00154         return $pageRecord;
00155     }
00156 
00157     /**
00158      * Find records from all registered TCA table & column values.
00159      *
00160      * @param string $pageIdList Comma seperated list of page IDs
00161      * @return array Records found in the database matching the searchQuery
00162      */
00163     protected function findByGlobalTableList($pageIdList) {
00164         $limit = $this->limitCount;
00165         $getRecordArray = array();
00166         foreach ($GLOBALS['TCA'] as $tableName => $value) {
00167             $recordArray = $this->findByTable($tableName, $pageIdList, '0,' . $limit);
00168             $recordCount = count($recordArray);
00169             if ($recordCount) {
00170                 $limit = $limit - $recordCount;
00171                 $getRecordArray[] = $recordArray;
00172 
00173                 if ($limit <= 0) {
00174                     break;
00175                 }
00176             }
00177         }
00178 
00179         return $getRecordArray;
00180     }
00181 
00182     /**
00183      * Find records by given table name.
00184      *
00185      * @param string $tableName Database table name
00186      * @param string $pageIdList Comma seperated list of page IDs
00187      * @param string $limit MySql Limit notation
00188      * @return array Records found in the database matching the searchQuery
00189      *
00190      * @see getRecordArray()
00191      * @see makeOrderByTable()
00192      * @see makeQuerySearchByTable()
00193      * @see extractSearchableFieldsFromTable()
00194      */
00195     protected function findByTable($tableName, $pageIdList, $limit) {
00196         $fieldsToSearchWithin = $this->extractSearchableFieldsFromTable($tableName);
00197 
00198         $getRecordArray = array();
00199         if (count($fieldsToSearchWithin) > 0) {
00200             $pageBasedPermission = ($tableName == 'pages' && $this->userPermissions) ? $this->userPermissions : '1=1 ';
00201             $where = 'pid IN (' . $pageIdList . ') AND ' . $pageBasedPermission . $this->makeQuerySearchByTable($tableName, $fieldsToSearchWithin);
00202             $orderBy = $this->makeOrderByTable($tableName);
00203             $getRecordArray = $this->getRecordArray(
00204                 $tableName,
00205                 $where,
00206                 $this->makeOrderByTable($tableName),
00207                 $limit
00208             );
00209         }
00210 
00211         return $getRecordArray;
00212     }
00213 
00214     /**
00215      * Process the Database operation to get the search result.
00216      *
00217      * @param string $tableName Database table name
00218      * @param string $where
00219      * @param string $orderBy
00220      * @param string $limit MySql Limit notation
00221      * @return array
00222      *
00223      * @see t3lib_db::exec_SELECT_queryArray()
00224      * @see t3lib_db::sql_num_rows()
00225      * @see t3lib_db::sql_fetch_assoc()
00226      * @see t3lib_iconWorks::getSpriteIconForRecord()
00227      * @see getTitleFromCurrentRow()
00228      * @see getEditLink()
00229      */
00230     protected function getRecordArray($tableName, $where, $orderBy, $limit) {
00231         $collect = array();
00232         $isFirst = TRUE;
00233         $queryParts = array(
00234             'SELECT' => '*',
00235             'FROM' => $tableName,
00236             'WHERE' => $where,
00237             'ORDERBY' => $orderBy,
00238             'LIMIT' => $limit
00239         );
00240         $result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryParts);
00241         $dbCount = $GLOBALS['TYPO3_DB']->sql_num_rows($result);
00242 
00243         while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result)) {
00244             $collect[] = array(
00245                 'id' => $tableName . ':' . $row['uid'],
00246                 'recordTitle' => ($isFirst) ? $this->getRecordTitlePrep($this->getTitleOfCurrentRecordType($tableName), self::GROUP_TITLE_MAX_LENGTH) : '',
00247                 'iconHTML' => t3lib_iconWorks::getSpriteIconForRecord($tableName, $row),
00248                 'title' => $this->getRecordTitlePrep($this->getTitleFromCurrentRow($tableName, $row), self::RECORD_TITLE_MAX_LENGTH),
00249                 'editLink' => $this->getEditLink($tableName, $row),
00250             );
00251             $isFirst = FALSE;
00252         }
00253 
00254         return $collect;
00255     }
00256 
00257     /**
00258      * Build a backend edit link based on given record.
00259      *
00260      * @param string $tableName Record table name
00261      * @param array  $row  Current record row from database.
00262      * @return string Link to open an edit window for record.
00263      *
00264      * @see t3lib_BEfunc::readPageAccess()
00265      */
00266     protected function getEditLink($tableName, $row) {
00267         $pageInfo = t3lib_BEfunc::readPageAccess($row['pid'], $this->userPermissions);
00268         $calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo);
00269         $editLink = '';
00270 
00271         if ($tableName == 'pages') {
00272             $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages', $row['uid']));
00273             $permsEdit = $localCalcPerms & 2;
00274         } else {
00275             $permsEdit = $calcPerms & 16;
00276         }
00277 
00278             // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
00279             // @todo Is there an existing function to generate this link?
00280         if ($permsEdit) {
00281             $editLink = 'alt_doc.php?' . '&edit[' . $tableName . '][' . $row['uid'] . ']=edit';
00282         }
00283 
00284         return $editLink;
00285     }
00286 
00287     /**
00288      * Retrieve the record name
00289      *
00290      * @param string $tableName Record table name
00291      * @return string
00292      */
00293     protected function getTitleOfCurrentRecordType($tableName) {
00294         return $GLOBALS['LANG']->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']);
00295     }
00296 
00297     /**
00298      * Crops a title string to a limited lenght and if it really was cropped, wrap it in a <span title="...">|</span>,
00299      * which offers a tooltip with the original title when moving mouse over it.
00300      *
00301      * @param   string      $title: The title string to be cropped
00302      * @param   integer     $titleLength: Crop title after this length - if not set, BE_USER->uc['titleLen'] is used
00303      * @return  string      The processed title string, wrapped in <span title="...">|</span> if cropped
00304      */
00305     public function getRecordTitlePrep($title, $titleLength = 0) {
00306             // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
00307         if (!$titleLength || !t3lib_div::testInt($titleLength) || $titleLength < 0) {
00308             $titleLength = $GLOBALS['BE_USER']->uc['titleLen'];
00309         }
00310 
00311         return htmlspecialchars(t3lib_div::fixed_lgd_cs($title, $titleLength));
00312         ;
00313     }
00314 
00315     /**
00316      * Retrieve the column name which contains the title value
00317      *
00318      * @param string $tableName Record table name
00319      * @param array $row Current record row from database.
00320      * @return string
00321      *
00322      * @todo Use the backend function to get the calculated label instead.
00323      */
00324     protected function getTitleFromCurrentRow($tableName, $row) {
00325         $titleColumnName = $GLOBALS['TCA'][$tableName]['ctrl']['label'];
00326         return $row[$titleColumnName];
00327     }
00328 
00329     /**
00330      * Build the MySql where clause by table.
00331      *
00332      * @param string $tableName Record table name
00333      * @param array $fieldsToSearchWithin User right based visible fields where we can search within.
00334      * @return string
00335      */
00336     protected function makeQuerySearchByTable($tableName, array $fieldsToSearchWithin) {
00337             // free text search
00338         $queryLikeStatement = ' LIKE \'%' . $this->getQueryString($tableName) . '%\'';
00339         $integerFieldsToSearchWithin = array();
00340         $queryEqualStatement = '';
00341 
00342         if (is_numeric($this->getQueryString($tableName))) {
00343             $queryEqualStatement = ' = \'' . $this->getQueryString($tableName) . '\'';
00344         }
00345         $uidPos = array_search('uid', $fieldsToSearchWithin);
00346         if ($uidPos) {
00347             $integerFieldsToSearchWithin[] = 'uid';
00348             unset($fieldsToSearchWithin[$uidPos]);
00349         }
00350         $pidPos = array_search('pid', $fieldsToSearchWithin);
00351         if ($pidPos) {
00352             $integerFieldsToSearchWithin[] = 'pid';
00353             unset($fieldsToSearchWithin[$pidPos]);
00354         }
00355 
00356         $queryPart = ' AND (';
00357         if (count($integerFieldsToSearchWithin) && $queryEqualStatement !== '') {
00358             $queryPart .= implode($queryEqualStatement . ' OR ', $integerFieldsToSearchWithin) . $queryEqualStatement . ' OR ';
00359         }
00360         $queryPart .= implode($queryLikeStatement . ' OR ', $fieldsToSearchWithin) . $queryLikeStatement . ')';
00361         $queryPart .= t3lib_BEfunc::deleteClause($tableName);
00362         $queryPart .= t3lib_BEfunc::versioningPlaceholderClause($tableName);
00363 
00364         return $queryPart;
00365     }
00366 
00367     /**
00368      * Build the MySql ORDER BY statement.
00369      *
00370      *
00371      * @param string $tableName Record table name
00372      * @return string
00373      * @see t3lib_db::stripOrderBy()
00374      */
00375     protected function makeOrderByTable($tableName) {
00376         $orderBy = '';
00377 
00378         if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('sortby', $GLOBALS['TCA'][$tableName]['ctrl'])) {
00379             $orderBy = 'ORDER BY ' . $GLOBALS['TCA'][$tableName]['ctrl']['sortby'];
00380         } else {
00381             $orderBy = $GLOBALS['TCA'][$tableName]['ctrl']['default_sortby'];
00382         }
00383 
00384         return $GLOBALS['TYPO3_DB']->stripOrderBy($orderBy);
00385     }
00386 
00387     /**
00388      * Get all fields from given table where we can search for.
00389      *
00390      * @param string $tableName
00391      * @return array
00392      */
00393     protected function extractSearchableFieldsFromTable($tableName) {
00394         $fieldListArray = array();
00395 
00396             // Traverse configured columns and add them to field array, if available for user.
00397         foreach ((array) $GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $fieldValue) {
00398                 // @todo Reformat
00399             if (
00400                 (!$fieldValue['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields', $tableName . ':' . $fieldName)) // does current user have access to the field
00401                 &&
00402                 ($fieldValue['config']['type'] != 'passthrough') // field type is not searchable
00403                 &&
00404                 (!preg_match('/date|time|int/', $fieldValue['config']['eval'])) // field can't be of type date, time, int
00405                 &&
00406                 (
00407                         ($fieldValue['config']['type'] == 'text')
00408                         ||
00409                         ($fieldValue['config']['type'] == 'input')
00410                 )
00411             ) {
00412                 $fieldListArray[] = $fieldName;
00413             }
00414         }
00415 
00416             // Add special fields:
00417         if ($GLOBALS['BE_USER']->isAdmin()) {
00418             $fieldListArray[] = 'uid';
00419             $fieldListArray[] = 'pid';
00420         }
00421 
00422         return $fieldListArray;
00423     }
00424 
00425     /**
00426      * Safely retrieve the queryString.
00427      *
00428      * @param string $tableName
00429      * @return string
00430      * @see t3lib_db::quoteStr()
00431      */
00432     public function getQueryString($tableName = '') {
00433         return $GLOBALS['TYPO3_DB']->quoteStr($this->queryString, $tableName);
00434     }
00435 
00436     /**
00437      * Setter for limit value.
00438      *
00439      * @param integer $limitCount
00440      * @return void
00441      */
00442     public function setLimitCount($limitCount) {
00443         $limit = t3lib_div::intval_positive($limitCount);
00444         if ($limit > 0) {
00445             $this->limitCount = $limit;
00446         }
00447     }
00448 
00449     /**
00450      * Setter for start count value.
00451      *
00452      * @param integer $startCount
00453      * @return void
00454      */
00455     public function setStartCount($startCount) {
00456         $this->startCount = t3lib_div::intval_positive($startCount);
00457     }
00458 
00459     /**
00460      * Setter for the search query string.
00461      *
00462      * @param string $queryString
00463      * @return void
00464      * @see t3lib_div::removeXSS()
00465      */
00466     public function setQueryString($queryString) {
00467         $this->queryString = t3lib_div::removeXSS($queryString);
00468     }
00469 
00470     /**
00471      * Creates an instance of t3lib_pageTree which will select a page tree to
00472      * $depth and return the object. In that object we will find the ids of the tree.
00473      *
00474      * @param   integer     Page id.
00475      * @param   integer     Depth to go down.
00476      *
00477      * @return  string      coma separated list of uids
00478      */
00479     protected function getAvailablePageIds($id, $depth) {
00480         $idList = '';
00481         $tree = t3lib_div::makeInstance('t3lib_pageTree');
00482         $tree->init('AND ' . $this->userPermissions);
00483         $tree->makeHTML = 0;
00484         $tree->fieldArray = array('uid', 'php_tree_stop');
00485         if ($depth) {
00486             $tree->getTree($id, $depth, '');
00487         }
00488         $tree->ids[] = $id;
00489         $idList = implode(',', $tree->ids);
00490         return $idList;
00491     }
00492 }
00493 
00494 ?>