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