1<?php 2 3use dokuwiki\Extension\Plugin; 4 5/** 6 * DokuWiki Plugin searchns (Helper Component) 7 * 8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 9 * @author Anna Dabrowska <dokuwiki@cosmocode.de> 10 */ 11class helper_plugin_searchns extends Plugin 12{ 13 public const SEARCHNS_HEADING = 'SEARCHNS_HEADING'; 14 15 /** 16 * @var array 17 */ 18 protected $ns; 19 20 /** 21 * Constructor 22 */ 23 public function __construct() 24 { 25 $this->ns = $this->getNsFromConfig(); 26 } 27 28 /** 29 * Converts config string to array 30 * 31 * @return array 32 */ 33 public function getNsFromConfig() 34 { 35 if (!is_null($this->ns)) { 36 return $this->ns; 37 } 38 39 $ns = []; 40 $config = $this->getConf('namespaces'); 41 42 if (empty($config)) return $ns; 43 44 $lines = array_filter(explode("\n", $config)); 45 foreach ($lines as $line) { 46 $n = sexplode(' ', $line, 2); 47 if (strpos($n[0], ':', -1) === false) { 48 msg('Search namespaces are not configured correctly!', -1); 49 return $ns; 50 } 51 $ns[trim($n[1])] = rtrim(trim($n[0], ':')); 52 } 53 54 if ($this->getConf('first all')) { 55 $ns = array_merge([$this->getLang('all label') => ''], $ns); 56 } else { 57 $ns[$this->getLang('all label')] = ''; 58 } 59 60 return $ns; 61 } 62 63 /** 64 * array_filter callback to remove namespaces that the current user can't read 65 * 66 * @param string $ns 67 * @return bool 68 */ 69 public function filterByACL($ns) 70 { 71 if ($ns === '') return true; // all is always allowed 72 return auth_quickaclcheck($ns . ':*') >= AUTH_READ; 73 } 74 75 /** 76 * Returns HTML list of search results. 77 * Based on core quicksearch 78 * @see \dokuwiki\Ajax::callQsearch() 79 * 80 * @return string 81 */ 82 public function qSearch() 83 { 84 global $INPUT; 85 86 $maxnumbersuggestions = 50; 87 88 // search parameters as posted via AJAX 89 $query = $INPUT->str('q'); 90 $ns = $INPUT->str('ns'); 91 if (empty($query)) return ''; 92 93 $query = urldecode($query) . ($ns ? " @$ns" : ''); 94 $data = ft_pageLookup($query, true, useHeading('navigation')); 95 96 if ($data === []) return ''; 97 98 $ret = '<ul>'; 99 100 $counter = 0; 101 foreach ($data as $id => $title) { 102 if (useHeading('navigation')) { 103 $name = $title; 104 } elseif (!$ns) { 105 $linkNs = getNS($id); 106 if ($linkNs) { 107 $name = noNS($id) . ' (' . $linkNs . ')'; 108 } else { 109 $name = $id; 110 } 111 } else { 112 $name = $id; 113 } 114 $ret .= '<li>'; 115 $ret .= $title === self::SEARCHNS_HEADING ? $id : html_wikilink(':' . $id, $name); 116 $ret .= '</li>'; 117 118 $counter++; 119 if ($counter > $maxnumbersuggestions) { 120 $ret .= '<li>...</li>'; 121 break; 122 } 123 } 124 125 $ret .= '</ul>'; 126 return $ret; 127 } 128 129 /** 130 * Group results based on configured namespaces. 131 * Insert headings as pseudo results. 132 * 133 * @param $results 134 * @return array 135 */ 136 public function sortAndGroup($results) 137 { 138 $namespaces = array_filter($this->getNsFromConfig()); 139 $original = $namespaces; 140 141 // make sure more specific namespaces are sorted first 142 uasort($namespaces, 'ft_pagesorter'); 143 $namespaces = array_reverse($namespaces); 144 145 $res = []; 146 foreach ($namespaces as $label => $ns) { 147 $res[$ns] = array_filter( 148 $results, 149 static fn($page) => strpos($page, $ns . ':') === 0, 150 ARRAY_FILTER_USE_KEY 151 ); 152 153 if (!empty($res[$ns])) { 154 // prepend namespace label 155 $res[$ns] = array_merge([$label => self::SEARCHNS_HEADING], $res[$ns]); 156 // remove matches from result set 157 $results = array_diff_key($results, $res[$ns]); 158 } 159 } 160 161 // reorder namespace as in the original configuration 162 $all = []; 163 foreach ($original as $ns) { 164 if (!empty($res[$ns])) { 165 $all = array_merge($all, $res[$ns]); 166 } 167 } 168 169 // add the remainder as "other" 170 $rest = array_diff_key($results, $all); 171 if ($rest !== []) { 172 $all[$this->getLang('other label')] = self::SEARCHNS_HEADING; 173 $all = array_merge($all, $rest); 174 } 175 176 return $all; 177 } 178} 179