1<?php 2/** 3 * DokuWiki Plugin elasticsearch (Action Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <gohr@cosmocode.de> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12class action_plugin_elasticsearch_search extends DokuWiki_Action_Plugin { 13 14 /** 15 * Registers a callback function for a given event 16 * 17 * @param Doku_Event_Handler $controller DokuWiki's event controller object 18 * @return void 19 */ 20 public function register(Doku_Event_Handler &$controller) { 21 22 $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_preprocess'); 23 $controller->register_hook('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handle_action'); 24 25 } 26 27 /** 28 * allow our custom do command 29 * 30 * @param Doku_Event $event 31 * @param $param 32 */ 33 public function handle_preprocess(Doku_Event &$event, $param) { 34 if($event->data != 'elasticsearch') return; 35 $event->preventDefault(); 36 $event->stopPropagation(); 37 } 38 39 /** 40 * do the actual search 41 * 42 * @param Doku_Event $event 43 * @param $param 44 */ 45 public function handle_action(Doku_Event &$event, $param) { 46 if($event->data != 'elasticsearch') return; 47 $event->preventDefault(); 48 $event->stopPropagation(); 49 global $QUERY; 50 global $INPUT; 51 global $USERINFO; 52 53 /** @var helper_plugin_elasticsearch_client $hlp */ 54 $hlp = plugin_load('helper', 'elasticsearch_client'); 55 56 $client = $hlp->connect(); 57 $index = $client->getIndex($this->getConf('indexname')); 58 59 // define the query string 60 $qstring = new \Elastica\Query\SimpleQueryString($QUERY); 61 62 // create the actual search object 63 $equery = new \Elastica\Query(); 64 $equery->setQuery($qstring); 65 $equery->setHighlight( 66 array( 67 "pre_tags" => array('ELASTICSEARCH_MARKER_IN'), 68 "post_tags" => array('ELASTICSEARCH_MARKER_OUT'), 69 "fields" => array("content" => new \StdClass()) 70 ) 71 ); 72 73 // paginate 74 $equery->setSize($this->getConf('perpage')); 75 $equery->setFrom($this->getConf('perpage') * ($INPUT->int('p', 1, true) - 1)); 76 77 $filters = new \Elastica\Filter\BoolAnd(); 78 79 // add groupfilter 80 $groups = array('all'); 81 if(isset($USERINFO['grps'])) { 82 $groups = array_merge($groups, $USERINFO['grps']); 83 } 84 $groupFilter = new \Elastica\Filter\BoolOr(); 85 foreach($groups as $group) { 86 $group = str_replace('-', '', strtolower($group)); 87 $filter = new \Elastica\Filter\Term(); 88 $filter->setTerm('groups', $group); 89 $groupFilter->addFilter($filter); 90 } 91 $filters->addFilter($groupFilter); 92 93 // add namespace filter 94 if($INPUT->has('ns')) { 95 $facetFilter = new \Elastica\Filter\BoolOr(); 96 foreach($INPUT->arr('ns') as $term) { 97 $filter = new \Elastica\Filter\Term(); 98 $filter->setTerm('namespace', $term); 99 $facetFilter->addFilter($filter); 100 } 101 $filters->addFilter($facetFilter); 102 } 103 104 // set all filters 105 $equery->setFilter($filters); 106 107 // add Facets for namespaces 108 $facet = new \Elastica\Facet\Terms('namespace'); 109 $facet->setField('namespace'); 110 $facet->setSize(25); 111 $equery->addFacet($facet); 112 113 try { 114 $result = $index->search($equery); 115 $facets = $result->getFacets(); 116 117 $this->print_intro(); 118 $this->print_facets($facets['namespace']['terms']); 119 $this->print_results($result); 120 $this->print_pagination($result); 121 } catch(Exception $e) { 122 msg('Something went wrong on searching please try again later or ask an admin for help.<br /><pre>' . hsc($e->getMessage()) . '</pre>', -1); 123 } 124 } 125 126 /** 127 * Prints the introduction text 128 */ 129 protected function print_intro() { 130 global $QUERY; 131 global $ID; 132 global $lang; 133 134 // just reuse the standard search page intro: 135 $intro = p_locale_xhtml('searchpage'); 136 // allow use of placeholder in search intro 137 $pagecreateinfo = (auth_quickaclcheck($ID) >= AUTH_CREATE) ? $lang['searchcreatepage'] : ''; 138 $intro = str_replace( 139 array('@QUERY@', '@SEARCH@', '@CREATEPAGEINFO@'), 140 array(hsc(rawurlencode($QUERY)), hsc($QUERY), $pagecreateinfo), 141 $intro 142 ); 143 echo $intro; 144 flush(); 145 } 146 147 /** 148 * Output the search results 149 * 150 * @param \Elastica\Result[] $results 151 */ 152 protected function print_results($results) { 153 global $lang; 154 155 // output results 156 $found = 0; 157 echo '<dl class="search_results">'; 158 foreach($results as $row) { 159 /** @var Elastica\Result $row */ 160 161 $page = $row->getSource()['uri']; 162 if(!page_exists($page) || auth_quickaclcheck($page) < AUTH_READ) continue; 163 164 echo '<dt>'; 165 echo html_wikilink($page); 166 echo '<div>'; 167 if($row->getSource()['namespace']) { 168 echo '<span class="ns"><b>' . $this->getLang('ns') . '</b> ' . hsc($row->getSource()['namespace']) . '</span><br />'; 169 } 170 if($row->getSource()['creator']) { 171 echo '<span class="author"><b>' . $this->getLang('author') . '</b> ' . hsc($row->getSource()['creator']) . '</span>'; 172 } 173 echo '</div>'; 174 echo '</dt>'; 175 176 // snippets 177 echo '<dd>'; 178 echo str_replace( 179 array('ELASTICSEARCH_MARKER_IN', 'ELASTICSEARCH_MARKER_OUT'), 180 array('<strong class="search_hit">', '</strong>'), 181 hsc(join(' … ', (array) $row->getHighlights()['content'])) 182 ); 183 echo '</dd>'; 184 $found++; 185 } 186 if(!$found) { 187 echo '<dt>' . $lang['nothingfound'] . '</dt>'; 188 } 189 echo '</dl>'; 190 } 191 192 /** 193 * Output the namespace facets 194 * 195 * @param array $facets Facet terms 196 */ 197 protected function print_facets($facets) { 198 global $INPUT; 199 global $QUERY; 200 global $lang; 201 202 echo '<form action="' . wl() . '" class="elastic_facets">'; 203 echo '<legend>' . $this->getLang('ns') . '</legend>'; 204 echo '<input name="id" type="hidden" value="' . formText($QUERY) . '" />'; 205 echo '<input name="do" type="hidden" value="elasticsearch" />'; 206 echo '<ul>'; 207 foreach($facets as $facet) { 208 209 echo '<li><div class="li">'; 210 if(in_array($facet['term'], $INPUT->arr('ns'))) { 211 $on = ' checked="checked"'; 212 } else { 213 $on = ''; 214 } 215 216 echo '<label><input name="ns[]" type="checkbox"' . $on . ' value="' . formText($facet['term']) . '" /> ' . hsc($facet['term']) . '</label>'; 217 echo '</div></li>'; 218 } 219 echo '</ul>'; 220 echo '<input type="submit" value="' . $lang['btn_search'] . '" class="button" />'; 221 echo '</form>'; 222 } 223 224 /** 225 * @param \Elastica\ResultSet $result 226 */ 227 protected function print_pagination($result) { 228 global $INPUT; 229 global $QUERY; 230 231 $all = $result->getTotalHits(); 232 $pages = ceil($all / $this->getConf('perpage')); 233 $cur = $INPUT->int('p', 1, true); 234 235 // which pages to show FIXME does this make any sense or am I drunk? 236 $toshow = array(1, 2, $cur, $pages, $pages - 1); 237 if($cur - 1 > 1) $toshow[] = $cur - 1; 238 if($cur + 1 < $pages) $toshow[] = $cur + 1; 239 $toshow = array_unique($toshow); 240 // fill up to seven, if possible 241 if(count($toshow) < 7) { 242 if($cur < 4) { 243 if($cur + 2 < $pages && count($toshow) < 7) $toshow[] = $cur + 2; 244 if($cur + 3 < $pages && count($toshow) < 7) $toshow[] = $cur + 3; 245 if($cur + 4 < $pages && count($toshow) < 7) $toshow[] = $cur + 4; 246 } else { 247 if($cur - 2 > 1 && count($toshow) < 7) $toshow[] = $cur - 2; 248 if($cur - 3 > 1 && count($toshow) < 7) $toshow[] = $cur - 3; 249 if($cur - 4 > 1 && count($toshow) < 7) $toshow[] = $cur - 4; 250 } 251 } 252 sort($toshow); 253 $showlen = count($toshow); 254 255 echo '<ul class="elastic_pagination">'; 256 if($cur > 1) { 257 echo '<li class="prev">'; 258 echo '<a href="' . wl('', http_build_query(array('id' => $QUERY, 'do' => 'elasticsearch', 'ns' => $INPUT->arr('ns'), 'p' => ($cur-1)))) . '">'; 259 echo '«'; 260 echo '</a>'; 261 echo '</li>'; 262 } 263 264 for($i = 0; $i < $showlen; $i++) { 265 if($toshow[$i] == $cur) { 266 echo '<li class="cur">' . $toshow[$i] . '</li>'; 267 } else { 268 echo '<li>'; 269 echo '<a href="' . wl('', http_build_query(array('id' => $QUERY, 'do' => 'elasticsearch', 'ns' => $INPUT->arr('ns'), 'p' => $toshow[$i]))) . '">'; 270 echo $toshow[$i]; 271 echo '</a>'; 272 echo '</li>'; 273 } 274 275 // show seperator when a jump follows 276 if(isset($toshow[$i + 1]) && $toshow[$i + 1] - $toshow[$i] > 1) { 277 echo '<li class="sep">…</li>'; 278 } 279 } 280 281 if($cur < $pages) { 282 echo '<li class="next">'; 283 echo '<a href="' . wl('', http_build_query(array('id' => $QUERY, 'do' => 'elasticsearch', 'ns' => $INPUT->arr('ns'), 'p' => ($cur-1)))) . '">'; 284 echo '»'; 285 echo '</a>'; 286 echo '</li>'; 287 } 288 289 echo '</ul>'; 290 } 291 292}