1<?php 2/** 3 * Utilities for collecting data from config files 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Harry Fuecks <hfuecks@gmail.com> 7 */ 8 9/* 10 * line prefix used to negate single value config items 11 * (scheme.conf & stopwords.conf), e.g. 12 * !gopher 13 */ 14 15use dokuwiki\Extension\AuthPlugin; 16use dokuwiki\Extension\Event; 17const DOKU_CONF_NEGATION = '!'; 18 19/** 20 * Returns the (known) extension and mimetype of a given filename 21 * 22 * If $knownonly is true (the default), then only known extensions 23 * are returned. 24 * 25 * @author Andreas Gohr <andi@splitbrain.org> 26 * 27 * @param string $file file name 28 * @param bool $knownonly 29 * @return array with extension, mimetype and if it should be downloaded 30 */ 31function mimetype($file, $knownonly = true) 32{ 33 $mtypes = getMimeTypes(); // known mimetypes 34 $ext = strrpos($file, '.'); 35 if ($ext === false) { 36 return [false, false, false]; 37 } 38 $ext = strtolower(substr($file, $ext + 1)); 39 if (!isset($mtypes[$ext])) { 40 if ($knownonly) { 41 return [false, false, false]; 42 } else { 43 return [$ext, 'application/octet-stream', true]; 44 } 45 } 46 if ($mtypes[$ext][0] == '!') { 47 return [$ext, substr($mtypes[$ext], 1), true]; 48 } else { 49 return [$ext, $mtypes[$ext], false]; 50 } 51} 52 53/** 54 * returns a hash of mimetypes 55 * 56 * @author Andreas Gohr <andi@splitbrain.org> 57 */ 58function getMimeTypes() 59{ 60 static $mime = null; 61 if ( !$mime ) { 62 $mime = retrieveConfig('mime', 'confToHash'); 63 $mime = array_filter($mime); 64 } 65 return $mime; 66} 67 68/** 69 * returns a hash of acronyms 70 * 71 * @author Harry Fuecks <hfuecks@gmail.com> 72 */ 73function getAcronyms() 74{ 75 static $acronyms = null; 76 if ( !$acronyms ) { 77 $acronyms = retrieveConfig('acronyms', 'confToHash'); 78 $acronyms = array_filter($acronyms, 'strlen'); 79 } 80 return $acronyms; 81} 82 83/** 84 * returns a hash of smileys 85 * 86 * @author Harry Fuecks <hfuecks@gmail.com> 87 */ 88function getSmileys() 89{ 90 static $smileys = null; 91 if ( !$smileys ) { 92 $smileys = retrieveConfig('smileys', 'confToHash'); 93 $smileys = array_filter($smileys, 'strlen'); 94 } 95 return $smileys; 96} 97 98/** 99 * returns a hash of entities 100 * 101 * @author Harry Fuecks <hfuecks@gmail.com> 102 */ 103function getEntities() 104{ 105 static $entities = null; 106 if ( !$entities ) { 107 $entities = retrieveConfig('entities', 'confToHash'); 108 $entities = array_filter($entities, 'strlen'); 109 } 110 return $entities; 111} 112 113/** 114 * returns a hash of interwikilinks 115 * 116 * @author Harry Fuecks <hfuecks@gmail.com> 117 */ 118function getInterwiki() 119{ 120 static $wikis = null; 121 if ( !$wikis ) { 122 $wikis = retrieveConfig('interwiki', 'confToHash', [true]); 123 $wikis = array_filter($wikis, 'strlen'); 124 125 //add sepecial case 'this' 126 $wikis['this'] = DOKU_URL.'{NAME}'; 127 } 128 return $wikis; 129} 130 131/** 132 * Returns the jquery script URLs for the versions defined in lib/scripts/jquery/versions 133 * 134 * @trigger CONFUTIL_CDN_SELECT 135 * @return array 136 */ 137function getCdnUrls() 138{ 139 global $conf; 140 141 // load version info 142 $versions = []; 143 $lines = file(DOKU_INC . 'lib/scripts/jquery/versions'); 144 foreach ($lines as $line) { 145 $line = trim(preg_replace('/#.*$/', '', $line)); 146 if ($line === '') continue; 147 [$key, $val] = sexplode('=', $line, 2, ''); 148 $key = trim($key); 149 $val = trim($val); 150 $versions[$key] = $val; 151 } 152 153 $src = []; 154 $data = ['versions' => $versions, 'src' => &$src]; 155 $event = new Event('CONFUTIL_CDN_SELECT', $data); 156 if ($event->advise_before()) { 157 if (!$conf['jquerycdn']) { 158 $jqmod = md5(implode('-', $versions)); 159 $src[] = DOKU_BASE . 'lib/exe/jquery.php' . '?tseed=' . $jqmod; 160 } elseif ($conf['jquerycdn'] == 'jquery') { 161 $src[] = sprintf('https://code.jquery.com/jquery-%s.min.js', $versions['JQ_VERSION']); 162 $src[] = sprintf('https://code.jquery.com/ui/%s/jquery-ui.min.js', $versions['JQUI_VERSION']); 163 } elseif ($conf['jquerycdn'] == 'cdnjs') { 164 $src[] = sprintf( 165 'https://cdnjs.cloudflare.com/ajax/libs/jquery/%s/jquery.min.js', 166 $versions['JQ_VERSION'] 167 ); 168 $src[] = sprintf( 169 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/%s/jquery-ui.min.js', 170 $versions['JQUI_VERSION'] 171 ); 172 } 173 } 174 $event->advise_after(); 175 176 return $src; 177} 178 179/** 180 * returns array of wordblock patterns 181 * 182 */ 183function getWordblocks() 184{ 185 static $wordblocks = null; 186 if ( !$wordblocks ) { 187 $wordblocks = retrieveConfig('wordblock', 'file', null, 'array_merge_with_removal'); 188 } 189 return $wordblocks; 190} 191 192/** 193 * Gets the list of configured schemes 194 * 195 * @return array the schemes 196 */ 197function getSchemes() 198{ 199 static $schemes = null; 200 if ( !$schemes ) { 201 $schemes = retrieveConfig('scheme', 'file', null, 'array_merge_with_removal'); 202 $schemes = array_map('trim', $schemes); 203 $schemes = preg_replace('/^#.*/', '', $schemes); 204 $schemes = array_filter($schemes); 205 } 206 return $schemes; 207} 208 209/** 210 * Builds a hash from an array of lines 211 * 212 * If $lower is set to true all hash keys are converted to 213 * lower case. 214 * 215 * @author Harry Fuecks <hfuecks@gmail.com> 216 * @author Andreas Gohr <andi@splitbrain.org> 217 * @author Gina Haeussge <gina@foosel.net> 218 * 219 * @param array $lines 220 * @param bool $lower 221 * 222 * @return array 223 */ 224function linesToHash($lines, $lower = false) 225{ 226 $conf = []; 227 // remove BOM 228 if (isset($lines[0]) && substr($lines[0], 0, 3) === pack('CCC', 0xef, 0xbb, 0xbf)) 229 $lines[0] = substr($lines[0], 3); 230 foreach ($lines as $line) { 231 //ignore comments (except escaped ones) 232 $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line); 233 $line = str_replace('\\#', '#', $line); 234 $line = trim($line); 235 if ($line === '') continue; 236 $line = preg_split('/\s+/', $line, 2); 237 $line = array_pad($line, 2, ''); 238 // Build the associative array 239 if ($lower) { 240 $conf[strtolower($line[0])] = $line[1]; 241 } else { 242 $conf[$line[0]] = $line[1]; 243 } 244 } 245 246 return $conf; 247} 248 249/** 250 * Builds a hash from a configfile 251 * 252 * If $lower is set to true all hash keys are converted to 253 * lower case. 254 * 255 * @author Harry Fuecks <hfuecks@gmail.com> 256 * @author Andreas Gohr <andi@splitbrain.org> 257 * @author Gina Haeussge <gina@foosel.net> 258 * 259 * @param string $file 260 * @param bool $lower 261 * 262 * @return array 263 */ 264function confToHash($file, $lower = false) 265{ 266 $conf = []; 267 $lines = @file($file); 268 if ( !$lines ) return $conf; 269 270 return linesToHash($lines, $lower); 271} 272 273/** 274 * Read a json config file into an array 275 * 276 * @param string $file 277 * @return array 278 */ 279function jsonToArray($file) 280{ 281 $json = file_get_contents($file); 282 283 $conf = json_decode($json, true, 512, JSON_THROW_ON_ERROR); 284 285 if ($conf === null) { 286 return []; 287 } 288 289 return $conf; 290} 291 292/** 293 * Retrieve the requested configuration information 294 * 295 * @author Chris Smith <chris@jalakai.co.uk> 296 * 297 * @param string $type the configuration settings to be read, must correspond to a key/array in $config_cascade 298 * @param callback $fn the function used to process the configuration file into an array 299 * @param array $params optional additional params to pass to the callback 300 * @param callback $combine the function used to combine arrays of values read from different configuration files; 301 * the function takes two parameters, 302 * $combined - the already read & merged configuration values 303 * $new - array of config values from the config cascade file being currently processed 304 * and returns an array of the merged configuration values. 305 * @return array configuration values 306 */ 307function retrieveConfig($type, $fn, $params = null, $combine = 'array_merge') 308{ 309 global $config_cascade; 310 311 if (!is_array($params)) $params = []; 312 313 $combined = []; 314 if (!is_array($config_cascade[$type])) trigger_error('Missing config cascade for "'.$type.'"', E_USER_WARNING); 315 foreach (['default', 'local', 'protected'] as $config_group) { 316 if (empty($config_cascade[$type][$config_group])) continue; 317 foreach ($config_cascade[$type][$config_group] as $file) { 318 if (file_exists($file)) { 319 $config = call_user_func_array($fn, array_merge([$file], $params)); 320 $combined = $combine($combined, $config); 321 } 322 } 323 } 324 325 return $combined; 326} 327 328/** 329 * Include the requested configuration information 330 * 331 * @author Chris Smith <chris@jalakai.co.uk> 332 * 333 * @param string $type the configuration settings to be read, must correspond to a key/array in $config_cascade 334 * @return array list of files, default before local before protected 335 */ 336function getConfigFiles($type) 337{ 338 global $config_cascade; 339 $files = []; 340 341 if (!is_array($config_cascade[$type])) trigger_error('Missing config cascade for "'.$type.'"', E_USER_WARNING); 342 foreach (['default', 'local', 'protected'] as $config_group) { 343 if (empty($config_cascade[$type][$config_group])) continue; 344 $files = array_merge($files, $config_cascade[$type][$config_group]); 345 } 346 347 return $files; 348} 349 350/** 351 * check if the given action was disabled in config 352 * 353 * @author Andreas Gohr <andi@splitbrain.org> 354 * @param string $action 355 * @returns boolean true if enabled, false if disabled 356 */ 357function actionOK($action) 358{ 359 static $disabled = null; 360 if (is_null($disabled) || defined('SIMPLE_TEST')) { 361 global $conf; 362 /** @var AuthPlugin $auth */ 363 global $auth; 364 365 // prepare disabled actions array and handle legacy options 366 $disabled = explode(',', $conf['disableactions']); 367 $disabled = array_map('trim', $disabled); 368 if ((isset($conf['openregister']) && !$conf['openregister']) || is_null($auth) || !$auth->canDo('addUser')) { 369 $disabled[] = 'register'; 370 } 371 if ((isset($conf['resendpasswd']) && !$conf['resendpasswd']) || is_null($auth) || !$auth->canDo('modPass')) { 372 $disabled[] = 'resendpwd'; 373 } 374 if ((isset($conf['subscribers']) && !$conf['subscribers']) || is_null($auth)) { 375 $disabled[] = 'subscribe'; 376 } 377 if (is_null($auth) || !$auth->canDo('Profile')) { 378 $disabled[] = 'profile'; 379 } 380 if (is_null($auth) || !$auth->canDo('delUser')) { 381 $disabled[] = 'profile_delete'; 382 } 383 if (is_null($auth)) { 384 $disabled[] = 'login'; 385 } 386 if (is_null($auth) || !$auth->canDo('logout')) { 387 $disabled[] = 'logout'; 388 } 389 $disabled = array_unique($disabled); 390 } 391 392 return !in_array($action, $disabled); 393} 394 395/** 396 * check if headings should be used as link text for the specified link type 397 * 398 * @author Chris Smith <chris@jalakai.co.uk> 399 * 400 * @param string $linktype 'content'|'navigation', content applies to links in wiki text 401 * navigation applies to all other links 402 * @return boolean true if headings should be used for $linktype, false otherwise 403 */ 404function useHeading($linktype) 405{ 406 static $useHeading = null; 407 if (defined('DOKU_UNITTEST')) $useHeading = null; // don't cache during unit tests 408 409 if (is_null($useHeading)) { 410 global $conf; 411 412 if (!empty($conf['useheading'])) { 413 switch ($conf['useheading']) { 414 case 'content': 415 $useHeading['content'] = true; 416 break; 417 418 case 'navigation': 419 $useHeading['navigation'] = true; 420 break; 421 default: 422 $useHeading['content'] = true; 423 $useHeading['navigation'] = true; 424 } 425 } else { 426 $useHeading = []; 427 } 428 } 429 430 return (!empty($useHeading[$linktype])); 431} 432 433/** 434 * obscure config data so information isn't plain text 435 * 436 * @param string $str data to be encoded 437 * @param string $code encoding method, values: plain, base64, uuencode. 438 * @return string the encoded value 439 */ 440function conf_encodeString($str, $code) 441{ 442 switch ($code) { 443 case 'base64' : return '<b>'.base64_encode($str); 444 case 'uuencode' : return '<u>'.convert_uuencode($str); 445 case 'plain': 446 default: 447 return $str; 448 } 449} 450/** 451 * return obscured data as plain text 452 * 453 * @param string $str encoded data 454 * @return string plain text 455 */ 456function conf_decodeString($str) 457{ 458 switch (substr($str, 0, 3)) { 459 case '<b>' : return base64_decode(substr($str, 3)); 460 case '<u>' : return convert_uudecode(substr($str, 3)); 461 default: // not encoded (or unknown) 462 return $str; 463 } 464} 465 466/** 467 * array combination function to remove negated values (prefixed by !) 468 * 469 * @param array $current 470 * @param array $new 471 * 472 * @return array the combined array, numeric keys reset 473 */ 474function array_merge_with_removal($current, $new) 475{ 476 foreach ($new as $val) { 477 if (substr($val, 0, 1) == DOKU_CONF_NEGATION) { 478 $idx = array_search(trim(substr($val, 1)), $current); 479 if ($idx !== false) { 480 unset($current[$idx]); 481 } 482 } else { 483 $current[] = trim($val); 484 } 485 } 486 487 return array_slice($current, 0); 488} 489//Setup VIM: ex: et ts=4 : 490