xref: /plugin/sphinxsearch-was/action.php (revision 110:5f1794a133f9)
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\".
54DokuWiki Sphinx Search plugin (version {$this->_versionNumber}) by <a href='http://www.ivinco.com/software/dokuwiki-sphinx-search-plugin/'>Ivinco</a>.
55";
56
57    }
58
59    /**
60	* Register to the content display event to place the results under it.
61	*/
62    /**
63     * register the eventhandlers
64     */
65    function register(&$controller){
66        $controller->register_hook('TPL_CONTENT_DISPLAY',   'BEFORE', $this, 'handle_act_unknown', array());
67    }
68
69    /**
70     * If our own action was given we produce our content here
71     */
72    function handle_act_unknown(&$event, $param){
73        global $ACT;
74        global $QUERY;
75        if($ACT != 'search') return; // nothing to do for us
76
77        // we can handle it -> prevent others
78        $event->stopPropagation();
79        $event->preventDefault();
80
81/*        if (!extension_loaded('sqlite')) {
82            echo "SQLite extension is not loaded!";
83            return;
84        }*/
85
86        if(!empty($_REQUEST['ssplugininfo'])){
87            $info = array();
88            echo p_render('xhtml',p_get_instructions($this->_helpMessage), $info);
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>. DokuWiki Sphinx Search plugin (version '.$this->_versionNumber.') by <a href="http://www.ivinco.com/software/dokuwiki-sphinx-search-plugin/">Ivinco</a>.';
269            echo '</div>';
270        }
271
272
273    }
274
275    function _showResult($row, $keywords, $subpages = false)
276    {
277        $page = $row['page'];
278        $bodyExcerpt = $row['bodyExcerpt'];
279        $titleTextExcerpt = $row['titleTextExcerpt'];
280        $hid = $row['hid'];
281
282        $metaData = p_get_metadata($page);
283
284        if (!empty($titleTextExcerpt)){
285            $titleText = $titleTextExcerpt;
286        } elseif(!empty($row['title_text'])){
287            $titleText = $row['title_text'];
288        } elseif(!empty($metaData['title'])){
289            $titleText = hsc($metaData['title']);
290        } else {
291            $titleText = hsc($page);
292        }
293
294        $namespaces = getNsLinks($page, $keywords, $this->_search);
295        $href = !empty($hid) ? (wl($page).'#'.$hid) : wl($page);
296
297        if($subpages){
298            echo '<div class="search_result_row_child">';
299        } else {
300            echo '<div class="search_result_row">';
301        }
302
303        echo '<a class="wikilink1 title" href="'.$href.'" title="" >'.$titleText.'</a><br/>';
304        echo '<div class="search_snippet">';
305        echo strip_tags($bodyExcerpt, '<b>,<strong>');
306        echo '</div>';
307        $sep=':';
308        $i = 0;
309        echo '<span class="search_nmsp">';
310        foreach ($namespaces as $name){
311            $link = $name['link'];
312            $pageTitle = $name['title'];
313            tpl_link($link, $pageTitle);
314            if ($i++ < count($namespaces)-1){
315                echo $sep;
316            }
317        }
318        if (!empty($hid)){
319            echo '#'.$hid;
320        }
321        echo '</span>';
322
323        if (!empty($metaData['last_change']['date'])){
324            echo '<span class="search_cnt"> - Last modified '.date("Y-m-d H:i",$metaData['last_change']['date']).'</span> ';
325        } else if (!empty($metaData['date']['created'])){
326            echo '<span class="search_cnt"> - Last modified '.date("Y-m-d H:i",$metaData['date']['created']).'</span> ';
327        }
328
329        if(!empty($metaData['last_change']['user'])){
330            echo '<span class="search_cnt">by '.$metaData['last_change']['user'].'</span> ';
331        } else if(!empty($metaData['creator'])){
332            echo '<span class="search_cnt">by '.$metaData['creator'].'</span> ';
333        }
334
335        if (!empty($row['subpages'])){
336            echo '<br />';
337            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>';
338        }else {
339            echo '<br />';
340        }
341        echo '<br />';
342        echo '</div>';
343    }
344
345     function searchform()
346    {
347          global $lang;
348          global $ACT;
349          global $QUERY;
350
351          // don't print the search form if search action has been disabled
352          if (!actionOk('search')) return false;
353
354          print '<form action="'.wl().'" accept-charset="utf-8" class="search" id="dw__search"><div class="no">';
355          print '<input type="hidden" name="do" value="search" />';
356          print '<input type="text" ';
357          if($ACT == 'search') print 'value="'.htmlspecialchars($QUERY).'" ';
358          print 'id="qsearch__in" accesskey="f" name="id" class="edit" title="[ALT+F]" />';
359          print '<input type="submit" value="'.$lang['btn_search'].'" class="button" title="'.$lang['btn_search'].'" />';
360          print '</div></form>';
361          return true;
362    }
363
364    function _getCategories($query)
365    {
366        $categories = '';
367        $query = urldecode($query);
368        if (false !== ($pos = strpos($query, "@ns"))){;
369            $categories = substr($query, $pos + strlen("@ns"));
370        }
371        return trim($categories);
372    }
373
374    function _getKeywords($query)
375    {
376        $keywords = $query;
377        $query = urldecode($query);
378        if (false !== ($pos = strpos($query, "-@ns"))){;
379            $keywords = substr($keywords, 0, $pos);
380        }else if (false !== ($pos = strpos($query, "@ns"))){;
381            $keywords = substr($keywords, 0, $pos);
382        }
383        return trim($keywords);
384    }
385
386    function _getMatchingPagenames($keywords, $categories)
387    {
388        $this->_search->setSearchCategoryQuery($keywords, $categories);
389        $this->_search->setNamespacePriority($this->getConf('mp_namespace_priority'));
390        $this->_search->setPagenamePriority($this->getConf('mp_pagename_priority'));
391
392        $res = $this->_search->search(0, 10);
393        if (!$res){
394            return false;
395        }
396        $pageIds = $this->_search->getPagesIds();
397
398        $matchPages = array();
399        foreach($pageIds as $page){
400            $matchPages[$page['page']] = $page['hid'];
401        }
402        return array_unique($matchPages);
403    }
404}
405
406?>
407