root/trunk/gregarius/cls/search.php

Revision 1775, 12.8 kB (checked in by cfriesen, 11 months ago)

Option on search form to show only read, unread, or both (default to both)

  • Property svn:eolstyle set to native
Line 
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
28define ('INFINE_RESULTS',-1);
29
30define ('QUERY_PRM','rss_query');
31define ('QUERY_MATCH_MODE', 'rss_query_match');
32define ('QUERY_MATCH_TYPE', 'rss_query_match_type');
33define ('QUERY_CHANNEL', 'rss_query_channel');
34define ('QUERY_RESULTS','rss_query_res_per_page');
35define ('QUERY_CURRENT_PAGE','rss_query_current_page');
36define ('HIT_BEFORE',"<span class=\"searchhit\">");
37define ('HIT_AFTER',"</span>");
38
39define ('QUERY_ORDER_BY','rss_order');
40define ('QUERY_ORDER_BY_DATE','date');
41define ('QUERY_ORDER_BY_CHANNEL','channel');
42define ('QUERY_MATCH_OR','or');
43define ('QUERY_MATCH_AND','and');
44define ('QUERY_MATCH_EXACT','exact');
45define ('QUERY_MATCH_WITHIN', 'within');
46
47define ('QUERY_MATCH_STATE', 'state');
48define ('QUERY_MATCH_UNREAD', 'unread');
49define ('QUERY_MATCH_READ', 'read');
50define ('QUERY_MATCH_BOTH', 'both');
51
52// This is needed for some constants
53rss_require('cls/wrappers/toolkit.php');
54
55class 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."&amp;"
301                            .QUERY_MATCH_MODE."=".$this->matchMode."&amp;"
302                            .QUERY_CHANNEL."=".$this->channelId ."&amp;"
303                            .QUERY_RESULTS."=".$this->resultsPerPage."&amp;"
304                            .QUERY_ORDER_BY."=".$this->orderBy."&amp;"
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."&amp;"
324                        .QUERY_MATCH_MODE."=".$this->matchMode."&amp;"
325                        .QUERY_CHANNEL."=".$this->channelId ."&amp;"
326                        .QUERY_RESULTS."=".$this->resultsPerPage."&amp;"
327                        .QUERY_ORDER_BY."=".$this->orderBy."&amp;"
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?>
Note: See TracBrowser for help on using the browser.