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