--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2009-2010 Michael Klapper <michael.klapper@aoemedia.de>
+ * (c) 2010 Jeff Segars <jeff@webempoweredchurch.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Class for handling backend live search.
+ *
+ * @author Michael Klapper <michael.klapper@aoemedia.de>
+ * @author Jeff Segars <jeff@webempoweredchurch.org>
+ * @package TYPO3
+ * @subpackage t3lib
+ */
+class t3lib_search_livesearch {
+
+ /**
+ * @var string
+ */
+ const PAGE_JUMP_TABLE = 'pages';
+
+ /**
+ * @var integer
+ */
+ const RECURSIVE_PAGE_LEVEL = 99;
+
+ /**
+ * @var integer
+ */
+ const GROUP_TITLE_MAX_LENGTH = 15;
+
+ /**
+ * @var integer
+ */
+ const RECORD_TITLE_MAX_LENGTH = 37;
+
+ /**
+ * @var string
+ */
+ private $queryString = '';
+
+ /**
+ * @var integer
+ */
+ private $startCount = 0;
+
+ /**
+ * @var integer
+ */
+ private $limitCount = 5;
+
+ /**
+ * @var string
+ */
+ protected $userPermissions = '';
+
+ /**
+ * @var t3lib_search_livesearch_queryParser
+ */
+ protected $queryParser = null;
+
+ /**
+ * Initialize access settings.
+ *
+ * @return void
+ */
+ public function __construct() {
+ $this->userPermissions = $GLOBALS['BE_USER']->getPagePermsClause(1);
+ $this->queryParser = t3lib_div::makeInstance('t3lib_search_livesearch_queryParser');
+ }
+
+ /**
+ * Find records from database based on the given $searchQuery.
+ *
+ * @param string $searchQuery
+ * @return string Edit link to an page record if exists. Otherwise an empty string will returned
+ */
+ public function findPage($searchQuery) {
+ $link = '';
+ $pageId = $this->queryParser->getId($searchQuery);
+ $pageRecord = $this->findPageById($pageId);
+
+ if (!empty($pageRecord)) {
+ $link = $this->getEditLink(self::PAGE_JUMP_TABLE, $this->findPageById($pageId));
+ }
+
+ return $link;
+ }
+
+ /**
+ * Find records from database based on the given $searchQuery.
+ *
+ * @param string $searchQuery
+ * @return array Result list of database search.
+ */
+ public function find($searchQuery) {
+ $recordArray = array();
+ $pageIdList = $this->getAvailablePageIds (
+ implode(',', $GLOBALS['BE_USER']->returnWebmounts()),
+ self::RECURSIVE_PAGE_LEVEL
+ );
+ $limit = $this->startCount . ',' . $this->limitCount;
+
+ if ($this->queryParser->isValidCommand($searchQuery)) {
+ $this->setQueryString($this->queryParser->getSearchQueryValue($searchQuery));
+ $tableName = $this->queryParser->getTableNameFromCommand($searchQuery);
+ if ($tableName) {
+ $recordArray[] = $this->findByTable($tableName, $pageIdList, $limit);
+ }
+ } else {
+ $this->setQueryString($searchQuery);
+ $recordArray = $this->findByGlobalTableList($pageIdList, $limit);
+ }
+
+ // @todo Need to make sure we don't return too many records. How do we handle this when querying across multiple tables?
+ $recordArray = array_slice($recordArray, 0, $this->limitCount);
+
+ return $recordArray;
+ }
+
+ /**
+ * Retrieve the page record from given $id.
+ *
+ * @param integer $id
+ * @return array
+ */
+ protected function findPageById($id) {
+ $pageRecord = array();
+ $row = t3lib_BEfunc::getRecord(self::PAGE_JUMP_TABLE, $id);
+
+ if (is_array($row)) {
+ $pageRecord = $row;
+ }
+
+ return $pageRecord;
+ }
+
+ /**
+ * Find records from all registered TCA table & column values.
+ *
+ * @param string $pageIdList Comma seperated list of page IDs
+ * @param string $limit MySql Limit notation
+ * @return array Records found in the database matching the searchQuery
+ */
+ protected function findByGlobalTableList($pageIdList, $limit) {
+ $getRecordArray = array();
+ foreach ($GLOBALS['TCA'] as $tableName => $value) {
+ $getRecordArray[] = $this->findByTable($tableName, $pageIdList, $limit);
+ }
+
+ return $getRecordArray;
+ }
+
+ /**
+ * Find records by given table name.
+ *
+ * @param string $tableName Database table name
+ * @param string $pageIdList Comma seperated list of page IDs
+ * @param string $limit MySql Limit notation
+ * @return array Records found in the database matching the searchQuery
+ *
+ * @see getRecordArray()
+ * @see makeOrderByTable()
+ * @see makeQuerySearchByTable()
+ * @see extractSearchableFieldsFromTable()
+ */
+ protected function findByTable($tableName, $pageIdList, $limit) {
+ $getRecordArray = array();
+ $fieldsToSearchWithin = $this->extractSearchableFieldsFromTable($tableName);
+ $pageBasedPermission = ($tableName == 'pages' && $this->userPermissions) ? $this->userPermissions : '1=1 ' ;
+ $where = 'pid IN(' . $pageIdList . ')' . $pageBasedPermission . $this->makeQuerySearchByTable($tableName, $fieldsToSearchWithin);
+ $orderBy = $this->makeOrderByTable($tableName);
+ $getRecordArray = $this->getRecordArray(
+ $tableName,
+ $pageBasedPermission . $this->makeQuerySearchByTable($tableName, $fieldsToSearchWithin),
+ $this->makeOrderByTable($tableName),
+ $limit
+ );
+
+ return $getRecordArray;
+ }
+
+ /**
+ * Process the Database operation to get the search result.
+ *
+ * @param string $tableName Database table name
+ * @param string $where
+ * @param string $orderBy
+ * @param string $limit MySql Limit notation
+ * @return array
+ *
+ * @see t3lib_db::exec_SELECT_queryArray()
+ * @see t3lib_db::sql_num_rows()
+ * @see t3lib_db::sql_fetch_assoc()
+ * @see t3lib_iconWorks::getSpriteIconForRecord()
+ * @see getTitleFromCurrentRow()
+ * @see getEditLink()
+ */
+ protected function getRecordArray($tableName, $where, $orderBy, $limit) {
+ $collect = array();
+ $isFirst = true;
+ $queryParts = array(
+ 'SELECT' => '*',
+ 'FROM' => $tableName,
+ 'WHERE' => $where,
+ 'ORDERBY' => $orderBy,
+ 'LIMIT' => $limit
+ );
+ $result = $GLOBALS['TYPO3_DB']->exec_SELECT_queryArray($queryParts);
+ $dbCount = $GLOBALS['TYPO3_DB']->sql_num_rows($result);
+
+ while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($result)) {
+ $collect[] = array (
+ 'id' => $tableName . ':' . $row['uid'],
+ 'recordTitle' => ($isFirst) ? $this->getRecordTitlePrep($this->getTitleOfCurrentRecordType($tableName), self::GROUP_TITLE_MAX_LENGTH) : '',
+ 'iconHTML' => t3lib_iconWorks::getSpriteIconForRecord($tableName, $row),
+ 'title' => $this->getRecordTitlePrep($this->getTitleFromCurrentRow($tableName, $row), self::RECORD_TITLE_MAX_LENGTH),
+ 'editLink' => $this->getEditLink($tableName, $row),
+ );
+ $isFirst = false;
+ }
+
+ return $collect;
+ }
+
+ /**
+ * Build a backend edit link based on given record.
+ *
+ * @param string $tableName Record table name
+ * @param array $row Current record row from database.
+ * @return string Link to open an edit window for record.
+ *
+ * @see t3lib_BEfunc::readPageAccess()
+ */
+ protected function getEditLink($tableName, $row) {
+ $pageInfo = t3lib_BEfunc::readPageAccess($row['pid'], $this->userPermissions);
+ $calcPerms = $GLOBALS['BE_USER']->calcPerms($pageInfo);
+ $editLink = '';
+
+ if ($tableName == 'pages') {
+ $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages',$row['uid']));
+ $permsEdit = $localCalcPerms&2;
+ } else {
+ $permsEdit = $calcPerms&16;
+ }
+
+ // "Edit" link: ( Only if permissions to edit the page-record of the content of the parent page ($this->id)
+ // @todo Is there an existing function to generate this link?
+ if ($permsEdit) {
+ $editLink = 'alt_doc.php?' . '&edit['.$tableName.']['.$row['uid'].']=edit';
+ }
+
+ return $editLink;
+ }
+
+ /**
+ * Retrieve the record name
+ *
+ * @param string $tableName Record table name
+ * @return string
+ */
+ protected function getTitleOfCurrentRecordType($tableName) {
+ return $GLOBALS['LANG']->sL($GLOBALS['TCA'][$tableName]['ctrl']['title']);
+ }
+
+ /**
+ * Crops a title string to a limited lenght and if it really was cropped, wrap it in a <span title="...">|</span>,
+ * which offers a tooltip with the original title when moving mouse over it.
+ *
+ * @param string $title: The title string to be cropped
+ * @param integer $titleLength: Crop title after this length - if not set, BE_USER->uc['titleLen'] is used
+ * @return string The processed title string, wrapped in <span title="...">|</span> if cropped
+ */
+ public function getRecordTitlePrep($title, $titleLength = 0) {
+ // If $titleLength is not a valid positive integer, use BE_USER->uc['titleLen']:
+ if (!$titleLength || !t3lib_div::testInt($titleLength) || $titleLength < 0) {
+ $titleLength = $GLOBALS['BE_USER']->uc['titleLen'];
+ }
+
+ return htmlspecialchars(t3lib_div::fixed_lgd_cs($title, $titleLength));;
+ }
+
+ /**
+ * Retrieve the column name which contains the title value
+ *
+ * @param string $tableName Record table name
+ * @param array $row Current record row from database.
+ * @return string
+ *
+ * @todo Use the backend function to get the calculated label instead.
+ */
+ protected function getTitleFromCurrentRow($tableName, $row) {
+ $titleColumnName = $GLOBALS['TCA'][$tableName]['ctrl']['label'];
+ return $row[$titleColumnName];
+ }
+
+ /**
+ * Build the MySql where clause by table.
+ *
+ * @param string $tableName Record table name
+ * @param array $fieldsToSearchWithin User right based visible fields where we can search within.
+ * @return string
+ */
+ protected function makeQuerySearchByTable($tableName, $fieldsToSearchWithin) {
+ // free text search
+ $queryLikeStatement = ' LIKE \'%' . $this->getQueryString() . '%\'';
+ $queryPart = ' AND (' . implode($queryLikeStatement . ' OR ', $fieldsToSearchWithin) . $queryLikeStatement . ')';
+ $queryPart .= t3lib_BEfunc::deleteClause($tableName);
+ $queryPart .= t3lib_BEfunc::versioningPlaceholderClause($tableName);
+
+ return $queryPart;
+ }
+
+ /**
+ * Build the MySql ORDER BY statement.
+ *
+ *
+ * @param string $tableName Record table name
+ * @return string
+ * @see t3lib_db::stripOrderBy()
+ */
+ protected function makeOrderByTable($tableName) {
+ $orderBy = '';
+
+ if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('sortby', $GLOBALS['TCA'][$tableName]['ctrl'])) {
+ $orderBy = 'ORDER BY '.$GLOBALS['TCA'][$tableName]['ctrl']['sortby'];
+ } else {
+ $orderBy = $GLOBALS['TCA'][$tableName]['ctrl']['default_sortby'];
+ }
+
+ return $GLOBALS['TYPO3_DB']->stripOrderBy($orderBy);
+ }
+
+ /**
+ * Get all fields from given table where we can search for.
+ *
+ * @param string $tableName
+ * @return array
+ */
+ protected function extractSearchableFieldsFromTable($tableName) {
+ $fieldListArray = array();
+
+ // Traverse configured columns and add them to field array, if available for user.
+ foreach((array) $GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $fieldValue) {
+ // @todo Reformat
+ if (
+ (!$fieldValue['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields', $tableName . ':' . $fieldName)) // does current user have access to the field
+ &&
+ ($fieldValue['config']['type'] != 'passthrough') // field type is not searchable
+ &&
+ (!preg_match('/date|time|int/', $fieldValue['config']['eval'])) // field can't be of type date, time, int
+ &&
+ (
+ ($fieldValue['config']['type'] == 'text')
+ ||
+ ($fieldValue['config']['type'] == 'input')
+ )
+ ) {
+ $fieldListArray[] = $fieldName;
+ }
+ }
+
+ // Add special fields:
+ if ($GLOBALS['BE_USER']->isAdmin()) {
+ $fieldListArray[] = 'uid';
+ $fieldListArray[] = 'pid';
+ }
+
+ return $fieldListArray;
+ }
+
+ /**
+ * Safely retrieve the queryString.
+ *
+ * @return string
+ * @see t3lib_db::quoteStr()
+ */
+ public function getQueryString() {
+ return $GLOBALS['TYPO3_DB']->quoteStr($this->queryString, '');
+ }
+
+ /**
+ * Setter for limit value.
+ *
+ * @param integer $limitCount
+ * @return void
+ */
+ public function setLimitCount($limitCount) {
+ $limit = t3lib_div::intval_positive($limitCount);
+ if ($limit > 0) {
+ $this->limitCount = $limit;
+ }
+ }
+
+ /**
+ * Setter for start count value.
+ *
+ * @param integer $startCount
+ * @return void
+ */
+ public function setStartCount($startCount) {
+ $this->startCount = t3lib_div::intval_positive($startCount);
+ }
+
+ /**
+ * Setter for the search query string.
+ *
+ * @param string $queryString
+ * @return void
+ * @see t3lib_div::removeXSS()
+ */
+ public function setQueryString($queryString) {
+ $this->queryString = t3lib_div::removeXSS($queryString);
+ }
+
+ /**
+ * Creates an instance of t3lib_pageTree which will select a page tree to
+ * $depth and return the object. In that object we will find the ids of the tree.
+ *
+ * @param integer Page id.
+ * @param integer Depth to go down.
+ *
+ * @return string coma separated list of uids
+ */
+ protected function getAvailablePageIds($id, $depth) {
+ $idList = '';
+ $tree = t3lib_div::makeInstance('t3lib_pageTree');
+ $tree->init('AND ' . $this->userPermissions);
+ $tree->makeHTML = 0;
+ $tree->fieldArray = Array('uid','php_tree_stop');
+ if ($depth) {
+ $tree->getTree($id, $depth, '');
+ }
+ $tree->ids[] = $id;
+ $idList = implode(',', $tree->ids);
+ return $idList;
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2009-2010 Michael Klapper <michael.klapper@aoemedia.de>
+ * (c) 2010 Jeff Segars <jeff@webempoweredchurch.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Class for parsing query parameters in backend live search.
+ *
+ * @author Michael Klapper <michael.klapper@aoemedia.de>
+ * @author Jeff Segars <jeff@webempoweredchurch.org>
+ * @package TYPO3
+ * @subpackage t3lib
+ */
+class t3lib_search_livesearch_queryParser {
+
+ /**
+ * @var string
+ */
+ protected $commandKey = '';
+
+ /**
+ * @var string
+ */
+ protected $tableName = '';
+
+ /**
+ * @var string
+ */
+ const COMMAND_KEY_INDICATOR = '#';
+
+ /**
+ * @var string
+ */
+ const COMMAND_SPLIT_INDICATOR = ':';
+
+ /**
+ * Retrive the validated command key
+ *
+ * @return string Command name
+ */
+ protected function extractKeyFromQuery($query) {
+ $keyAndValue = substr($query, 1);
+ $key = explode(':', $keyAndValue);
+ $this->commandKey = $key[0];
+ }
+
+ /**
+ * Extract the search value from the full search query which contains also the command part.
+ *
+ * @param string $query For example #news:weather
+ * @return string The extracted search value
+ */
+ public function getSearchQueryValue($query) {
+ $this->extractKeyFromQuery($query);
+ return str_replace(self::COMMAND_KEY_INDICATOR . $this->commandKey . self::COMMAND_SPLIT_INDICATOR, '', $query);
+ }
+
+ /**
+ * Find the registerd table command and retrieve the matching table name.
+ *
+ * @param string $query
+ * @return string Database Table name
+ */
+ public function getTableNameFromCommand($query) {
+ $tableName = '';
+ $this->extractKeyFromQuery($query);
+ if (is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['livesearch']) && array_key_exists($this->commandKey, $GLOBALS['TYPO3_CONF_VARS']['SYS']['livesearch'])) {
+ $tableName = $GLOBALS['TYPO3_CONF_VARS']['SYS']['livesearch'][$this->commandKey];
+ }
+ return $tableName;
+ }
+
+ /**
+ * Verify if an given query contains a page jump command.
+ *
+ * @param string $query A valid value looks like '#14'
+ * @return integer
+ */
+ public function getId($query) {
+ return str_replace(self::COMMAND_KEY_INDICATOR, '', $query);
+ }
+
+ /**
+ * Verify if a given query contains a page jump command.
+ *
+ * @param string $query A valid value looks like '#14'
+ * @return boolean
+ */
+ public function isValidPageJump($query) {
+ $isValid = false;
+
+ if (preg_match('~^#(\d)+$~', $query)) {
+ $isValid = true;
+ }
+
+ return $isValid;
+ }
+
+ /**
+ * Verify if an given query contains an registered command key.
+ *
+ * @param string $query
+ * @return boolean
+ */
+ public function isValidCommand($query) {
+ $isValid = false;
+ if (strpos($query, self::COMMAND_KEY_INDICATOR) === 0 &&
+ strpos($query, self::COMMAND_SPLIT_INDICATOR) > 1 &&
+ $this->getTableNameFromCommand($query)) {
+ $isValid = true;
+ }
+
+ return $isValid;
+ }
+
+ /**
+ * Gets the command for the given table.
+ *
+ * @param string $tableName The table to find a command for.
+ * @return string
+ */
+ public function getCommandForTable($tableName) {
+ $commandArray = array_keys($GLOBALS['TYPO3_CONF_VARS']['SYS']['livesearch'], $tableName);
+ if (is_array($commandArray)) {
+ $command = $commandArray[0];
+ } else {
+ $command = FALSE;
+ }
+
+ return $command;
+ }
+
+ /**
+ * Gets the page jump command for a given query.
+ *
+ * @param string $query
+ * @return string
+ */
+ public function getCommandForPageJump($query) {
+ if ($this->isValidPageJump($query)) {
+ $command = $this->getCommandForTable('pages');
+ $id = $this->getId($query);
+
+ $resultQuery = self::COMMAND_KEY_INDICATOR . $command . self::COMMAND_SPLIT_INDICATOR . $id;
+ } else {
+ $resultQuery = FALSE;
+ }
+
+ return $resultQuery;
+ }
+}
+?>
\ No newline at end of file