xref: /dokuwiki/inc/confutils.php (revision fe15e2c063a38f65804c55e581c72b96ac36edf7)
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':
444            return '<b>' . base64_encode($str);
445        case 'uuencode':
446            return '<u>' . convert_uuencode($str);
447        case 'plain':
448        default:
449            return $str;
450    }
451}
452/**
453 * return obscured data as plain text
454 *
455 * @param  string      $str   encoded data
456 * @return string             plain text
457 */
458function conf_decodeString($str)
459{
460    switch (substr($str, 0, 3)) {
461        case '<b>':
462            return base64_decode(substr($str, 3));
463        case '<u>':
464            return convert_uudecode(substr($str, 3));
465        default:  // not encoded (or unknown)
466            return $str;
467    }
468}
469
470/**
471 * array combination function to remove negated values (prefixed by !)
472 *
473 * @param  array $current
474 * @param  array $new
475 *
476 * @return array the combined array, numeric keys reset
477 */
478function array_merge_with_removal($current, $new)
479{
480    foreach ($new as $val) {
481        if (substr($val, 0, 1) == DOKU_CONF_NEGATION) {
482            $idx = array_search(trim(substr($val, 1)), $current);
483            if ($idx !== false) {
484                unset($current[$idx]);
485            }
486        } else {
487            $current[] = trim($val);
488        }
489    }
490
491    return array_slice($current, 0);
492}
493//Setup VIM: ex: et ts=4 :
494