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