com.lokorin.lokorin.lib
[ class tree: com.lokorin.lokorin.lib ] [ index: com.lokorin.lokorin.lib ] [ all elements ]

Source for file lib_search.php

Documentation is available at lib_search.php

  1. <?php
  2. /*
  3. * Lokorin.com
  4. * Copyright 2004-2006
  5. * Licensed under the GNU LGPL. See COPYING for full terms.
  6. */
  7. /**
  8. * A library that contains functions and classes related to searching
  9. * databases.
  10. * @author Andreas Launila
  11. * @version $Revision: 1.7 $
  12. * @package com.lokorin.lokorin.lib
  13. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  14. */
  15.  
  16. /**
  17. * For access to common constants and functions.
  18. */
  19. require_once('common.php');
  20.  
  21. /**
  22. * Highlight specific words in a specified text.
  23. * @param string $text The text in which words should be highlighted.
  24. * @param array $words An array with the words that should be highlighted as
  25. * values.
  26. * @return string The text with the specified words highlighted.
  27. * @param public
  28. */
  29. function highlightWords($text, $words = array()) {
  30. $colors = array(
  31. 'FFFF66',
  32. 'A0FFFF',
  33. '99FF99',
  34. 'FF9999',
  35. '880000',
  36. '00AA00');
  37. if(!is_array($words) || strlen(implode('',$words)) == 0) {
  38. return($text);
  39. }
  40. $result = '';
  41. $pattern = '/((<[^!][\/]*?[^<>]*?>)([^<]*))|((<!--[ \r\n\t]*)(.*)[ \r\n\t]*-->([^<]*))/si';
  42. $matches = array();
  43. preg_match_all($pattern, $text ,$matches);
  44.  
  45. for($i=0; isset($matches[0][$i]); $i++) {
  46. if((preg_match('/<!/i', $matches[0][$i])) ||
  47. (preg_match('/<textarea/i', $matches[2][$i])) ||
  48. (preg_match('/<script/i', $matches[2][$i]))) {
  49. $result .= $matches[0][$i];
  50. } else {
  51. $result .= $matches[2][$i];
  52. $temp = ' '.$matches[3][$i].' ';
  53. foreach($words as $id => $word) {
  54. $temp = preg_replace('/(.*?)(\W)('.preg_quote($word).
  55. ')(\W)(.*?)/iu','$1$2<span style="background: #'.
  56. $colors[bcmod($id, sizeof($colors))].'">$3</span>$4$5', $temp);
  57. }
  58. $result .= substr($temp, 1, (strlen($temp)-2));
  59. }
  60. }
  61. return($result);
  62. }
  63.  
  64. /**
  65. * Converts a wildcard search where '*' means '0 or more characters' to the
  66. * corresponding search in a LIKE search (using '%' instead of '*').
  67. * @param string $wildcardSeatch The search parameter that contains wildcards
  68. * as '*'.
  69. * @return string The corresponding LIKE search parameter.
  70. * @param public
  71. */
  72. function convertWildcardToLike($wildcardSearch) {
  73. $replacements = array(
  74. '_' => '\_',
  75. '%' => '\%',
  76. '*' => '%');
  77. $value = $wildcardSearch;
  78. foreach($replacements as $needle => $replacement) {
  79. $value = str_replace($needle, $replacement, $value);
  80. }
  81. return $value;
  82. }
  83.  
  84. /**
  85. * Checks if a wildcard search can be done with fulltext or if LIKE has to be
  86. * used (fulltext is faster).
  87. * @param string $value The wildcard search value that is going to be used.
  88. * @return True if fulltext can be used, false otherwise.
  89. * @param public
  90. */
  91. function isFulltextSearchable($value) {
  92. $fulltextLength = false;
  93. $words = explode(' ', $value);
  94. foreach($words as $word) {
  95. if(strlen($word) >= 4) {
  96. $fulltextLength = true;
  97. break;
  98. }
  99. }
  100. $pos = strpos($value, '*');
  101. return (($pos === false) || ($pos == strlen($value)-1)) && $fulltextLength;
  102. }
  103.  
  104. /**
  105. * Gets the appriopriate instance of a text search field (FulltextSearch or
  106. * LikeSearch) to use with the specified constructor parameters. Fulltext is
  107. * used if possible, otherwise LIKE is used.
  108. * @param string $requestName The name that identifies the field's value in
  109. * the $_REQUEST array. It should be identical to names used in forms
  110. * and so on.
  111. * @param string $tableField The name of the table field that should be
  112. * associated with the search field.
  113. * @param string $processCallback An optional callback that will be used by
  114. * the search field to process parameters before they are used in the
  115. * sql query. The callback should take one value which is the value of
  116. * the parameter from $_REQUEST and return one value, which is the
  117. * desired value to use.
  118. * @return SearchField The text search field that fits the parameters.
  119. * @param public
  120. */
  121. function getTextSearchField($requestName, $tableField, $processCallback = '') {
  122. if(isset($_REQUEST[$requestName]) &&
  123. isFulltextSearchable($_REQUEST[$requestName])) {
  124. //We can use fulltext, append a wildcard to make it search properly.
  125. if(strpos($_REQUEST[$requestName], '*') === false) {
  126. $_REQUEST[$requestName] .= '*';
  127. }
  128. return new FulltextSearch($requestName, $tableField, $processCallback);
  129. } else {
  130. //We have to use LIKE.
  131. return new LikeSearch($requestName, $tableField, $processCallback);
  132. }
  133. }
  134.  
  135. /**
  136. * Describes a utility that searches tables for rows that satisfy specified
  137. * conditions. The result of a search can be retrieved directly as rows or as
  138. * a sql statement describing the conditions.
  139. * @author Andreas Launila
  140. * @package com.lokorin.lokorin.lib
  141. */
  142. class TableSearcher {
  143. /**
  144. * The name of the table that the search should be performed on.
  145. * @var string
  146. */
  147. private $table;
  148. /**
  149. * An array of SearchField instances.
  150. * @var array
  151. */
  152. private $fields;
  153. /**
  154. * An optional query appendage to use when searching. This can be used to
  155. * specify a specific subset or ordering and is appended on the end of the
  156. * query.
  157. * @var string
  158. */
  159. private $queryAppendage;
  160. /**
  161. * Constructor for SearchGenerator.
  162. * @param string $tableToUse The name of the table that should be seached.
  163. * @param string $queryAppendage An optional query appendage that is
  164. * appended to the end of the used search query. This can be
  165. * used to specify a specific subset or ordering and is appended on
  166. * the end of the query.
  167. */
  168. public function __construct($tableToUse, $queryAppendage = '') {
  169. $this->table = $tableToUse;
  170. $this->queryAppendage = $queryAppendage;
  171. $this->fields = array();
  172. }
  173. /**
  174. * Adds a search field to the table searcher. The field specifies the
  175. * restrictions that should be put on the corresponding table field.
  176. * @param SearchField $fieldToAdd The field that should be added, any sub
  177. * class of SearchField will also do.
  178. */
  179. public function addField($fieldToAdd) {
  180. if(!is_subclass_of($fieldToAdd, 'SearchField')) {
  181. logException('lib_search:TableSearcher The parameter is not a ' .
  182. 'subclass of SearchField.');
  183. } else {
  184. $this->fields[] = $fieldToAdd;
  185. }
  186. }
  187. /**
  188. * Gets all rows that matched the search parameters (if any).
  189. * @param array $fieldsToGet An array with the names of the table fields
  190. * that should be retrieved from the database. This parameter is
  191. * optional, if nothing is specified then all table fields are
  192. * retrieved.
  193. * @return array The selected rows and fields. The array contains one
  194. * array per row. Each of those array rows contain keys with
  195. * the specified field names along with the connected values.
  196. * If no values were found then an empty array will be returned.
  197. */
  198. public function getMatchedRows($fieldsToGet = array()) {
  199. global $db;
  200. if(sizeof($fieldsToGet) == 0) {
  201. //No fields to get were specified, get all.
  202. return $db->querySelectAll($this->table, $this->getSqlSelector());
  203. } else {
  204. return $db->querySelect($this->table, $fieldsToGet,
  205. $this->getSqlSelector());
  206. }
  207. }
  208. /**
  209. * Gets the sql selector that represents the current configuration of the
  210. * table searcher. The selector describes what the search will match.
  211. * @return string A sql statement describing what the search will match.
  212. */
  213. public function getSqlSelector() {
  214. $whereConditions = array();
  215. foreach($this->fields as $field) {
  216. if($field->isValid()) {
  217. $whereConditions[] = '('.$field->getSqlWhereCondition().')';
  218. }
  219. }
  220. $selector = "WHERE ".implode(' AND ', $whereConditions);
  221. if($this->queryAppendage != '') {
  222. $selector .= ' '.$this->queryAppendage;
  223. }
  224. return $selector;
  225. }
  226. /**
  227. * Checks if the table searcher has valid results. I.e. if any search
  228. * paramters have been caught by it.
  229. * @return boolean True if the table searcher has valid results, false
  230. * otherwise.
  231. */
  232. public function isValid() {
  233. foreach($this->fields as $field) {
  234. if($field->isValid()) {
  235. return true;
  236. }
  237. }
  238. return false;
  239. }
  240. /**
  241. * Sets the search parameter of all search fields connected to the request
  242. * name to the specified value.
  243. * @param string $requestName The request name connected to the search
  244. * fields, i.e. the names specified at construction.
  245. * @param mixed $newValue The new value to use as search parameter.
  246. */
  247. public function setSearchParameter($requestName, $newValue) {
  248. foreach($this->fields as $key => $field) {
  249. if($field->getRequestName() == $requestName) {
  250. $this->fields[$key]->setParameter($newValue);
  251. }
  252. }
  253. }
  254. }
  255.  
  256. /**
  257. * Describes a field that should be used in a search.
  258. * @author Andreas Launila
  259. * @package com.lokorin.lokorin.lib
  260. */
  261. abstract class SearchField {
  262. /**
  263. * The name of the field in the table.
  264. * @var string
  265. */
  266. protected $tableField;
  267. /**
  268. * The parameter (if any) that has been caught by the search field.
  269. * Parameters are caught by $_REQUEST[$this->requestName] specifying a non
  270. * empty value at construction.
  271. * @var string
  272. */
  273. protected $parameter;
  274. /**
  275. * The request index that is connected to this request field. I.e. the
  276. * index in $_REQUEST that the search field should attempt to catch
  277. * parameters from.
  278. * @var string
  279. */
  280. private $requestName;
  281. /**
  282. * An optional callback that all parameters should be passed through.
  283. * @var callback
  284. */
  285. private $processCallback;
  286. /**
  287. * Constructor for SearchField.
  288. * @param string $requestName The name that identifies the field's value in
  289. * the $_REQUEST array. It should be identical to names used in forms
  290. * and so on. The value does not have to be unique but collisions
  291. * might occur if it is not.
  292. * @param string $tableField The name of the table field that should be
  293. * associated with the search field.
  294. * @param string $processCallback An optional callback that will be used by
  295. * the search field to process parameters before they are used in the
  296. * sql query. The callback should take one value which is the value of
  297. * the parameter from $_REQUEST and return one value, which is the
  298. * desired value to use.
  299. */
  300. public function __construct($requestName, $tableField, $processCallback = '') {
  301. $this->tableField = $tableField;
  302. $this->processCallback = $processCallback;
  303. $this->requestName = $requestName;
  304. if(isset($_REQUEST[$requestName])) {
  305. $this->setParameter($_REQUEST[$requestName]);
  306. }
  307. }
  308. /**
  309. * Checks if the search field is valid, meaning if it has has caugth a non
  310. * empty parameter.
  311. * @return boolean True if the search field has a non empty parameter,
  312. * false otherwise.
  313. */
  314. public function isValid() {
  315. return $this->parameter != '';
  316. }
  317. /**
  318. * Gets the where condition of the search field. This should be a sql
  319. * statement that can be placed after "WHERE" in the sql query. A couple
  320. * of examples: "id > 5", "(solved = 1) AND (tested = 0)".
  321. * @return string An sql statement to be used in the WHERE section.
  322. */
  323. public abstract function getSqlWhereCondition();
  324. /**
  325. * Sets the parameter for the search done by the field to a new value.
  326. * @param mixed $newParameter The new search parameter that should be used
  327. * by the field.
  328. */
  329. public function setParameter($newParameter) {
  330. if($this->processCallback != '') {
  331. $processCallback = $this->processCallback;
  332. $newParameter = $processCallback($newParameter);
  333. }
  334. $this->parameter = $newParameter;
  335. }
  336. /**
  337. * Gets the name of the table field that the search field is connected to.
  338. * @return string A table field name.
  339. */
  340. public function getRequestName() {
  341. return $this->requestName;
  342. }
  343. }
  344.  
  345. /**
  346. * Describes a search field that implements fulltext searches.
  347. * @author Andreas Launila
  348. * @package com.lokorin.lokorin.lib
  349. */
  350. class FulltextSearch extends SearchField {
  351. /**
  352. * Constructor for FulltextSearch.
  353. * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
  354. */
  355. public function __construct($requestName, $tableField, $processCallback = '') {
  356. parent::__construct($requestName, $tableField, $processCallback);
  357. }
  358. /**
  359. * @see SearchField.getSqlWhereCondition()
  360. */
  361. public function getSqlWhereCondition() {
  362. global $db;
  363. return "MATCH (".$this->tableField.") AGAINST " .
  364. "(".$db->escape(parent::$this->parameter)." IN BOOLEAN MODE)";
  365. }
  366. }
  367.  
  368. /**
  369. * Describes a search field that implements LIKE searches.
  370. * @author Andreas Launila
  371. * @package com.lokorin.lokorin.lib
  372. */
  373. class LikeSearch extends SearchField {
  374. /**
  375. * Constructor for LikeSearch.
  376. * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
  377. * @param string $processCallback An optional callback that will be used by
  378. * the search field to process parameters before they are used in the
  379. * sql query. The callback should take one value which is the value of
  380. * the parameter from $_REQUEST and return one value, which is the
  381. * desired value to use. The default is that wilcards (*) are
  382. * converted to LIKE wildcards.
  383. */
  384. public function __construct($requestName, $tableField,
  385. $processCallback = 'convertWildcardToLike') {
  386. parent::__construct($requestName, $tableField, $processCallback);
  387. }
  388. /**
  389. * @see SearchField.getSqlWhereCondition()
  390. * @access public
  391. */
  392. public function getSqlWhereCondition() {
  393. global $db;
  394. $value = $db->escape('%'.parent::$this->parameter.'%');
  395. if(is_numeric($value)) {
  396. //We want it to be treated like a string, even if it is numerical.
  397. $value = "'".$value."'";
  398. }
  399. return parent::$this->tableField.' LIKE '.$value;
  400. }
  401. }
  402.  
  403. /**
  404. * Describes a search field that searches for things that are equal to the
  405. * parameter.
  406. * @author Andreas Launila
  407. * @package com.lokorin.lokorin.lib
  408. */
  409. class EqualSearch extends SearchField {
  410. /**
  411. * Constructor for EqualSearch.
  412. * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
  413. */
  414. public function __construct($requestName, $tableField, $processCallback = '') {
  415. parent::__construct($requestName, $tableField, $processCallback);
  416. }
  417. /**
  418. * @see SearchField.getSqlWhereCondition()
  419. */
  420. public function getSqlWhereCondition() {
  421. global $db;
  422. return parent::$this->tableField.'='.$db->escape(parent::$this->parameter);
  423. }
  424. }
  425.  
  426. /**
  427. * Describes a search field that searches for things that are lesser than the
  428. * parameter.
  429. * @author Andreas Launila
  430. * @package com.lokorin.lokorin.lib
  431. */
  432. class LesserSearch extends SearchField {
  433. /**
  434. * Constructor for LesserSearch.
  435. * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
  436. */
  437. public function __construct($requestName, $tableField, $processCallback = '') {
  438. parent::__construct($requestName, $tableField, $processCallback);
  439. }
  440. /**
  441. * @see SearchField.getSqlWhereCondition()
  442. */
  443. public function getSqlWhereCondition() {
  444. global $db;
  445. return parent::$this->tableField.'<'.$db->escape(parent::$this->parameter);
  446. }
  447. }
  448.  
  449. /**
  450. * Describes a search field that searches for things that are greater than the
  451. * parameter.
  452. * @author Andreas Launila
  453. * @package com.lokorin.lokorin.lib
  454. */
  455. class GreaterSearch extends SearchField {
  456. /**
  457. * Constructor for GreaterSearch.
  458. * @see SearchField.searchField($requestName, $tableField,
  459. * $processCallback = '')
  460. */
  461. public function __construct($requestName, $tableField, $processCallback = '') {
  462. parent::__construct($requestName, $tableField, $processCallback);
  463. }
  464. /**
  465. * @see SearchField.getSqlWhereCondition()
  466. */
  467. public function getSqlWhereCondition() {
  468. global $db;
  469. return parent::$this->tableField.'>'.$db->escape(parent::$this->parameter);
  470. }
  471. }
  472.  
  473. /**
  474. * Describes a search field that searches for things that are greater than or
  475. * equal to the parameter.
  476. * @author Andreas Launila
  477. * @package com.lokorin.lokorin.lib
  478. */
  479. class GreaterEqualSearch extends SearchField {
  480. /**
  481. * Constructor for GreaterEqualSearch.
  482. * @see SearchField.searchField($requestName, $tableField,
  483. * $processCallback = '')
  484. */
  485. public function __construct($requestName, $tableField, $processCallback = '') {
  486. parent::__construct($requestName, $tableField, $processCallback);
  487. }
  488. /**
  489. * @see SearchField.getSqlWhereCondition()
  490. */
  491. public function getSqlWhereCondition() {
  492. global $db;
  493. return parent::$this->tableField.'>='.$db->escape(parent::$this->parameter);
  494. }
  495. }
  496.  
  497. /**
  498. * Describes a search field that searches for things that are lesser than or
  499. * equal to the parameter.
  500. * @author Andreas Launila
  501. * @package com.lokorin.lokorin.lib
  502. */
  503. class LesserEqualSearch extends SearchField {
  504. /**
  505. * Constructor for LesserSearch.
  506. * @see SearchField.searchField($requestName, $tableField, $processCallback = '')
  507. */
  508. public function __construct($requestName, $tableField, $processCallback = '') {
  509. parent::__construct($requestName, $tableField, $processCallback);
  510. }
  511. /**
  512. * @see SearchField.getSqlWhereCondition()
  513. */
  514. public function getSqlWhereCondition() {
  515. global $db;
  516. return parent::$this->tableField.'<='.$db->escape(parent::$this->parameter);
  517. }
  518. }
  519.  
  520. /**
  521. * Describes a search field that uses a custom pattern to construct the sql
  522. * conditions.
  523. * @author Andreas Launila
  524. * @package com.lokorin.lokorin.lib
  525. */
  526. class CustomSearch extends SearchField {
  527. /**
  528. * The custom pattern that should be used when constructing the where
  529. * conditions.
  530. * @var string
  531. */
  532. private $customPattern;
  533. /**
  534. * Constructor for CustomSearch.
  535. * @see SearchField.searchField($requestName, $tableField,
  536. * $processCallback = '')
  537. * @param string $customPattern The custom pattern that should be used
  538. * when searching. All instances of '%p' will be replaced with the
  539. * search parameter.
  540. */
  541. public function __construct($requestName, $customPattern, $processCallback = '') {
  542. parent::__construct($requestName, '', $processCallback);
  543. $this->customPattern = $customPattern;
  544. }
  545. /**
  546. * @see SearchField.getSqlWhereCondition()
  547. */
  548. public function getSqlWhereCondition() {
  549. global $db;
  550. return str_replace('%p', $db->escape(parent::$this->parameter),
  551. parent::$this->customPattern);
  552. }
  553. }
  554.  
  555. ?>

Documentation generated on Sun, 16 Apr 2006 21:03:53 +0200 by phpDocumentor 1.3.0RC4