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