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