xref: /dokuwiki/install.php (revision 177d6836e2f75d0e404be9c566e61725852a1e07)
1<?php
2/*><div style="width:60%; margin: auto; background-color: #fcc;
3                border: 1px solid #faa; padding: 0.5em 1em;">
4    <h1 style="font-size: 120%">No PHP Support</h1>
5
6    It seems this server has no PHP support enabled. You will need to
7    enable PHP before you can install and run DokuWiki. Contact your hosting
8    provider if you're unsure what this means.
9
10</div>*/
11
12use dokuwiki\PassHash;
13
14if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/');
15if (!defined('DOKU_CONF')) define('DOKU_CONF', DOKU_INC . 'conf/');
16if (!defined('DOKU_LOCAL')) define('DOKU_LOCAL', DOKU_INC . 'conf/');
17
18// load and initialize the core system
19require_once(DOKU_INC . 'inc/init.php');
20require_once(DOKU_INC . 'inc/pageutils.php');
21
22// check for error reporting override or set error reporting to sane values
23if (!defined('DOKU_E_LEVEL')) {
24    error_reporting(E_ALL ^ E_NOTICE);
25} else {
26    error_reporting(DOKU_E_LEVEL);
27}
28
29// language strings
30require_once(DOKU_INC . 'inc/lang/en/lang.php');
31if (isset($_REQUEST['l']) && !is_array($_REQUEST['l'])) {
32    $LC = preg_replace('/[^a-z\-]+/', '', $_REQUEST['l']);
33}
34if (empty($LC)) $LC = 'en';
35if ($LC && $LC != 'en') {
36    require_once(DOKU_INC . 'inc/lang/' . $LC . '/lang.php');
37}
38
39// initialise variables ...
40$error = [];
41
42// begin output
43header('Content-Type: text/html; charset=utf-8');
44?>
45    <!DOCTYPE html>
46    <html lang="<?php echo $LC ?>" dir="<?php echo $lang['direction'] ?>">
47    <head>
48        <meta charset="utf-8"/>
49        <title><?php echo $lang['i_installer'] ?></title>
50        <style>
51            body {
52                width: 90%;
53                margin: 0 auto;
54                font: 84% Verdana, Helvetica, Arial, sans-serif;
55            }
56
57            img {
58                border: none
59            }
60
61            br.cl {
62                clear: both;
63            }
64
65            code {
66                font-size: 110%;
67                color: #800000;
68            }
69
70            fieldset {
71                border: none
72            }
73
74            label {
75                display: block;
76                margin-top: 0.5em;
77            }
78
79            select.text, input.text {
80                width: 30em;
81                margin: 0 0.5em;
82            }
83
84            a {
85                text-decoration: none
86            }
87        </style>
88        <script>
89            function acltoggle() {
90                var cb = document.getElementById('acl');
91                var fs = document.getElementById('acldep');
92                if (!cb || !fs) return;
93                if (cb.checked) {
94                    fs.style.display = '';
95                } else {
96                    fs.style.display = 'none';
97                }
98            }
99
100            window.onload = function () {
101                acltoggle();
102                var cb = document.getElementById('acl');
103                if (cb) cb.onchange = acltoggle;
104            };
105        </script>
106    </head>
107    <body style="">
108    <h1 style="float:left">
109        <img src="lib/exe/fetch.php?media=wiki:dokuwiki-128.png"
110             style="vertical-align: middle;" alt="" height="64" width="64"/>
111        <?php echo $lang['i_installer'] ?>
112    </h1>
113    <div style="float:right; margin: 1em;">
114        <?php langsel() ?>
115    </div>
116    <br class="cl"/>
117
118    <div style="float: right; width: 34%;">
119        <?php
120        if (file_exists(DOKU_INC . 'inc/lang/' . $LC . '/install.html')) {
121            include(DOKU_INC . 'inc/lang/' . $LC . '/install.html');
122        } else {
123            print "<div lang=\"en\" dir=\"ltr\">\n";
124            include(DOKU_INC . 'inc/lang/en/install.html');
125            print "</div>\n";
126        }
127        ?>
128        <a style="
129                background: transparent
130                url(data/dont-panic-if-you-see-this-in-your-logs-it-means-your-directory-permissions-are-correct.png)
131                left top no-repeat;
132                display: block; width:380px; height:73px; border:none; clear:both;"
133           target="_blank"
134           href="https://www.dokuwiki.org/security#web_access_security"></a>
135    </div>
136
137    <div style="float: left; width: 58%;">
138        <?php
139        try {
140            if (!(check_functions() && check_permissions())) {
141                echo '<p>' . $lang['i_problems'] . '</p>';
142                print_errors();
143                print_retry();
144            } elseif (!check_configs()) {
145                echo '<p>' . $lang['i_modified'] . '</p>';
146                print_errors();
147            } elseif (check_data($_REQUEST['d'])) {
148                // check_data has sanitized all input parameters
149                if (!store_data($_REQUEST['d'])) {
150                    echo '<p>' . $lang['i_failure'] . '</p>';
151                    print_errors();
152                } else {
153                    echo '<p>' . $lang['i_success'] . '</p>';
154                }
155            } else {
156                print_errors();
157                print_form($_REQUEST['d']);
158            }
159        } catch (Exception $e) {
160            echo 'Caught exception: ', $e->getMessage(), "\n";
161        }
162        ?>
163    </div>
164
165
166    <div style="clear: both">
167        <a href="https://dokuwiki.org/"><img src="lib/tpl/dokuwiki/images/button-dw.png" alt="driven by DokuWiki"/></a>
168        <a href="https://php.net"><img src="lib/tpl/dokuwiki/images/button-php.gif" alt="powered by PHP"/></a>
169    </div>
170    </body>
171    </html>
172<?php
173
174/**
175 * Print the input form
176 *
177 * @param array $d submitted entry 'd' of request data
178 */
179function print_form($d)
180{
181    global $lang;
182    global $LC;
183
184    include(DOKU_CONF . 'license.php');
185
186    if (!is_array($d)) $d = [];
187    $d = array_map('hsc', $d);
188
189    if (!isset($d['acl'])) $d['acl'] = 1;
190    if (!isset($d['pop'])) $d['pop'] = 1;
191
192    ?>
193    <form action="" method="post">
194        <input type="hidden" name="l" value="<?php echo $LC ?>"/>
195        <fieldset>
196            <label for="title"><?php echo $lang['i_wikiname'] ?>
197                <input type="text" name="d[title]" id="title" value="<?php echo $d['title'] ?>" style="width: 20em;"/>
198            </label>
199
200            <fieldset style="margin-top: 1em;">
201                <label for="acl">
202                    <input type="checkbox" name="d[acl]"
203                           id="acl" <?php echo(($d['acl'] ? ' checked="checked"' : '')); ?> />
204                    <?php echo $lang['i_enableacl'] ?></label>
205
206                <fieldset id="acldep">
207                    <label for="superuser"><?php echo $lang['i_superuser'] ?></label>
208                    <input class="text" type="text" name="d[superuser]" id="superuser"
209                           value="<?php echo $d['superuser'] ?>"/>
210
211                    <label for="fullname"><?php echo $lang['fullname'] ?></label>
212                    <input class="text" type="text" name="d[fullname]" id="fullname"
213                           value="<?php echo $d['fullname'] ?>"/>
214
215                    <label for="email"><?php echo $lang['email'] ?></label>
216                    <input class="text" type="text" name="d[email]" id="email" value="<?php echo $d['email'] ?>"/>
217
218                    <label for="password"><?php echo $lang['pass'] ?></label>
219                    <input class="text" type="password" name="d[password]" id="password"/>
220
221                    <label for="confirm"><?php echo $lang['passchk'] ?></label>
222                    <input class="text" type="password" name="d[confirm]" id="confirm"/>
223
224                    <label for="policy"><?php echo $lang['i_policy'] ?></label>
225                    <select class="text" name="d[policy]" id="policy">
226                        <option value="0" <?php echo ($d['policy'] == 0) ? 'selected="selected"' : '' ?>><?php
227                            echo $lang['i_pol0'] ?></option>
228                        <option value="1" <?php echo ($d['policy'] == 1) ? 'selected="selected"' : '' ?>><?php
229                            echo $lang['i_pol1'] ?></option>
230                        <option value="2" <?php echo ($d['policy'] == 2) ? 'selected="selected"' : '' ?>><?php
231                            echo $lang['i_pol2'] ?></option>
232                    </select>
233
234                    <label for="allowreg">
235                        <input type="checkbox" name="d[allowreg]" id="allowreg" <?php
236                        echo(($d['allowreg'] ? ' checked="checked"' : '')); ?> />
237                        <?php echo $lang['i_allowreg'] ?>
238                    </label>
239                </fieldset>
240            </fieldset>
241
242            <fieldset>
243                <p><?php echo $lang['i_license'] ?></p>
244                <?php
245                $license[] = ['name' => $lang['i_license_none'], 'url' => ''];
246                if (empty($d['license'])) $d['license'] = 'cc-by-sa';
247                foreach ($license as $key => $lic) {
248                    echo '<label for="lic_' . $key . '">';
249                    echo '<input type="radio" name="d[license]" value="' . hsc($key) . '" id="lic_' . $key . '"' .
250                        (($d['license'] === $key) ? ' checked="checked"' : '') . '>';
251                    echo hsc($lic['name']);
252                    if ($lic['url']) echo ' <a href="' . $lic['url'] . '" target="_blank"><sup>[?]</sup></a>';
253                    echo '</label>';
254                }
255                ?>
256            </fieldset>
257
258            <fieldset>
259                <p><?php echo $lang['i_pop_field'] ?></p>
260                <label for="pop">
261                    <input type="checkbox" name="d[pop]" id="pop" <?php
262                    echo(($d['pop'] ? ' checked="checked"' : '')); ?> />
263                    <?php echo $lang['i_pop_label'] ?>
264                    <a href="https://www.dokuwiki.org/popularity" target="_blank"><sup>[?]</sup></a>
265                </label>
266            </fieldset>
267
268        </fieldset>
269        <fieldset id="process">
270            <button type="submit" name="submit"><?php echo $lang['btn_save'] ?></button>
271        </fieldset>
272    </form>
273    <?php
274}
275
276function print_retry()
277{
278    global $lang;
279    global $LC;
280    ?>
281    <form action="" method="get">
282        <fieldset>
283            <input type="hidden" name="l" value="<?php echo $LC ?>"/>
284            <button type="submit"><?php echo $lang['i_retry']; ?></button>
285        </fieldset>
286    </form>
287    <?php
288}
289
290/**
291 * Check validity of data
292 *
293 * @param array $d
294 * @return bool ok?
295 * @author Andreas Gohr
296 *
297 */
298function check_data(&$d)
299{
300    static $form_default = [
301        'title' => '',
302        'acl' => '1',
303        'superuser' => '',
304        'fullname' => '',
305        'email' => '',
306        'password' => '',
307        'confirm' => '',
308        'policy' => '0',
309        'allowreg' => '0',
310        'license' => 'cc-by-sa'
311    ];
312    global $lang;
313    global $error;
314
315    if (!is_array($d)) $d = [];
316    foreach ($d as $k => $v) {
317        if (is_array($v))
318            unset($d[$k]);
319        else $d[$k] = (string)$v;
320    }
321
322    //autolowercase the username
323    $d['superuser'] = isset($d['superuser']) ? strtolower($d['superuser']) : "";
324
325    $ok = false;
326
327    if (isset($_REQUEST['submit'])) {
328        $ok = true;
329
330        // check input
331        if (empty($d['title'])) {
332            $error[] = sprintf($lang['i_badval'], $lang['i_wikiname']);
333            $ok = false;
334        }
335        if (isset($d['acl'])) {
336            if (empty($d['superuser']) || ($d['superuser'] !== cleanID($d['superuser']))) {
337                $error[] = sprintf($lang['i_badval'], $lang['i_superuser']);
338                $ok = false;
339            }
340            if (empty($d['password'])) {
341                $error[] = sprintf($lang['i_badval'], $lang['pass']);
342                $ok = false;
343            } elseif (!isset($d['confirm']) || $d['confirm'] != $d['password']) {
344                $error[] = sprintf($lang['i_badval'], $lang['passchk']);
345                $ok = false;
346            }
347            if (empty($d['fullname']) || strstr($d['fullname'], ':')) {
348                $error[] = sprintf($lang['i_badval'], $lang['fullname']);
349                $ok = false;
350            }
351            if (empty($d['email']) || strstr($d['email'], ':') || !strstr($d['email'], '@')) {
352                $error[] = sprintf($lang['i_badval'], $lang['email']);
353                $ok = false;
354            }
355        } else {
356            // Since default = 1, browser won't send acl=0 when user untick acl
357            $d['acl'] = '0';
358        }
359    }
360    $d = array_merge($form_default, $d);
361    return $ok;
362}
363
364/**
365 * Writes the data to the config files
366 *
367 * @param array $d
368 * @return bool
369 * @throws Exception
370 *
371 * @author  Chris Smith <chris@jalakai.co.uk>
372 */
373function store_data($d)
374{
375    global $LC;
376    $ok = true;
377    $d['policy'] = (int)$d['policy'];
378
379    // create local.php
380    $now = gmdate('r');
381    $output = <<<EOT
382<?php
383/**
384 * Dokuwiki's Main Configuration File - Local Settings
385 * Auto-generated by install script
386 * Date: $now
387 */
388
389EOT;
390    // add any config options set by a previous installer
391    $preset = __DIR__ . '/install.conf';
392    if (file_exists($preset)) {
393        $output .= "# preset config options\n";
394        $output .= file_get_contents($preset);
395        $output .= "\n\n";
396        $output .= "# options selected in installer\n";
397        @unlink($preset);
398    }
399
400    $output .= '$conf[\'title\'] = \'' . addslashes($d['title']) . "';\n";
401    $output .= '$conf[\'lang\'] = \'' . addslashes($LC) . "';\n";
402    $output .= '$conf[\'license\'] = \'' . addslashes($d['license']) . "';\n";
403    if ($d['acl']) {
404        $output .= '$conf[\'useacl\'] = 1' . ";\n";
405        $output .= "\$conf['superuser'] = '@admin';\n";
406    }
407    if (!$d['allowreg']) {
408        $output .= '$conf[\'disableactions\'] = \'register\'' . ";\n";
409    }
410    $ok = $ok && fileWrite(DOKU_LOCAL . 'local.php', $output);
411
412    if ($d['acl']) {
413        // hash the password
414        $phash = new PassHash();
415        $pass = $phash->hash_bcrypt($d['password']);
416
417        // create users.auth.php
418        $output = <<<EOT
419# users.auth.php
420# <?php exit()?>
421# Don't modify the lines above
422#
423# Userfile
424#
425# Auto-generated by install script
426# Date: $now
427#
428# Format:
429# login:passwordhash:Real Name:email:groups,comma,separated
430
431EOT;
432        // --- user:bcryptpasswordhash:Real Name:email:groups,comma,seperated
433        $output = $output . "\n" . implode(':', [
434                $d['superuser'],
435                $pass,
436                $d['fullname'],
437                $d['email'],
438                'admin,user',
439            ]) . "\n";
440        $ok = $ok && fileWrite(DOKU_LOCAL . 'users.auth.php', $output);
441
442        // create acl.auth.php
443        $output = <<<EOT
444# acl.auth.php
445# <?php exit()?>
446# Don't modify the lines above
447#
448# Access Control Lists
449#
450# Auto-generated by install script
451# Date: $now
452
453EOT;
454        if ($d['policy'] == 2) {
455            $output .= "*               @ALL          0\n";
456            $output .= "*               @user         8\n";
457        } elseif ($d['policy'] == 1) {
458            $output .= "*               @ALL          1\n";
459            $output .= "*               @user         8\n";
460        } else {
461            $output .= "*               @ALL          8\n";
462        }
463        $ok = $ok && fileWrite(DOKU_LOCAL . 'acl.auth.php', $output);
464    }
465
466    // enable popularity submission
467    if (isset($d['pop']) && $d['pop']) {
468        @touch(DOKU_INC . 'data/cache/autosubmit.txt');
469    }
470
471    // disable auth plugins til needed
472    $output = <<<EOT
473<?php
474/*
475 * Local plugin enable/disable settings
476 *
477 * Auto-generated by install script
478 * Date: $now
479 */
480
481\$plugins['authad']    = 0;
482\$plugins['authldap']  = 0;
483\$plugins['authmysql'] = 0;
484\$plugins['authpgsql'] = 0;
485
486EOT;
487    $ok = $ok && fileWrite(DOKU_LOCAL . 'plugins.local.php', $output);
488
489    return $ok;
490}
491
492/**
493 * Write the given content to a file
494 *
495 * @param string $filename
496 * @param string $data
497 * @return bool
498 *
499 * @author  Chris Smith <chris@jalakai.co.uk>
500 */
501function fileWrite($filename, $data)
502{
503    global $error;
504    global $lang;
505
506    if (($fp = @fopen($filename, 'wb')) === false) {
507        $filename = str_replace($_SERVER['DOCUMENT_ROOT'], '{DOCUMENT_ROOT}/', $filename);
508        $error[] = sprintf($lang['i_writeerr'], $filename);
509        return false;
510    }
511
512    if (!empty($data)) {
513        fwrite($fp, $data);
514    }
515    fclose($fp);
516    return true;
517}
518
519
520/**
521 * check installation dependent local config files and tests for a known
522 * unmodified main config file
523 *
524 * @return bool
525 *
526 * @author      Chris Smith <chris@jalakai.co.uk>
527 */
528function check_configs()
529{
530    global $error;
531    global $lang;
532
533    $ok = true;
534
535    $config_files = [
536        'local' => DOKU_LOCAL . 'local.php',
537        'users' => DOKU_LOCAL . 'users.auth.php',
538        'auth' => DOKU_LOCAL . 'acl.auth.php'
539    ];
540
541    // configs shouldn't exist
542    foreach ($config_files as $file) {
543        if (file_exists($file) && filesize($file)) {
544            $file = str_replace($_SERVER['DOCUMENT_ROOT'], '{DOCUMENT_ROOT}/', $file);
545            $error[] = sprintf($lang['i_confexists'], $file);
546            $ok = false;
547        }
548    }
549    return $ok;
550}
551
552
553/**
554 * Check other installation dir/file permission requirements
555 *
556 * @return bool
557 *
558 * @author      Chris Smith <chris@jalakai.co.uk>
559 */
560function check_permissions()
561{
562    global $error;
563    global $lang;
564
565    $dirs = [
566        'conf' => DOKU_LOCAL,
567        'data' => DOKU_INC . 'data',
568        'pages' => DOKU_INC . 'data/pages',
569        'attic' => DOKU_INC . 'data/attic',
570        'media' => DOKU_INC . 'data/media',
571        'media_attic' => DOKU_INC . 'data/media_attic',
572        'media_meta' => DOKU_INC . 'data/media_meta',
573        'meta' => DOKU_INC . 'data/meta',
574        'cache' => DOKU_INC . 'data/cache',
575        'locks' => DOKU_INC . 'data/locks',
576        'index' => DOKU_INC . 'data/index',
577        'tmp' => DOKU_INC . 'data/tmp'
578    ];
579
580    $ok = true;
581    foreach ($dirs as $dir) {
582        if (!file_exists("$dir/.") || !is_writable($dir)) {
583            $dir = str_replace($_SERVER['DOCUMENT_ROOT'], '{DOCUMENT_ROOT}', $dir);
584            $error[] = sprintf($lang['i_permfail'], $dir);
585            $ok = false;
586        }
587    }
588    return $ok;
589}
590
591/**
592 * Check the availability of functions used in DokuWiki and the PHP version
593 *
594 * @return bool
595 *
596 * @author Andreas Gohr <andi@splitbrain.org>
597 */
598function check_functions()
599{
600    global $error;
601    global $lang;
602    $ok = true;
603
604    if (version_compare(phpversion(), '7.4.0', '<')) {
605        $error[] = sprintf($lang['i_phpver'], phpversion(), '7.4.0');
606        $ok = false;
607    }
608
609    if (ini_get('mbstring.func_overload') != 0) {
610        $error[] = $lang['i_mbfuncoverload'];
611        $ok = false;
612    }
613
614    try {
615        random_bytes(1);
616    } catch (Exception $th) {
617        // If an appropriate source of randomness cannot be found, an Exception will be thrown by PHP 7+
618        $error[] = $lang['i_urandom'];
619        $ok = false;
620    }
621
622    if (ini_get('mbstring.func_overload') != 0) {
623        $error[] = $lang['i_mbfuncoverload'];
624        $ok = false;
625    }
626
627    $funcs = explode(' ', 'addslashes call_user_func chmod copy fgets ' .
628        'file file_exists fseek flush filesize ftell fopen ' .
629        'glob header ignore_user_abort ini_get mkdir ' .
630        'ob_start opendir parse_ini_file readfile realpath ' .
631        'rename rmdir serialize session_start unlink usleep ' .
632        'preg_replace file_get_contents htmlspecialchars_decode ' .
633        'spl_autoload_register stream_select fsockopen pack xml_parser_create');
634
635    if (!function_exists('mb_substr')) {
636        $funcs[] = 'utf8_encode';
637        $funcs[] = 'utf8_decode';
638    }
639
640    if (!function_exists('mail')) {
641        if (strpos(ini_get('disable_functions'), 'mail') !== false) {
642            $disabled = $lang['i_disabled'];
643        } else {
644            $disabled = "";
645        }
646        $error[] = sprintf($lang['i_funcnmail'], $disabled);
647    }
648
649    foreach ($funcs as $func) {
650        if (!function_exists($func)) {
651            $error[] = sprintf($lang['i_funcna'], $func);
652            $ok = false;
653        }
654    }
655    return $ok;
656}
657
658/**
659 * Print language selection
660 *
661 * @author Andreas Gohr <andi@splitbrain.org>
662 */
663function langsel()
664{
665    global $lang;
666    global $LC;
667
668    $dir = DOKU_INC . 'inc/lang';
669    $dh = opendir($dir);
670    if (!$dh) return;
671
672    $langs = [];
673    while (($file = readdir($dh)) !== false) {
674        if (preg_match('/^[._]/', $file)) continue;
675        if (is_dir($dir . '/' . $file) && file_exists($dir . '/' . $file . '/lang.php')) {
676            $langs[] = $file;
677        }
678    }
679    closedir($dh);
680    sort($langs);
681
682    echo '<form action="">';
683    echo $lang['i_chooselang'];
684    echo ': <select name="l" onchange="submit()">';
685    foreach ($langs as $l) {
686        $sel = ($l == $LC) ? 'selected="selected"' : '';
687        echo '<option value="' . $l . '" ' . $sel . '>' . $l . '</option>';
688    }
689    echo '</select> ';
690    echo '<button type="submit">' . $lang['btn_update'] . '</button>';
691    echo '</form>';
692}
693
694/**
695 * Print global error array
696 *
697 * @author Andreas Gohr <andi@splitbrain.org>
698 */
699function print_errors()
700{
701    global $error;
702    if (!empty($error)) {
703        echo '<ul>';
704        foreach ($error as $err) {
705            echo "<li>$err</li>";
706        }
707        echo '</ul>';
708    }
709}
710