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