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