1<?php 2/* 3 * $Id: Hook.php 1939 2007-07-05 23:47:48Z zYne $ 4 * 5 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 6 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 7 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 8 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 9 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 10 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 11 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 12 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 13 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 14 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 15 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 * 17 * This software consists of voluntary contributions made by many individuals 18 * and is licensed under the LGPL. For more information, see 19 * <http://www.doctrine-project.org>. 20 */ 21 22/** 23 * Doctrine_Search_Query 24 * 25 * @package Doctrine 26 * @subpackage Search 27 * @author Konsta Vesterinen <kvesteri@cc.hut.fi> 28 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL 29 * @version $Revision$ 30 * @link www.doctrine-project.org 31 * @since 1.0 32 */ 33class Doctrine_Search_Query 34{ 35 36 /** 37 * @var Doctrine_Table $_table the index table 38 */ 39 protected $_table = array(); 40 41 protected $_sql = ''; 42 43 protected $_params = array(); 44 45 protected $_words = array(); 46 47 protected $_tokenizer; 48 49 protected $_condition; 50 51 /** 52 * @param Doctrine_Table $_table the index table 53 */ 54 public function __construct($table) 55 { 56 if (is_string($table)) { 57 $table = Doctrine_Core::getTable($table); 58 } else { 59 if ( ! $table instanceof Doctrine_Table) { 60 throw new Doctrine_Search_Exception('Invalid argument type. Expected instance of Doctrine_Table.'); 61 } 62 } 63 64 $this->_tokenizer = new Doctrine_Query_Tokenizer(); 65 $this->_table = $table; 66 67 $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position'))); 68 69 $this->_condition = $foreignId . ' %s (SELECT ' . $foreignId . ' FROM ' . $this->_table->getTableName() . ' WHERE '; 70 } 71 72 73 public function query($text, $includeRelevance = true) 74 { 75 $text = trim($text); 76 77 $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position'))); 78 79 $weighted = false; 80 if (strpos($text, '^') === false) { 81 if ($includeRelevance) { 82 $select = 'SELECT COUNT(keyword) AS relevance, ' . $foreignId; 83 } else { 84 $select = 'SELECT ' . $foreignId; 85 } 86 } else { 87 if ($includeRelevance) { 88 $select = 'SELECT SUM(sub_relevance) AS relevance, ' . $foreignId; 89 } else { 90 $select = 'SELECT ' . $foreignId; 91 } 92 } 93 94 $from = 'FROM ' . $this->_table->getTableName(); 95 $where = 'WHERE '; 96 $where .= $this->parseClause($text); 97 98 $groupby = 'GROUP BY ' . $foreignId; 99 if ($includeRelevance) { 100 $orderBy = 'ORDER BY relevance DESC'; 101 } else { 102 $orderBy = null; 103 } 104 $this->_sql = $select . ' ' . $from . ' ' . $where . ' ' . $groupby; 105 if (isset($orderBy) && $orderBy !== null) { 106 $this->_sql .= ' ' . $orderBy; 107 } 108 } 109 110 public function parseClause($originalClause, $recursive = false) 111 { 112 $clause = $this->_tokenizer->bracketTrim($originalClause); 113 114 $brackets = false; 115 116 if ($clause !== $originalClause) { 117 $brackets = true; 118 } 119 120 $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position'))); 121 122 $terms = $this->_tokenizer->sqlExplode($clause, ' OR ', '(', ')'); 123 124 $ret = array(); 125 126 if (count($terms) > 1) { 127 $leavesOnly = true; 128 129 foreach ($terms as $k => $term) { 130 if ($this->isExpression($term)) { 131 $ret[$k] = $this->parseClause($term, true); 132 $leavesOnly = false; 133 } else { 134 $ret[$k] = $this->parseTerm($term); 135 } 136 } 137 138 $return = implode(' OR ', $ret); 139 140 if ($leavesOnly && $recursive) { 141 $return = sprintf($this->_condition, 'IN') . $return . ')'; 142 $brackets = false; 143 } 144 } else { 145 $terms = $this->_tokenizer->sqlExplode($clause, ' ', '(', ')'); 146 147 if (count($terms) === 1 && ! $recursive) { 148 $return = $this->parseTerm($clause); 149 } else { 150 foreach ($terms as $k => $term) { 151 $term = trim($term); 152 153 if ($term === 'AND') { 154 continue; 155 } 156 157 if (substr($term, 0, 1) === '-') { 158 $operator = 'NOT IN'; 159 $term = substr($term, 1); 160 } else { 161 $operator = 'IN'; 162 } 163 164 if ($this->isExpression($term)) { 165 $ret[$k] = $this->parseClause($term, true); 166 } else { 167 $ret[$k] = sprintf($this->_condition, $operator) . $this->parseTerm($term) . ')'; 168 } 169 } 170 $return = implode(' AND ', $ret); 171 } 172 } 173 174 if ($brackets) { 175 return '(' . $return . ')'; 176 } else { 177 return $return; 178 } 179 } 180 181 public function isExpression($term) 182 { 183 if (strpos($term, '(') !== false) { 184 return true; 185 } else { 186 $terms = $this->_tokenizer->quoteExplode($term); 187 188 return (count($terms) > 1); 189 } 190 } 191 192 public function parseTerm($term) 193 { 194 $negation = false; 195 196 if (strpos($term, "'") === false) { 197 $where = $this->parseWord($term); 198 } else { 199 $term = trim($term, "' "); 200 201 $terms = $this->_tokenizer->quoteExplode($term); 202 $where = $this->parseWord($terms[0]); 203 204 foreach ($terms as $k => $word) { 205 if ($k === 0) { 206 continue; 207 } 208 $where .= ' AND (position + ' . $k . ') IN (SELECT position FROM ' . $this->_table->getTableName() . ' WHERE ' . $this->parseWord($word) . ')'; 209 } 210 } 211 return $where; 212 } 213 214 public function parseWord($word) 215 { 216 $this->_words[] = str_replace('*', '', $word); 217 218 if (strpos($word, '?') !== false || 219 strpos($word, '*') !== false) { 220 221 $word = str_replace('*', '%', $word); 222 223 $where = 'keyword LIKE ?'; 224 225 $params = array($word); 226 } else { 227 $where = 'keyword = ?'; 228 } 229 230 $this->_params[] = $word; 231 232 return $where; 233 } 234 235 public function getWords() 236 { 237 return $this->_words; 238 } 239 240 public function getParams() 241 { 242 return $this->_params; 243 } 244 245 public function getSqlQuery() 246 { 247 return $this->_sql; 248 } 249}