xref: /dokuwiki/inc/init.php (revision 7d34963b3e75ea04c63ec066a6b7a692e123cb53)
1<?php
2/**
3 * Initialize some defaults needed for DokuWiki
4 */
5use dokuwiki\Extension\PluginController;
6use dokuwiki\ErrorHandler;
7use dokuwiki\Input\Input;
8use dokuwiki\Extension\Event;
9use dokuwiki\Extension\EventHandler;
10
11/**
12 * timing Dokuwiki execution
13 *
14 * @param integer $start
15 *
16 * @return mixed
17 */
18function delta_time($start = 0)
19{
20    return microtime(true)-((float)$start);
21}
22define('DOKU_START_TIME', delta_time());
23
24global $config_cascade;
25$config_cascade = [];
26
27// if available load a preload config file
28$preload = fullpath(__DIR__).'/preload.php';
29if (file_exists($preload)) include($preload);
30
31// define the include path
32if (!defined('DOKU_INC')) define('DOKU_INC', fullpath(__DIR__.'/../').'/');
33
34// define Plugin dir
35if (!defined('DOKU_PLUGIN'))  define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
36
37// define config path (packagers may want to change this to /etc/dokuwiki/)
38if (!defined('DOKU_CONF')) define('DOKU_CONF', DOKU_INC.'conf/');
39
40// check for error reporting override or set error reporting to sane values
41if (!defined('DOKU_E_LEVEL') && file_exists(DOKU_CONF.'report_e_all')) {
42    define('DOKU_E_LEVEL', E_ALL);
43}
44if (!defined('DOKU_E_LEVEL')) {
45    error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT);
46} else {
47    error_reporting(DOKU_E_LEVEL);
48}
49
50// avoid caching issues #1594
51header('Vary: Cookie');
52
53// init memory caches
54global $cache_revinfo;
55       $cache_revinfo = [];
56global $cache_wikifn;
57       $cache_wikifn = [];
58global $cache_cleanid;
59       $cache_cleanid = [];
60global $cache_authname;
61       $cache_authname = [];
62global $cache_metadata;
63       $cache_metadata = [];
64
65// always include 'inc/config_cascade.php'
66// previously in preload.php set fields of $config_cascade will be merged with the defaults
67include(DOKU_INC.'inc/config_cascade.php');
68
69//prepare config array()
70global $conf;
71$conf = [];
72
73// load the global config file(s)
74foreach (['default', 'local', 'protected'] as $config_group) {
75    if (empty($config_cascade['main'][$config_group])) continue;
76    foreach ($config_cascade['main'][$config_group] as $config_file) {
77        if (file_exists($config_file)) {
78            include($config_file);
79        }
80    }
81}
82
83//prepare license array()
84global $license;
85$license = [];
86
87// load the license file(s)
88foreach (['default', 'local'] as $config_group) {
89    if (empty($config_cascade['license'][$config_group])) continue;
90    foreach ($config_cascade['license'][$config_group] as $config_file) {
91        if (file_exists($config_file)) {
92            include($config_file);
93        }
94    }
95}
96
97// set timezone (as in pre 5.3.0 days)
98date_default_timezone_set(@date_default_timezone_get());
99
100// define baseURL
101if (!defined('DOKU_REL')) define('DOKU_REL', getBaseURL(false));
102if (!defined('DOKU_URL')) define('DOKU_URL', getBaseURL(true));
103if (!defined('DOKU_BASE')) {
104    if ($conf['canonical']) {
105        define('DOKU_BASE', DOKU_URL);
106    } else {
107        define('DOKU_BASE', DOKU_REL);
108    }
109}
110
111// define whitespace
112if (!defined('NL')) define('NL', "\n");
113if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
114if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
115
116// define cookie and session id, append server port when securecookie is configured FS#1664
117if (!defined('DOKU_COOKIE')) {
118    $serverPort = $_SERVER['SERVER_PORT'] ?? '';
119    define('DOKU_COOKIE', 'DW' . md5(DOKU_REL . (($conf['securecookie']) ? $serverPort : '')));
120    unset($serverPort);
121}
122
123// define main script
124if (!defined('DOKU_SCRIPT')) define('DOKU_SCRIPT', 'doku.php');
125
126if (!defined('DOKU_TPL')) {
127    /**
128     * @deprecated 2012-10-13 replaced by more dynamic method
129     * @see tpl_basedir()
130     */
131    define('DOKU_TPL', DOKU_BASE.'lib/tpl/'.$conf['template'].'/');
132}
133
134if (!defined('DOKU_TPLINC')) {
135    /**
136     * @deprecated 2012-10-13 replaced by more dynamic method
137     * @see tpl_incdir()
138     */
139    define('DOKU_TPLINC', DOKU_INC.'lib/tpl/'.$conf['template'].'/');
140}
141
142// make session rewrites XHTML compliant
143@ini_set('arg_separator.output', '&amp;');
144
145// make sure global zlib does not interfere FS#1132
146@ini_set('zlib.output_compression', 'off');
147
148// increase PCRE backtrack limit
149@ini_set('pcre.backtrack_limit', '20971520');
150
151// enable gzip compression if supported
152$httpAcceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '';
153$conf['gzip_output'] &= (strpos($httpAcceptEncoding, 'gzip') !== false);
154global $ACT;
155if (
156    $conf['gzip_output'] &&
157        !defined('DOKU_DISABLE_GZIP_OUTPUT') &&
158        function_exists('ob_gzhandler') &&
159        // Disable compression when a (compressed) sitemap might be delivered
160        // See https://bugs.dokuwiki.org/index.php?do=details&task_id=2576
161        $ACT != 'sitemap'
162) {
163    ob_start('ob_gzhandler');
164}
165
166// init session
167if (!headers_sent() && !defined('NOSESSION')) {
168    if (!defined('DOKU_SESSION_NAME'))     define('DOKU_SESSION_NAME', "DokuWiki");
169    if (!defined('DOKU_SESSION_LIFETIME')) define('DOKU_SESSION_LIFETIME', 0);
170    if (!defined('DOKU_SESSION_PATH')) {
171        $cookieDir = empty($conf['cookiedir']) ? DOKU_REL : $conf['cookiedir'];
172        define('DOKU_SESSION_PATH', $cookieDir);
173    }
174    if (!defined('DOKU_SESSION_DOMAIN'))   define('DOKU_SESSION_DOMAIN', '');
175
176    // start the session
177    init_session();
178
179    // load left over messages
180    if (isset($_SESSION[DOKU_COOKIE]['msg'])) {
181        $MSG = $_SESSION[DOKU_COOKIE]['msg'];
182        unset($_SESSION[DOKU_COOKIE]['msg']);
183    }
184}
185
186// don't let cookies ever interfere with request vars
187$_REQUEST = array_merge($_GET, $_POST);
188
189// we don't want a purge URL to be digged
190if (isset($_REQUEST['purge']) && !empty($_SERVER['HTTP_REFERER'])) unset($_REQUEST['purge']);
191
192// precalculate file creation modes
193init_creationmodes();
194
195// make real paths and check them
196init_paths();
197init_files();
198
199// setup plugin controller class (can be overwritten in preload.php)
200global $plugin_controller_class, $plugin_controller;
201if (empty($plugin_controller_class)) $plugin_controller_class = PluginController::class;
202
203// load libraries
204require_once(DOKU_INC.'vendor/autoload.php');
205require_once(DOKU_INC.'inc/load.php');
206
207// from now on everything is an exception
208ErrorHandler::register();
209
210// disable gzip if not available
211define('DOKU_HAS_BZIP', function_exists('bzopen'));
212define('DOKU_HAS_GZIP', function_exists('gzopen'));
213if ($conf['compression'] == 'bz2' && !DOKU_HAS_BZIP) {
214    $conf['compression'] = 'gz';
215}
216if ($conf['compression'] == 'gz' && !DOKU_HAS_GZIP) {
217    $conf['compression'] = 0;
218}
219
220// input handle class
221global $INPUT;
222$INPUT = new Input();
223
224// initialize plugin controller
225$plugin_controller = new $plugin_controller_class();
226
227// initialize the event handler
228global $EVENT_HANDLER;
229$EVENT_HANDLER = new EventHandler();
230
231$local = $conf['lang'];
232Event::createAndTrigger('INIT_LANG_LOAD', $local, 'init_lang', true);
233
234
235// setup authentication system
236if (!defined('NOSESSION')) {
237    auth_setup();
238}
239
240// setup mail system
241mail_setup();
242
243$nil = null;
244Event::createAndTrigger('DOKUWIKI_INIT_DONE', $nil, null, false);
245
246/**
247 * Initializes the session
248 *
249 * Makes sure the passed session cookie is valid, invalid ones are ignored an a new session ID is issued
250 *
251 * @link http://stackoverflow.com/a/33024310/172068
252 * @link http://php.net/manual/en/session.configuration.php#ini.session.sid-length
253 */
254function init_session()
255{
256    global $conf;
257    session_name(DOKU_SESSION_NAME);
258    session_set_cookie_params([
259        'lifetime' => DOKU_SESSION_LIFETIME,
260        'path' => DOKU_SESSION_PATH,
261        'domain' => DOKU_SESSION_DOMAIN,
262        'secure' => ($conf['securecookie'] && is_ssl()),
263        'httponly' => true,
264        'samesite' => 'Lax',
265    ]);
266
267    // make sure the session cookie contains a valid session ID
268    if (isset($_COOKIE[DOKU_SESSION_NAME]) && !preg_match('/^[-,a-zA-Z0-9]{22,256}$/', $_COOKIE[DOKU_SESSION_NAME])) {
269        unset($_COOKIE[DOKU_SESSION_NAME]);
270    }
271
272    session_start();
273}
274
275
276/**
277 * Checks paths from config file
278 */
279function init_paths()
280{
281    global $conf;
282
283    $paths = [
284        'datadir'   => 'pages',
285        'olddir'    => 'attic',
286        'mediadir'  => 'media',
287        'mediaolddir' => 'media_attic',
288        'metadir'   => 'meta',
289        'mediametadir' => 'media_meta',
290        'cachedir'  => 'cache',
291        'indexdir'  => 'index',
292        'lockdir'   => 'locks',
293        'tmpdir'    => 'tmp',
294        'logdir'    => 'log',
295    ];
296
297    foreach ($paths as $c => $p) {
298        $path = empty($conf[$c]) ? $conf['savedir'].'/'.$p : $conf[$c];
299        $conf[$c] = init_path($path);
300        if (empty($conf[$c])) {
301            $path = fullpath($path);
302            nice_die("The $c ('$p') at $path is not found, isn't accessible or writable.
303                You should check your config and permission settings.
304                Or maybe you want to <a href=\"install.php\">run the
305                installer</a>?");
306        }
307    }
308
309    // path to old changelog only needed for upgrading
310    $conf['changelog_old'] = init_path(
311        $conf['changelog'] ?? $conf['savedir'] . '/changes.log'
312    );
313    if ($conf['changelog_old']=='') {
314unset($conf['changelog_old']); }
315    // hardcoded changelog because it is now a cache that lives in meta
316    $conf['changelog'] = $conf['metadir'].'/_dokuwiki.changes';
317    $conf['media_changelog'] = $conf['metadir'].'/_media.changes';
318}
319
320/**
321 * Load the language strings
322 *
323 * @param string $langCode language code, as passed by event handler
324 */
325function init_lang($langCode)
326{
327    //prepare language array
328    global $lang, $config_cascade;
329    $lang = [];
330
331    //load the language files
332    require(DOKU_INC.'inc/lang/en/lang.php');
333    foreach ($config_cascade['lang']['core'] as $config_file) {
334        if (file_exists($config_file . 'en/lang.php')) {
335            include($config_file . 'en/lang.php');
336        }
337    }
338
339    if ($langCode && $langCode != 'en') {
340        if (file_exists(DOKU_INC."inc/lang/$langCode/lang.php")) {
341            require(DOKU_INC."inc/lang/$langCode/lang.php");
342        }
343        foreach ($config_cascade['lang']['core'] as $config_file) {
344            if (file_exists($config_file . "$langCode/lang.php")) {
345                include($config_file . "$langCode/lang.php");
346            }
347        }
348    }
349}
350
351/**
352 * Checks the existence of certain files and creates them if missing.
353 */
354function init_files()
355{
356    global $conf;
357
358    $files = [$conf['indexdir'].'/page.idx'];
359
360    foreach ($files as $file) {
361        if (!file_exists($file)) {
362            $fh = @fopen($file, 'a');
363            if ($fh) {
364                fclose($fh);
365                if ($conf['fperm']) chmod($file, $conf['fperm']);
366            } else {
367                nice_die("$file is not writable. Check your permissions settings!");
368            }
369        }
370    }
371}
372
373/**
374 * Returns absolute path
375 *
376 * This tries the given path first, then checks in DOKU_INC.
377 * Check for accessibility on directories as well.
378 *
379 * @author Andreas Gohr <andi@splitbrain.org>
380 *
381 * @param string $path
382 *
383 * @return bool|string
384 */
385function init_path($path)
386{
387    // check existence
388    $p = fullpath($path);
389    if (!file_exists($p)) {
390        $p = fullpath(DOKU_INC.$path);
391        if (!file_exists($p)) {
392            return '';
393        }
394    }
395
396    // check writability
397    if (!@is_writable($p)) {
398        return '';
399    }
400
401    // check accessability (execute bit) for directories
402    if (@is_dir($p) && !file_exists("$p/.")) {
403        return '';
404    }
405
406    return $p;
407}
408
409/**
410 * Sets the internal config values fperm and dperm which, when set,
411 * will be used to change the permission of a newly created dir or
412 * file with chmod. Considers the influence of the system's umask
413 * setting the values only if needed.
414 */
415function init_creationmodes()
416{
417    global $conf;
418
419    // Legacy support for old umask/dmask scheme
420    unset($conf['dmask']);
421    unset($conf['fmask']);
422    unset($conf['umask']);
423
424    $conf['fperm'] = false;
425    $conf['dperm'] = false;
426
427    // get system umask, fallback to 0 if none available
428    $umask = @umask();
429    if (!$umask) $umask = 0000;
430
431    // check what is set automatically by the system on file creation
432    // and set the fperm param if it's not what we want
433    $auto_fmode = 0666 & ~$umask;
434    if ($auto_fmode != $conf['fmode']) $conf['fperm'] = $conf['fmode'];
435
436    // check what is set automatically by the system on directory creation
437    // and set the dperm param if it's not what we want.
438    $auto_dmode = 0777 & ~$umask;
439    if ($auto_dmode != $conf['dmode']) $conf['dperm'] = $conf['dmode'];
440}
441
442/**
443 * Returns the full absolute URL to the directory where
444 * DokuWiki is installed in (includes a trailing slash)
445 *
446 * !! Can not access $_SERVER values through $INPUT
447 * !! here as this function is called before $INPUT is
448 * !! initialized.
449 *
450 * @author Andreas Gohr <andi@splitbrain.org>
451 *
452 * @param null|string $abs
453 *
454 * @return string
455 */
456function getBaseURL($abs = null)
457{
458    global $conf;
459    //if canonical url enabled always return absolute
460    if (is_null($abs)) $abs = $conf['canonical'];
461
462    if (!empty($conf['basedir'])) {
463        $dir = $conf['basedir'];
464    } elseif (substr($_SERVER['SCRIPT_NAME'], -4) == '.php') {
465        $dir = dirname($_SERVER['SCRIPT_NAME']);
466    } elseif (substr($_SERVER['PHP_SELF'], -4) == '.php') {
467        $dir = dirname($_SERVER['PHP_SELF']);
468    } elseif ($_SERVER['DOCUMENT_ROOT'] && $_SERVER['SCRIPT_FILENAME']) {
469        $dir = preg_replace(
470            '/^'.preg_quote($_SERVER['DOCUMENT_ROOT'], '/').'/',
471            '',
472            $_SERVER['SCRIPT_FILENAME']
473        );
474        $dir = dirname('/'.$dir);
475    } else {
476        $dir = '.'; //probably wrong
477    }
478
479    $dir = str_replace('\\', '/', $dir);             // bugfix for weird WIN behaviour
480    $dir = preg_replace('#//+#', '/', "/$dir/");     // ensure leading and trailing slashes
481
482    //handle script in lib/exe dir
483    $dir = preg_replace('!lib/exe/$!', '', $dir);
484
485    //handle script in lib/plugins dir
486    $dir = preg_replace('!lib/plugins/.*$!', '', $dir);
487
488    //finish here for relative URLs
489    if (!$abs) return $dir;
490
491    //use config if available, trim any slash from end of baseurl to avoid multiple consecutive slashes in the path
492    if (!empty($conf['baseurl'])) return rtrim($conf['baseurl'], '/').$dir;
493
494    //split hostheader into host and port
495    if (isset($_SERVER['HTTP_HOST'])) {
496        $parsed_host = parse_url('http://'.$_SERVER['HTTP_HOST']);
497        $host = $parsed_host['host'] ?? null;
498        $port = $parsed_host['port'] ?? null;
499    } elseif (isset($_SERVER['SERVER_NAME'])) {
500        $parsed_host = parse_url('http://'.$_SERVER['SERVER_NAME']);
501        $host = $parsed_host['host'] ?? null;
502        $port = $parsed_host['port'] ?? null;
503    } else {
504        $host = php_uname('n');
505        $port = '';
506    }
507
508    if (is_null($port)) {
509        $port = '';
510    }
511
512    if (!is_ssl()) {
513        $proto = 'http://';
514        if ($port == '80') {
515            $port = '';
516        }
517    } else {
518        $proto = 'https://';
519        if ($port == '443') {
520            $port = '';
521        }
522    }
523
524    if ($port !== '') $port = ':'.$port;
525
526    return $proto.$host.$port.$dir;
527}
528
529/**
530 * Check if accessed via HTTPS
531 *
532 * Apache leaves ,$_SERVER['HTTPS'] empty when not available, IIS sets it to 'off'.
533 * 'false' and 'disabled' are just guessing
534 *
535 * @returns bool true when SSL is active
536 */
537function is_ssl()
538{
539    // check if we are behind a reverse proxy
540    if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
541        if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
542            return true;
543        } else {
544            return false;
545        }
546    }
547    if (
548        !isset($_SERVER['HTTPS']) ||
549        preg_match('/^(|off|false|disabled)$/i', $_SERVER['HTTPS'])
550    ) {
551        return false;
552    } else {
553        return true;
554    }
555}
556
557/**
558 * checks it is windows OS
559 * @return bool
560 */
561function isWindows()
562{
563    return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
564}
565
566/**
567 * print a nice message even if no styles are loaded yet.
568 *
569 * @param integer|string $msg
570 */
571function nice_die($msg)
572{
573    echo<<<EOT
574<!DOCTYPE html>
575<html>
576<head><title>DokuWiki Setup Error</title></head>
577<body style="font-family: Arial, sans-serif">
578    <div style="width:60%; margin: auto; background-color: #fcc;
579                border: 1px solid #faa; padding: 0.5em 1em;">
580        <h1 style="font-size: 120%">DokuWiki Setup Error</h1>
581        <p>$msg</p>
582    </div>
583</body>
584</html>
585EOT;
586    if (defined('DOKU_UNITTEST')) {
587        throw new RuntimeException('nice_die: '.$msg);
588    }
589    exit(1);
590}
591
592/**
593 * A realpath() replacement
594 *
595 * This function behaves similar to PHP's realpath() but does not resolve
596 * symlinks or accesses upper directories
597 *
598 * @author Andreas Gohr <andi@splitbrain.org>
599 * @author <richpageau at yahoo dot co dot uk>
600 * @link   http://php.net/manual/en/function.realpath.php#75992
601 *
602 * @param string $path
603 * @param bool $exists
604 *
605 * @return bool|string
606 */
607function fullpath($path, $exists = false)
608{
609    static $run = 0;
610    $root  = '';
611    $iswin = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' || !empty($GLOBALS['DOKU_UNITTEST_ASSUME_WINDOWS']));
612
613    // find the (indestructable) root of the path - keeps windows stuff intact
614    if ($path[0] == '/') {
615        $root = '/';
616    } elseif ($iswin) {
617        // match drive letter and UNC paths
618        if (preg_match('!^([a-zA-z]:)(.*)!', $path, $match)) {
619            $root = $match[1].'/';
620            $path = $match[2];
621        } elseif (preg_match('!^(\\\\\\\\[^\\\\/]+\\\\[^\\\\/]+[\\\\/])(.*)!', $path, $match)) {
622            $root = $match[1];
623            $path = $match[2];
624        }
625    }
626    $path = str_replace('\\', '/', $path);
627
628    // if the given path wasn't absolute already, prepend the script path and retry
629    if (!$root) {
630        $base = dirname($_SERVER['SCRIPT_FILENAME']);
631        $path = $base.'/'.$path;
632        if ($run == 0) { // avoid endless recursion when base isn't absolute for some reason
633            $run++;
634            return fullpath($path, $exists);
635        }
636    }
637    $run = 0;
638
639    // canonicalize
640    $path=explode('/', $path);
641    $newpath=[];
642    foreach ($path as $p) {
643        if ($p === '' || $p === '.') continue;
644        if ($p==='..') {
645            array_pop($newpath);
646            continue;
647        }
648        $newpath[] = $p;
649    }
650    $finalpath = $root.implode('/', $newpath);
651
652    // check for existence when needed (except when unit testing)
653    if ($exists && !defined('DOKU_UNITTEST') && !file_exists($finalpath)) {
654        return false;
655    }
656    return $finalpath;
657}
658