- <?php
- /*
- * Lokorin.com
- * Copyright 2004-2006
- * Licensed under the GNU LGPL. See COPYING for full terms.
- */
- /**
- * A library that contains functions and classes related to searching
- * databases.
- * @author Andreas Launila
- * @version $Revision: 1.7 $
- * @package com.lokorin.lokorin.lib
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
-
- /**
- * For access to common constants and functions.
- */
- require_once('common.php');
-
- /**
- * Highlight specific words in a specified text.
- * @param string $text The text in which words should be highlighted.
- * @param array $words An array with the words that should be highlighted as
- * values.
- * @return string The text with the specified words highlighted.
- * @param public
- */
- function highlightWords($text, $words = array()) {
- $colors = array(
- 'FFFF66',
- 'A0FFFF',
- '99FF99',
- 'FF9999',
- '880000',
- '00AA00');
- if(!is_array($words) || strlen(implode('',$words)) == 0) {
- return($text);
- }
- $result = '';
-
- $pattern = '/((<[^!][\/]*?[^<>]*?>)([^<]*))|((<!--[ \r\n\t]*)(.*)[ \r\n\t]*-->([^<]*))/si';
- $matches = array();
- preg_match_all($pattern, $text ,$matches);
-
- for($i=0; isset($matches[0][$i]); $i++) {
- if((preg_match('/<!/i', $matches[0][$i])) ||
- (preg_match('/<textarea/i', $matches[2][$i])) ||
- (preg_match('/<script/i', $matches[2][$i]))) {
- $result .= $matches[0][$i];
- } else {
- $result .= $matches[2][$i];
- $temp = ' '.$matches[3][$i].' ';
- foreach($words as $id => $word) {
- $temp = preg_replace('/(.*?)(\W)('.preg_quote($word).
- ')(\W)(.*?)/iu','$1$2<span style="background: #'.
- $colors[bcmod($id, sizeof($colors))].'">$3</span>$4$5', $temp);
- }
- $result .= substr($temp, 1, (strlen($temp)-2));
- }
- }
- return($result);
- }
-
- /**
- * Converts a wildcard search where '*' means '0 or more characters' to the
- * corresponding search in a LIKE search (using '%' instead of '*').
- * @param string $wildcardSeatch The search parameter that contains wildcards
- * as '*'.
- * @return string The corresponding LIKE search parameter.
- * @param public
- */
- function convertWildcardToLike($wildcardSearch) {
- $replacements = array(
- '_' => '\_',
- '%' => '\%',
- '*' => '%');
- $value = $wildcardSearch;
- foreach($replacements as $needle => $replacement) {
- $value = str_replace($needle, $replacement, $value);
- }
- return $value;
- }
-
- /**
- * Checks if a wildcard search can be done with fulltext or if LIKE has to be
- * used (fulltext is faster).
- * @param string $value The wildcard search value that is going to be used.
- * @return True if fulltext can be used, false otherwise.
- * @param public
- */
- function isFulltextSearchable($value) {
- $fulltextLength = false;
- $words = explode(' ', $value);
- foreach($words as $word) {
- if(strlen($word) >= 4) {
- $fulltextLength = true;
- break;
- }
- }
- $pos = strpos($value, '*');
- return (($pos === false) || ($pos == strlen($value)-1)) && $fulltextLength;
- }
-
- /**
- * Gets the appriopriate instance of a text search field (FulltextSearch or
- * LikeSearch) to use with the specified constructor parameters. Fulltext is
- * used if possible, otherwise LIKE is used.
- * @param string $requestName The name that identifies the field's value in
- * the $_REQUEST array. It should be identical to names used in forms
- * and so on.
- * @param string $tableField The name of the table field that should be
- * associated with the search field.
- * @param string $processCallback An optional callback that will be used by
- * the search field to process parameters before they are used in the
- * sql query. The callback should take one value which is the value of
- * the parameter from $_REQUEST and return one value, which is the
- * desired value to use.
- * @return SearchField The text search field that fits the parameters.
- * @param public
- */
- function getTextSearchField($requestName, $tableField, $processCallback = '') {
- if(isset($_REQUEST[$requestName]) &&
- isFulltextSearchable($_REQUEST[$requestName])) {
- //We can use fulltext, append a wildcard to make it search properly.
- if(strpos($_REQUEST[$requestName], '*') === false) {
- $_REQUEST[$requestName] .= '*';
- }
- return new FulltextSearch($requestName, $tableField, $processCallback);
- } else {
- //We have to use LIKE.
- return new LikeSearch($requestName, $tableField, $processCallback);
- }
- }
-
- /**
- * Describes a utility that searches tables for rows that satisfy specified
- * conditions. The result of a search can be retrieved directly as rows or as
- * a sql statement describing the conditions.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class TableSearcher {
- /**
- * The name of the table that the search should be performed on.
- * @var string
- */
- private $table;
- /**
- * An array of SearchField instances.
- * @var array
- */
- private $fields;
- /**
- * An optional query appendage to use when searching. This can be used to
- * specify a specific subset or ordering and is appended on the end of the
- * query.
- * @var string
- */
- private $queryAppendage;
-
- /**
- * Constructor for SearchGenerator.
- * @param string $tableToUse The name of the table that should be seached.
- * @param string $queryAppendage An optional query appendage that is
- * appended to the end of the used search query. This can be
- * used to specify a specific subset or ordering and is appended on
- * the end of the query.
- */
- public function __construct($tableToUse, $queryAppendage = '') {
- $this->table = $tableToUse;
- $this->queryAppendage = $queryAppendage;
-
- $this->fields = array();
- }
-
- /**
- * Adds a search field to the table searcher. The field specifies the
- * restrictions that should be put on the corresponding table field.
- * @param SearchField $fieldToAdd The field that should be added, any sub
- * class of SearchField will also do.
- */
- public function addField($fieldToAdd) {
- if(!is_subclass_of($fieldToAdd, 'SearchField')) {
- logException('lib_search:TableSearcher The parameter is not a ' .
- 'subclass of SearchField.');
- } else {
- $this->fields[] = $fieldToAdd;
- }
- }
-
- /**
- * Gets all rows that matched the search parameters (if any).
- * @param array $fieldsToGet An array with the names of the table fields
- * that should be retrieved from the database. This parameter is
- * optional, if nothing is specified then all table fields are
- * retrieved.
- * @return array The selected rows and fields. The array contains one
- * array per row. Each of those array rows contain keys with
- * the specified field names along with the connected values.
- * If no values were found then an empty array will be returned.
- */
- public function getMatchedRows($fieldsToGet = array()) {
- global $db;
-
- if(sizeof($fieldsToGet) == 0) {
- //No fields to get were specified, get all.
- return $db->querySelectAll($this->table, $this->getSqlSelector());
- } else {
- return $db->querySelect($this->table, $fieldsToGet,
- $this->getSqlSelector());
- }
- }
-
- /**
- * Gets the sql selector that represents the current configuration of the
- * table searcher. The selector describes what the search will match.
- * @return string A sql statement describing what the search will match.
- */
- public function getSqlSelector() {
- $whereConditions = array();
- foreach($this->fields as $field) {
- if($field->isValid()) {
- $whereConditions[] = '('.$field->getSqlWhereCondition().')';
- }
- }
- $selector = "WHERE ".implode(' AND ', $whereConditions);
- if($this->queryAppendage != '') {
- $selector .= ' '.$this->queryAppendage;
- }
- return $selector;
- }
-
- /**
- * Checks if the table searcher has valid results. I.e. if any search
- * paramters have been caught by it.
- * @return boolean True if the table searcher has valid results, false
- * otherwise.
- */
- public function isValid() {
- foreach($this->fields as $field) {
- if($field->isValid()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Sets the search parameter of all search fields connected to the request
- * name to the specified value.
- * @param string $requestName The request name connected to the search
- * fields, i.e. the names specified at construction.
- * @param mixed $newValue The new value to use as search parameter.
- */
- public function setSearchParameter($requestName, $newValue) {
- foreach($this->fields as $key => $field) {
- if($field->getRequestName() == $requestName) {
- $this->fields[$key]->setParameter($newValue);
- }
- }
- }
- }
-
- /**
- * Describes a field that should be used in a search.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- abstract class SearchField {
- /**
- * The name of the field in the table.
- * @var string
- */
- protected $tableField;
- /**
- * The parameter (if any) that has been caught by the search field.
- * Parameters are caught by $_REQUEST[$this->requestName] specifying a non
- * empty value at construction.
- * @var string
- */
- protected $parameter;
- /**
- * The request index that is connected to this request field. I.e. the
- * index in $_REQUEST that the search field should attempt to catch
- * parameters from.
- * @var string
- */
- private $requestName;
- /**
- * An optional callback that all parameters should be passed through.
- * @var callback
- */
- private $processCallback;
-
- /**
- * Constructor for SearchField.
- * @param string $requestName The name that identifies the field's value in
- * the $_REQUEST array. It should be identical to names used in forms
- * and so on. The value does not have to be unique but collisions
- * might occur if it is not.
- * @param string $tableField The name of the table field that should be
- * associated with the search field.
- * @param string $processCallback An optional callback that will be used by
- * the search field to process parameters before they are used in the
- * sql query. The callback should take one value which is the value of
- * the parameter from $_REQUEST and return one value, which is the
- * desired value to use.
- */
- public function __construct($requestName, $tableField, $processCallback = '') {
- $this->tableField = $tableField;
- $this->processCallback = $processCallback;
- $this->requestName = $requestName;
-
- if(isset($_REQUEST[$requestName])) {
- $this->setParameter($_REQUEST[$requestName]);
- }
- }
-
- /**
- * Checks if the search field is valid, meaning if it has has caugth a non
- * empty parameter.
- * @return boolean True if the search field has a non empty parameter,
- * false otherwise.
- */
- public function isValid() {
- return $this->parameter != '';
- }
-
- /**
- * Gets the where condition of the search field. This should be a sql
- * statement that can be placed after "WHERE" in the sql query. A couple
- * of examples: "id > 5", "(solved = 1) AND (tested = 0)".
- * @return string An sql statement to be used in the WHERE section.
- */
- public abstract function getSqlWhereCondition();
-
- /**
- * Sets the parameter for the search done by the field to a new value.
- * @param mixed $newParameter The new search parameter that should be used
- * by the field.
- */
- public function setParameter($newParameter) {
- if($this->processCallback != '') {
- $processCallback = $this->processCallback;
- $newParameter = $processCallback($newParameter);
- }
- $this->parameter = $newParameter;
- }
-
- /**
- * Gets the name of the table field that the search field is connected to.
- * @return string A table field name.
- */
- public function getRequestName() {
- return $this->requestName;
- }
- }
-
- /**
- * Describes a search field that implements fulltext searches.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class FulltextSearch extends SearchField {
- /**
- * Constructor for FulltextSearch.
- * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
- */
- public function __construct($requestName, $tableField, $processCallback = '') {
- parent::__construct($requestName, $tableField, $processCallback);
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- */
- public function getSqlWhereCondition() {
- global $db;
-
- return "MATCH (".$this->tableField.") AGAINST " .
- "(".$db->escape(parent::$this->parameter)." IN BOOLEAN MODE)";
- }
- }
-
- /**
- * Describes a search field that implements LIKE searches.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class LikeSearch extends SearchField {
- /**
- * Constructor for LikeSearch.
- * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
- * @param string $processCallback An optional callback that will be used by
- * the search field to process parameters before they are used in the
- * sql query. The callback should take one value which is the value of
- * the parameter from $_REQUEST and return one value, which is the
- * desired value to use. The default is that wilcards (*) are
- * converted to LIKE wildcards.
- */
- public function __construct($requestName, $tableField,
- $processCallback = 'convertWildcardToLike') {
- parent::__construct($requestName, $tableField, $processCallback);
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- * @access public
- */
- public function getSqlWhereCondition() {
- global $db;
-
- $value = $db->escape('%'.parent::$this->parameter.'%');
- if(is_numeric($value)) {
- //We want it to be treated like a string, even if it is numerical.
- $value = "'".$value."'";
- }
- return parent::$this->tableField.' LIKE '.$value;
- }
- }
-
- /**
- * Describes a search field that searches for things that are equal to the
- * parameter.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class EqualSearch extends SearchField {
- /**
- * Constructor for EqualSearch.
- * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
- */
- public function __construct($requestName, $tableField, $processCallback = '') {
- parent::__construct($requestName, $tableField, $processCallback);
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- */
- public function getSqlWhereCondition() {
- global $db;
-
- return parent::$this->tableField.'='.$db->escape(parent::$this->parameter);
- }
- }
-
- /**
- * Describes a search field that searches for things that are lesser than the
- * parameter.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class LesserSearch extends SearchField {
- /**
- * Constructor for LesserSearch.
- * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
- */
- public function __construct($requestName, $tableField, $processCallback = '') {
- parent::__construct($requestName, $tableField, $processCallback);
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- */
- public function getSqlWhereCondition() {
- global $db;
-
- return parent::$this->tableField.'<'.$db->escape(parent::$this->parameter);
- }
- }
-
- /**
- * Describes a search field that searches for things that are greater than the
- * parameter.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class GreaterSearch extends SearchField {
- /**
- * Constructor for GreaterSearch.
- * @see SearchField.searchField($requestName, $tableField,
- * $processCallback = '')
- */
- public function __construct($requestName, $tableField, $processCallback = '') {
- parent::__construct($requestName, $tableField, $processCallback);
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- */
- public function getSqlWhereCondition() {
- global $db;
-
- return parent::$this->tableField.'>'.$db->escape(parent::$this->parameter);
- }
- }
-
- /**
- * Describes a search field that searches for things that are greater than or
- * equal to the parameter.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class GreaterEqualSearch extends SearchField {
- /**
- * Constructor for GreaterEqualSearch.
- * @see SearchField.searchField($requestName, $tableField,
- * $processCallback = '')
- */
- public function __construct($requestName, $tableField, $processCallback = '') {
- parent::__construct($requestName, $tableField, $processCallback);
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- */
- public function getSqlWhereCondition() {
- global $db;
-
- return parent::$this->tableField.'>='.$db->escape(parent::$this->parameter);
- }
- }
-
- /**
- * Describes a search field that searches for things that are lesser than or
- * equal to the parameter.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class LesserEqualSearch extends SearchField {
- /**
- * Constructor for LesserSearch.
- * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
- */
- public function __construct($requestName, $tableField, $processCallback = '') {
- parent::__construct($requestName, $tableField, $processCallback);
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- */
- public function getSqlWhereCondition() {
- global $db;
-
- return parent::$this->tableField.'<='.$db->escape(parent::$this->parameter);
- }
- }
-
- /**
- * Describes a search field that uses a custom pattern to construct the sql
- * conditions.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class CustomSearch extends SearchField {
- /**
- * The custom pattern that should be used when constructing the where
- * conditions.
- * @var string
- */
- private $customPattern;
-
- /**
- * Constructor for CustomSearch.
- * @see SearchField.searchField($requestName, $tableField,
- * $processCallback = '')
- * @param string $customPattern The custom pattern that should be used
- * when searching. All instances of '%p' will be replaced with the
- * search parameter.
- */
- public function __construct($requestName, $customPattern, $processCallback = '') {
- parent::__construct($requestName, '', $processCallback);
- $this->customPattern = $customPattern;
- }
-
- /**
- * @see SearchField.getSqlWhereCondition()
- */
- public function getSqlWhereCondition() {
- global $db;
-
- return str_replace('%p', $db->escape(parent::$this->parameter),
- parent::$this->customPattern);
- }
- }
-
- ?>