xref: /plugin/elasticsearch/action/search.php (revision dcb5f690081eb851b3829bdf737de931ba5d4d6b) !
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}