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