xref: /plugin/upgrade/admin.php (revision 6b2d1b30c3ee1139558c022d41f3174ce0725c28)
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            $this->_rdel($this->tgzdir);
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    /**
104     * Recursive delete
105     *
106     * @author zibi at nora dot pl
107     * @link http://de.php.net/manual/en/function.unlink.php#100092
108     */
109    private function _rdel($path) {
110        return is_file($path)?
111               @unlink($path):
112               array_map(array($this,'_rdel'),glob($path.'/*'))==@rmdir($path);
113    }
114
115    private function _step_download(){
116        $this->_say('Downloading from %s',$this->tgzurl);
117
118        @set_time_limit(120);
119        @ignore_user_abort();
120
121        $http = new DokuHTTPClient();
122        $http->timeout = 120;
123        $data = $http->get($this->tgzurl);
124
125        if(!$data){
126            $this->_say($http->error);
127            $this->_say("Download failed.");
128            return false;
129        }
130
131        $this->_say('Received %d bytes',strlen($data));
132
133        if(!io_saveFile($this->tgzfile,$data)){
134            $this->_say("Failed to save download.");
135            return false;
136        }
137
138        return true;
139    }
140
141    private function _step_unpack(){
142        global $conf;
143        $this->_say('Extracting the archive...');
144
145        @set_time_limit(120);
146        @ignore_user_abort();
147
148        $tar = new VerboseTarLib($this->tgzfile);
149        if($tar->_initerror < 0){
150            $this->_say($tar->TarErrorStr($tar->_initerror));
151            $this->_say('Extraction failed on init');
152            return false;
153        }
154
155        $ok = $tar->Extract(VerboseTarLib::FULL_ARCHIVE,$this->tgzdir,1,$conf['fmode'],'/^(_cs|_test|\.gitignore)/');
156        if($ok < 1){
157            $this->_say($tar->TarErrorStr($ok));
158            $this->_say('Extraction failed');
159            return false;
160        }
161
162        $this->_say('Extraction done.');
163        return true;
164    }
165
166    private function _step_copy($dryrun=true){
167        $ok = $this->_traverse('',$dryrun);
168        if($dryrun){
169            if($ok){
170                $this->_say('<b>All files are writable, ready to upgrade</b>');
171            }else{
172                $this->_say('<b>Some files aren\'t writable. Uprade not possible.</b>');
173            }
174        }else{
175            if($ok){
176                $this->_say('<b>All files upgraded successfully</b>');
177
178                #FIXME delete unused files
179            }else{
180                $this->_say('<b>Some files couldn\'t be upgraded. Uh-oh. Better check manually.</b>');
181            }
182        }
183        return $ok;
184    }
185
186    private function _traverse($dir,$dryrun){
187        $base = $this->tgzdir;
188        $ok = true;
189
190        $dh = @opendir($base.'/'.$dir);
191        if(!$dh) return;
192        while(($file = readdir($dh)) !== false){
193            if($file == '.' || $file == '..') continue;
194            $from = "$base/$dir/$file";
195            $to   = DOKU_INC."$dir/$file";
196
197            if(is_dir($from)){
198                if($dryrun){
199                    // just check for writability
200                    if(!is_dir($to)){
201                        if(is_dir(dirname($to)) && !is_writable(dirname($to))){
202                            $this->_say("<b>%s is not writable</b>",hsc("$dir/$file"));
203                            $ok = false;
204                        }
205                    }
206                }
207
208                // recursion
209                if(!$this->_traverse("$dir/$file",$dryrun)){
210                    $ok = false;
211                }
212            }else{
213                $fmd5 = md5($from);
214                $tmd5 = md5($to);
215                if($fmd5 != $tmd5){
216                    if($dryrun){
217                        // just check for writability
218                        if( (file_exists($to) && !is_writable($to)) ||
219                            (!file_exists($to) && is_dir(dirname($to)) && !is_writable(dirname($to))) ){
220
221                            $this->_say("<b>%s is not writable</b>",hsc("$dir/$file"));
222                            $ok = false;
223                        }else{
224                            $this->_say("%s needs update",hsc("$dir/$file"));
225                        }
226                    }else{
227                        // check dir
228                        if(io_mkdir_p(dirname($to))){
229                            // copy
230                            if(!copy($from,$to)){
231                                $this->_say("<b>%s couldn't be copied</b>",hsc("$dir/$file"));
232                                $ok = false;
233                            }else{
234                                $this->_say("%s updated",hsc("$dir/$file"));
235                            }
236                        }else{
237                            $this->_say("<b>failed to create %s</b>",hsc("$dir"));
238                            $ok = false;
239                        }
240                    }
241                }
242            }
243        }
244        closedir($dh);
245        return $ok;
246    }
247}
248
249// vim:ts=4:sw=4:et:enc=utf-8:
250