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