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