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