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