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