xref: /dokuwiki/inc/Ui/Search.php (revision b005809cbc9d5e96a687d5d0445452fb317981fb)
121fcef82SMichael Große<?php
221fcef82SMichael Große
321fcef82SMichael Großenamespace dokuwiki\Ui;
421fcef82SMichael Große
5427ed988SMichael Großeuse \dokuwiki\Form\Form;
6427ed988SMichael Große
721fcef82SMichael Großeclass Search extends Ui
821fcef82SMichael Große{
921fcef82SMichael Große    protected $query;
104c924eb8SMichael Große    protected $parsedQuery;
1118856c5dSMichael Große    protected $searchState;
1221fcef82SMichael Große    protected $pageLookupResults = array();
1321fcef82SMichael Große    protected $fullTextResults = array();
1421fcef82SMichael Große    protected $highlight = array();
1521fcef82SMichael Große
1621fcef82SMichael Große    /**
1721fcef82SMichael Große     * Search constructor.
186639a152SMichael Große     *
196639a152SMichael Große     * @param array  $pageLookupResults
206639a152SMichael Große     * @param array  $fullTextResults
216639a152SMichael Große     * @param string $highlight
2221fcef82SMichael Große     */
236639a152SMichael Große    public function __construct(array $pageLookupResults, array $fullTextResults, $highlight)
2421fcef82SMichael Große    {
25d09b5b64SMichael Große        global $QUERY;
264c924eb8SMichael Große        $Indexer = idx_get_indexer();
27d09b5b64SMichael Große
28d09b5b64SMichael Große        $this->query = $QUERY;
29bbc1da2eSMichael Große        $this->parsedQuery = ft_queryParser($Indexer, $QUERY);
3018856c5dSMichael Große        $this->searchState = new SearchState($this->parsedQuery);
3121fcef82SMichael Große
326639a152SMichael Große        $this->pageLookupResults = $pageLookupResults;
336639a152SMichael Große        $this->fullTextResults = $fullTextResults;
3421fcef82SMichael Große        $this->highlight = $highlight;
35b3cfe85aSMichael Große    }
36bbc1da2eSMichael Große
37b3cfe85aSMichael Große    /**
3821fcef82SMichael Große     * display the search result
3921fcef82SMichael Große     *
4021fcef82SMichael Große     * @return void
4121fcef82SMichael Große     */
4221fcef82SMichael Große    public function show()
4321fcef82SMichael Große    {
4421fcef82SMichael Große        $searchHTML = '';
4521fcef82SMichael Große
46427ed988SMichael Große        $searchHTML .= $this->getSearchFormHTML($this->query);
47427ed988SMichael Große
4821fcef82SMichael Große        $searchHTML .= $this->getSearchIntroHTML($this->query);
4921fcef82SMichael Große
5021fcef82SMichael Große        $searchHTML .= $this->getPageLookupHTML($this->pageLookupResults);
5121fcef82SMichael Große
5221fcef82SMichael Große        $searchHTML .= $this->getFulltextResultsHTML($this->fullTextResults, $this->highlight);
5321fcef82SMichael Große
5421fcef82SMichael Große        echo $searchHTML;
5521fcef82SMichael Große    }
5621fcef82SMichael Große
5721fcef82SMichael Große    /**
58427ed988SMichael Große     * Get a form which can be used to adjust/refine the search
59427ed988SMichael Große     *
60427ed988SMichael Große     * @param string $query
61427ed988SMichael Große     *
62427ed988SMichael Große     * @return string
63427ed988SMichael Große     */
64427ed988SMichael Große    protected function getSearchFormHTML($query)
65427ed988SMichael Große    {
66bbc1da2eSMichael Große        global $lang, $ID, $INPUT;
67427ed988SMichael Große
68bb8ef867SMichael Große        $searchForm = (new Form())->attrs(['method' => 'get'])->addClass('search-results-form');
69bb8ef867SMichael Große        $searchForm->setHiddenField('do', 'search');
70d22b78c8SMichael Große        $searchForm->setHiddenField('id', $ID);
71d09b5b64SMichael Große        $searchForm->setHiddenField('searchPageForm', '1');
72bbc1da2eSMichael Große        if ($INPUT->has('after')) {
73bbc1da2eSMichael Große            $searchForm->setHiddenField('after', $INPUT->str('after'));
74bbc1da2eSMichael Große        }
75bbc1da2eSMichael Große        if ($INPUT->has('before')) {
76bbc1da2eSMichael Große            $searchForm->setHiddenField('before', $INPUT->str('before'));
77bbc1da2eSMichael Große        }
788d0e286aSMichael Große        if ($INPUT->has('sort')) {
798d0e286aSMichael Große            $searchForm->setHiddenField('sort', $INPUT->str('sort'));
808d0e286aSMichael Große        }
81bb8ef867SMichael Große        $searchForm->addFieldsetOpen()->addClass('search-results-form__fieldset');
82d22b78c8SMichael Große        $searchForm->addTextInput('q')->val($query)->useInput(false);
83427ed988SMichael Große        $searchForm->addButton('', $lang['btn_search'])->attr('type', 'submit');
84bb8ef867SMichael Große
854c924eb8SMichael Große        if ($this->isSearchAssistanceAvailable($this->parsedQuery)) {
8618856c5dSMichael Große            $this->addSearchAssistanceElements($searchForm);
87bb8ef867SMichael Große        } else {
88bb8ef867SMichael Große            $searchForm->addClass('search-results-form--no-assistance');
89bb8ef867SMichael Große            $searchForm->addTagOpen('span')->addClass('search-results-form__no-assistance-message');
90bb8ef867SMichael Große            $searchForm->addHTML('FIXME Your query is too complex. Search assistance is unavailable. See <a href="https://doku.wiki/search">doku.wiki/search</a> for more help.');
91bb8ef867SMichael Große            $searchForm->addTagClose('span');
92bb8ef867SMichael Große        }
93bb8ef867SMichael Große
94427ed988SMichael Große        $searchForm->addFieldsetClose();
95427ed988SMichael Große
9681a0edd9SMichael Große        trigger_event('SEARCH_FORM_DISPLAY', $searchForm);
9781a0edd9SMichael Große
98427ed988SMichael Große        return $searchForm->toHTML();
99427ed988SMichael Große    }
100427ed988SMichael Große
101*b005809cSMichael Große    protected function addSortTool(Form $searchForm)
102*b005809cSMichael Große    {
103*b005809cSMichael Große        global $INPUT, $lang;
104*b005809cSMichael Große
105*b005809cSMichael Große        $options = [
106*b005809cSMichael Große            'hits' => [
107*b005809cSMichael Große                'label' => $lang['search_sort_by_hits'],
108*b005809cSMichael Große                'sort' => '',
109*b005809cSMichael Große            ],
110*b005809cSMichael Große            'mtime' => [
111*b005809cSMichael Große                'label' => $lang['search_sort_by_mtime'],
112*b005809cSMichael Große                'sort' => 'mtime',
113*b005809cSMichael Große            ],
114*b005809cSMichael Große        ];
115*b005809cSMichael Große        $activeOption = 'hits';
116*b005809cSMichael Große
117*b005809cSMichael Große        if ($INPUT->str('sort') === 'mtime') {
118*b005809cSMichael Große            $activeOption = 'mtime';
119*b005809cSMichael Große        }
120*b005809cSMichael Große
121*b005809cSMichael Große        $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool');
122*b005809cSMichael Große        // render current
123*b005809cSMichael Große        $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current');
124*b005809cSMichael Große        if ($activeOption !== 'hits') {
125*b005809cSMichael Große            $currentWrapper->addClass('search-tool__current--changed');
126*b005809cSMichael Große        }
127*b005809cSMichael Große        $searchForm->addHTML($options[$activeOption]['label']);
128*b005809cSMichael Große        $searchForm->addTagClose('div');
129*b005809cSMichael Große
130*b005809cSMichael Große        // render options list
131*b005809cSMichael Große        $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList');
132*b005809cSMichael Große
133*b005809cSMichael Große        foreach ($options as $key => $option) {
134*b005809cSMichael Große            $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item');
135*b005809cSMichael Große
136*b005809cSMichael Große            if ($key === $activeOption) {
137*b005809cSMichael Große                $listItem->addClass('search-tool__options-list-item--active');
138*b005809cSMichael Große                $searchForm->addHTML($option['label']);
139*b005809cSMichael Große            } else {
140*b005809cSMichael Große                $this->searchState->addSearchLinkSort(
141*b005809cSMichael Große                    $searchForm,
142*b005809cSMichael Große                    $option['label'],
143*b005809cSMichael Große                    $option['sort']
144*b005809cSMichael Große                );
145*b005809cSMichael Große            }
146*b005809cSMichael Große            $searchForm->addTagClose('li');
147*b005809cSMichael Große        }
148*b005809cSMichael Große        $searchForm->addTagClose('ul');
149*b005809cSMichael Große
150*b005809cSMichael Große        $searchForm->addTagClose('div');
151*b005809cSMichael Große
152*b005809cSMichael Große    }
153*b005809cSMichael Große
154427ed988SMichael Große    /**
155bb8ef867SMichael Große     * Decide if the given query is simple enough to provide search assistance
156bb8ef867SMichael Große     *
157bb8ef867SMichael Große     * @param array $parsedQuery
158bb8ef867SMichael Große     *
159bb8ef867SMichael Große     * @return bool
160bb8ef867SMichael Große     */
161bb8ef867SMichael Große    protected function isSearchAssistanceAvailable(array $parsedQuery)
162bb8ef867SMichael Große    {
163bb8ef867SMichael Große        if (count($parsedQuery['words']) > 1) {
164bb8ef867SMichael Große            return false;
165bb8ef867SMichael Große        }
166bb8ef867SMichael Große        if (!empty($parsedQuery['not'])) {
167bb8ef867SMichael Große            return false;
168bb8ef867SMichael Große        }
169bb8ef867SMichael Große
170bb8ef867SMichael Große        if (!empty($parsedQuery['phrases'])) {
171bb8ef867SMichael Große            return false;
172bb8ef867SMichael Große        }
173bb8ef867SMichael Große
174bb8ef867SMichael Große        if (!empty($parsedQuery['notns'])) {
175bb8ef867SMichael Große            return false;
176bb8ef867SMichael Große        }
177bb8ef867SMichael Große        if (count($parsedQuery['ns']) > 1) {
178bb8ef867SMichael Große            return false;
179bb8ef867SMichael Große        }
180bb8ef867SMichael Große
181bb8ef867SMichael Große        return true;
182bb8ef867SMichael Große    }
183bb8ef867SMichael Große
184bb8ef867SMichael Große    /**
185bb8ef867SMichael Große     * Add the elements to be used for search assistance
186bb8ef867SMichael Große     *
187bb8ef867SMichael Große     * @param Form $searchForm
188bb8ef867SMichael Große     */
18918856c5dSMichael Große    protected function addSearchAssistanceElements(Form $searchForm)
190bb8ef867SMichael Große    {
191bb8ef867SMichael Große        $searchForm->addButton('toggleAssistant', 'toggle search assistant')
192bb8ef867SMichael Große            ->attr('type', 'button')
193bb8ef867SMichael Große            ->id('search-results-form__show-assistance-button')
194bb8ef867SMichael Große            ->addClass('search-results-form__show-assistance-button');
195bb8ef867SMichael Große
196bb8ef867SMichael Große        $searchForm->addTagOpen('div')
197bb8ef867SMichael Große            ->addClass('js-advancedSearchOptions')
198bb8ef867SMichael Große            ->attr('style', 'display: none;');
199bb8ef867SMichael Große
20018856c5dSMichael Große        $this->addFragmentBehaviorLinks($searchForm);
20118856c5dSMichael Große        $this->addNamespaceSelector($searchForm);
20218856c5dSMichael Große        $this->addDateSelector($searchForm);
203*b005809cSMichael Große        $this->addSortTool($searchForm);
204bb8ef867SMichael Große
205bb8ef867SMichael Große        $searchForm->addTagClose('div');
206bb8ef867SMichael Große    }
207bb8ef867SMichael Große
20818856c5dSMichael Große    protected function addFragmentBehaviorLinks(Form $searchForm)
2094d0cb6e1SMichael Große    {
210*b005809cSMichael Große        global $lang;
2114d0cb6e1SMichael Große
212*b005809cSMichael Große        $options = [
213*b005809cSMichael Große            'exact' => [
214*b005809cSMichael Große                'label' => $lang['search_exact_match'],
215*b005809cSMichael Große                'and' => array_map(function ($term) {
216*b005809cSMichael Große                    return trim($term, '*');
217*b005809cSMichael Große                }, $this->parsedQuery['and']),
218*b005809cSMichael Große            ],
219*b005809cSMichael Große            'starts' => [
220*b005809cSMichael Große                'label' => $lang['search_starts_with'],
221*b005809cSMichael Große                'and' => array_map(function ($term) {
222*b005809cSMichael Große                    return trim($term, '*') . '*';
223*b005809cSMichael Große                }, $this->parsedQuery['and'])
224*b005809cSMichael Große            ],
225*b005809cSMichael Große            'ends' => [
226*b005809cSMichael Große                'label' => $lang['search_ends_with'],
227*b005809cSMichael Große                'and' => array_map(function ($term) {
228*b005809cSMichael Große                    return '*' . trim($term, '*');
229*b005809cSMichael Große                }, $this->parsedQuery['and'])
230*b005809cSMichael Große            ],
231*b005809cSMichael Große            'contains' => [
232*b005809cSMichael Große                'label' => $lang['search_contains'],
233*b005809cSMichael Große                'and' => array_map(function ($term) {
234*b005809cSMichael Große                    return '*' . trim($term, '*') . '*';
235*b005809cSMichael Große                }, $this->parsedQuery['and'])
236*b005809cSMichael Große            ]
237*b005809cSMichael Große        ];
238*b005809cSMichael Große
239*b005809cSMichael Große        // detect current
240*b005809cSMichael Große        $activeOption = 'exact';
241*b005809cSMichael Große        foreach ($options as $key => $option) {
242*b005809cSMichael Große            if ($this->parsedQuery['and'] === $option['and']) {
243*b005809cSMichael Große                $activeOption = $key;
244*b005809cSMichael Große            }
245*b005809cSMichael Große        }
246*b005809cSMichael Große
247*b005809cSMichael Große        $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool');
248*b005809cSMichael Große        // render current
249*b005809cSMichael Große        $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current');
250*b005809cSMichael Große        if ($activeOption !== 'exact') {
251*b005809cSMichael Große            $currentWrapper->addClass('search-tool__current--changed');
252*b005809cSMichael Große        }
253*b005809cSMichael Große        $searchForm->addHTML($options[$activeOption]['label']);
254*b005809cSMichael Große        $searchForm->addTagClose('div');
255*b005809cSMichael Große
256*b005809cSMichael Große        // render options list
257*b005809cSMichael Große        $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList');
258*b005809cSMichael Große
259*b005809cSMichael Große        foreach ($options as $key => $option) {
260*b005809cSMichael Große            $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item');
261*b005809cSMichael Große
262*b005809cSMichael Große            if ($key === $activeOption) {
263*b005809cSMichael Große                $listItem->addClass('search-tool__options-list-item--active');
264*b005809cSMichael Große                $searchForm->addHTML($option['label']);
265*b005809cSMichael Große            } else {
26618856c5dSMichael Große                $this->searchState->addSearchLinkFragment(
2674d0cb6e1SMichael Große                    $searchForm,
268*b005809cSMichael Große                    $option['label'],
269*b005809cSMichael Große                    $option['and']
2704d0cb6e1SMichael Große                );
271*b005809cSMichael Große            }
272*b005809cSMichael Große            $searchForm->addTagClose('li');
273*b005809cSMichael Große        }
274*b005809cSMichael Große        $searchForm->addTagClose('ul');
2754d0cb6e1SMichael Große
2764d0cb6e1SMichael Große        $searchForm->addTagClose('div');
277*b005809cSMichael Große
278*b005809cSMichael Große        // render options list
2794d0cb6e1SMichael Große    }
2804d0cb6e1SMichael Große
281bb8ef867SMichael Große    /**
282bb8ef867SMichael Große     * Add the elements for the namespace selector
283bb8ef867SMichael Große     *
284bb8ef867SMichael Große     * @param Form $searchForm
285bb8ef867SMichael Große     */
28618856c5dSMichael Große    protected function addNamespaceSelector(Form $searchForm)
287bb8ef867SMichael Große    {
288*b005809cSMichael Große        global $lang;
289*b005809cSMichael Große
29018856c5dSMichael Große        $baseNS = empty($this->parsedQuery['ns']) ? '' : $this->parsedQuery['ns'][0];
291bbc1da2eSMichael Große        $extraNS = $this->getAdditionalNamespacesFromResults($baseNS);
2924d0cb6e1SMichael Große
293*b005809cSMichael Große        $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool');
294*b005809cSMichael Große        // render current
295*b005809cSMichael Große        $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current');
296bbc1da2eSMichael Große        if ($baseNS) {
297*b005809cSMichael Große            $currentWrapper->addClass('search-tool__current--changed');
298*b005809cSMichael Große            $searchForm->addHTML('@' . $baseNS);
299*b005809cSMichael Große        } else {
300*b005809cSMichael Große            $searchForm->addHTML($lang['search_any_ns']);
301*b005809cSMichael Große        }
302*b005809cSMichael Große        $searchForm->addTagClose('div');
303*b005809cSMichael Große
304*b005809cSMichael Große        // render options list
305*b005809cSMichael Große        $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList');
306*b005809cSMichael Große
307*b005809cSMichael Große        $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item');
308*b005809cSMichael Große        if ($baseNS) {
309*b005809cSMichael Große            $listItem->addClass('search-tool__options-list-item--active');
31018856c5dSMichael Große            $this->searchState->addSeachLinkNS(
3114d0cb6e1SMichael Große                $searchForm,
312*b005809cSMichael Große                $lang['search_any_ns'],
31318856c5dSMichael Große                ''
3144d0cb6e1SMichael Große            );
315*b005809cSMichael Große        } else {
316*b005809cSMichael Große            $searchForm->addHTML($lang['search_any_ns']);
317bb8ef867SMichael Große        }
318*b005809cSMichael Große        $searchForm->addTagClose('li');
319bb8ef867SMichael Große
32018856c5dSMichael Große        foreach ($extraNS as $ns => $count) {
321*b005809cSMichael Große            $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item');
32218856c5dSMichael Große            $label = $ns . ($count ? " ($count)" : '');
3234d0cb6e1SMichael Große
324*b005809cSMichael Große            if ($ns === $baseNS) {
325*b005809cSMichael Große                $listItem->addClass('search-tool__options-list-item--active');
326*b005809cSMichael Große                $searchForm->addHTML($label);
327*b005809cSMichael Große            } else {
328*b005809cSMichael Große                $this->searchState->addSeachLinkNS(
329*b005809cSMichael Große                    $searchForm,
330*b005809cSMichael Große                    $label,
331*b005809cSMichael Große                    $ns
332*b005809cSMichael Große                );
333bb8ef867SMichael Große            }
334*b005809cSMichael Große            $searchForm->addTagClose('li');
335bb8ef867SMichael Große        }
336*b005809cSMichael Große        $searchForm->addTagClose('ul');
337bb8ef867SMichael Große
338bb8ef867SMichael Große        $searchForm->addTagClose('div');
339*b005809cSMichael Große
340bb8ef867SMichael Große    }
341bb8ef867SMichael Große
342bb8ef867SMichael Große    /**
343bb8ef867SMichael Große     * Parse the full text results for their top namespaces below the given base namespace
344bb8ef867SMichael Große     *
345bb8ef867SMichael Große     * @param string $baseNS the namespace within which was searched, empty string for root namespace
346bb8ef867SMichael Große     *
347bb8ef867SMichael Große     * @return array an associative array with namespace => #number of found pages, sorted descending
348bb8ef867SMichael Große     */
349bb8ef867SMichael Große    protected function getAdditionalNamespacesFromResults($baseNS)
350bb8ef867SMichael Große    {
351bb8ef867SMichael Große        $namespaces = [];
352bb8ef867SMichael Große        $baseNSLength = strlen($baseNS);
353bb8ef867SMichael Große        foreach ($this->fullTextResults as $page => $numberOfHits) {
354bb8ef867SMichael Große            $namespace = getNS($page);
355bb8ef867SMichael Große            if (!$namespace) {
356bb8ef867SMichael Große                continue;
357bb8ef867SMichael Große            }
358bb8ef867SMichael Große            if ($namespace === $baseNS) {
359bb8ef867SMichael Große                continue;
360bb8ef867SMichael Große            }
361bb8ef867SMichael Große            $firstColon = strpos((string)$namespace, ':', $baseNSLength + 1) ?: strlen($namespace);
362bb8ef867SMichael Große            $subtopNS = substr($namespace, 0, $firstColon);
363bb8ef867SMichael Große            if (empty($namespaces[$subtopNS])) {
364bb8ef867SMichael Große                $namespaces[$subtopNS] = 0;
365bb8ef867SMichael Große            }
366bb8ef867SMichael Große            $namespaces[$subtopNS] += 1;
367bb8ef867SMichael Große        }
368bb8ef867SMichael Große        arsort($namespaces);
369bb8ef867SMichael Große        return $namespaces;
370bb8ef867SMichael Große    }
371bb8ef867SMichael Große
372bb8ef867SMichael Große    /**
373bbc1da2eSMichael Große     * @ToDo: custom date input
374bbc1da2eSMichael Große     *
375bbc1da2eSMichael Große     * @param Form $searchForm
376bbc1da2eSMichael Große     */
377*b005809cSMichael Große    protected function addDateSelector(Form $searchForm)
378*b005809cSMichael Große    {
379*b005809cSMichael Große        global $INPUT, $lang;
380bbc1da2eSMichael Große
381*b005809cSMichael Große        $options = [
382*b005809cSMichael Große            'any' => [
383*b005809cSMichael Große                'before' => false,
384*b005809cSMichael Große                'after' => false,
385*b005809cSMichael Große                'label' => $lang['search_any_time'],
386*b005809cSMichael Große            ],
387*b005809cSMichael Große            'week' => [
388*b005809cSMichael Große                'before' => false,
389*b005809cSMichael Große                'after' => '1 week ago',
390*b005809cSMichael Große                'label' => $lang['search_past_7_days'],
391*b005809cSMichael Große            ],
392*b005809cSMichael Große            'month' => [
393*b005809cSMichael Große                'before' => false,
394*b005809cSMichael Große                'after' => '1 month ago',
395*b005809cSMichael Große                'label' => $lang['search_past_month'],
396*b005809cSMichael Große            ],
397*b005809cSMichael Große            'year' => [
398*b005809cSMichael Große                'before' => false,
399*b005809cSMichael Große                'after' => '1 year ago',
400*b005809cSMichael Große                'label' => $lang['search_past_year'],
401*b005809cSMichael Große            ],
402*b005809cSMichael Große        ];
403*b005809cSMichael Große        $activeOption = 'any';
404*b005809cSMichael Große        foreach ($options as $key => $option) {
405*b005809cSMichael Große            if ($INPUT->str('after') === $option['after']) {
406*b005809cSMichael Große                $activeOption = $key;
407*b005809cSMichael Große                break;
408*b005809cSMichael Große            }
409*b005809cSMichael Große        }
410*b005809cSMichael Große
411*b005809cSMichael Große        $searchForm->addTagOpen('div')->addClass('search-tool js-search-tool');
412*b005809cSMichael Große        // render current
413*b005809cSMichael Große        $currentWrapper = $searchForm->addTagOpen('div')->addClass('search-tool__current js-current');
414bbc1da2eSMichael Große        if ($INPUT->has('before') || $INPUT->has('after')) {
415*b005809cSMichael Große            $currentWrapper->addClass('search-tool__current--changed');
416bbc1da2eSMichael Große        }
417*b005809cSMichael Große        $searchForm->addHTML($options[$activeOption]['label']);
418*b005809cSMichael Große        $searchForm->addTagClose('div');
419bbc1da2eSMichael Große
420*b005809cSMichael Große        // render options list
421*b005809cSMichael Große        $searchForm->addTagOpen('ul')->addClass('search-tool__options-list js-optionsList');
422*b005809cSMichael Große
423*b005809cSMichael Große        foreach ($options as $key => $option) {
424*b005809cSMichael Große            $listItem = $searchForm->addTagOpen('li')->addClass('search-tool__options-list-item');
425*b005809cSMichael Große
426*b005809cSMichael Große            if ($key === $activeOption) {
427*b005809cSMichael Große                $listItem->addClass('search-tool__options-list-item--active');
428*b005809cSMichael Große                $searchForm->addHTML($option['label']);
429bbc1da2eSMichael Große            } else {
43018856c5dSMichael Große                $this->searchState->addSearchLinkTime(
431bbc1da2eSMichael Große                    $searchForm,
432*b005809cSMichael Große                    $option['label'],
433*b005809cSMichael Große                    $option['after'],
434*b005809cSMichael Große                    $option['before']
435bbc1da2eSMichael Große                );
436bbc1da2eSMichael Große            }
437*b005809cSMichael Große            $searchForm->addTagClose('li');
438bbc1da2eSMichael Große        }
439*b005809cSMichael Große        $searchForm->addTagClose('ul');
440bbc1da2eSMichael Große
441bbc1da2eSMichael Große        $searchForm->addTagClose('div');
442bbc1da2eSMichael Große    }
443bbc1da2eSMichael Große
444bbc1da2eSMichael Große
445bbc1da2eSMichael Große    /**
44621fcef82SMichael Große     * Build the intro text for the search page
44721fcef82SMichael Große     *
44821fcef82SMichael Große     * @param string $query the search query
44921fcef82SMichael Große     *
45021fcef82SMichael Große     * @return string
45121fcef82SMichael Große     */
45221fcef82SMichael Große    protected function getSearchIntroHTML($query)
45321fcef82SMichael Große    {
45421fcef82SMichael Große        global $ID, $lang;
45521fcef82SMichael Große
45621fcef82SMichael Große        $intro = p_locale_xhtml('searchpage');
45721fcef82SMichael Große        // allow use of placeholder in search intro
45821fcef82SMichael Große        $pagecreateinfo = (auth_quickaclcheck($ID) >= AUTH_CREATE) ? $lang['searchcreatepage'] : '';
45921fcef82SMichael Große        $intro = str_replace(
46021fcef82SMichael Große            array('@QUERY@', '@SEARCH@', '@CREATEPAGEINFO@'),
46121fcef82SMichael Große            array(hsc(rawurlencode($query)), hsc($query), $pagecreateinfo),
46221fcef82SMichael Große            $intro
46321fcef82SMichael Große        );
46421fcef82SMichael Große        return $intro;
46521fcef82SMichael Große    }
46621fcef82SMichael Große
46721fcef82SMichael Große    /**
46821fcef82SMichael Große     * Build HTML for a list of pages with matching pagenames
46921fcef82SMichael Große     *
47021fcef82SMichael Große     * @param array $data search results
47121fcef82SMichael Große     *
47221fcef82SMichael Große     * @return string
47321fcef82SMichael Große     */
47421fcef82SMichael Große    protected function getPageLookupHTML($data)
47521fcef82SMichael Große    {
47621fcef82SMichael Große        if (empty($data)) {
47721fcef82SMichael Große            return '';
47821fcef82SMichael Große        }
47921fcef82SMichael Große
48021fcef82SMichael Große        global $lang;
48121fcef82SMichael Große
48221fcef82SMichael Große        $html = '<div class="search_quickresult">';
48321fcef82SMichael Große        $html .= '<h3>' . $lang['quickhits'] . ':</h3>';
48421fcef82SMichael Große        $html .= '<ul class="search_quickhits">';
48521fcef82SMichael Große        foreach ($data as $id => $title) {
4864eab6f7cSMichael Große            $link = html_wikilink(':' . $id);
4874eab6f7cSMichael Große            $eventData = [
4884eab6f7cSMichael Große                'listItemContent' => [$link],
4894eab6f7cSMichael Große                'page' => $id,
4904eab6f7cSMichael Große            ];
4914eab6f7cSMichael Große            trigger_event('SEARCH_RESULT_PAGELOOKUP', $eventData);
4924eab6f7cSMichael Große            $html .= '<li>' . implode('', $eventData['listItemContent']) . '</li>';
49321fcef82SMichael Große        }
49421fcef82SMichael Große        $html .= '</ul> ';
49521fcef82SMichael Große        //clear float (see http://www.complexspiral.com/publications/containing-floats/)
49621fcef82SMichael Große        $html .= '<div class="clearer"></div>';
49721fcef82SMichael Große        $html .= '</div>';
49821fcef82SMichael Große
49921fcef82SMichael Große        return $html;
50021fcef82SMichael Große    }
50121fcef82SMichael Große
50221fcef82SMichael Große    /**
50321fcef82SMichael Große     * Build HTML for fulltext search results or "no results" message
50421fcef82SMichael Große     *
50521fcef82SMichael Große     * @param array $data      the results of the fulltext search
50621fcef82SMichael Große     * @param array $highlight the terms to be highlighted in the results
50721fcef82SMichael Große     *
50821fcef82SMichael Große     * @return string
50921fcef82SMichael Große     */
51021fcef82SMichael Große    protected function getFulltextResultsHTML($data, $highlight)
51121fcef82SMichael Große    {
51221fcef82SMichael Große        global $lang;
51321fcef82SMichael Große
51421fcef82SMichael Große        if (empty($data)) {
51521fcef82SMichael Große            return '<div class="nothing">' . $lang['nothingfound'] . '</div>';
51621fcef82SMichael Große        }
51721fcef82SMichael Große
51821fcef82SMichael Große        $html = '';
51921fcef82SMichael Große        $html .= '<dl class="search_results">';
52021fcef82SMichael Große        $num = 1;
5214c924eb8SMichael Große
52221fcef82SMichael Große        foreach ($data as $id => $cnt) {
5234eab6f7cSMichael Große            $resultLink = html_wikilink(':' . $id, null, $highlight);
5244c924eb8SMichael Große
5254c924eb8SMichael Große            $resultHeader = [$resultLink];
5264c924eb8SMichael Große
5274eab6f7cSMichael Große
5284c924eb8SMichael Große            $restrictQueryToNSLink = $this->restrictQueryToNSLink(getNS($id));
5294c924eb8SMichael Große            if ($restrictQueryToNSLink) {
5304c924eb8SMichael Große                $resultHeader[] = $restrictQueryToNSLink;
5314c924eb8SMichael Große            }
5324c924eb8SMichael Große
5339a75abfbSMichael Große            $snippet = '';
5349a75abfbSMichael Große            $lastMod = '';
5359a75abfbSMichael Große            $mtime = filemtime(wikiFN($id));
5369a75abfbSMichael Große            if ($cnt !== 0) {
5379a75abfbSMichael Große                $resultHeader[] = $cnt . ' ' . $lang['hits'];
5389a75abfbSMichael Große                if ($num < FT_SNIPPET_NUMBER) { // create snippets for the first number of matches only
5399a75abfbSMichael Große                    $snippet = '<dd>' . ft_snippet($id, $highlight) . '</dd>';
5409a75abfbSMichael Große                    $lastMod = '<span class="search_results__lastmod">' . $lang['lastmod'] . ' ';
5419a75abfbSMichael Große                    $lastMod .= '<time datetime="' . date_iso8601($mtime) . '">' . dformat($mtime) . '</time>';
5429a75abfbSMichael Große                    $lastMod .= '</span>';
5439a75abfbSMichael Große                }
5449a75abfbSMichael Große                $num++;
5459a75abfbSMichael Große            }
5469a75abfbSMichael Große
5479a75abfbSMichael Große            $metaLine = '<div class="search_results__metaLine">';
5489a75abfbSMichael Große            $metaLine .= $lastMod;
5499a75abfbSMichael Große            $metaLine .= '</div>';
5509a75abfbSMichael Große
5519a75abfbSMichael Große
5524eab6f7cSMichael Große            $eventData = [
5534c924eb8SMichael Große                'resultHeader' => $resultHeader,
5549a75abfbSMichael Große                'resultBody' => [$metaLine, $snippet],
5554eab6f7cSMichael Große                'page' => $id,
5564eab6f7cSMichael Große            ];
5574eab6f7cSMichael Große            trigger_event('SEARCH_RESULT_FULLPAGE', $eventData);
5584eab6f7cSMichael Große            $html .= '<div class="search_fullpage_result">';
5594eab6f7cSMichael Große            $html .= '<dt>' . implode(' ', $eventData['resultHeader']) . '</dt>';
5604eab6f7cSMichael Große            $html .= implode('', $eventData['resultBody']);
5614eab6f7cSMichael Große            $html .= '</div>';
56221fcef82SMichael Große        }
56321fcef82SMichael Große        $html .= '</dl>';
56421fcef82SMichael Große
56521fcef82SMichael Große        return $html;
56621fcef82SMichael Große    }
5674c924eb8SMichael Große
5684c924eb8SMichael Große    /**
5694c924eb8SMichael Große     * create a link to restrict the current query to a namespace
5704c924eb8SMichael Große     *
5714c924eb8SMichael Große     * @param bool|string $ns the namespace to which to restrict the query
5724c924eb8SMichael Große     *
5734c924eb8SMichael Große     * @return bool|string
5744c924eb8SMichael Große     */
5754c924eb8SMichael Große    protected function restrictQueryToNSLink($ns)
5764c924eb8SMichael Große    {
5774c924eb8SMichael Große        if (!$ns) {
5784c924eb8SMichael Große            return false;
5794c924eb8SMichael Große        }
5804c924eb8SMichael Große        if (!$this->isSearchAssistanceAvailable($this->parsedQuery)) {
5814c924eb8SMichael Große            return false;
5824c924eb8SMichael Große        }
5834c924eb8SMichael Große        if (!empty($this->parsedQuery['ns']) && $this->parsedQuery['ns'][0] === $ns) {
5844c924eb8SMichael Große            return false;
5854c924eb8SMichael Große        }
5864c924eb8SMichael Große        $name = '@' . $ns;
587bbc1da2eSMichael Große        $tmpForm = new Form();
58818856c5dSMichael Große        $this->searchState->addSeachLinkNS($tmpForm, $name, $ns);
589bbc1da2eSMichael Große        return $tmpForm->toHTML();
5904c924eb8SMichael Große    }
59121fcef82SMichael Große}
592