Failed Conditions
Pull Request — master (#2943)
by Andreas
03:32
created

MetadataSearch   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 190
rs 9.52
c 0
b 0
f 0
wmc 36
lcom 1
cbo 3

7 Methods

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