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