xref: /dokuwiki/inc/init.php (revision d868eb89f182718a31113373a6272670bd7f8012)
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']=='') { unset($conf['changelog_old']); }
312    // hardcoded changelog because it is now a cache that lives in meta
313    $conf['changelog'] = $conf['metadir'].'/_dokuwiki.changes';
314    $conf['media_changelog'] = $conf['metadir'].'/_media.changes';
315}
316
317/**
318 * Load the language strings
319 *
320 * @param string $langCode language code, as passed by event handler
321 */
322function init_lang($langCode)
323{
324    //prepare language array
325    global $lang, $config_cascade;
326    $lang = [];
327
328    //load the language files
329    require(DOKU_INC.'inc/lang/en/lang.php');
330    foreach ($config_cascade['lang']['core'] as $config_file) {
331        if (file_exists($config_file . 'en/lang.php')) {
332            include($config_file . 'en/lang.php');
333        }
334    }
335
336    if ($langCode && $langCode != 'en') {
337        if (file_exists(DOKU_INC."inc/lang/$langCode/lang.php")) {
338            require(DOKU_INC."inc/lang/$langCode/lang.php");
339        }
340        foreach ($config_cascade['lang']['core'] as $config_file) {
341            if (file_exists($config_file . "$langCode/lang.php")) {
342                include($config_file . "$langCode/lang.php");
343            }
344        }
345    }
346}
347
348/**
349 * Checks the existence of certain files and creates them if missing.
350 */
351function init_files()
352{
353    global $conf;
354
355    $files = [$conf['indexdir'].'/page.idx'];
356
357    foreach($files as $file){
358        if(!file_exists($file)){
359            $fh = @fopen($file, 'a');
360            if($fh){
361                fclose($fh);
362                if($conf['fperm']) chmod($file, $conf['fperm']);
363            }else{
364                nice_die("$file is not writable. Check your permissions settings!");
365            }
366        }
367    }
368}
369
370/**
371 * Returns absolute path
372 *
373 * This tries the given path first, then checks in DOKU_INC.
374 * Check for accessibility on directories as well.
375 *
376 * @author Andreas Gohr <andi@splitbrain.org>
377 *
378 * @param string $path
379 *
380 * @return bool|string
381 */
382function init_path($path)
383{
384    // check existence
385    $p = fullpath($path);
386    if(!file_exists($p)){
387        $p = fullpath(DOKU_INC.$path);
388        if(!file_exists($p)){
389            return '';
390        }
391    }
392
393    // check writability
394    if(!@is_writable($p)){
395        return '';
396    }
397
398    // check accessability (execute bit) for directories
399    if(@is_dir($p) && !file_exists("$p/.")){
400        return '';
401    }
402
403    return $p;
404}
405
406/**
407 * Sets the internal config values fperm and dperm which, when set,
408 * will be used to change the permission of a newly created dir or
409 * file with chmod. Considers the influence of the system's umask
410 * setting the values only if needed.
411 */
412function init_creationmodes()
413{
414    global $conf;
415
416    // Legacy support for old umask/dmask scheme
417    unset($conf['dmask']);
418    unset($conf['fmask']);
419    unset($conf['umask']);
420
421    $conf['fperm'] = false;
422    $conf['dperm'] = false;
423
424    // get system umask, fallback to 0 if none available
425    $umask = @umask();
426    if(!$umask) $umask = 0000;
427
428    // check what is set automatically by the system on file creation
429    // and set the fperm param if it's not what we want
430    $auto_fmode = 0666 & ~$umask;
431    if($auto_fmode != $conf['fmode']) $conf['fperm'] = $conf['fmode'];
432
433    // check what is set automatically by the system on directory creation
434    // and set the dperm param if it's not what we want.
435    $auto_dmode = 0777 & ~$umask;
436    if($auto_dmode != $conf['dmode']) $conf['dperm'] = $conf['dmode'];
437}
438
439/**
440 * Returns the full absolute URL to the directory where
441 * DokuWiki is installed in (includes a trailing slash)
442 *
443 * !! Can not access $_SERVER values through $INPUT
444 * !! here as this function is called before $INPUT is
445 * !! initialized.
446 *
447 * @author Andreas Gohr <andi@splitbrain.org>
448 *
449 * @param null|string $abs
450 *
451 * @return string
452 */
453function getBaseURL($abs = null)
454{
455    global $conf;
456    //if canonical url enabled always return absolute
457    if(is_null($abs)) $abs = $conf['canonical'];
458
459    if(!empty($conf['basedir'])){
460        $dir = $conf['basedir'];
461    }elseif(substr($_SERVER['SCRIPT_NAME'], -4) == '.php'){
462        $dir = dirname($_SERVER['SCRIPT_NAME']);
463    }elseif(substr($_SERVER['PHP_SELF'], -4) == '.php'){
464        $dir = dirname($_SERVER['PHP_SELF']);
465    }elseif($_SERVER['DOCUMENT_ROOT'] && $_SERVER['SCRIPT_FILENAME']){
466        $dir = preg_replace ('/^'.preg_quote($_SERVER['DOCUMENT_ROOT'], '/').'/', '',
467                $_SERVER['SCRIPT_FILENAME']);
468        $dir = dirname('/'.$dir);
469    }else{
470        $dir = '.'; //probably wrong
471    }
472
473    $dir = str_replace('\\', '/', $dir);             // bugfix for weird WIN behaviour
474    $dir = preg_replace('#//+#', '/', "/$dir/");     // ensure leading and trailing slashes
475
476    //handle script in lib/exe dir
477    $dir = preg_replace('!lib/exe/$!', '', $dir);
478
479    //handle script in lib/plugins dir
480    $dir = preg_replace('!lib/plugins/.*$!', '', $dir);
481
482    //finish here for relative URLs
483    if(!$abs) return $dir;
484
485    //use config if available, trim any slash from end of baseurl to avoid multiple consecutive slashes in the path
486    if(!empty($conf['baseurl'])) return rtrim($conf['baseurl'], '/').$dir;
487
488    //split hostheader into host and port
489    if(isset($_SERVER['HTTP_HOST'])){
490        $parsed_host = parse_url('http://'.$_SERVER['HTTP_HOST']);
491        $host = $parsed_host['host'] ?? null;
492        $port = $parsed_host['port'] ?? null;
493    }elseif(isset($_SERVER['SERVER_NAME'])){
494        $parsed_host = parse_url('http://'.$_SERVER['SERVER_NAME']);
495        $host = $parsed_host['host'] ?? null;
496        $port = $parsed_host['port'] ?? null;
497    }else{
498        $host = php_uname('n');
499        $port = '';
500    }
501
502    if(is_null($port)){
503        $port = '';
504    }
505
506    if(!is_ssl()){
507        $proto = 'http://';
508        if ($port == '80') {
509            $port = '';
510        }
511    }else{
512        $proto = 'https://';
513        if ($port == '443') {
514            $port = '';
515        }
516    }
517
518    if($port !== '') $port = ':'.$port;
519
520    return $proto.$host.$port.$dir;
521}
522
523/**
524 * Check if accessed via HTTPS
525 *
526 * Apache leaves ,$_SERVER['HTTPS'] empty when not available, IIS sets it to 'off'.
527 * 'false' and 'disabled' are just guessing
528 *
529 * @returns bool true when SSL is active
530 */
531function is_ssl()
532{
533    // check if we are behind a reverse proxy
534    if(isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
535        if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
536            return true;
537        } else {
538            return false;
539        }
540    }
541    if(!isset($_SERVER['HTTPS']) ||
542        preg_match('/^(|off|false|disabled)$/i', $_SERVER['HTTPS'])) {
543        return false;
544    } else {
545        return true;
546    }
547}
548
549/**
550 * checks it is windows OS
551 * @return bool
552 */
553function isWindows()
554{
555    return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
556}
557
558/**
559 * print a nice message even if no styles are loaded yet.
560 *
561 * @param integer|string $msg
562 */
563function nice_die($msg)
564{
565    echo<<<EOT
566<!DOCTYPE html>
567<html>
568<head><title>DokuWiki Setup Error</title></head>
569<body style="font-family: Arial, sans-serif">
570    <div style="width:60%; margin: auto; background-color: #fcc;
571                border: 1px solid #faa; padding: 0.5em 1em;">
572        <h1 style="font-size: 120%">DokuWiki Setup Error</h1>
573        <p>$msg</p>
574    </div>
575</body>
576</html>
577EOT;
578    if(defined('DOKU_UNITTEST')) {
579        throw new RuntimeException('nice_die: '.$msg);
580    }
581    exit(1);
582}
583
584/**
585 * A realpath() replacement
586 *
587 * This function behaves similar to PHP's realpath() but does not resolve
588 * symlinks or accesses upper directories
589 *
590 * @author Andreas Gohr <andi@splitbrain.org>
591 * @author <richpageau at yahoo dot co dot uk>
592 * @link   http://php.net/manual/en/function.realpath.php#75992
593 *
594 * @param string $path
595 * @param bool $exists
596 *
597 * @return bool|string
598 */
599function fullpath($path, $exists = false)
600{
601    static $run = 0;
602    $root  = '';
603    $iswin = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' || !empty($GLOBALS['DOKU_UNITTEST_ASSUME_WINDOWS']));
604
605    // find the (indestructable) root of the path - keeps windows stuff intact
606    if($path[0] == '/'){
607        $root = '/';
608    }elseif($iswin){
609        // match drive letter and UNC paths
610        if (preg_match('!^([a-zA-z]:)(.*)!', $path, $match)) {
611            $root = $match[1].'/';
612            $path = $match[2];
613        } elseif (preg_match('!^(\\\\\\\\[^\\\\/]+\\\\[^\\\\/]+[\\\\/])(.*)!', $path, $match)) {
614            $root = $match[1];
615            $path = $match[2];
616        }
617    }
618    $path = str_replace('\\', '/', $path);
619
620    // if the given path wasn't absolute already, prepend the script path and retry
621    if(!$root){
622        $base = dirname($_SERVER['SCRIPT_FILENAME']);
623        $path = $base.'/'.$path;
624        if($run == 0){ // avoid endless recursion when base isn't absolute for some reason
625            $run++;
626            return fullpath($path, $exists);
627        }
628    }
629    $run = 0;
630
631    // canonicalize
632    $path=explode('/', $path);
633    $newpath=[];
634    foreach($path as $p) {
635        if ($p === '' || $p === '.') continue;
636        if ($p==='..') {
637            array_pop($newpath);
638            continue;
639        }
640        $newpath[] = $p;
641    }
642    $finalpath = $root.implode('/', $newpath);
643
644    // check for existence when needed (except when unit testing)
645    if($exists && !defined('DOKU_UNITTEST') && !file_exists($finalpath)) {
646        return false;
647    }
648    return $finalpath;
649}
650
651