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