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