xref: /plugin/sphinxsearch-was/action.php (revision 87:ec4f02ae6183)
1<?php
2/**
3 * Script to search in dokuwiki documents
4 *
5 * @author Yaroslav Vorozhko <yaroslav@ivinco.com>
6 */
7
8if(!defined('DOKU_INC')) die();
9if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
10
11require_once(DOKU_INC.'inc/parser/parser.php');
12
13require_once(DOKU_PLUGIN . 'action.php');
14require_once(DOKU_PLUGIN . 'sphinxsearch/sphinxapi.php');
15require_once(DOKU_PLUGIN . 'sphinxsearch/PageMapper.php');
16require_once(DOKU_PLUGIN . 'sphinxsearch/SphinxSearch.php');
17require_once(DOKU_PLUGIN . 'sphinxsearch/functions.php');
18
19
20class action_plugin_sphinxsearch extends DokuWiki_Action_Plugin {
21    var $_search = null;
22
23    var $_helpMessage = "
24===== DokuWiki Sphinx Search plugin features=====
25
26To use the search you need to just enter your search keywords into the searchbox at the top right corner of the DokuWiki. When basic simple search is not enough you can try using the methods listed below:
27
28=== Phrase search (\"\") ===
29Put double quotes around a set of words to enable phrase search mode. For example:
30<code>\"James Bond\"</code>
31
32=== Search within a namespace ===
33You can add \"@ns\" parameter to limit the search to some namespace. For exapmle:
34<code>hotel @ns personal:mike:travel</code>
35Such query will return only results from \"personal:mike:travel\" namespace for keyword \"hotel\".
36
37=== Excluding keywords or namespaces from search ===
38You can add a minus sign to a keyword or a category name exclude it from search. For example:
39<code>hotel @ns -personal:mike</code>
40Such query will look for \"hotel\" everywhere except the \"personal:mike\" namespace.
41<code>blog -post</code>
42Such query will look for documents that have keyword \"blog\" but don't have keyword \"post\".
43";
44
45    /**
46	* return some info
47	*/
48    function getInfo() {
49        return confToHash(dirname(__FILE__).'/plugin.info.txt');
50	}
51
52    /**
53	* Register to the content display event to place the results under it.
54	*/
55    /**
56     * register the eventhandlers
57     */
58    function register(&$controller){
59        $controller->register_hook('TPL_CONTENT_DISPLAY',   'BEFORE', $this, 'handle_act_unknown', array());
60    }
61
62    /**
63     * If our own 'googlesearch' action was given we produce our content here
64     */
65    function handle_act_unknown(&$event, $param){
66        global $ACT;
67        global $QUERY;
68        if($ACT != 'search') return; // nothing to do for us
69
70        // we can handle it -> prevent others
71        $event->stopPropagation();
72        $event->preventDefault();
73
74        if (!extension_loaded('sqlite')) {
75            echo "SQLite extension is not loaded!";
76            return;
77        }
78
79        if(!empty($_REQUEST['ssplugininfo'])){
80            $info = array();
81            echo p_render('xhtml',p_get_instructions($this->_helpMessage), $info);
82            return;
83        }
84
85        $this->_search($QUERY,$_REQUEST['start'],$_REQUEST['prev']);
86    }
87
88    /**
89     * do the search and displays the result
90     */
91    function _search($query, $start, $prev) {
92        global $conf;
93
94        $start = (int) $start;
95        if($start < 0){
96            $start = 0;
97        }
98        if(empty($prev)){
99            $prev = 0;
100        }
101
102        $categories = $this->_getCategories($query);
103        $keywords = $this->_getKeywords($query);
104
105        $search = new SphinxSearch($this->getConf('host'), $this->getConf('port'), $this->getConf('index'));
106        $search->setSnippetSize($this->getConf('snippetsize'));
107        $search->setArroundWordsCount($this->getConf('aroundwords'));
108        $search->setTitlePriority($this->getConf('title_priority'));
109        $search->setBodyPriority($this->getConf('body_priority'));
110        $search->setNamespacePriority($this->getConf('namespace_priority'));
111        $search->setPagenamePriority($this->getConf('pagename_priority'));
112
113        if (!empty($keywords) && empty($categories)){
114            $search->setSearchAllQuery($keywords, $categories);
115        } elseif (!empty($keywords)) {
116            $search->setSearchAllQueryWithCategoryFilter($keywords, $categories);
117        } else {
118            echo 'Your search - <strong>' . $query . '</strong> - did not match any documents.<br>
119                <a href="?do=search&ssplugininfo=1&id='.$query.'">Search help</a>';
120            return;
121        }
122        $result = $search->search($start, $this->getConf('maxresults'));
123        $this->_search = $search;
124
125        if (!$result || $search->getError()){
126            echo 'Your search - <strong>' . $query . '</strong> - did not match any documents.<br/>
127                <a href="?do=search&ssplugininfo=1&id='.$query.'">Search help</a>';
128            return;
129        }
130
131        $pagesList = $search->getPages($keywords);
132
133        $totalFound = $search->getTotalFound();
134        if(empty($pagesList) || 0 == $totalFound){
135            echo 'Your search - <strong>' . $query . '</strong> - did not match any documents.<br/>
136                <a href="?do=search&ssplugininfo=1&id='.$query.'">Search help</a>';
137            return;
138        } else {
139            echo '<style type="text/css">
140                div.dokuwiki .search{
141                    width:1024px;
142                }
143                div.dokuwiki .search_snippet{
144                    color:#000000;
145                    margin-left:0px;
146                    font-size: 13px;
147                }
148                div.dokuwiki .search_result{
149                    width:600px;
150                    float:left;
151                }
152                div.dokuwiki .search_result a.title{
153                    font:16px Arial,Helvetica,sans-serif;
154                }
155                div.dokuwiki .search_result span{
156                    font:12px Arial,Helvetica,sans-serif;
157                }
158                div.dokuwiki .search_sidebar{
159                    width:300px;
160                    float:right;
161                    margin-right: 30px;
162                }
163                div.dokuwiki .search_result_row{
164                    color:#000000;
165                    margin-left:0px;
166                    width:600px;
167                    text-align:left;
168                }
169                div.dokuwiki .search_result_row_child{
170                    color:#000000;
171                    margin-left:30px;
172                    width:600px;
173                    text-align:left;
174                }
175                div.dokuwiki .hide{
176                    display:none;
177                }
178                div.dokuwiki .search_cnt{
179                    color:#909090;
180                    font:12px Arial,Helvetica,sans-serif;
181                }
182                div.dokuwiki .search_nmsp{
183                    font-size: 10px;
184                }
185                div.dokuwiki .sphinxsearch_nav{
186                    clear:both;
187                }
188                </style>
189                <script type="text/javascript">
190function sh(id)
191{
192    var e = document.getElementById(id);
193    if(e.style.display == "block")
194        e.style.display = "none";
195    else
196        e.style.display = "block";
197}
198</script>
199';
200
201            echo '<h2>Found '.$totalFound . ($totalFound == 1  ? ' match ' : ' matches ') . ' for query "' . hsc($query).'"</h2>';
202            echo '<div class="search">';
203            echo '<div class="search_result">';
204            // printout the results
205            $pageListGroupByPage = array();
206            foreach ($pagesList as $row) {
207                $page = $row['page'];
208                if(!isset ($pageListGroupByPage[$page])){
209                    $pageListGroupByPage[$page] = $row;
210                } else {
211                    $pageListGroupByPage[$page]['subpages'][] = $row;
212                }
213            }
214            foreach ($pageListGroupByPage as $row) {
215                $this->_showResult($row, $keywords, false);
216                if(!empty($row['subpages'])){
217                    echo '<div id="more'.$row['page'].'" class="hide">';
218                    foreach($row['subpages'] as $sub){
219                        $this->_showResult($sub, $keywords, true);
220                    }
221                    echo '</div>';
222                }
223
224            }
225            echo '</div>';
226            echo '<div class="search_sidebar">';
227            printNamespacesNew($this->_getMatchingPagenames($keywords, $categories));
228            echo '</div>';
229            echo '<div class="sphinxsearch_nav">';
230            if ($start > 1){
231                if(false !== strpos($prev, ',')){
232                    $prevArry = explode(",", $prev);
233                    $prevNum = $prevArry[count($prevArry)-1];
234                    unset($prevArry[count($prevArry)-1]);
235                    $prevString = implode(",", $prevArry);
236                } else {
237                    $prevNum = 0;
238                }
239
240                echo $this->external_link(wl('',array('do'=>'search','id'=>$query,'start'=>$prevNum, 'prev'=>$prevString),'false','&'),
241                                          'prev','wikilink1 gs_prev',$conf['target']['interwiki']);
242            }
243            echo ' ';
244
245            //if($start + $this->getConf('maxresults') < $totalFound){
246                //$next = $start + $this->getConf('maxresults');
247            if($start + $search->getOffset()< $totalFound){
248                $next = $start + $search->getOffset();
249                if($start > 1){
250                    $prevString = $prev.','.$start;
251                }
252                echo $this->external_link(wl('',array('do'=>'search','id'=>$query,'start'=>$next,'prev'=>$prevString),'false','&'),
253                                          'next','wikilink1 gs_next',$conf['target']['interwiki']);
254            }
255            echo '</div>';
256            echo '<a href="?do=search&ssplugininfo=1&id='.$query.'">Search help</a>';
257            echo '</div>';
258        }
259
260
261    }
262
263    function _showResult($row, $keywords, $subpages = false)
264    {
265        $page = $row['page'];
266        $bodyExcerpt = $row['bodyExcerpt'];
267        $titleTextExcerpt = $row['titleTextExcerpt'];
268        $hid = $row['hid'];
269
270        $metaData = p_get_metadata($page);
271
272        if (!empty($titleTextExcerpt)){
273            $titleText = $titleTextExcerpt;
274        } elseif(!empty($row['title_text'])){
275            $titleText = $row['title_text'];
276        } elseif(!empty($metaData['title'])){
277            $titleText = hsc($metaData['title']);
278        } else {
279            $titleText = hsc($page);
280        }
281
282        $namespaces = getNsLinks($page, $keywords, $this->_search);
283        $href = !empty($hid) ? (wl($page).'#'.$hid) : wl($page);
284
285        if($subpages){
286            echo '<div class="search_result_row_child">';
287        } else {
288            echo '<div class="search_result_row">';
289        }
290
291        echo '<a class="wikilink1 title" href="'.$href.'" title="" >'.$titleText.'</a><br/>';
292        echo '<div class="search_snippet">';
293        echo strip_tags($bodyExcerpt, '<b>,<strong>');
294        echo '</div>';
295        $sep=':';
296        $i = 0;
297        echo '<span class="search_nmsp">';
298        foreach ($namespaces as $name){
299            $link = $name['link'];
300            $pageTitle = $name['title'];
301            tpl_link($link, $pageTitle);
302            if ($i++ < count($namespaces)-1){
303                echo $sep;
304            }
305        }
306        if (!empty($hid)){
307            echo '#'.$hid;
308        }
309        echo '</span>';
310
311        if (!empty($metaData['last_change']['date'])){
312            echo '<span class="search_cnt"> - Last modified '.date("Y-m-d H:i",$metaData['last_change']['date']).'</span> ';
313        } else if (!empty($metaData['date']['created'])){
314            echo '<span class="search_cnt"> - Last modified '.date("Y-m-d H:i",$metaData['date']['created']).'</span> ';
315        }
316
317        if(!empty($metaData['last_change']['user'])){
318            echo '<span class="search_cnt">by '.$metaData['last_change']['user'].'</span> ';
319        } else if(!empty($metaData['creator'])){
320            echo '<span class="search_cnt">by '.$metaData['creator'].'</span> ';
321        }
322
323        if (!empty($row['subpages'])){
324            echo '<br />';
325            echo '<div style="text-align:right;font:12px Arial,Helvetica,sans-serif;text-decoration:underline;"><a href="javascript:void(0)" onClick="sh('."'more".$page."'".');" >More matches in this document</a></div>';
326        }else {
327            echo '<br />';
328        }
329        echo '<br />';
330        echo '</div>';
331    }
332
333     function searchform()
334    {
335          global $lang;
336          global $ACT;
337          global $QUERY;
338
339          // don't print the search form if search action has been disabled
340          if (!actionOk('search')) return false;
341
342          print '<form action="'.wl().'" accept-charset="utf-8" class="search" id="dw__search"><div class="no">';
343          print '<input type="hidden" name="do" value="search" />';
344          print '<input type="text" ';
345          if($ACT == 'search') print 'value="'.htmlspecialchars($QUERY).'" ';
346          print 'id="qsearch__in" accesskey="f" name="id" class="edit" title="[ALT+F]" />';
347          print '<input type="submit" value="'.$lang['btn_search'].'" class="button" title="'.$lang['btn_search'].'" />';
348          print '</div></form>';
349          return true;
350    }
351
352    function _getCategories($query)
353    {
354        $categories = '';
355        $query = urldecode($query);
356        if (false !== ($pos = strpos($query, "@ns"))){;
357            $categories = substr($query, $pos + strlen("@ns"));
358        }
359        return trim($categories);
360    }
361
362    function _getKeywords($query)
363    {
364        $keywords = $query;
365        $query = urldecode($query);
366        if (false !== ($pos = strpos($query, "-@ns"))){;
367            $keywords = substr($keywords, 0, $pos);
368        }else if (false !== ($pos = strpos($query, "@ns"))){;
369            $keywords = substr($keywords, 0, $pos);
370        }
371        return trim($keywords);
372    }
373
374    function _getMatchingPagenames($keywords, $categories)
375    {
376        $this->_search->setSearchCategoryQuery($keywords, $categories);
377        $this->_search->setNamespacePriority($this->getConf('mp_namespace_priority'));
378        $this->_search->setPagenamePriority($this->getConf('mp_pagename_priority'));
379
380        $res = $this->_search->search(0, 10);
381        if (!$res){
382            return false;
383        }
384        $pageIds = $this->_search->getPagesIds();
385
386        $matchPages = array();
387        foreach($pageIds as $page){
388            $matchPages[$page['page']] = $page['hid'];
389        }
390        return array_unique($matchPages);
391    }
392}
393
394?>
395