xref: /dokuwiki/inc/Ui/Search.php (revision 1b48999c32bc54c39ae10cb3107127678160ee93)
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;
1121fcef82SMichael Große    protected $pageLookupResults = array();
1221fcef82SMichael Große    protected $fullTextResults = array();
1321fcef82SMichael Große    protected $highlight = array();
1421fcef82SMichael Große
1521fcef82SMichael Große    /**
1621fcef82SMichael Große     * Search constructor.
1721fcef82SMichael Große     *
1821fcef82SMichael Große     * @param string $query the search query
1921fcef82SMichael Große     */
20d09b5b64SMichael Große    public function __construct()
2121fcef82SMichael Große    {
22d09b5b64SMichael Große        global $QUERY;
234c924eb8SMichael Große        $Indexer = idx_get_indexer();
24d09b5b64SMichael Große
25d09b5b64SMichael Große        $this->query = $QUERY;
26bbc1da2eSMichael Große        $this->parsedQuery = ft_queryParser($Indexer, $QUERY);
2721fcef82SMichael Große    }
2821fcef82SMichael Große
2921fcef82SMichael Große    /**
3021fcef82SMichael Große     * run the search
3121fcef82SMichael Große     */
3221fcef82SMichael Große    public function execute()
3321fcef82SMichael Große    {
34*1b48999cSMichael Große        $this->pageLookupResults = ft_pageLookup($this->query, true, useHeading('navigation'));
35*1b48999cSMichael Große        $this->fullTextResults = ft_pageSearch($this->query, $highlight);
3621fcef82SMichael Große        $this->highlight = $highlight;
37b3cfe85aSMichael Große    }
38bbc1da2eSMichael Große
39b3cfe85aSMichael Große    /**
4021fcef82SMichael Große     * display the search result
4121fcef82SMichael Große     *
4221fcef82SMichael Große     * @return void
4321fcef82SMichael Große     */
4421fcef82SMichael Große    public function show()
4521fcef82SMichael Große    {
4621fcef82SMichael Große        $searchHTML = '';
4721fcef82SMichael Große
48427ed988SMichael Große        $searchHTML .= $this->getSearchFormHTML($this->query);
49427ed988SMichael Große
5021fcef82SMichael Große        $searchHTML .= $this->getSearchIntroHTML($this->query);
5121fcef82SMichael Große
5221fcef82SMichael Große        $searchHTML .= $this->getPageLookupHTML($this->pageLookupResults);
5321fcef82SMichael Große
5421fcef82SMichael Große        $searchHTML .= $this->getFulltextResultsHTML($this->fullTextResults, $this->highlight);
5521fcef82SMichael Große
5621fcef82SMichael Große        echo $searchHTML;
5721fcef82SMichael Große    }
5821fcef82SMichael Große
5921fcef82SMichael Große    /**
60427ed988SMichael Große     * Get a form which can be used to adjust/refine the search
61427ed988SMichael Große     *
62427ed988SMichael Große     * @param string $query
63427ed988SMichael Große     *
64427ed988SMichael Große     * @return string
65427ed988SMichael Große     */
66427ed988SMichael Große    protected function getSearchFormHTML($query)
67427ed988SMichael Große    {
68bbc1da2eSMichael Große        global $lang, $ID, $INPUT;
69427ed988SMichael Große
70bb8ef867SMichael Große        $searchForm = (new Form())->attrs(['method' => 'get'])->addClass('search-results-form');
71bb8ef867SMichael Große        $searchForm->setHiddenField('do', 'search');
72d22b78c8SMichael Große        $searchForm->setHiddenField('id', $ID);
73d09b5b64SMichael Große        $searchForm->setHiddenField('searchPageForm', '1');
74bbc1da2eSMichael Große        if ($INPUT->has('after')) {
75bbc1da2eSMichael Große            $searchForm->setHiddenField('after', $INPUT->str('after'));
76bbc1da2eSMichael Große        }
77bbc1da2eSMichael Große        if ($INPUT->has('before')) {
78bbc1da2eSMichael Große            $searchForm->setHiddenField('before', $INPUT->str('before'));
79bbc1da2eSMichael Große        }
80bb8ef867SMichael Große        $searchForm->addFieldsetOpen()->addClass('search-results-form__fieldset');
81d22b78c8SMichael Große        $searchForm->addTextInput('q')->val($query)->useInput(false);
82427ed988SMichael Große        $searchForm->addButton('', $lang['btn_search'])->attr('type', 'submit');
83bb8ef867SMichael Große
844c924eb8SMichael Große        if ($this->isSearchAssistanceAvailable($this->parsedQuery)) {
854c924eb8SMichael Große            $this->addSearchAssistanceElements($searchForm, $this->parsedQuery);
86bb8ef867SMichael Große        } else {
87bb8ef867SMichael Große            $searchForm->addClass('search-results-form--no-assistance');
88bb8ef867SMichael Große            $searchForm->addTagOpen('span')->addClass('search-results-form__no-assistance-message');
89bb8ef867SMichael 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.');
90bb8ef867SMichael Große            $searchForm->addTagClose('span');
91bb8ef867SMichael Große        }
92bb8ef867SMichael Große
93427ed988SMichael Große        $searchForm->addFieldsetClose();
94427ed988SMichael Große
9581a0edd9SMichael Große        trigger_event('SEARCH_FORM_DISPLAY', $searchForm);
9681a0edd9SMichael Große
97427ed988SMichael Große        return $searchForm->toHTML();
98427ed988SMichael Große    }
99427ed988SMichael Große
100427ed988SMichael Große    /**
101bb8ef867SMichael Große     * Decide if the given query is simple enough to provide search assistance
102bb8ef867SMichael Große     *
103bb8ef867SMichael Große     * @param array $parsedQuery
104bb8ef867SMichael Große     *
105bb8ef867SMichael Große     * @return bool
106bb8ef867SMichael Große     */
107bb8ef867SMichael Große    protected function isSearchAssistanceAvailable(array $parsedQuery)
108bb8ef867SMichael Große    {
109bb8ef867SMichael Große        if (count($parsedQuery['words']) > 1) {
110bb8ef867SMichael Große            return false;
111bb8ef867SMichael Große        }
112bb8ef867SMichael Große        if (!empty($parsedQuery['not'])) {
113bb8ef867SMichael Große            return false;
114bb8ef867SMichael Große        }
115bb8ef867SMichael Große
116bb8ef867SMichael Große        if (!empty($parsedQuery['phrases'])) {
117bb8ef867SMichael Große            return false;
118bb8ef867SMichael Große        }
119bb8ef867SMichael Große
120bb8ef867SMichael Große        if (!empty($parsedQuery['notns'])) {
121bb8ef867SMichael Große            return false;
122bb8ef867SMichael Große        }
123bb8ef867SMichael Große        if (count($parsedQuery['ns']) > 1) {
124bb8ef867SMichael Große            return false;
125bb8ef867SMichael Große        }
126bb8ef867SMichael Große
127bb8ef867SMichael Große        return true;
128bb8ef867SMichael Große    }
129bb8ef867SMichael Große
130bb8ef867SMichael Große    /**
131bb8ef867SMichael Große     * Add the elements to be used for search assistance
132bb8ef867SMichael Große     *
133bb8ef867SMichael Große     * @param Form  $searchForm
134bb8ef867SMichael Große     * @param array $parsedQuery
135bb8ef867SMichael Große     */
136bb8ef867SMichael Große    protected function addSearchAssistanceElements(Form $searchForm, array $parsedQuery)
137bb8ef867SMichael Große    {
138bb8ef867SMichael Große        $searchForm->addButton('toggleAssistant', 'toggle search assistant')
139bb8ef867SMichael Große            ->attr('type', 'button')
140bb8ef867SMichael Große            ->id('search-results-form__show-assistance-button')
141bb8ef867SMichael Große            ->addClass('search-results-form__show-assistance-button');
142bb8ef867SMichael Große
143bb8ef867SMichael Große        $searchForm->addTagOpen('div')
144bb8ef867SMichael Große            ->addClass('js-advancedSearchOptions')
145bb8ef867SMichael Große            ->attr('style', 'display: none;');
146bb8ef867SMichael Große
1474d0cb6e1SMichael Große        $this->addFragmentBehaviorLinks($searchForm, $parsedQuery);
148bb8ef867SMichael Große        $this->addNamespaceSelector($searchForm, $parsedQuery);
149bbc1da2eSMichael Große        $this->addDateSelector($searchForm, $parsedQuery);
150bb8ef867SMichael Große
151bb8ef867SMichael Große        $searchForm->addTagClose('div');
152bb8ef867SMichael Große    }
153bb8ef867SMichael Große
1544d0cb6e1SMichael Große    protected function addFragmentBehaviorLinks(Form $searchForm, array $parsedQuery)
1554d0cb6e1SMichael Große    {
1564d0cb6e1SMichael Große        $searchForm->addTagOpen('div')->addClass('search-results-form__subwrapper');
157bbc1da2eSMichael Große        $searchForm->addHTML('fragment behavior: ');
1584d0cb6e1SMichael Große
1594d0cb6e1SMichael Große        $this->addSearchLink(
1604d0cb6e1SMichael Große            $searchForm,
161bbc1da2eSMichael Große            'exact match',
162bbc1da2eSMichael Große            array_map(function($term){return trim($term, '*');},$this->parsedQuery['and'])
1634d0cb6e1SMichael Große        );
1644d0cb6e1SMichael Große
165bbc1da2eSMichael Große        $searchForm->addHTML(', ');
1664d0cb6e1SMichael Große
1674d0cb6e1SMichael Große        $this->addSearchLink(
1684d0cb6e1SMichael Große            $searchForm,
1694d0cb6e1SMichael Große            'starts with',
170bbc1da2eSMichael Große            array_map(function($term){return trim($term, '*') . '*';},$this->parsedQuery['and'])
1714d0cb6e1SMichael Große        );
1724d0cb6e1SMichael Große
173bbc1da2eSMichael Große        $searchForm->addHTML(', ');
1744d0cb6e1SMichael Große
1754d0cb6e1SMichael Große        $this->addSearchLink(
1764d0cb6e1SMichael Große            $searchForm,
1774d0cb6e1SMichael Große            'ends with',
178bbc1da2eSMichael Große            array_map(function($term){return '*' . trim($term, '*');},$this->parsedQuery['and'])
1794d0cb6e1SMichael Große        );
1804d0cb6e1SMichael Große
181bbc1da2eSMichael Große        $searchForm->addHTML(', ');
1824d0cb6e1SMichael Große
1834d0cb6e1SMichael Große        $this->addSearchLink(
1844d0cb6e1SMichael Große            $searchForm,
1854d0cb6e1SMichael Große            'contains',
186bbc1da2eSMichael Große            array_map(function($term){return '*' . trim($term, '*') . '*';},$this->parsedQuery['and'])
1874d0cb6e1SMichael Große        );
1884d0cb6e1SMichael Große
1894d0cb6e1SMichael Große        $searchForm->addTagClose('div');
1904d0cb6e1SMichael Große    }
1914d0cb6e1SMichael Große
192bbc1da2eSMichael Große    protected function addSearchLink(
193bbc1da2eSMichael Große        Form $searchForm,
194bbc1da2eSMichael Große        $label,
195bbc1da2eSMichael Große        array $and = null,
196bbc1da2eSMichael Große        array $ns = null,
197bbc1da2eSMichael Große        array $not = null,
198bbc1da2eSMichael Große        array $notns = null,
199bbc1da2eSMichael Große        array $phrases = null,
200bbc1da2eSMichael Große        $after = null,
201bbc1da2eSMichael Große        $before = null
202bbc1da2eSMichael Große    ) {
203d22b78c8SMichael Große        global $INPUT, $ID;
204bbc1da2eSMichael Große        if (null === $and) {
205bbc1da2eSMichael Große            $and = $this->parsedQuery['and'];
206bbc1da2eSMichael Große        }
207bbc1da2eSMichael Große        if (null === $ns) {
208bbc1da2eSMichael Große            $ns = $this->parsedQuery['ns'];
209bbc1da2eSMichael Große        }
210bbc1da2eSMichael Große        if (null === $not) {
211bbc1da2eSMichael Große            $not = $this->parsedQuery['not'];
212bbc1da2eSMichael Große        }
213bbc1da2eSMichael Große        if (null === $phrases) {
214bbc1da2eSMichael Große            $phrases = $this->parsedQuery['phrases'];
215bbc1da2eSMichael Große        }
216bbc1da2eSMichael Große        if (null === $notns) {
217bbc1da2eSMichael Große            $notns = $this->parsedQuery['notns'];
218bbc1da2eSMichael Große        }
219bbc1da2eSMichael Große        if (null === $after) {
220bbc1da2eSMichael Große            $after = $INPUT->str('after');
221bbc1da2eSMichael Große        }
222bbc1da2eSMichael Große        if (null === $before) {
223bbc1da2eSMichael Große            $before = $INPUT->str('before');
224bbc1da2eSMichael Große        }
225bbc1da2eSMichael Große
2264d0cb6e1SMichael Große        $newQuery = ft_queryUnparser_simple(
2274d0cb6e1SMichael Große            $and,
228bbc1da2eSMichael Große            $not,
229bbc1da2eSMichael Große            $phrases,
2304d0cb6e1SMichael Große            $ns,
231bbc1da2eSMichael Große            $notns
2324d0cb6e1SMichael Große        );
233d22b78c8SMichael Große        $hrefAttributes = ['do' => 'search', 'searchPageForm' => '1', 'q' => $newQuery];
234bbc1da2eSMichael Große        if ($after) {
235bbc1da2eSMichael Große            $hrefAttributes['after'] = $after;
236bbc1da2eSMichael Große        }
237bbc1da2eSMichael Große        if ($before) {
238bbc1da2eSMichael Große            $hrefAttributes['before'] = $before;
239bbc1da2eSMichael Große        }
2404d0cb6e1SMichael Große        $searchForm->addTagOpen('a')
2414d0cb6e1SMichael Große            ->attrs([
242d22b78c8SMichael Große                'href' => wl($ID, $hrefAttributes, false, '&')
2434d0cb6e1SMichael Große            ])
2444d0cb6e1SMichael Große        ;
2454d0cb6e1SMichael Große        $searchForm->addHTML($label);
2464d0cb6e1SMichael Große        $searchForm->addTagClose('a');
2474d0cb6e1SMichael Große    }
2484d0cb6e1SMichael Große
249bb8ef867SMichael Große    /**
250bb8ef867SMichael Große     * Add the elements for the namespace selector
251bb8ef867SMichael Große     *
252bb8ef867SMichael Große     * @param Form  $searchForm
253bb8ef867SMichael Große     * @param array $parsedQuery
254bb8ef867SMichael Große     */
255bb8ef867SMichael Große    protected function addNamespaceSelector(Form $searchForm, array $parsedQuery)
256bb8ef867SMichael Große    {
257bb8ef867SMichael Große        $baseNS = empty($parsedQuery['ns']) ? '' : $parsedQuery['ns'][0];
258bb8ef867SMichael Große        $searchForm->addTagOpen('div')->addClass('search-results-form__subwrapper');
2594d0cb6e1SMichael Große
260bbc1da2eSMichael Große        $extraNS = $this->getAdditionalNamespacesFromResults($baseNS);
261bbc1da2eSMichael Große        if (!empty($extraNS) || $baseNS) {
2624d0cb6e1SMichael Große            $searchForm->addTagOpen('div');
263bbc1da2eSMichael Große            $searchForm->addHTML('limit to namespace: ');
2644d0cb6e1SMichael Große
265bbc1da2eSMichael Große            if ($baseNS) {
2664d0cb6e1SMichael Große                $this->addSearchLink(
2674d0cb6e1SMichael Große                    $searchForm,
268bbc1da2eSMichael Große                    '(remove limit)',
269bbc1da2eSMichael Große                    null,
270bbc1da2eSMichael Große                    [],
271bbc1da2eSMichael Große                    null,
2724d0cb6e1SMichael Große                    []
2734d0cb6e1SMichael Große                );
274bb8ef867SMichael Große            }
275bb8ef867SMichael Große
2764d0cb6e1SMichael Große            foreach ($extraNS as $extraNS => $count) {
2774d0cb6e1SMichael Große                $searchForm->addHTML(' ');
278bb8ef867SMichael Große                $label = $extraNS . ($count ? " ($count)" : '');
2794d0cb6e1SMichael Große
280bbc1da2eSMichael Große                $this->addSearchLink($searchForm, $label, null, [$extraNS], null, []);
281bb8ef867SMichael Große            }
2824d0cb6e1SMichael Große            $searchForm->addTagClose('div');
283bb8ef867SMichael Große        }
284bb8ef867SMichael Große
285bb8ef867SMichael Große        $searchForm->addTagClose('div');
286bb8ef867SMichael Große    }
287bb8ef867SMichael Große
288bb8ef867SMichael Große    /**
289bb8ef867SMichael Große     * Parse the full text results for their top namespaces below the given base namespace
290bb8ef867SMichael Große     *
291bb8ef867SMichael Große     * @param string $baseNS the namespace within which was searched, empty string for root namespace
292bb8ef867SMichael Große     *
293bb8ef867SMichael Große     * @return array an associative array with namespace => #number of found pages, sorted descending
294bb8ef867SMichael Große     */
295bb8ef867SMichael Große    protected function getAdditionalNamespacesFromResults($baseNS)
296bb8ef867SMichael Große    {
297bb8ef867SMichael Große        $namespaces = [];
298bb8ef867SMichael Große        $baseNSLength = strlen($baseNS);
299bb8ef867SMichael Große        foreach ($this->fullTextResults as $page => $numberOfHits) {
300bb8ef867SMichael Große            $namespace = getNS($page);
301bb8ef867SMichael Große            if (!$namespace) {
302bb8ef867SMichael Große                continue;
303bb8ef867SMichael Große            }
304bb8ef867SMichael Große            if ($namespace === $baseNS) {
305bb8ef867SMichael Große                continue;
306bb8ef867SMichael Große            }
307bb8ef867SMichael Große            $firstColon = strpos((string)$namespace, ':', $baseNSLength + 1) ?: strlen($namespace);
308bb8ef867SMichael Große            $subtopNS = substr($namespace, 0, $firstColon);
309bb8ef867SMichael Große            if (empty($namespaces[$subtopNS])) {
310bb8ef867SMichael Große                $namespaces[$subtopNS] = 0;
311bb8ef867SMichael Große            }
312bb8ef867SMichael Große            $namespaces[$subtopNS] += 1;
313bb8ef867SMichael Große        }
314bb8ef867SMichael Große        arsort($namespaces);
315bb8ef867SMichael Große        return $namespaces;
316bb8ef867SMichael Große    }
317bb8ef867SMichael Große
318bb8ef867SMichael Große    /**
319bbc1da2eSMichael Große     * @ToDo: we need to remember this date when clicking on other links
320bbc1da2eSMichael Große     * @ToDo: custom date input
321bbc1da2eSMichael Große     *
322bbc1da2eSMichael Große     * @param Form $searchForm
323bbc1da2eSMichael Große     * @param      $parsedQuery
324bbc1da2eSMichael Große     */
325bbc1da2eSMichael Große    protected function addDateSelector(Form $searchForm, $parsedQuery) {
326bbc1da2eSMichael Große        $searchForm->addTagOpen('div')->addClass('search-results-form__subwrapper');
327bbc1da2eSMichael Große        $searchForm->addHTML('limit by date: ');
328bbc1da2eSMichael Große
329bbc1da2eSMichael Große        global $INPUT;
330bbc1da2eSMichael Große        if ($INPUT->has('before') || $INPUT->has('after')) {
331bbc1da2eSMichael Große            $this->addSearchLink(
332bbc1da2eSMichael Große                $searchForm,
333bbc1da2eSMichael Große                '(remove limit)',
334bbc1da2eSMichael Große                null,
335bbc1da2eSMichael Große                null,
336bbc1da2eSMichael Große                null,
337bbc1da2eSMichael Große                null,
338bbc1da2eSMichael Große                null,
339bbc1da2eSMichael Große                false,
340bbc1da2eSMichael Große                false
341bbc1da2eSMichael Große            );
342bbc1da2eSMichael Große
343bbc1da2eSMichael Große            $searchForm->addHTML(', ');
344bbc1da2eSMichael Große        }
345bbc1da2eSMichael Große
346bbc1da2eSMichael Große        if ($INPUT->str('after') === '1 week ago') {
347bbc1da2eSMichael Große            $searchForm->addHTML('<span class="active">past 7 days</span>');
348bbc1da2eSMichael Große        } else {
349bbc1da2eSMichael Große            $this->addSearchLink(
350bbc1da2eSMichael Große                $searchForm,
351bbc1da2eSMichael Große                'past 7 days',
352bbc1da2eSMichael Große                null,
353bbc1da2eSMichael Große                null,
354bbc1da2eSMichael Große                null,
355bbc1da2eSMichael Große                null,
356bbc1da2eSMichael Große                null,
357bbc1da2eSMichael Große                '1 week ago',
358bbc1da2eSMichael Große                false
359bbc1da2eSMichael Große            );
360bbc1da2eSMichael Große        }
361bbc1da2eSMichael Große
362bbc1da2eSMichael Große        $searchForm->addHTML(', ');
363bbc1da2eSMichael Große
364bbc1da2eSMichael Große        if ($INPUT->str('after') === '1 month ago') {
365bbc1da2eSMichael Große            $searchForm->addHTML('<span class="active">past month</span>');
366bbc1da2eSMichael Große        } else {
367bbc1da2eSMichael Große            $this->addSearchLink(
368bbc1da2eSMichael Große                $searchForm,
369bbc1da2eSMichael Große                'past month',
370bbc1da2eSMichael Große                null,
371bbc1da2eSMichael Große                null,
372bbc1da2eSMichael Große                null,
373bbc1da2eSMichael Große                null,
374bbc1da2eSMichael Große                null,
375bbc1da2eSMichael Große                '1 month ago',
376bbc1da2eSMichael Große                false
377bbc1da2eSMichael Große            );
378bbc1da2eSMichael Große        }
379bbc1da2eSMichael Große
380bbc1da2eSMichael Große        $searchForm->addHTML(', ');
381bbc1da2eSMichael Große
382bbc1da2eSMichael Große        if ($INPUT->str('after') === '1 year ago') {
383bbc1da2eSMichael Große            $searchForm->addHTML('<span class="active">past year</span>');
384bbc1da2eSMichael Große        } else {
385bbc1da2eSMichael Große            $this->addSearchLink(
386bbc1da2eSMichael Große                $searchForm,
387bbc1da2eSMichael Große                'past year',
388bbc1da2eSMichael Große                null,
389bbc1da2eSMichael Große                null,
390bbc1da2eSMichael Große                null,
391bbc1da2eSMichael Große                null,
392bbc1da2eSMichael Große                null,
393bbc1da2eSMichael Große                '1 year ago',
394bbc1da2eSMichael Große                false
395bbc1da2eSMichael Große            );
396bbc1da2eSMichael Große        }
397bbc1da2eSMichael Große
398bbc1da2eSMichael Große        $searchForm->addTagClose('div');
399bbc1da2eSMichael Große    }
400bbc1da2eSMichael Große
401bbc1da2eSMichael Große
402bbc1da2eSMichael Große    /**
40321fcef82SMichael Große     * Build the intro text for the search page
40421fcef82SMichael Große     *
40521fcef82SMichael Große     * @param string $query the search query
40621fcef82SMichael Große     *
40721fcef82SMichael Große     * @return string
40821fcef82SMichael Große     */
40921fcef82SMichael Große    protected function getSearchIntroHTML($query)
41021fcef82SMichael Große    {
41121fcef82SMichael Große        global $ID, $lang;
41221fcef82SMichael Große
41321fcef82SMichael Große        $intro = p_locale_xhtml('searchpage');
41421fcef82SMichael Große        // allow use of placeholder in search intro
41521fcef82SMichael Große        $pagecreateinfo = (auth_quickaclcheck($ID) >= AUTH_CREATE) ? $lang['searchcreatepage'] : '';
41621fcef82SMichael Große        $intro = str_replace(
41721fcef82SMichael Große            array('@QUERY@', '@SEARCH@', '@CREATEPAGEINFO@'),
41821fcef82SMichael Große            array(hsc(rawurlencode($query)), hsc($query), $pagecreateinfo),
41921fcef82SMichael Große            $intro
42021fcef82SMichael Große        );
42121fcef82SMichael Große        return $intro;
42221fcef82SMichael Große    }
42321fcef82SMichael Große
42421fcef82SMichael Große    /**
42521fcef82SMichael Große     * Build HTML for a list of pages with matching pagenames
42621fcef82SMichael Große     *
42721fcef82SMichael Große     * @param array $data search results
42821fcef82SMichael Große     *
42921fcef82SMichael Große     * @return string
43021fcef82SMichael Große     */
43121fcef82SMichael Große    protected function getPageLookupHTML($data)
43221fcef82SMichael Große    {
43321fcef82SMichael Große        if (empty($data)) {
43421fcef82SMichael Große            return '';
43521fcef82SMichael Große        }
43621fcef82SMichael Große
43721fcef82SMichael Große        global $lang;
43821fcef82SMichael Große
43921fcef82SMichael Große        $html = '<div class="search_quickresult">';
44021fcef82SMichael Große        $html .= '<h3>' . $lang['quickhits'] . ':</h3>';
44121fcef82SMichael Große        $html .= '<ul class="search_quickhits">';
44221fcef82SMichael Große        foreach ($data as $id => $title) {
4434eab6f7cSMichael Große            $link = html_wikilink(':' . $id);
4444eab6f7cSMichael Große            $eventData = [
4454eab6f7cSMichael Große                'listItemContent' => [$link],
4464eab6f7cSMichael Große                'page' => $id,
4474eab6f7cSMichael Große            ];
4484eab6f7cSMichael Große            trigger_event('SEARCH_RESULT_PAGELOOKUP', $eventData);
4494eab6f7cSMichael Große            $html .= '<li>' . implode('', $eventData['listItemContent']) . '</li>';
45021fcef82SMichael Große        }
45121fcef82SMichael Große        $html .= '</ul> ';
45221fcef82SMichael Große        //clear float (see http://www.complexspiral.com/publications/containing-floats/)
45321fcef82SMichael Große        $html .= '<div class="clearer"></div>';
45421fcef82SMichael Große        $html .= '</div>';
45521fcef82SMichael Große
45621fcef82SMichael Große        return $html;
45721fcef82SMichael Große    }
45821fcef82SMichael Große
45921fcef82SMichael Große    /**
46021fcef82SMichael Große     * Build HTML for fulltext search results or "no results" message
46121fcef82SMichael Große     *
46221fcef82SMichael Große     * @param array $data      the results of the fulltext search
46321fcef82SMichael Große     * @param array $highlight the terms to be highlighted in the results
46421fcef82SMichael Große     *
46521fcef82SMichael Große     * @return string
46621fcef82SMichael Große     */
46721fcef82SMichael Große    protected function getFulltextResultsHTML($data, $highlight)
46821fcef82SMichael Große    {
46921fcef82SMichael Große        global $lang;
47021fcef82SMichael Große
47121fcef82SMichael Große        if (empty($data)) {
47221fcef82SMichael Große            return '<div class="nothing">' . $lang['nothingfound'] . '</div>';
47321fcef82SMichael Große        }
47421fcef82SMichael Große
47521fcef82SMichael Große        $html = '';
47621fcef82SMichael Große        $html .= '<dl class="search_results">';
47721fcef82SMichael Große        $num = 1;
4784c924eb8SMichael Große
47921fcef82SMichael Große        foreach ($data as $id => $cnt) {
4804eab6f7cSMichael Große            $resultLink = html_wikilink(':' . $id, null, $highlight);
4814c924eb8SMichael Große
4824c924eb8SMichael Große            $resultHeader = [$resultLink];
4834c924eb8SMichael Große
4844eab6f7cSMichael Große
4854c924eb8SMichael Große            $restrictQueryToNSLink = $this->restrictQueryToNSLink(getNS($id));
4864c924eb8SMichael Große            if ($restrictQueryToNSLink) {
4874c924eb8SMichael Große                $resultHeader[] = $restrictQueryToNSLink;
4884c924eb8SMichael Große            }
4894c924eb8SMichael Große
4909a75abfbSMichael Große            $snippet = '';
4919a75abfbSMichael Große            $lastMod = '';
4929a75abfbSMichael Große            $mtime = filemtime(wikiFN($id));
4939a75abfbSMichael Große            if ($cnt !== 0) {
4949a75abfbSMichael Große                $resultHeader[] = $cnt . ' ' . $lang['hits'];
4959a75abfbSMichael Große                if ($num < FT_SNIPPET_NUMBER) { // create snippets for the first number of matches only
4969a75abfbSMichael Große                    $snippet = '<dd>' . ft_snippet($id, $highlight) . '</dd>';
4979a75abfbSMichael Große                    $lastMod = '<span class="search_results__lastmod">'. $lang['lastmod'] . ' ';
4989a75abfbSMichael Große                    $lastMod .= '<time datetime="' . date_iso8601($mtime) . '">'. dformat($mtime) . '</time>';
4999a75abfbSMichael Große                    $lastMod .= '</span>';
5009a75abfbSMichael Große                }
5019a75abfbSMichael Große                $num++;
5029a75abfbSMichael Große            }
5039a75abfbSMichael Große
5049a75abfbSMichael Große            $metaLine = '<div class="search_results__metaLine">';
5059a75abfbSMichael Große            $metaLine .= $lastMod;
5069a75abfbSMichael Große            $metaLine .= '</div>';
5079a75abfbSMichael Große
5089a75abfbSMichael Große
5094eab6f7cSMichael Große            $eventData = [
5104c924eb8SMichael Große                'resultHeader' => $resultHeader,
5119a75abfbSMichael Große                'resultBody' => [$metaLine, $snippet],
5124eab6f7cSMichael Große                'page' => $id,
5134eab6f7cSMichael Große            ];
5144eab6f7cSMichael Große            trigger_event('SEARCH_RESULT_FULLPAGE', $eventData);
5154eab6f7cSMichael Große            $html .= '<div class="search_fullpage_result">';
5164eab6f7cSMichael Große            $html .= '<dt>' . implode(' ', $eventData['resultHeader']) . '</dt>';
5174eab6f7cSMichael Große            $html .= implode('', $eventData['resultBody']);
5184eab6f7cSMichael Große            $html .= '</div>';
51921fcef82SMichael Große        }
52021fcef82SMichael Große        $html .= '</dl>';
52121fcef82SMichael Große
52221fcef82SMichael Große        return $html;
52321fcef82SMichael Große    }
5244c924eb8SMichael Große
5254c924eb8SMichael Große    /**
5264c924eb8SMichael Große     * create a link to restrict the current query to a namespace
5274c924eb8SMichael Große     *
5284c924eb8SMichael Große     * @param bool|string $ns the namespace to which to restrict the query
5294c924eb8SMichael Große     *
5304c924eb8SMichael Große     * @return bool|string
5314c924eb8SMichael Große     */
5324c924eb8SMichael Große    protected function restrictQueryToNSLink($ns)
5334c924eb8SMichael Große    {
5344c924eb8SMichael Große        if (!$ns) {
5354c924eb8SMichael Große            return false;
5364c924eb8SMichael Große        }
5374c924eb8SMichael Große        if (!$this->isSearchAssistanceAvailable($this->parsedQuery)) {
5384c924eb8SMichael Große            return false;
5394c924eb8SMichael Große        }
5404c924eb8SMichael Große        if (!empty($this->parsedQuery['ns']) && $this->parsedQuery['ns'][0] === $ns) {
5414c924eb8SMichael Große            return false;
5424c924eb8SMichael Große        }
5434c924eb8SMichael Große        $name = '@' . $ns;
544bbc1da2eSMichael Große        $tmpForm = new Form();
545bbc1da2eSMichael Große        $this->addSearchLink($tmpForm, $name, null, [$ns], null, []);
546bbc1da2eSMichael Große        return $tmpForm->toHTML();
5474c924eb8SMichael Große    }
54821fcef82SMichael Große}
549