xref: /plugin/sphinxsearch-was/action.php (revision 97:dfcc5c0823af)
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 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 ($search->getError()){
126            echo "Could not connect to Sphinx search engine.";
127            return;
128        }
129
130        if(!$result){
131            echo 'Your search - <strong>' . $query . '</strong> - did not match any documents.<br/>
132                <a href="?do=search&ssplugininfo=1&id='.$query.'">Search help</a>';
133            return;
134        }
135
136        $pagesList = $search->getPages($keywords);
137
138        $totalFound = $search->getTotalFound();
139        if(empty($pagesList) || 0 == $totalFound){
140            echo 'Your search - <strong>' . $query . '</strong> - did not match any documents.<br/>
141                <a href="?do=search&ssplugininfo=1&id='.$query.'">Search help</a>';
142            return;
143        } else {
144            echo '<style type="text/css">
145                div.dokuwiki .search{
146                    width:1024px;
147                }
148                div.dokuwiki .search_snippet{
149                    color:#000000;
150                    margin-left:0px;
151                    font-size: 13px;
152                }
153                div.dokuwiki .search_result{
154                    width:600px;
155                    float:left;
156                }
157                div.dokuwiki .search_result a.title{
158                    font:16px Arial,Helvetica,sans-serif;
159                }
160                div.dokuwiki .search_result span{
161                    font:12px Arial,Helvetica,sans-serif;
162                }
163                div.dokuwiki .search_sidebar{
164                    width:300px;
165                    float:right;
166                    margin-right: 30px;
167                }
168                div.dokuwiki .search_result_row{
169                    color:#000000;
170                    margin-left:0px;
171                    width:600px;
172                    text-align:left;
173                }
174                div.dokuwiki .search_result_row_child{
175                    color:#000000;
176                    margin-left:30px;
177                    width:600px;
178                    text-align:left;
179                }
180                div.dokuwiki .hide{
181                    display:none;
182                }
183                div.dokuwiki .search_cnt{
184                    color:#909090;
185                    font:12px Arial,Helvetica,sans-serif;
186                }
187                div.dokuwiki .search_nmsp{
188                    font-size: 10px;
189                }
190                div.dokuwiki .sphinxsearch_nav{
191                    clear:both;
192                }
193                </style>
194                <script type="text/javascript">
195function sh(id)
196{
197    var e = document.getElementById(id);
198    if(e.style.display == "block")
199        e.style.display = "none";
200    else
201        e.style.display = "block";
202}
203</script>
204';
205
206            echo '<h2>Found '.$totalFound . ($totalFound == 1  ? ' match ' : ' matches ') . ' for query "' . hsc($query).'"</h2>';
207            echo '<div class="search">';
208            echo '<div class="search_result">';
209            // printout the results
210            $pageListGroupByPage = array();
211            foreach ($pagesList as $row) {
212                $page = $row['page'];
213                if(!isset ($pageListGroupByPage[$page])){
214                    $pageListGroupByPage[$page] = $row;
215                } else {
216                    $pageListGroupByPage[$page]['subpages'][] = $row;
217                }
218            }
219            foreach ($pageListGroupByPage as $row) {
220                $this->_showResult($row, $keywords, false);
221                if(!empty($row['subpages'])){
222                    echo '<div id="more'.$row['page'].'" class="hide">';
223                    foreach($row['subpages'] as $sub){
224                        $this->_showResult($sub, $keywords, true);
225                    }
226                    echo '</div>';
227                }
228
229            }
230            echo '</div>';
231            echo '<div class="search_sidebar">';
232            printNamespacesNew($this->_getMatchingPagenames($keywords, $categories));
233            echo '</div>';
234            echo '<div class="sphinxsearch_nav">';
235            if ($start > 1){
236                if(false !== strpos($prev, ',')){
237                    $prevArry = explode(",", $prev);
238                    $prevNum = $prevArry[count($prevArry)-1];
239                    unset($prevArry[count($prevArry)-1]);
240                    $prevString = implode(",", $prevArry);
241                } else {
242                    $prevNum = 0;
243                }
244
245                echo $this->external_link(wl('',array('do'=>'search','id'=>$query,'start'=>$prevNum, 'prev'=>$prevString),'false','&'),
246                                          'prev','wikilink1 gs_prev',$conf['target']['interwiki']);
247            }
248            echo ' ';
249
250            //if($start + $this->getConf('maxresults') < $totalFound){
251                //$next = $start + $this->getConf('maxresults');
252            if($start + $search->getOffset()< $totalFound){
253                $next = $start + $search->getOffset();
254                if($start > 1){
255                    $prevString = $prev.','.$start;
256                }
257                echo $this->external_link(wl('',array('do'=>'search','id'=>$query,'start'=>$next,'prev'=>$prevString),'false','&'),
258                                          'next','wikilink1 gs_next',$conf['target']['interwiki']);
259            }
260            echo '</div>';
261            echo '<a href="?do=search&ssplugininfo=1&id='.$query.'">Search help</a>';
262            echo '</div>';
263        }
264
265
266    }
267
268    function _showResult($row, $keywords, $subpages = false)
269    {
270        $page = $row['page'];
271        $bodyExcerpt = $row['bodyExcerpt'];
272        $titleTextExcerpt = $row['titleTextExcerpt'];
273        $hid = $row['hid'];
274
275        $metaData = p_get_metadata($page);
276
277        if (!empty($titleTextExcerpt)){
278            $titleText = $titleTextExcerpt;
279        } elseif(!empty($row['title_text'])){
280            $titleText = $row['title_text'];
281        } elseif(!empty($metaData['title'])){
282            $titleText = hsc($metaData['title']);
283        } else {
284            $titleText = hsc($page);
285        }
286
287        $namespaces = getNsLinks($page, $keywords, $this->_search);
288        $href = !empty($hid) ? (wl($page).'#'.$hid) : wl($page);
289
290        if($subpages){
291            echo '<div class="search_result_row_child">';
292        } else {
293            echo '<div class="search_result_row">';
294        }
295
296        echo '<a class="wikilink1 title" href="'.$href.'" title="" >'.$titleText.'</a><br/>';
297        echo '<div class="search_snippet">';
298        echo strip_tags($bodyExcerpt, '<b>,<strong>');
299        echo '</div>';
300        $sep=':';
301        $i = 0;
302        echo '<span class="search_nmsp">';
303        foreach ($namespaces as $name){
304            $link = $name['link'];
305            $pageTitle = $name['title'];
306            tpl_link($link, $pageTitle);
307            if ($i++ < count($namespaces)-1){
308                echo $sep;
309            }
310        }
311        if (!empty($hid)){
312            echo '#'.$hid;
313        }
314        echo '</span>';
315
316        if (!empty($metaData['last_change']['date'])){
317            echo '<span class="search_cnt"> - Last modified '.date("Y-m-d H:i",$metaData['last_change']['date']).'</span> ';
318        } else if (!empty($metaData['date']['created'])){
319            echo '<span class="search_cnt"> - Last modified '.date("Y-m-d H:i",$metaData['date']['created']).'</span> ';
320        }
321
322        if(!empty($metaData['last_change']['user'])){
323            echo '<span class="search_cnt">by '.$metaData['last_change']['user'].'</span> ';
324        } else if(!empty($metaData['creator'])){
325            echo '<span class="search_cnt">by '.$metaData['creator'].'</span> ';
326        }
327
328        if (!empty($row['subpages'])){
329            echo '<br />';
330            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>';
331        }else {
332            echo '<br />';
333        }
334        echo '<br />';
335        echo '</div>';
336    }
337
338     function searchform()
339    {
340          global $lang;
341          global $ACT;
342          global $QUERY;
343
344          // don't print the search form if search action has been disabled
345          if (!actionOk('search')) return false;
346
347          print '<form action="'.wl().'" accept-charset="utf-8" class="search" id="dw__search"><div class="no">';
348          print '<input type="hidden" name="do" value="search" />';
349          print '<input type="text" ';
350          if($ACT == 'search') print 'value="'.htmlspecialchars($QUERY).'" ';
351          print 'id="qsearch__in" accesskey="f" name="id" class="edit" title="[ALT+F]" />';
352          print '<input type="submit" value="'.$lang['btn_search'].'" class="button" title="'.$lang['btn_search'].'" />';
353          print '</div></form>';
354          return true;
355    }
356
357    function _getCategories($query)
358    {
359        $categories = '';
360        $query = urldecode($query);
361        if (false !== ($pos = strpos($query, "@ns"))){;
362            $categories = substr($query, $pos + strlen("@ns"));
363        }
364        return trim($categories);
365    }
366
367    function _getKeywords($query)
368    {
369        $keywords = $query;
370        $query = urldecode($query);
371        if (false !== ($pos = strpos($query, "-@ns"))){;
372            $keywords = substr($keywords, 0, $pos);
373        }else if (false !== ($pos = strpos($query, "@ns"))){;
374            $keywords = substr($keywords, 0, $pos);
375        }
376        return trim($keywords);
377    }
378
379    function _getMatchingPagenames($keywords, $categories)
380    {
381        $this->_search->setSearchCategoryQuery($keywords, $categories);
382        $this->_search->setNamespacePriority($this->getConf('mp_namespace_priority'));
383        $this->_search->setPagenamePriority($this->getConf('mp_pagename_priority'));
384
385        $res = $this->_search->search(0, 10);
386        if (!$res){
387            return false;
388        }
389        $pageIds = $this->_search->getPagesIds();
390
391        $matchPages = array();
392        foreach($pageIds as $page){
393            $matchPages[$page['page']] = $page['hid'];
394        }
395        return array_unique($matchPages);
396    }
397}
398
399?>
400