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