xref: /plugin/upgrade/admin.php (revision 34aae6db7e6952fa49c22c6dc013e0e2d0dff57a)
1<?php
2/**
3 * DokuWiki Plugin update (Admin Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Andreas Gohr <andi@splitbrain.org>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) die();
11
12if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
13if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
14if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
15
16require_once DOKU_PLUGIN.'admin.php';
17require_once DOKU_PLUGIN.'update/VerboseTarLib.class.php';
18
19class admin_plugin_update extends DokuWiki_Admin_Plugin {
20    private $tgzurl;
21    private $tgzfile;
22    private $tgzdir;
23
24    public function __construct(){
25        global $conf;
26
27        $branch = 'stable';
28
29        $this->tgzurl  = 'http://github.com/splitbrain/dokuwiki/tarball/'.$branch;
30        $this->tgzfile = $conf['tmpdir'].'/dokuwiki-update.tgz';
31        $this->tgzdir  = $conf['tmpdir'].'/dokuwiki-update/';
32    }
33
34    function getMenuSort() { return 555; }
35
36    function handle() {
37    }
38
39    public function html() {
40        $abrt = false;
41        $next = false;
42
43        echo '<h1>' . $this->getLang('menu') . '</h1>';
44#FIXME check and abort on safemode
45
46        $this->_stepit(&$abrt, &$next);
47
48#FIXME add security check
49        echo '<form action="" method="get">';
50        echo '<input type="hidden" name="do" value="admin" />';
51        echo '<input type="hidden" name="page" value="update" />';
52        if($next) echo '<input type="submit" name="step['.$next.']" value="Continue" />';
53        if($abrt) echo '<input type="submit" name="step[cancel]" value="Abort" />';
54        echo '</form>';
55    }
56
57    private function _stepit(&$abrt, &$next){
58        if(isset($_REQUEST['step']) && is_array($_REQUEST['step'])){
59            $step = array_shift(array_keys($_REQUEST['step']));
60        }else{
61            $step = '';
62        }
63
64        if($step == 'cancel'){
65            # cleanup
66            @unlink($this->tgzfile);
67
68            $step = '';
69        }
70
71        if($step){
72            $abrt = true;
73            $next = false;
74            $this->_say('<div id="plugin__update">');
75            if(!file_exists($this->tgzfile)){
76                if($this->_step_download()) $next = 'unpack';
77            }elseif(!is_dir($this->tgzdir)){
78                if($this->_step_unpack()) $next = 'check';
79            }elseif($step != 'upgrade'){
80                if($this->_step_copy(true)) $next = 'upgrade';
81            }elseif($step == 'upgrade'){
82                if($this->_step_copy(false)) $next = 'cancel';
83            }else{
84                #continue
85                echo 'huh';
86            }
87            $this->_say('</div>');
88        }else{
89            # first time run, show intro
90            echo $this->locale_xhtml('step0');
91            $abrt = false;
92            $next = 'download';
93        }
94    }
95
96    private function _say(){
97        $args = func_get_args();
98        echo vsprintf(array_shift($args)."<br />\n",$args);
99        flush();
100        ob_flush();
101    }
102
103    private function _step_download(){
104        $this->_say('Downloading from %s',$this->tgzurl);
105
106        @set_time_limit(120);
107        @ignore_user_abort();
108
109        $http = new DokuHTTPClient();
110        $http->timeout = 120;
111        $data = $http->get($this->tgzurl);
112
113        if(!$data){
114            $this->_say($http->error);
115            $this->_say("Download failed.");
116            return false;
117        }
118
119        $this->_say('Received %d bytes',strlen($data));
120
121        if(!io_saveFile($this->tgzfile,$data)){
122            $this->_say("Failed to save download.");
123            return false;
124        }
125
126        return true;
127    }
128
129    private function _step_unpack(){
130        global $conf;
131        $this->_say('Extracting the archive...');
132
133        @set_time_limit(120);
134        @ignore_user_abort();
135
136        $tar = new VerboseTarLib($this->tgzfile);
137        if($tar->_initerror < 0){
138            $this->_say($tar->TarErrorStr($tar->_initerror));
139            $this->_say('Extraction failed on init');
140            return false;
141        }
142
143        $ok = $tar->Extract(VerboseTarLib::FULL_ARCHIVE,$this->tgzdir,1,$conf['fmode'],'/^(_cs|_test|\.gitignore)/');
144        if($ok < 1){
145            $this->_say($tar->TarErrorStr($ok));
146            $this->_say('Extraction failed');
147            return false;
148        }
149
150        $this->_say('Extraction done.');
151        return true;
152    }
153
154    private function _step_copy($dryrun=true){
155        $ok = $this->_traverse('',$dryrun);
156        if($dryrun){
157            if($ok){
158                $this->_say('<b>All files are writable, ready to upgrade</b>');
159            }else{
160                $this->_say('<b>Some files aren\'t writable. Uprade not possible.</b>');
161            }
162        }else{
163            if($ok){
164                $this->_say('<b>All files upgraded successfully</b>');
165
166                #FIXME delete unused files
167            }else{
168                $this->_say('<b>Some files couldn\'t be upgraded. Uh-oh. Better check manually.</b>');
169            }
170        }
171        return $ok;
172    }
173
174    private function _traverse($dir,$dryrun){
175        $base = $this->tgzdir;
176        $ok = true;
177
178        $dh = @opendir($base.'/'.$dir);
179        if(!$dh) return;
180        while(($file = readdir($dh)) !== false){
181            if($file == '.' || $file == '..') continue;
182            $from = "$base/$dir/$file";
183            $to   = DOKU_INC."$dir/$file";
184
185            if(is_dir($from)){
186                if($dryrun){
187                    // just check for writability
188                    if(!is_dir($to)){
189                        if(is_dir(dirname($to)) && !is_writable(dirname($to))){
190                            $this->_say("<b>%s is not writable</b>",hsc("$dir/$file"));
191                            $ok = false;
192                        }
193                    }
194                }
195
196                // recursion
197                if(!$this->_traverse("$dir/$file",$dryrun)){
198                    $ok = false;
199                }
200            }else{
201                $fmd5 = md5($from);
202                $tmd5 = md5($to);
203                if($fmd5 != $tmd5){
204                    if($dryrun){
205                        // just check for writability
206                        if( (file_exists($to) && !is_writable($to)) ||
207                            (!file_exists($to) && is_dir(dirname($to)) && !is_writable(dirname($to))) ){
208
209                            $this->_say("<b>%s is not writable</b>",hsc("$dir/$file"));
210                            $ok = false;
211                        }else{
212                            $this->_say("%s needs update",hsc("$dir/$file"));
213                        }
214                    }else{
215                        // check dir
216                        if(io_mkdir_p(dirname($to))){
217                            // copy
218                            if(!copy($from,$to)){
219                                $this->_say("<b>%s couldn't be copied</b>",hsc("$dir/$file"));
220                                $ok = false;
221                            }else{
222                                $this->_say("%s updated",hsc("$dir/$file"));
223                            }
224                        }else{
225                            $this->_say("<b>failed to create %s</b>",hsc("$dir"));
226                            $ok = false;
227                        }
228                    }
229                }
230            }
231        }
232        closedir($dh);
233        return $ok;
234    }
235}
236
237// vim:ts=4:sw=4:et:enc=utf-8:
238