xref: /plugin/upgrade/admin.php (revision bd08ebd1656ec632c41a18142010f16ccf0d3b40)
134aae6dbSAndreas Gohr<?php
234aae6dbSAndreas Gohr/**
3d391262fSAndreas Gohr * DokuWiki Plugin upgrade (Admin Component)
434aae6dbSAndreas Gohr *
534aae6dbSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
634aae6dbSAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
734aae6dbSAndreas Gohr */
834aae6dbSAndreas Gohr
934aae6dbSAndreas Gohr// must be run within Dokuwiki
1034aae6dbSAndreas Gohrif(!defined('DOKU_INC')) die();
1134aae6dbSAndreas Gohrif(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
1234aae6dbSAndreas Gohrrequire_once DOKU_PLUGIN.'admin.php';
13d391262fSAndreas Gohrrequire_once DOKU_PLUGIN.'upgrade/VerboseTarLib.class.php';
1434aae6dbSAndreas Gohr
15ff284f1fSAndreas Gohrclass admin_plugin_upgrade extends DokuWiki_Admin_Plugin {
1634aae6dbSAndreas Gohr    private $tgzurl;
1734aae6dbSAndreas Gohr    private $tgzfile;
1834aae6dbSAndreas Gohr    private $tgzdir;
1934aae6dbSAndreas Gohr
2034aae6dbSAndreas Gohr    public function __construct() {
2134aae6dbSAndreas Gohr        global $conf;
2234aae6dbSAndreas Gohr
2334aae6dbSAndreas Gohr        $branch = 'stable';
2434aae6dbSAndreas Gohr
25*bd08ebd1SAndreas Gohr        $this->tgzurl  = "https://github.com/splitbrain/dokuwiki/archive/$branch.tar.gz";
26d391262fSAndreas Gohr        $this->tgzfile = $conf['tmpdir'].'/dokuwiki-upgrade.tgz';
27d391262fSAndreas Gohr        $this->tgzdir  = $conf['tmpdir'].'/dokuwiki-upgrade/';
2834aae6dbSAndreas Gohr    }
2934aae6dbSAndreas Gohr
30*bd08ebd1SAndreas Gohr    public function getMenuSort() {
31*bd08ebd1SAndreas Gohr        return 555;
32*bd08ebd1SAndreas Gohr    }
3334aae6dbSAndreas Gohr
3441163f44SAndreas Gohr    public function handle() {
3541163f44SAndreas Gohr        if($_REQUEST['step'] && !checkSecurityToken()) {
3641163f44SAndreas Gohr            unset($_REQUEST['step']);
3741163f44SAndreas Gohr        }
3834aae6dbSAndreas Gohr    }
3934aae6dbSAndreas Gohr
4034aae6dbSAndreas Gohr    public function html() {
4134aae6dbSAndreas Gohr        $abrt = false;
4234aae6dbSAndreas Gohr        $next = false;
4334aae6dbSAndreas Gohr
4434aae6dbSAndreas Gohr        echo '<h1>'.$this->getLang('menu').'</h1>';
4534aae6dbSAndreas Gohr
46d391262fSAndreas Gohr        $this->_say('<div id="plugin__upgrade">');
4775e9d164SAndreas Gohr        // enable auto scroll
4875e9d164SAndreas Gohr        ?>
4975e9d164SAndreas Gohr        <script language="javascript" type="text/javascript">
50d391262fSAndreas Gohr            var plugin_upgrade = window.setInterval(function () {
51d4376367SAndreas Gohr                var obj = document.getElementById('plugin__upgrade');
5275e9d164SAndreas Gohr                if (obj) obj.scrollTop = obj.scrollHeight;
5375e9d164SAndreas Gohr            }, 25);
5475e9d164SAndreas Gohr        </script>
5575e9d164SAndreas Gohr        <?php
5675e9d164SAndreas Gohr
5775e9d164SAndreas Gohr        // handle current step
5822c39b33SAndreas Gohr        $this->_stepit($abrt, $next);
5934aae6dbSAndreas Gohr
6075e9d164SAndreas Gohr        // disable auto scroll
6175e9d164SAndreas Gohr        ?>
6275e9d164SAndreas Gohr        <script language="javascript" type="text/javascript">
6375e9d164SAndreas Gohr            window.setTimeout(function () {
64d391262fSAndreas Gohr                window.clearInterval(plugin_upgrade);
6575e9d164SAndreas Gohr            }, 50);
6675e9d164SAndreas Gohr        </script>
6775e9d164SAndreas Gohr        <?php
6875e9d164SAndreas Gohr        $this->_say('</div>');
6975e9d164SAndreas Gohr
70d391262fSAndreas Gohr        echo '<form action="" method="get" id="plugin__upgrade_form">';
7134aae6dbSAndreas Gohr        echo '<input type="hidden" name="do" value="admin" />';
72d391262fSAndreas Gohr        echo '<input type="hidden" name="page" value="upgrade" />';
7341163f44SAndreas Gohr        echo '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />';
7475e9d164SAndreas Gohr        if($next) echo '<input type="submit" name="step['.$next.']" value="Continue" class="button continue" />';
7575e9d164SAndreas Gohr        if($abrt) echo '<input type="submit" name="step[cancel]" value="Abort" class="button abort" />';
7634aae6dbSAndreas Gohr        echo '</form>';
7734aae6dbSAndreas Gohr    }
7834aae6dbSAndreas Gohr
79*bd08ebd1SAndreas Gohr    /**
80*bd08ebd1SAndreas Gohr     * Decides the current step and executes it
81*bd08ebd1SAndreas Gohr     *
82*bd08ebd1SAndreas Gohr     * @param bool $abrt
83*bd08ebd1SAndreas Gohr     * @param bool $next
84*bd08ebd1SAndreas Gohr     */
8534aae6dbSAndreas Gohr    private function _stepit(&$abrt, &$next) {
864bed5591SAndreas Gohr        global $conf;
874bed5591SAndreas Gohr        if($conf['safemodehack']) {
884bed5591SAndreas Gohr            $abrt = false;
894bed5591SAndreas Gohr            $next = false;
904bed5591SAndreas Gohr            echo $this->locale_xhtml('safemode');
914bed5591SAndreas Gohr        }
924bed5591SAndreas Gohr
9334aae6dbSAndreas Gohr        if(isset($_REQUEST['step']) && is_array($_REQUEST['step'])) {
9434aae6dbSAndreas Gohr            $step = array_shift(array_keys($_REQUEST['step']));
9534aae6dbSAndreas Gohr        } else {
9634aae6dbSAndreas Gohr            $step = '';
9734aae6dbSAndreas Gohr        }
9834aae6dbSAndreas Gohr
9934aae6dbSAndreas Gohr        if($step == 'cancel') {
10034aae6dbSAndreas Gohr            # cleanup
10134aae6dbSAndreas Gohr            @unlink($this->tgzfile);
1026b2d1b30SAndreas Gohr            $this->_rdel($this->tgzdir);
10334aae6dbSAndreas Gohr            $step = '';
10434aae6dbSAndreas Gohr        }
10534aae6dbSAndreas Gohr
10634aae6dbSAndreas Gohr        if($step) {
10734aae6dbSAndreas Gohr            $abrt = true;
10834aae6dbSAndreas Gohr            $next = false;
10934aae6dbSAndreas Gohr            if(!file_exists($this->tgzfile)) {
11034aae6dbSAndreas Gohr                if($this->_step_download()) $next = 'unpack';
11134aae6dbSAndreas Gohr            } elseif(!is_dir($this->tgzdir)) {
11234aae6dbSAndreas Gohr                if($this->_step_unpack()) $next = 'check';
11334aae6dbSAndreas Gohr            } elseif($step != 'upgrade') {
1143c38de15SAndreas Gohr                if($this->_step_check()) $next = 'upgrade';
11534aae6dbSAndreas Gohr            } elseif($step == 'upgrade') {
1163c38de15SAndreas Gohr                if($this->_step_copy()) $next = 'cancel';
11734aae6dbSAndreas Gohr            } else {
11875e9d164SAndreas Gohr                echo 'uhm. what happened? where am I? This should not happen';
11934aae6dbSAndreas Gohr            }
12034aae6dbSAndreas Gohr        } else {
12134aae6dbSAndreas Gohr            # first time run, show intro
12234aae6dbSAndreas Gohr            echo $this->locale_xhtml('step0');
12334aae6dbSAndreas Gohr            $abrt = false;
12434aae6dbSAndreas Gohr            $next = 'download';
12534aae6dbSAndreas Gohr        }
12634aae6dbSAndreas Gohr    }
12734aae6dbSAndreas Gohr
128*bd08ebd1SAndreas Gohr    /**
129*bd08ebd1SAndreas Gohr     * Output the given arguments using vsprintf and flush buffers
130*bd08ebd1SAndreas Gohr     */
13134aae6dbSAndreas Gohr    private function _say() {
13234aae6dbSAndreas Gohr        $args = func_get_args();
13334aae6dbSAndreas Gohr        echo vsprintf(array_shift($args)."<br />\n", $args);
13434aae6dbSAndreas Gohr        flush();
13534aae6dbSAndreas Gohr        ob_flush();
13634aae6dbSAndreas Gohr    }
13734aae6dbSAndreas Gohr
1386b2d1b30SAndreas Gohr    /**
1396b2d1b30SAndreas Gohr     * Recursive delete
1406b2d1b30SAndreas Gohr     *
1419285faa5SAndreas Gohr     * @author Jon Hassall
1429285faa5SAndreas Gohr     * @link   http://de.php.net/manual/en/function.unlink.php#87045
1436b2d1b30SAndreas Gohr     */
1449285faa5SAndreas Gohr    private function _rdel($dir) {
1459285faa5SAndreas Gohr        if(!$dh = @opendir($dir)) {
146*bd08ebd1SAndreas Gohr            return false;
1479285faa5SAndreas Gohr        }
1489285faa5SAndreas Gohr        while(false !== ($obj = readdir($dh))) {
1499285faa5SAndreas Gohr            if($obj == '.' || $obj == '..') continue;
1509285faa5SAndreas Gohr
1519285faa5SAndreas Gohr            if(!@unlink($dir.'/'.$obj)) {
1529285faa5SAndreas Gohr                $this->_rdel($dir.'/'.$obj);
1539285faa5SAndreas Gohr            }
1549285faa5SAndreas Gohr        }
1559285faa5SAndreas Gohr        closedir($dh);
156e32047e3SAndreas Gohr        return @rmdir($dir);
1576b2d1b30SAndreas Gohr    }
1586b2d1b30SAndreas Gohr
159*bd08ebd1SAndreas Gohr    /**
160*bd08ebd1SAndreas Gohr     * Download the tarball
161*bd08ebd1SAndreas Gohr     *
162*bd08ebd1SAndreas Gohr     * @return bool
163*bd08ebd1SAndreas Gohr     */
16434aae6dbSAndreas Gohr    private function _step_download() {
1653c38de15SAndreas Gohr        $this->_say($this->getLang('dl_from'), $this->tgzurl);
16634aae6dbSAndreas Gohr
16734aae6dbSAndreas Gohr        @set_time_limit(120);
16834aae6dbSAndreas Gohr        @ignore_user_abort();
16934aae6dbSAndreas Gohr
17034aae6dbSAndreas Gohr        $http          = new DokuHTTPClient();
17134aae6dbSAndreas Gohr        $http->timeout = 120;
17234aae6dbSAndreas Gohr        $data          = $http->get($this->tgzurl);
17334aae6dbSAndreas Gohr
17434aae6dbSAndreas Gohr        if(!$data) {
17534aae6dbSAndreas Gohr            $this->_say($http->error);
1763c38de15SAndreas Gohr            $this->_say($this->getLang('dl_fail'));
17734aae6dbSAndreas Gohr            return false;
17834aae6dbSAndreas Gohr        }
17934aae6dbSAndreas Gohr
18034aae6dbSAndreas Gohr        if(!io_saveFile($this->tgzfile, $data)) {
1813c38de15SAndreas Gohr            $this->_say($this->getLang('dl_fail'));
18234aae6dbSAndreas Gohr            return false;
18334aae6dbSAndreas Gohr        }
18434aae6dbSAndreas Gohr
185a7b56078SAndreas Gohr        $this->_say($this->getLang('dl_done'), filesize_h(strlen($data)));
1863c38de15SAndreas Gohr
18734aae6dbSAndreas Gohr        return true;
18834aae6dbSAndreas Gohr    }
18934aae6dbSAndreas Gohr
190*bd08ebd1SAndreas Gohr    /**
191*bd08ebd1SAndreas Gohr     * Unpack the tarball
192*bd08ebd1SAndreas Gohr     *
193*bd08ebd1SAndreas Gohr     * @return bool
194*bd08ebd1SAndreas Gohr     */
19534aae6dbSAndreas Gohr    private function _step_unpack() {
19634aae6dbSAndreas Gohr        global $conf;
1973c38de15SAndreas Gohr        $this->_say('<b>'.$this->getLang('pk_extract').'</b>');
19834aae6dbSAndreas Gohr
19934aae6dbSAndreas Gohr        @set_time_limit(120);
20034aae6dbSAndreas Gohr        @ignore_user_abort();
20134aae6dbSAndreas Gohr
20234aae6dbSAndreas Gohr        $tar = new VerboseTarLib($this->tgzfile);
20334aae6dbSAndreas Gohr        if($tar->_initerror < 0) {
20434aae6dbSAndreas Gohr            $this->_say($tar->TarErrorStr($tar->_initerror));
2053c38de15SAndreas Gohr            $this->_say($this->getLang('pk_fail'));
20634aae6dbSAndreas Gohr            return false;
20734aae6dbSAndreas Gohr        }
20834aae6dbSAndreas Gohr
20934aae6dbSAndreas Gohr        $ok = $tar->Extract(VerboseTarLib::FULL_ARCHIVE, $this->tgzdir, 1, $conf['fmode'], '/^(_cs|_test|\.gitignore)/');
21034aae6dbSAndreas Gohr        if($ok < 1) {
21134aae6dbSAndreas Gohr            $this->_say($tar->TarErrorStr($ok));
2123c38de15SAndreas Gohr            $this->_say($this->getLang('pk_fail'));
21334aae6dbSAndreas Gohr            return false;
21434aae6dbSAndreas Gohr        }
21534aae6dbSAndreas Gohr
2163c38de15SAndreas Gohr        $this->_say($this->getLang('pk_done'));
217738c0102SAndreas Gohr
218*bd08ebd1SAndreas Gohr        $this->_say(
219*bd08ebd1SAndreas Gohr            $this->getLang('pk_version'),
220738c0102SAndreas Gohr            hsc(file_get_contents($this->tgzdir.'/VERSION')),
221*bd08ebd1SAndreas Gohr            getVersion()
222*bd08ebd1SAndreas Gohr        );
22334aae6dbSAndreas Gohr        return true;
22434aae6dbSAndreas Gohr    }
22534aae6dbSAndreas Gohr
226*bd08ebd1SAndreas Gohr    /**
227*bd08ebd1SAndreas Gohr     * Check permissions of files to change
228*bd08ebd1SAndreas Gohr     *
229*bd08ebd1SAndreas Gohr     * @return bool
230*bd08ebd1SAndreas Gohr     */
2313c38de15SAndreas Gohr    private function _step_check() {
2323c38de15SAndreas Gohr        $this->_say($this->getLang('ck_start'));
2333c38de15SAndreas Gohr        $ok = $this->_traverse('', true);
23434aae6dbSAndreas Gohr        if($ok) {
2353c38de15SAndreas Gohr            $this->_say('<b>'.$this->getLang('ck_done').'</b>');
23634aae6dbSAndreas Gohr        } else {
2373c38de15SAndreas Gohr            $this->_say('<b>'.$this->getLang('ck_fail').'</b>');
23834aae6dbSAndreas Gohr        }
2393c38de15SAndreas Gohr        return $ok;
2403c38de15SAndreas Gohr    }
24134aae6dbSAndreas Gohr
242*bd08ebd1SAndreas Gohr    /**
243*bd08ebd1SAndreas Gohr     * Copy over new files
244*bd08ebd1SAndreas Gohr     *
245*bd08ebd1SAndreas Gohr     * @return bool
246*bd08ebd1SAndreas Gohr     */
2473c38de15SAndreas Gohr    private function _step_copy() {
2483c38de15SAndreas Gohr        $this->_say($this->getLang('cp_start'));
2493c38de15SAndreas Gohr        $ok = $this->_traverse('', false);
2503c38de15SAndreas Gohr        if($ok) {
2513c38de15SAndreas Gohr            $this->_say('<b>'.$this->getLang('cp_done').'</b>');
25263712694SAndreas Gohr            $this->_rmold();
253e32047e3SAndreas Gohr            $this->_say('<b>'.$this->getLang('finish').'</b>');
25434aae6dbSAndreas Gohr        } else {
2553c38de15SAndreas Gohr            $this->_say('<b>'.$this->getLang('cp_fail').'</b>');
25634aae6dbSAndreas Gohr        }
25734aae6dbSAndreas Gohr        return $ok;
25834aae6dbSAndreas Gohr    }
25934aae6dbSAndreas Gohr
260*bd08ebd1SAndreas Gohr    /**
261*bd08ebd1SAndreas Gohr     * Delete outdated files
262*bd08ebd1SAndreas Gohr     */
26363712694SAndreas Gohr    private function _rmold() {
26463712694SAndreas Gohr        $list = file($this->tgzdir.'data/deleted.files');
26563712694SAndreas Gohr        foreach($list as $line) {
26663712694SAndreas Gohr            $line = trim(preg_replace('/#.*$/', '', $line));
26763712694SAndreas Gohr            if(!$line) continue;
26863712694SAndreas Gohr            $file = DOKU_INC.$line;
26963712694SAndreas Gohr            if(!file_exists($file)) continue;
27063712694SAndreas Gohr            if((is_dir($file) && $this->_rdel($file)) ||
271*bd08ebd1SAndreas Gohr                @unlink($file)
272*bd08ebd1SAndreas Gohr            ) {
273e32047e3SAndreas Gohr                $this->_say($this->getLang('rm_done'), hsc($line));
27463712694SAndreas Gohr            } else {
275e32047e3SAndreas Gohr                $this->_say($this->getLang('rm_fail'), hsc($line));
27663712694SAndreas Gohr            }
27763712694SAndreas Gohr        }
27863712694SAndreas Gohr    }
27963712694SAndreas Gohr
280*bd08ebd1SAndreas Gohr    /**
281*bd08ebd1SAndreas Gohr     * Traverse over the given dir and compare it to the DokuWiki dir
282*bd08ebd1SAndreas Gohr     *
283*bd08ebd1SAndreas Gohr     * Checks what files need an update, tests for writability and copies
284*bd08ebd1SAndreas Gohr     *
285*bd08ebd1SAndreas Gohr     * @param string $dir
286*bd08ebd1SAndreas Gohr     * @param bool $dryrun do not copy but only check permissions
287*bd08ebd1SAndreas Gohr     * @return bool
288*bd08ebd1SAndreas Gohr     */
28934aae6dbSAndreas Gohr    private function _traverse($dir, $dryrun) {
29034aae6dbSAndreas Gohr        $base = $this->tgzdir;
29134aae6dbSAndreas Gohr        $ok   = true;
29234aae6dbSAndreas Gohr
29334aae6dbSAndreas Gohr        $dh = @opendir($base.'/'.$dir);
294*bd08ebd1SAndreas Gohr        if(!$dh) return false;
29534aae6dbSAndreas Gohr        while(($file = readdir($dh)) !== false) {
29634aae6dbSAndreas Gohr            if($file == '.' || $file == '..') continue;
29734aae6dbSAndreas Gohr            $from = "$base/$dir/$file";
29834aae6dbSAndreas Gohr            $to   = DOKU_INC."$dir/$file";
29934aae6dbSAndreas Gohr
30034aae6dbSAndreas Gohr            if(is_dir($from)) {
30134aae6dbSAndreas Gohr                if($dryrun) {
30234aae6dbSAndreas Gohr                    // just check for writability
30334aae6dbSAndreas Gohr                    if(!is_dir($to)) {
30434aae6dbSAndreas Gohr                        if(is_dir(dirname($to)) && !is_writable(dirname($to))) {
305a7b56078SAndreas Gohr                            $this->_say('<b>'.$this->getLang('tv_noperm').'</b>', hsc("$dir/$file"));
30634aae6dbSAndreas Gohr                            $ok = false;
30734aae6dbSAndreas Gohr                        }
30834aae6dbSAndreas Gohr                    }
30934aae6dbSAndreas Gohr                }
31034aae6dbSAndreas Gohr
31134aae6dbSAndreas Gohr                // recursion
31234aae6dbSAndreas Gohr                if(!$this->_traverse("$dir/$file", $dryrun)) {
31334aae6dbSAndreas Gohr                    $ok = false;
31434aae6dbSAndreas Gohr                }
31534aae6dbSAndreas Gohr            } else {
316f2fa6d10SAndreas Gohr                $fmd5 = md5(@file_get_contents($from));
317f2fa6d10SAndreas Gohr                $tmd5 = md5(@file_get_contents($to));
3186deeb3b1SAndreas Gohr                if($fmd5 != $tmd5 || !file_exists($to)) {
31934aae6dbSAndreas Gohr                    if($dryrun) {
32034aae6dbSAndreas Gohr                        // just check for writability
32134aae6dbSAndreas Gohr                        if((file_exists($to) && !is_writable($to)) ||
322*bd08ebd1SAndreas Gohr                            (!file_exists($to) && is_dir(dirname($to)) && !is_writable(dirname($to)))
323*bd08ebd1SAndreas Gohr                        ) {
32434aae6dbSAndreas Gohr
325a7b56078SAndreas Gohr                            $this->_say('<b>'.$this->getLang('tv_noperm').'</b>', hsc("$dir/$file"));
32634aae6dbSAndreas Gohr                            $ok = false;
32734aae6dbSAndreas Gohr                        } else {
3283c38de15SAndreas Gohr                            $this->_say($this->getLang('tv_upd'), hsc("$dir/$file"));
32934aae6dbSAndreas Gohr                        }
33034aae6dbSAndreas Gohr                    } else {
33134aae6dbSAndreas Gohr                        // check dir
33234aae6dbSAndreas Gohr                        if(io_mkdir_p(dirname($to))) {
33334aae6dbSAndreas Gohr                            // copy
33434aae6dbSAndreas Gohr                            if(!copy($from, $to)) {
3353c38de15SAndreas Gohr                                $this->_say('<b>'.$this->getLang('tv_nocopy').'</b>', hsc("$dir/$file"));
33634aae6dbSAndreas Gohr                                $ok = false;
33734aae6dbSAndreas Gohr                            } else {
3383c38de15SAndreas Gohr                                $this->_say($this->getLang('tv_done'), hsc("$dir/$file"));
33934aae6dbSAndreas Gohr                            }
34034aae6dbSAndreas Gohr                        } else {
3413c38de15SAndreas Gohr                            $this->_say('<b>'.$this->getLang('tv_nodir').'</b>', hsc("$dir"));
34234aae6dbSAndreas Gohr                            $ok = false;
34334aae6dbSAndreas Gohr                        }
34434aae6dbSAndreas Gohr                    }
34534aae6dbSAndreas Gohr                }
34634aae6dbSAndreas Gohr            }
34734aae6dbSAndreas Gohr        }
34834aae6dbSAndreas Gohr        closedir($dh);
34934aae6dbSAndreas Gohr        return $ok;
35034aae6dbSAndreas Gohr    }
35134aae6dbSAndreas Gohr}
35234aae6dbSAndreas Gohr
35334aae6dbSAndreas Gohr// vim:ts=4:sw=4:et:enc=utf-8:
354