| 1 | <?php |
|---|
| 2 | ############################################################################### |
|---|
| 3 | # Gregarius - A PHP based RSS aggregator. |
|---|
| 4 | # Copyright (C) 2003 - 2006 Marco Bonetti |
|---|
| 5 | # |
|---|
| 6 | ############################################################################### |
|---|
| 7 | # This program is free software and open source software; you can redistribute |
|---|
| 8 | # it and/or modify it under the terms of the GNU General Public License as |
|---|
| 9 | # published by the Free Software Foundation; either version 2 of the License, |
|---|
| 10 | # or (at your option) any later version. |
|---|
| 11 | # |
|---|
| 12 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|---|
| 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
|---|
| 15 | # more details. |
|---|
| 16 | # |
|---|
| 17 | # You should have received a copy of the GNU General Public License along |
|---|
| 18 | # with this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 19 | # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA or visit |
|---|
| 20 | # http://www.gnu.org/licenses/gpl.html |
|---|
| 21 | # |
|---|
| 22 | ############################################################################### |
|---|
| 23 | # E-mail: mbonetti at gmail dot com |
|---|
| 24 | # Web page: http://gregarius.net/ |
|---|
| 25 | # |
|---|
| 26 | ############################################################################### |
|---|
| 27 | |
|---|
| 28 | define ('INFINE_RESULTS',-1); |
|---|
| 29 | |
|---|
| 30 | define ('QUERY_PRM','rss_query'); |
|---|
| 31 | define ('QUERY_MATCH_MODE', 'rss_query_match'); |
|---|
| 32 | define ('QUERY_MATCH_TYPE', 'rss_query_match_type'); |
|---|
| 33 | define ('QUERY_CHANNEL', 'rss_query_channel'); |
|---|
| 34 | define ('QUERY_RESULTS','rss_query_res_per_page'); |
|---|
| 35 | define ('QUERY_CURRENT_PAGE','rss_query_current_page'); |
|---|
| 36 | define ('HIT_BEFORE',"<span class=\"searchhit\">"); |
|---|
| 37 | define ('HIT_AFTER',"</span>"); |
|---|
| 38 | |
|---|
| 39 | define ('QUERY_ORDER_BY','rss_order'); |
|---|
| 40 | define ('QUERY_ORDER_BY_DATE','date'); |
|---|
| 41 | define ('QUERY_ORDER_BY_CHANNEL','channel'); |
|---|
| 42 | define ('QUERY_MATCH_OR','or'); |
|---|
| 43 | define ('QUERY_MATCH_AND','and'); |
|---|
| 44 | define ('QUERY_MATCH_EXACT','exact'); |
|---|
| 45 | define ('QUERY_MATCH_WITHIN', 'within'); |
|---|
| 46 | |
|---|
| 47 | define ('QUERY_MATCH_STATE', 'state'); |
|---|
| 48 | define ('QUERY_MATCH_UNREAD', 'unread'); |
|---|
| 49 | define ('QUERY_MATCH_READ', 'read'); |
|---|
| 50 | define ('QUERY_MATCH_BOTH', 'both'); |
|---|
| 51 | |
|---|
| 52 | // This is needed for some constants |
|---|
| 53 | rss_require('cls/wrappers/toolkit.php'); |
|---|
| 54 | |
|---|
| 55 | class SearchItemList extends ItemList { |
|---|
| 56 | |
|---|
| 57 | var $searchTerms = array(); |
|---|
| 58 | var $matchMode; |
|---|
| 59 | var $matchType; |
|---|
| 60 | var $regMatch = ""; |
|---|
| 61 | |
|---|
| 62 | var $currentPage; |
|---|
| 63 | var $resultsPerPage = 0; |
|---|
| 64 | var $startItem; |
|---|
| 65 | var $endItem; |
|---|
| 66 | var $orderBy; |
|---|
| 67 | var $query = ""; |
|---|
| 68 | var $logicSep; |
|---|
| 69 | |
|---|
| 70 | function SearchItemList($query=null,$results=0) { |
|---|
| 71 | parent::ItemList(); |
|---|
| 72 | if ($query) { |
|---|
| 73 | $this -> query=$query; |
|---|
| 74 | } elseif(isset($_REQUEST[QUERY_PRM])) { |
|---|
| 75 | $this->query = $_REQUEST[QUERY_PRM]; |
|---|
| 76 | }else{ |
|---|
| 77 | $this -> query = null; |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | |
|---|
| 81 | // Sanitize the query parameters: |
|---|
| 82 | // fixme: this probably breaks on queries with weird characters, depending |
|---|
| 83 | // on the locale. |
|---|
| 84 | // see: http://php.benscom.com/manual/en/reference.pcre.pattern.syntax.php |
|---|
| 85 | if ($this -> query) { |
|---|
| 86 | $this -> query = trim(preg_replace('#[^\w\s\x80-\xff]#','',$this -> query)); |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | $this->resultsPerPage = (int) $results; |
|---|
| 90 | |
|---|
| 91 | $this -> populate(); |
|---|
| 92 | |
|---|
| 93 | $this->humanReadableQuery = implode(" ".strtoupper($this->logicSep)." ", $this->searchTerms); |
|---|
| 94 | } |
|---|
| 95 | |
|---|
| 96 | function filterItems() { |
|---|
| 97 | $cntr = 0; |
|---|
| 98 | foreach($this->feeds as $fkey => $feed) { |
|---|
| 99 | foreach ($this -> feeds[$fkey]->items as $ikey => $item) { |
|---|
| 100 | $descr_noTags = strip_tags($item -> description); |
|---|
| 101 | $title_noTags = strip_tags($item -> title); |
|---|
| 102 | $match = false; |
|---|
| 103 | reset($this->searchTerms); |
|---|
| 104 | $match = ($this->matchMode == QUERY_MATCH_AND || $this->matchMode == QUERY_MATCH_EXACT); |
|---|
| 105 | |
|---|
| 106 | foreach ($this->searchTerms as $term) { |
|---|
| 107 | if ($this->matchMode == QUERY_MATCH_AND || $this->matchMode == QUERY_MATCH_EXACT) { |
|---|
| 108 | if($this->matchType == QUERY_MATCH_WITHIN) { |
|---|
| 109 | $match = ((stristr($descr_noTags, $term) || stristr($title_noTags, $term)) && $match); |
|---|
| 110 | } else { |
|---|
| 111 | $match = ((preg_match("/\b" . $term . "\b/i", $descr_noTags) || preg_match("/\b" . $term . "\b/i", $title_noTags)) && $match); |
|---|
| 112 | } |
|---|
| 113 | } else { |
|---|
| 114 | if($this->matchType == QUERY_MATCH_WITHIN) { |
|---|
| 115 | $match = ($match || (stristr($descr_noTags, $term) || stristr($title_noTags, $term))); |
|---|
| 116 | } else { |
|---|
| 117 | $match = ($match || (preg_match("/\b" . $term . "\b/i", $descr_noTags) || preg_match("/\b" . $term . "\b/i", $title_noTags))); |
|---|
| 118 | } |
|---|
| 119 | } |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | if (!$match) { |
|---|
| 123 | $this->removeItem($fkey,$ikey,true); |
|---|
| 124 | } else { |
|---|
| 125 | |
|---|
| 126 | if ($cntr >= $this->startItem && $cntr <= $this->endItem) { |
|---|
| 127 | $this->feeds[$fkey] -> items[$ikey] -> description = |
|---|
| 128 | preg_replace("'(?!<.*?)(".$this->regMatch.")(?![^<>]*?>)'si", |
|---|
| 129 | HIT_BEFORE."\\1".HIT_AFTER, $item -> description); |
|---|
| 130 | } else { |
|---|
| 131 | $this->removeItem($fkey,$ikey,false); |
|---|
| 132 | } |
|---|
| 133 | |
|---|
| 134 | $cntr++; |
|---|
| 135 | } |
|---|
| 136 | } |
|---|
| 137 | } |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | function populate() { |
|---|
| 141 | |
|---|
| 142 | if (!$this->query) { |
|---|
| 143 | return; |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | $this->matchMode = sanitize( |
|---|
| 147 | (!array_key_exists(QUERY_MATCH_MODE, $_REQUEST) ? QUERY_MATCH_AND : $_REQUEST[QUERY_MATCH_MODE]), |
|---|
| 148 | RSS_SANITIZER_CHARACTERS_EXT); |
|---|
| 149 | |
|---|
| 150 | $this->matchType = sanitize( |
|---|
| 151 | (!array_key_exists(QUERY_MATCH_TYPE, $_REQUEST) ? "" : $_REQUEST[QUERY_MATCH_TYPE]), |
|---|
| 152 | RSS_SANITIZER_CHARACTERS_EXT); |
|---|
| 153 | |
|---|
| 154 | $this->channelId = sanitize( |
|---|
| 155 | ((array_key_exists(QUERY_CHANNEL, $_REQUEST)) ? $_REQUEST[QUERY_CHANNEL] : ALL_CHANNELS_ID), |
|---|
| 156 | RSS_SANITIZER_NUMERIC); |
|---|
| 157 | |
|---|
| 158 | if (!$this->resultsPerPage) { |
|---|
| 159 | $this->resultsPerPage = sanitize( |
|---|
| 160 | ((array_key_exists(QUERY_RESULTS, $_REQUEST)) ? $_REQUEST[QUERY_RESULTS] : 15), |
|---|
| 161 | RSS_SANITIZER_NUMERIC); |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | $this->currentPage = sanitize( |
|---|
| 165 | (array_key_exists(QUERY_CURRENT_PAGE, $_REQUEST) ? $_REQUEST[QUERY_CURRENT_PAGE] : 0), |
|---|
| 166 | RSS_SANITIZER_NUMERIC); |
|---|
| 167 | |
|---|
| 168 | $this->startItem = $this->resultsPerPage * $this->currentPage; |
|---|
| 169 | |
|---|
| 170 | $this->endItem = $this->startItem + $this->resultsPerPage -1; |
|---|
| 171 | |
|---|
| 172 | if ($this->resultsPerPage == INFINE_RESULTS) { |
|---|
| 173 | $this->endItem = 99999999; |
|---|
| 174 | } |
|---|
| 175 | |
|---|
| 176 | $this->orderBy = sanitize( |
|---|
| 177 | (array_key_exists(QUERY_ORDER_BY, $_REQUEST) ? $_REQUEST[QUERY_ORDER_BY] : QUERY_ORDER_BY_DATE), |
|---|
| 178 | RSS_SANITIZER_CHARACTERS_EXT); |
|---|
| 179 | |
|---|
| 180 | $qWhere = ""; |
|---|
| 181 | $this->regMatch = ""; |
|---|
| 182 | $term = ""; |
|---|
| 183 | |
|---|
| 184 | if ($this->matchMode == QUERY_MATCH_OR || $this->matchMode == QUERY_MATCH_AND) { |
|---|
| 185 | |
|---|
| 186 | $this->logicSep = ($this->matchMode == QUERY_MATCH_OR ? "or" : "and"); |
|---|
| 187 | $this->searchTerms = explode(" ", $this->query); |
|---|
| 188 | foreach ($this->searchTerms as $term) { |
|---|
| 189 | $term = trim($term); |
|---|
| 190 | if ($term != "") { |
|---|
| 191 | $qWhere .= "(i.description like '%$term%' or "." i.title like '%$term%') ".$this->logicSep; |
|---|
| 192 | } |
|---|
| 193 | // this will be used later for the highliting regexp |
|---|
| 194 | if ($this->regMatch != "") { |
|---|
| 195 | $this->regMatch .= "|"; |
|---|
| 196 | } |
|---|
| 197 | $this->regMatch .= $term; |
|---|
| 198 | } |
|---|
| 199 | |
|---|
| 200 | $qWhere .= ($this->matchMode == QUERY_MATCH_OR ? " 1=0 " : " 1=1 "); |
|---|
| 201 | } else { |
|---|
| 202 | $this->logicSep = ""; |
|---|
| 203 | $this->searchTerms[0] = $this->query; |
|---|
| 204 | $term = $this->query; |
|---|
| 205 | $qWhere .= "(i.description like '%$term%' or "." i.title like '%$term%') "; |
|---|
| 206 | $this->regMatch = $this->query; |
|---|
| 207 | } |
|---|
| 208 | |
|---|
| 209 | $qWhere= "(" . $qWhere. ") "; |
|---|
| 210 | |
|---|
| 211 | |
|---|
| 212 | |
|---|
| 213 | if ($this->channelId != ALL_CHANNELS_ID) { |
|---|
| 214 | $qWhere .= " and c.id = " . $this->channelId . " "; |
|---|
| 215 | } |
|---|
| 216 | |
|---|
| 217 | if (hidePrivate()) { |
|---|
| 218 | $qWhere .= " and not(i.unread & ".RSS_MODE_PRIVATE_STATE.") "; |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | $qWhere .= " and not(i.unread & ".RSS_MODE_DELETED_STATE.") "; |
|---|
| 222 | |
|---|
| 223 | if(array_key_exists(QUERY_MATCH_STATE, $_REQUEST) && QUERY_MATCH_READ == $_REQUEST[QUERY_MATCH_STATE]) { |
|---|
| 224 | // Show only read items. |
|---|
| 225 | $qWhere .= " and not (i.unread & " . RSS_MODE_UNREAD_STATE . ") "; |
|---|
| 226 | } |
|---|
| 227 | else if(array_key_exists(QUERY_MATCH_STATE, $_REQUEST) && QUERY_MATCH_UNREAD == $_REQUEST[QUERY_MATCH_STATE]) { |
|---|
| 228 | // Show only unread items. |
|---|
| 229 | $qWhere .= " and (i.unread & " . RSS_MODE_UNREAD_STATE . ") "; |
|---|
| 230 | } |
|---|
| 231 | |
|---|
| 232 | if ($this->orderBy == QUERY_ORDER_BY_DATE) { |
|---|
| 233 | $qOrder = " ts desc"; |
|---|
| 234 | } else { |
|---|
| 235 | if (getConfig('rss.config.absoluteordering')) { |
|---|
| 236 | $qOrder = " f.position asc, c.position asc"; |
|---|
| 237 | } else { |
|---|
| 238 | $qOrder = " f.name asc, c.title asc"; |
|---|
| 239 | } |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | |
|---|
| 243 | |
|---|
| 244 | $qOrder .= ", i.added desc"; |
|---|
| 245 | |
|---|
| 246 | |
|---|
| 247 | |
|---|
| 248 | parent::populate($qWhere,$qOrder,0,getConfig("rss.search.maxitems"),ITEM_SORT_HINT_MIXED,true); |
|---|
| 249 | |
|---|
| 250 | $this -> filterItems(); |
|---|
| 251 | $this -> nav(); |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | function nav() { |
|---|
| 255 | $nav = ""; |
|---|
| 256 | |
|---|
| 257 | if ($this->resultsPerPage != INFINE_RESULTS && $this->itemCount > $this->resultsPerPage) { |
|---|
| 258 | $nav .= "<div class=\"readmore\">"; |
|---|
| 259 | $nav .= __('Results: '); |
|---|
| 260 | |
|---|
| 261 | // first page |
|---|
| 262 | $fp = 0; |
|---|
| 263 | //last page |
|---|
| 264 | $lp = floor(($this->itemCount -1) / $this -> resultsPerPage); |
|---|
| 265 | // current page |
|---|
| 266 | $cp = $this -> currentPage; |
|---|
| 267 | //shown pages |
|---|
| 268 | $pages = array (); |
|---|
| 269 | |
|---|
| 270 | for ($i = 0; $i < 4; $i ++) { |
|---|
| 271 | if ($cp - $i >= 0) { |
|---|
| 272 | $pages[$cp - $i] = true; |
|---|
| 273 | } else { |
|---|
| 274 | if ($cp + $i < $lp) { |
|---|
| 275 | $pages[$cp + $i] = true; |
|---|
| 276 | } |
|---|
| 277 | } |
|---|
| 278 | if ($cp + $i < $lp) { |
|---|
| 279 | $pages[$cp + $i] = true; |
|---|
| 280 | } else { |
|---|
| 281 | if ($cp - $i >= 0) { |
|---|
| 282 | $pages[$cp - $i] = true; |
|---|
| 283 | } |
|---|
| 284 | } |
|---|
| 285 | } |
|---|
| 286 | |
|---|
| 287 | $pages[0] = true; |
|---|
| 288 | $pages[$lp] = true; |
|---|
| 289 | |
|---|
| 290 | for ($p = $fp; $p < $lp; $p ++) { |
|---|
| 291 | if (!array_key_exists($p, $pages)) { |
|---|
| 292 | if (array_key_exists($p -1, $pages)) { |
|---|
| 293 | $nav .= " ... "; |
|---|
| 294 | } |
|---|
| 295 | continue; |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | $cpp = ($p * $this -> resultsPerPage == $this->startItem); |
|---|
| 299 | if (!$cpp) { |
|---|
| 300 | $nav .= " <a href=\"".$_SERVER['PHP_SELF']."?".QUERY_PRM."=".$this->query."&" |
|---|
| 301 | .QUERY_MATCH_MODE."=".$this->matchMode."&" |
|---|
| 302 | .QUERY_CHANNEL."=".$this->channelId ."&" |
|---|
| 303 | .QUERY_RESULTS."=".$this->resultsPerPage."&" |
|---|
| 304 | .QUERY_ORDER_BY."=".$this->orderBy."&" |
|---|
| 305 | .QUERY_CURRENT_PAGE."=$p"."\">"; |
|---|
| 306 | } else { |
|---|
| 307 | $nav .= HIT_BEFORE; |
|---|
| 308 | } |
|---|
| 309 | |
|---|
| 310 | $nav .= "". (1 + $p * $this -> resultsPerPage)."-". ((1 + $p) * $this -> resultsPerPage).""; |
|---|
| 311 | |
|---|
| 312 | if (!$cpp) { |
|---|
| 313 | $nav .= "</a>"; |
|---|
| 314 | } else { |
|---|
| 315 | $nav .= HIT_AFTER; |
|---|
| 316 | } |
|---|
| 317 | if ((1 + $p) * $this -> resultsPerPage < $this->itemCount) { |
|---|
| 318 | $nav .= ", \n"; |
|---|
| 319 | } |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | if ($p * $this -> resultsPerPage >= $this->endItem) { |
|---|
| 323 | $nav .= " <a href=\"".$_SERVER['PHP_SELF']."?".QUERY_PRM."=".$this->query."&" |
|---|
| 324 | .QUERY_MATCH_MODE."=".$this->matchMode."&" |
|---|
| 325 | .QUERY_CHANNEL."=".$this->channelId ."&" |
|---|
| 326 | .QUERY_RESULTS."=".$this->resultsPerPage."&" |
|---|
| 327 | .QUERY_ORDER_BY."=".$this->orderBy."&" |
|---|
| 328 | .QUERY_CURRENT_PAGE."=$p"."\">"; |
|---|
| 329 | $nav .= (1 + $p * $this -> resultsPerPage)."-" . $this->itemCount; |
|---|
| 330 | $nav .= "</a> \n"; |
|---|
| 331 | } else { |
|---|
| 332 | $nav .= HIT_BEFORE. (1 + $p * $this -> resultsPerPage)."-" . $this->itemCount.HIT_AFTER; |
|---|
| 333 | } |
|---|
| 334 | $nav .= "<hr class=\"clearer hidden\"/>\n</div>\n"; |
|---|
| 335 | } |
|---|
| 336 | |
|---|
| 337 | if ($nav) { |
|---|
| 338 | $this -> beforeList .= $nav; |
|---|
| 339 | $this -> afterList .= $nav ; |
|---|
| 340 | } |
|---|
| 341 | } |
|---|
| 342 | |
|---|
| 343 | function render() { |
|---|
| 344 | $GLOBALS['rss'] -> searchFormTitle = $this->title; |
|---|
| 345 | $this->title=""; |
|---|
| 346 | require($GLOBALS['rss'] ->getTemplateFile("searchform.php")); |
|---|
| 347 | |
|---|
| 348 | $GLOBALS['rss'] -> currentItemList = $this; |
|---|
| 349 | rss_plugin_hook('rss.plugins.items.beforeitems', null); |
|---|
| 350 | require($GLOBALS['rss'] ->getTemplateFile("itemlist.php")); |
|---|
| 351 | rss_plugin_hook('rss.plugins.items.afteritems', null); |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | } |
|---|
| 355 | ?> |
|---|