xref: /dokuwiki/inc/Search/MetadataSearch.php (revision 0b1bbbbb7d4e3c531cd255dbf878ce27d5967a0c)
1fe2d1da1SSatoshi Sahara<?php
246b83514SSatoshi Sahara
3fe2d1da1SSatoshi Saharanamespace dokuwiki\Search;
4fe2d1da1SSatoshi Sahara
5fe2d1da1SSatoshi Saharause dokuwiki\Extension\Event;
686fc7283SSatoshi Saharause dokuwiki\Search\MetadataIndex;
7*0b1bbbbbSAndreas Gohruse dokuwiki\Search\Query\QueryParser;
8a02395a1SSatoshi Saharause dokuwiki\Utf8;
9fe2d1da1SSatoshi Sahara
10fe2d1da1SSatoshi Sahara/**
11fe2d1da1SSatoshi Sahara * Class DokuWiki Metadata Search
12fe2d1da1SSatoshi Sahara *
13fe2d1da1SSatoshi Sahara * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
14fe2d1da1SSatoshi Sahara * @author     Andreas Gohr <andi@splitbrain.org>
15fe2d1da1SSatoshi Sahara */
16fe2d1da1SSatoshi Saharaclass MetadataSearch
17fe2d1da1SSatoshi Sahara{
18fe2d1da1SSatoshi Sahara    /**
19fe2d1da1SSatoshi Sahara     * Quicksearch for pagenames
20fe2d1da1SSatoshi Sahara     *
21fe2d1da1SSatoshi Sahara     * By default it only matches the pagename and ignores the namespace.
22fe2d1da1SSatoshi Sahara     * This can be changed with the second parameter.
23fe2d1da1SSatoshi Sahara     * The third parameter allows to search in titles as well.
24fe2d1da1SSatoshi Sahara     *
25fe2d1da1SSatoshi Sahara     * The function always returns titles as well
26fe2d1da1SSatoshi Sahara     *
27fe2d1da1SSatoshi Sahara     * @triggers SEARCH_QUERY_PAGELOOKUP
28fe2d1da1SSatoshi Sahara     * @author   Andreas Gohr <andi@splitbrain.org>
29fe2d1da1SSatoshi Sahara     * @author   Adrian Lang <lang@cosmocode.de>
30fe2d1da1SSatoshi Sahara     *
31fe2d1da1SSatoshi Sahara     * @param string     $id       page id
32fe2d1da1SSatoshi Sahara     * @param bool       $in_ns    match against namespace as well?
33fe2d1da1SSatoshi Sahara     * @param bool       $in_title search in title?
34fe2d1da1SSatoshi Sahara     * @param int|string $after    only show results with mtime after this date,
35fe2d1da1SSatoshi Sahara     *                             accepts timestap or strtotime arguments
36fe2d1da1SSatoshi Sahara     * @param int|string $before   only show results with mtime before this date,
37fe2d1da1SSatoshi Sahara     *                             accepts timestap or strtotime arguments
38fe2d1da1SSatoshi Sahara     *
39fe2d1da1SSatoshi Sahara     * @return string[]
40fe2d1da1SSatoshi Sahara     */
419329b002SSatoshi Sahara    public function pageLookup($id, $in_ns = false, $in_title = false, $after = null, $before = null)
42fe2d1da1SSatoshi Sahara    {
43fe2d1da1SSatoshi Sahara        $data = [
44fe2d1da1SSatoshi Sahara            'id' => $id,
45fe2d1da1SSatoshi Sahara            'in_ns' => $in_ns,
46fe2d1da1SSatoshi Sahara            'in_title' => $in_title,
47fe2d1da1SSatoshi Sahara            'after' => $after,
48fe2d1da1SSatoshi Sahara            'before' => $before
49fe2d1da1SSatoshi Sahara        ];
50fe2d1da1SSatoshi Sahara        $data['has_titles'] = true; // for plugin backward compatibility check
519329b002SSatoshi Sahara        $action = [$this, 'pageLookupCallBack'];
52fe2d1da1SSatoshi Sahara        return Event::createAndTrigger('SEARCH_QUERY_PAGELOOKUP', $data, $action);
53fe2d1da1SSatoshi Sahara    }
54fe2d1da1SSatoshi Sahara
55fe2d1da1SSatoshi Sahara    /**
56fe2d1da1SSatoshi Sahara     * Returns list of pages as array(pageid => First Heading)
57fe2d1da1SSatoshi Sahara     *
58fe2d1da1SSatoshi Sahara     * @param array $data  event data
59fe2d1da1SSatoshi Sahara     * @return string[]
60fe2d1da1SSatoshi Sahara     */
619329b002SSatoshi Sahara    public function pageLookupCallBack(&$data)
62fe2d1da1SSatoshi Sahara    {
63fe2d1da1SSatoshi Sahara        // split out original parameters
64fe2d1da1SSatoshi Sahara        $id = $data['id'];
659329b002SSatoshi Sahara        $parsedQuery = (new QueryParser)->convert($id);
66fe2d1da1SSatoshi Sahara
67fe2d1da1SSatoshi Sahara        if (count($parsedQuery['ns']) > 0) {
68fe2d1da1SSatoshi Sahara            $ns = cleanID($parsedQuery['ns'][0]) . ':';
69fe2d1da1SSatoshi Sahara            $id = implode(' ', $parsedQuery['highlight']);
70fe2d1da1SSatoshi Sahara        }
71fab81cc8SSatoshi Sahara        if (count($parsedQuery['notns']) > 0) {
72fab81cc8SSatoshi Sahara            $notns = cleanID($parsedQuery['notns'][0]) . ':';
73fab81cc8SSatoshi Sahara            $id = implode(' ', $parsedQuery['highlight']);
74fab81cc8SSatoshi Sahara        }
75fe2d1da1SSatoshi Sahara
76fe2d1da1SSatoshi Sahara        $in_ns    = $data['in_ns'];
77fe2d1da1SSatoshi Sahara        $in_title = $data['in_title'];
78fe2d1da1SSatoshi Sahara        $cleaned = cleanID($id);
79fe2d1da1SSatoshi Sahara
80a32da6ddSSatoshi Sahara        $MetadataIndex = new MetadataIndex();
8102361d2aSSatoshi Sahara        $page_idx = $MetadataIndex->getPages();
82fab81cc8SSatoshi Sahara
83fab81cc8SSatoshi Sahara        $pages = array();
84fab81cc8SSatoshi Sahara        if ($id !== '' && $cleaned !== '') {
85fe2d1da1SSatoshi Sahara            foreach ($page_idx as $p_id) {
86fe2d1da1SSatoshi Sahara                if ((strpos($in_ns ? $p_id : noNSorNS($p_id), $cleaned) !== false)) {
87fe2d1da1SSatoshi Sahara                    if (!isset($pages[$p_id])) {
88fe2d1da1SSatoshi Sahara                        $pages[$p_id] = p_get_first_heading($p_id, METADATA_DONT_RENDER);
89fe2d1da1SSatoshi Sahara                    }
90fe2d1da1SSatoshi Sahara                }
91fe2d1da1SSatoshi Sahara            }
92fe2d1da1SSatoshi Sahara            if ($in_title) {
939329b002SSatoshi Sahara                $func = [$this, 'pageLookupTitleCompare'];
94be5c1ea2SSatoshi Sahara                foreach ($MetadataIndex->lookupKey('title', $id, $func) as $p_id) {
95fe2d1da1SSatoshi Sahara                    if (!isset($pages[$p_id])) {
96fe2d1da1SSatoshi Sahara                        $pages[$p_id] = p_get_first_heading($p_id, METADATA_DONT_RENDER);
97fe2d1da1SSatoshi Sahara                    }
98fe2d1da1SSatoshi Sahara                }
99fe2d1da1SSatoshi Sahara            }
100fe2d1da1SSatoshi Sahara        }
101fe2d1da1SSatoshi Sahara
102fe2d1da1SSatoshi Sahara        if (isset($ns)) {
103fe2d1da1SSatoshi Sahara            foreach (array_keys($pages) as $p_id) {
104fe2d1da1SSatoshi Sahara                if (strpos($p_id, $ns) !== 0) {
105fe2d1da1SSatoshi Sahara                    unset($pages[$p_id]);
106fe2d1da1SSatoshi Sahara                }
107fe2d1da1SSatoshi Sahara            }
108fe2d1da1SSatoshi Sahara        }
109fab81cc8SSatoshi Sahara        if (isset($notns)) {
110fab81cc8SSatoshi Sahara            foreach (array_keys($pages) as $p_id) {
111fab81cc8SSatoshi Sahara                if (strpos($p_id, $notns) === 0) {
112fab81cc8SSatoshi Sahara                    unset($pages[$p_id]);
113fab81cc8SSatoshi Sahara                }
114fab81cc8SSatoshi Sahara            }
115fab81cc8SSatoshi Sahara        }
116fe2d1da1SSatoshi Sahara
117fe2d1da1SSatoshi Sahara        // discard hidden pages
118fe2d1da1SSatoshi Sahara        // discard nonexistent pages
119fe2d1da1SSatoshi Sahara        // check ACL permissions
120fe2d1da1SSatoshi Sahara        foreach (array_keys($pages) as $idx) {
121fe2d1da1SSatoshi Sahara            if (!isVisiblePage($idx) || !page_exists($idx) || auth_quickaclcheck($idx) < AUTH_READ) {
122fe2d1da1SSatoshi Sahara                unset($pages[$idx]);
123fe2d1da1SSatoshi Sahara            }
124fe2d1da1SSatoshi Sahara        }
125fe2d1da1SSatoshi Sahara
1269329b002SSatoshi Sahara        $pages = $this->filterResultsByTime($pages, $data['after'], $data['before']);
127fe2d1da1SSatoshi Sahara
1289329b002SSatoshi Sahara        uksort($pages, [$this, 'pagesorter']);
129fe2d1da1SSatoshi Sahara        return $pages;
130fe2d1da1SSatoshi Sahara    }
131fe2d1da1SSatoshi Sahara
132fe2d1da1SSatoshi Sahara    /**
133fe2d1da1SSatoshi Sahara     * Tiny helper function for comparing the searched title with the title
134fe2d1da1SSatoshi Sahara     * from the search index. This function is a wrapper around stripos with
135fe2d1da1SSatoshi Sahara     * adapted argument order and return value.
136fe2d1da1SSatoshi Sahara     *
137fe2d1da1SSatoshi Sahara     * @param string $search searched title
138fe2d1da1SSatoshi Sahara     * @param string $title  title from index
139fe2d1da1SSatoshi Sahara     * @return bool
140fe2d1da1SSatoshi Sahara     */
1419329b002SSatoshi Sahara    protected function pageLookupTitleCompare($search, $title)
142fe2d1da1SSatoshi Sahara    {
143fe2d1da1SSatoshi Sahara        return stripos($title, $search) !== false;
144fe2d1da1SSatoshi Sahara    }
145fe2d1da1SSatoshi Sahara
146fe2d1da1SSatoshi Sahara    /**
147fe2d1da1SSatoshi Sahara     * Sort pages based on their namespace level first, then on their string
148fe2d1da1SSatoshi Sahara     * values. This makes higher hierarchy pages rank higher than lower hierarchy
149fe2d1da1SSatoshi Sahara     * pages.
150fe2d1da1SSatoshi Sahara     *
151fe2d1da1SSatoshi Sahara     * @param string $a
152fe2d1da1SSatoshi Sahara     * @param string $b
153fe2d1da1SSatoshi Sahara     * @return int Returns < 0 if $a is less than $b; > 0 if $a is greater than $b,
154fe2d1da1SSatoshi Sahara     *             and 0 if they are equal.
155fe2d1da1SSatoshi Sahara     */
1569329b002SSatoshi Sahara    protected function pagesorter($a, $b)
157fe2d1da1SSatoshi Sahara    {
158fe2d1da1SSatoshi Sahara        $ac = count(explode(':',$a));
159fe2d1da1SSatoshi Sahara        $bc = count(explode(':',$b));
160fe2d1da1SSatoshi Sahara        if ($ac < $bc) {
161fe2d1da1SSatoshi Sahara            return -1;
162fe2d1da1SSatoshi Sahara        } elseif ($ac > $bc) {
163fe2d1da1SSatoshi Sahara            return 1;
164fe2d1da1SSatoshi Sahara        }
165a02395a1SSatoshi Sahara        return Utf8\Sort::strcmp ($a,$b);
166fe2d1da1SSatoshi Sahara    }
167fe2d1da1SSatoshi Sahara
168fe2d1da1SSatoshi Sahara    /**
169fe2d1da1SSatoshi Sahara     * @param array      $results search results in the form pageid => value
170fe2d1da1SSatoshi Sahara     * @param int|string $after   only returns results with mtime after this date,
171fe2d1da1SSatoshi Sahara     *                            accepts timestap or strtotime arguments
172fe2d1da1SSatoshi Sahara     * @param int|string $before  only returns results with mtime after this date,
173fe2d1da1SSatoshi Sahara     *                            accepts timestap or strtotime arguments
174fe2d1da1SSatoshi Sahara     *
175fe2d1da1SSatoshi Sahara     * @return array
176fe2d1da1SSatoshi Sahara     */
1779329b002SSatoshi Sahara    protected function filterResultsByTime(array $results, $after, $before)
178fe2d1da1SSatoshi Sahara    {
179fe2d1da1SSatoshi Sahara        if ($after || $before) {
180fe2d1da1SSatoshi Sahara            $after = is_int($after) ? $after : strtotime($after);
181fe2d1da1SSatoshi Sahara            $before = is_int($before) ? $before : strtotime($before);
182fe2d1da1SSatoshi Sahara
183fe2d1da1SSatoshi Sahara            foreach ($results as $id => $value) {
184fe2d1da1SSatoshi Sahara                $mTime = filemtime(wikiFN($id));
185fe2d1da1SSatoshi Sahara                if ($after && $after > $mTime) {
186fe2d1da1SSatoshi Sahara                    unset($results[$id]);
187fe2d1da1SSatoshi Sahara                    continue;
188fe2d1da1SSatoshi Sahara                }
189fe2d1da1SSatoshi Sahara                if ($before && $before < $mTime) {
190fe2d1da1SSatoshi Sahara                    unset($results[$id]);
191fe2d1da1SSatoshi Sahara                }
192fe2d1da1SSatoshi Sahara            }
193fe2d1da1SSatoshi Sahara        }
194fe2d1da1SSatoshi Sahara        return $results;
195fe2d1da1SSatoshi Sahara    }
196fe2d1da1SSatoshi Sahara}
197