xref: /plugin/upgrade/helper.php (revision 4e3e87e45b11e1184e5fb1995e14bdc3ab70ccf5)
1*4e3e87e4SAndreas Gohr<?php
2*4e3e87e4SAndreas Gohr/**
3*4e3e87e4SAndreas Gohr * DokuWiki Plugin upgrade (Helper Component)
4*4e3e87e4SAndreas Gohr *
5*4e3e87e4SAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6*4e3e87e4SAndreas Gohr * @author  Andreas Gohr <andi@splitbrain.org>
7*4e3e87e4SAndreas Gohr */
8*4e3e87e4SAndreas Gohr
9*4e3e87e4SAndreas Gohruse dokuwiki\plugin\upgrade\HTTP\DokuHTTPClient;
10*4e3e87e4SAndreas Gohruse splitbrain\PHPArchive\FileInfo;
11*4e3e87e4SAndreas Gohruse splitbrain\PHPArchive\Tar;
12*4e3e87e4SAndreas Gohr
13*4e3e87e4SAndreas Gohrclass helper_plugin_upgrade extends DokuWiki_Plugin
14*4e3e87e4SAndreas Gohr{
15*4e3e87e4SAndreas Gohr    /** @var string download URL for the new DokuWiki release */
16*4e3e87e4SAndreas Gohr    public $tgzurl;
17*4e3e87e4SAndreas Gohr    /** @var string full path to where the file will be downloaded to */
18*4e3e87e4SAndreas Gohr    public $tgzfile;
19*4e3e87e4SAndreas Gohr    /** @var string full path to where the file will be extracted to */
20*4e3e87e4SAndreas Gohr    public $tgzdir;
21*4e3e87e4SAndreas Gohr    /** @var string URL to the VERSION file of the new DokuWiki release */
22*4e3e87e4SAndreas Gohr    public $tgzversion;
23*4e3e87e4SAndreas Gohr    /** @var string URL to the plugin.info.txt file of the upgrade plugin */
24*4e3e87e4SAndreas Gohr    public $pluginversion;
25*4e3e87e4SAndreas Gohr
26*4e3e87e4SAndreas Gohr    /** @var admin_plugin_upgrade|cli_plugin_upgrade */
27*4e3e87e4SAndreas Gohr    protected $logger;
28*4e3e87e4SAndreas Gohr
29*4e3e87e4SAndreas Gohr    public function __construct()
30*4e3e87e4SAndreas Gohr    {
31*4e3e87e4SAndreas Gohr        global $conf;
32*4e3e87e4SAndreas Gohr
33*4e3e87e4SAndreas Gohr        $branch = 'stable';
34*4e3e87e4SAndreas Gohr
35*4e3e87e4SAndreas Gohr        $this->tgzurl = "https://github.com/splitbrain/dokuwiki/archive/$branch.tar.gz";
36*4e3e87e4SAndreas Gohr        $this->tgzfile = $conf['tmpdir'] . '/dokuwiki-upgrade.tgz';
37*4e3e87e4SAndreas Gohr        $this->tgzdir = $conf['tmpdir'] . '/dokuwiki-upgrade/';
38*4e3e87e4SAndreas Gohr        $this->tgzversion = "https://raw.githubusercontent.com/splitbrain/dokuwiki/$branch/VERSION";
39*4e3e87e4SAndreas Gohr        $this->pluginversion = "https://raw.githubusercontent.com/splitbrain/dokuwiki-plugin-upgrade/master/plugin.info.txt";
40*4e3e87e4SAndreas Gohr    }
41*4e3e87e4SAndreas Gohr
42*4e3e87e4SAndreas Gohr    /**
43*4e3e87e4SAndreas Gohr     * @param admin_plugin_upgrade|cli_plugin_upgrade $logger Logger object
44*4e3e87e4SAndreas Gohr     * @return void
45*4e3e87e4SAndreas Gohr     */
46*4e3e87e4SAndreas Gohr    public function setLogger($logger)
47*4e3e87e4SAndreas Gohr    {
48*4e3e87e4SAndreas Gohr        $this->logger = $logger;
49*4e3e87e4SAndreas Gohr    }
50*4e3e87e4SAndreas Gohr
51*4e3e87e4SAndreas Gohr    // region Steps
52*4e3e87e4SAndreas Gohr
53*4e3e87e4SAndreas Gohr    /**
54*4e3e87e4SAndreas Gohr     * Check various versions
55*4e3e87e4SAndreas Gohr     *
56*4e3e87e4SAndreas Gohr     * @return bool
57*4e3e87e4SAndreas Gohr     */
58*4e3e87e4SAndreas Gohr    public function checkVersions()
59*4e3e87e4SAndreas Gohr    {
60*4e3e87e4SAndreas Gohr        $ok = true;
61*4e3e87e4SAndreas Gohr
62*4e3e87e4SAndreas Gohr        // we need SSL - only newer HTTPClients check that themselves
63*4e3e87e4SAndreas Gohr        if (!in_array('ssl', stream_get_transports())) {
64*4e3e87e4SAndreas Gohr            $this->log('error', $this->getLang('vs_ssl'));
65*4e3e87e4SAndreas Gohr            $ok = false;
66*4e3e87e4SAndreas Gohr        }
67*4e3e87e4SAndreas Gohr
68*4e3e87e4SAndreas Gohr        // get the available version
69*4e3e87e4SAndreas Gohr        $http = new DokuHTTPClient();
70*4e3e87e4SAndreas Gohr        $tgzversion = trim($http->get($this->tgzversion));
71*4e3e87e4SAndreas Gohr        if (!$tgzversion) {
72*4e3e87e4SAndreas Gohr            $this->log('error', $this->getLang('vs_tgzno') . ' ' . hsc($http->error));
73*4e3e87e4SAndreas Gohr            $ok = false;
74*4e3e87e4SAndreas Gohr        }
75*4e3e87e4SAndreas Gohr        $tgzversionnum = $this->dateFromVersion($tgzversion);
76*4e3e87e4SAndreas Gohr        if ($tgzversionnum === 0) {
77*4e3e87e4SAndreas Gohr            $this->log('error', $this->getLang('vs_tgzno'));
78*4e3e87e4SAndreas Gohr            $ok = false;
79*4e3e87e4SAndreas Gohr        } else {
80*4e3e87e4SAndreas Gohr            $this->log('notice', $this->getLang('vs_tgz'), $tgzversion);
81*4e3e87e4SAndreas Gohr        }
82*4e3e87e4SAndreas Gohr
83*4e3e87e4SAndreas Gohr        // get the current version
84*4e3e87e4SAndreas Gohr        $versiondata = getVersionData();
85*4e3e87e4SAndreas Gohr        $version = trim($versiondata['date']);
86*4e3e87e4SAndreas Gohr        $versionnum = $this->dateFromVersion($version);
87*4e3e87e4SAndreas Gohr        $this->log('notice', $this->getLang('vs_local'), $version);
88*4e3e87e4SAndreas Gohr
89*4e3e87e4SAndreas Gohr        // compare versions
90*4e3e87e4SAndreas Gohr        if (!$versionnum) {
91*4e3e87e4SAndreas Gohr            $this->log('warning', $this->getLang('vs_localno'));
92*4e3e87e4SAndreas Gohr            $ok = false;
93*4e3e87e4SAndreas Gohr        } else if ($tgzversionnum) {
94*4e3e87e4SAndreas Gohr            if ($tgzversionnum < $versionnum) {
95*4e3e87e4SAndreas Gohr                $this->log('warning', $this->getLang('vs_newer'));
96*4e3e87e4SAndreas Gohr                $ok = false;
97*4e3e87e4SAndreas Gohr            } elseif ($tgzversionnum == $versionnum && $tgzversion == $version) {
98*4e3e87e4SAndreas Gohr                $this->log('warning', $this->getLang('vs_same'));
99*4e3e87e4SAndreas Gohr                $ok = false;
100*4e3e87e4SAndreas Gohr            }
101*4e3e87e4SAndreas Gohr        }
102*4e3e87e4SAndreas Gohr
103*4e3e87e4SAndreas Gohr        // check plugin version
104*4e3e87e4SAndreas Gohr        $pluginversion = $http->get($this->pluginversion);
105*4e3e87e4SAndreas Gohr        if ($pluginversion) {
106*4e3e87e4SAndreas Gohr            $plugininfo = linesToHash(explode("\n", $pluginversion));
107*4e3e87e4SAndreas Gohr            $myinfo = $this->getInfo();
108*4e3e87e4SAndreas Gohr            if ($plugininfo['date'] > $myinfo['date']) {
109*4e3e87e4SAndreas Gohr                $this->log('warning', $this->getLang('vs_plugin'), $plugininfo['date']);
110*4e3e87e4SAndreas Gohr                $ok = false;
111*4e3e87e4SAndreas Gohr            }
112*4e3e87e4SAndreas Gohr        }
113*4e3e87e4SAndreas Gohr
114*4e3e87e4SAndreas Gohr        // check if PHP is up to date
115*4e3e87e4SAndreas Gohr        $minphp = '7.2'; // FIXME get this from the composer file upstream
116*4e3e87e4SAndreas Gohr        if (version_compare(phpversion(), $minphp, '<')) {
117*4e3e87e4SAndreas Gohr            $this->log('error', $this->getLang('vs_php'), $minphp, phpversion());
118*4e3e87e4SAndreas Gohr            $ok = false;
119*4e3e87e4SAndreas Gohr        }
120*4e3e87e4SAndreas Gohr
121*4e3e87e4SAndreas Gohr        return $ok;
122*4e3e87e4SAndreas Gohr    }
123*4e3e87e4SAndreas Gohr
124*4e3e87e4SAndreas Gohr    /**
125*4e3e87e4SAndreas Gohr     * Download the tarball
126*4e3e87e4SAndreas Gohr     *
127*4e3e87e4SAndreas Gohr     * @return bool
128*4e3e87e4SAndreas Gohr     */
129*4e3e87e4SAndreas Gohr    public function downloadTarball()
130*4e3e87e4SAndreas Gohr    {
131*4e3e87e4SAndreas Gohr        $this->log('notice', $this->getLang('dl_from'), $this->tgzurl);
132*4e3e87e4SAndreas Gohr
133*4e3e87e4SAndreas Gohr        @set_time_limit(300);
134*4e3e87e4SAndreas Gohr        @ignore_user_abort();
135*4e3e87e4SAndreas Gohr
136*4e3e87e4SAndreas Gohr        $http = new DokuHTTPClient();
137*4e3e87e4SAndreas Gohr        $http->timeout = 300;
138*4e3e87e4SAndreas Gohr        $data = $http->get($this->tgzurl);
139*4e3e87e4SAndreas Gohr
140*4e3e87e4SAndreas Gohr        if (!$data) {
141*4e3e87e4SAndreas Gohr            $this->log('error', $http->error);
142*4e3e87e4SAndreas Gohr            $this->log('error', $this->getLang('dl_fail'));
143*4e3e87e4SAndreas Gohr            return false;
144*4e3e87e4SAndreas Gohr        }
145*4e3e87e4SAndreas Gohr
146*4e3e87e4SAndreas Gohr        if (!io_saveFile($this->tgzfile, $data)) {
147*4e3e87e4SAndreas Gohr            $this->log('error', $this->getLang('dl_fail'));
148*4e3e87e4SAndreas Gohr            return false;
149*4e3e87e4SAndreas Gohr        }
150*4e3e87e4SAndreas Gohr
151*4e3e87e4SAndreas Gohr        $this->log('success', $this->getLang('dl_done'), filesize_h(strlen($data)));
152*4e3e87e4SAndreas Gohr        return true;
153*4e3e87e4SAndreas Gohr    }
154*4e3e87e4SAndreas Gohr
155*4e3e87e4SAndreas Gohr    /**
156*4e3e87e4SAndreas Gohr     * Unpack the tarball
157*4e3e87e4SAndreas Gohr     *
158*4e3e87e4SAndreas Gohr     * @return bool
159*4e3e87e4SAndreas Gohr     */
160*4e3e87e4SAndreas Gohr    public function extractTarball()
161*4e3e87e4SAndreas Gohr    {
162*4e3e87e4SAndreas Gohr        $this->log('notice', '<b>' . $this->getLang('pk_extract') . '</b>');
163*4e3e87e4SAndreas Gohr
164*4e3e87e4SAndreas Gohr        @set_time_limit(300);
165*4e3e87e4SAndreas Gohr        @ignore_user_abort();
166*4e3e87e4SAndreas Gohr
167*4e3e87e4SAndreas Gohr        try {
168*4e3e87e4SAndreas Gohr            $tar = new Tar();
169*4e3e87e4SAndreas Gohr            $tar->setCallback(function ($file) {
170*4e3e87e4SAndreas Gohr                /** @var FileInfo $file */
171*4e3e87e4SAndreas Gohr                $this->log('info', $file->getPath());
172*4e3e87e4SAndreas Gohr            });
173*4e3e87e4SAndreas Gohr            $tar->open($this->tgzfile);
174*4e3e87e4SAndreas Gohr            $tar->extract($this->tgzdir, 1);
175*4e3e87e4SAndreas Gohr            $tar->close();
176*4e3e87e4SAndreas Gohr        } catch (Exception $e) {
177*4e3e87e4SAndreas Gohr            $this->log('error', $e->getMessage());
178*4e3e87e4SAndreas Gohr            $this->log('error', $this->getLang('pk_fail'));
179*4e3e87e4SAndreas Gohr            return false;
180*4e3e87e4SAndreas Gohr        }
181*4e3e87e4SAndreas Gohr
182*4e3e87e4SAndreas Gohr        $this->log('success', $this->getLang('pk_done'));
183*4e3e87e4SAndreas Gohr
184*4e3e87e4SAndreas Gohr        $this->log(
185*4e3e87e4SAndreas Gohr            'notice',
186*4e3e87e4SAndreas Gohr            $this->getLang('pk_version'),
187*4e3e87e4SAndreas Gohr            hsc(file_get_contents($this->tgzdir . '/VERSION')),
188*4e3e87e4SAndreas Gohr            getVersion()
189*4e3e87e4SAndreas Gohr        );
190*4e3e87e4SAndreas Gohr        return true;
191*4e3e87e4SAndreas Gohr    }
192*4e3e87e4SAndreas Gohr
193*4e3e87e4SAndreas Gohr    /**
194*4e3e87e4SAndreas Gohr     * Check permissions of files to change
195*4e3e87e4SAndreas Gohr     *
196*4e3e87e4SAndreas Gohr     * @return bool
197*4e3e87e4SAndreas Gohr     */
198*4e3e87e4SAndreas Gohr    public function checkPermissions()
199*4e3e87e4SAndreas Gohr    {
200*4e3e87e4SAndreas Gohr        $this->log('notice', $this->getLang('ck_start'));
201*4e3e87e4SAndreas Gohr        $ok = $this->traverseCheckAndCopy('', true);
202*4e3e87e4SAndreas Gohr        if ($ok) {
203*4e3e87e4SAndreas Gohr            $this->log('success', '<b>' . $this->getLang('ck_done') . '</b>');
204*4e3e87e4SAndreas Gohr        } else {
205*4e3e87e4SAndreas Gohr            $this->log('error', '<b>' . $this->getLang('ck_fail') . '</b>');
206*4e3e87e4SAndreas Gohr        }
207*4e3e87e4SAndreas Gohr        return $ok;
208*4e3e87e4SAndreas Gohr    }
209*4e3e87e4SAndreas Gohr
210*4e3e87e4SAndreas Gohr    /**
211*4e3e87e4SAndreas Gohr     * Copy over new files
212*4e3e87e4SAndreas Gohr     *
213*4e3e87e4SAndreas Gohr     * @return bool
214*4e3e87e4SAndreas Gohr     */
215*4e3e87e4SAndreas Gohr    public function copyFiles()
216*4e3e87e4SAndreas Gohr    {
217*4e3e87e4SAndreas Gohr        $this->log('notice', $this->getLang('cp_start'));
218*4e3e87e4SAndreas Gohr        $ok = $this->traverseCheckAndCopy('', false);
219*4e3e87e4SAndreas Gohr        if ($ok) {
220*4e3e87e4SAndreas Gohr            $this->log('success', '<b>' . $this->getLang('cp_done') . '</b>');
221*4e3e87e4SAndreas Gohr        } else {
222*4e3e87e4SAndreas Gohr            $this->log('error', '<b>' . $this->getLang('cp_fail') . '</b>');
223*4e3e87e4SAndreas Gohr        }
224*4e3e87e4SAndreas Gohr        return $ok;
225*4e3e87e4SAndreas Gohr    }
226*4e3e87e4SAndreas Gohr
227*4e3e87e4SAndreas Gohr    /**
228*4e3e87e4SAndreas Gohr     * Delete outdated files
229*4e3e87e4SAndreas Gohr     */
230*4e3e87e4SAndreas Gohr    public function deleteObsoleteFiles()
231*4e3e87e4SAndreas Gohr    {
232*4e3e87e4SAndreas Gohr        global $conf;
233*4e3e87e4SAndreas Gohr
234*4e3e87e4SAndreas Gohr        $list = file($this->tgzdir . 'data/deleted.files');
235*4e3e87e4SAndreas Gohr        foreach ($list as $line) {
236*4e3e87e4SAndreas Gohr            $line = trim(preg_replace('/#.*$/', '', $line));
237*4e3e87e4SAndreas Gohr            if (!$line) continue;
238*4e3e87e4SAndreas Gohr            $file = DOKU_INC . $line;
239*4e3e87e4SAndreas Gohr            if (!file_exists($file)) continue;
240*4e3e87e4SAndreas Gohr
241*4e3e87e4SAndreas Gohr            // check that the given file is a case sensitive match
242*4e3e87e4SAndreas Gohr            if (basename(realpath($file)) != basename($file)) {
243*4e3e87e4SAndreas Gohr                $this->log('info', $this->getLang('rm_mismatch'), hsc($line));
244*4e3e87e4SAndreas Gohr                continue;
245*4e3e87e4SAndreas Gohr            }
246*4e3e87e4SAndreas Gohr
247*4e3e87e4SAndreas Gohr            if ((is_dir($file) && $this->recursiveDelete($file)) ||
248*4e3e87e4SAndreas Gohr                @unlink($file)
249*4e3e87e4SAndreas Gohr            ) {
250*4e3e87e4SAndreas Gohr                $this->log('info', $this->getLang('rm_done'), hsc($line));
251*4e3e87e4SAndreas Gohr            } else {
252*4e3e87e4SAndreas Gohr                $this->log('error', $this->getLang('rm_fail'), hsc($line));
253*4e3e87e4SAndreas Gohr            }
254*4e3e87e4SAndreas Gohr        }
255*4e3e87e4SAndreas Gohr        // delete install
256*4e3e87e4SAndreas Gohr        @unlink(DOKU_INC . 'install.php');
257*4e3e87e4SAndreas Gohr
258*4e3e87e4SAndreas Gohr        // make sure update message will be gone
259*4e3e87e4SAndreas Gohr        @touch(DOKU_INC . 'doku.php');
260*4e3e87e4SAndreas Gohr        @unlink($conf['cachedir'] . '/messages.txt');
261*4e3e87e4SAndreas Gohr
262*4e3e87e4SAndreas Gohr        // clear opcache
263*4e3e87e4SAndreas Gohr        if (function_exists('opcache_reset')) {
264*4e3e87e4SAndreas Gohr            opcache_reset();
265*4e3e87e4SAndreas Gohr        }
266*4e3e87e4SAndreas Gohr
267*4e3e87e4SAndreas Gohr        $this->log('success', '<b>' . $this->getLang('finish') . '</b>');
268*4e3e87e4SAndreas Gohr        return true;
269*4e3e87e4SAndreas Gohr    }
270*4e3e87e4SAndreas Gohr
271*4e3e87e4SAndreas Gohr    /**
272*4e3e87e4SAndreas Gohr     * Remove the downloaded and extracted files
273*4e3e87e4SAndreas Gohr     *
274*4e3e87e4SAndreas Gohr     * @return bool
275*4e3e87e4SAndreas Gohr     */
276*4e3e87e4SAndreas Gohr    public function cleanUp()
277*4e3e87e4SAndreas Gohr    {
278*4e3e87e4SAndreas Gohr        @unlink($this->tgzfile);
279*4e3e87e4SAndreas Gohr        $this->recursiveDelete($this->tgzdir);
280*4e3e87e4SAndreas Gohr        return true;
281*4e3e87e4SAndreas Gohr    }
282*4e3e87e4SAndreas Gohr
283*4e3e87e4SAndreas Gohr    // endregion
284*4e3e87e4SAndreas Gohr
285*4e3e87e4SAndreas Gohr    /**
286*4e3e87e4SAndreas Gohr     * Traverse over the given dir and compare it to the DokuWiki dir
287*4e3e87e4SAndreas Gohr     *
288*4e3e87e4SAndreas Gohr     * Checks what files need an update, tests for writability and copies
289*4e3e87e4SAndreas Gohr     *
290*4e3e87e4SAndreas Gohr     * @param string $dir
291*4e3e87e4SAndreas Gohr     * @param bool $dryrun do not copy but only check permissions
292*4e3e87e4SAndreas Gohr     * @return bool
293*4e3e87e4SAndreas Gohr     */
294*4e3e87e4SAndreas Gohr    private function traverseCheckAndCopy($dir, $dryrun)
295*4e3e87e4SAndreas Gohr    {
296*4e3e87e4SAndreas Gohr        $base = $this->tgzdir;
297*4e3e87e4SAndreas Gohr        $ok = true;
298*4e3e87e4SAndreas Gohr
299*4e3e87e4SAndreas Gohr        $dh = @opendir($base . '/' . $dir);
300*4e3e87e4SAndreas Gohr        if (!$dh) return false;
301*4e3e87e4SAndreas Gohr        while (($file = readdir($dh)) !== false) {
302*4e3e87e4SAndreas Gohr            if ($file == '.' || $file == '..') continue;
303*4e3e87e4SAndreas Gohr            $from = "$base/$dir/$file";
304*4e3e87e4SAndreas Gohr            $to = DOKU_INC . "$dir/$file";
305*4e3e87e4SAndreas Gohr
306*4e3e87e4SAndreas Gohr            if (is_dir($from)) {
307*4e3e87e4SAndreas Gohr                if ($dryrun) {
308*4e3e87e4SAndreas Gohr                    // just check for writability
309*4e3e87e4SAndreas Gohr                    if (!is_dir($to)) {
310*4e3e87e4SAndreas Gohr                        if (is_dir(dirname($to)) && !is_writable(dirname($to))) {
311*4e3e87e4SAndreas Gohr                            $this->log('error', '<b>' . $this->getLang('tv_noperm') . '</b>', hsc("$dir/$file"));
312*4e3e87e4SAndreas Gohr                            $ok = false;
313*4e3e87e4SAndreas Gohr                        }
314*4e3e87e4SAndreas Gohr                    }
315*4e3e87e4SAndreas Gohr                }
316*4e3e87e4SAndreas Gohr
317*4e3e87e4SAndreas Gohr                // recursion
318*4e3e87e4SAndreas Gohr                if (!$this->traverseCheckAndCopy("$dir/$file", $dryrun)) {
319*4e3e87e4SAndreas Gohr                    $ok = false;
320*4e3e87e4SAndreas Gohr                }
321*4e3e87e4SAndreas Gohr            } else {
322*4e3e87e4SAndreas Gohr                $fmd5 = md5(@file_get_contents($from));
323*4e3e87e4SAndreas Gohr                $tmd5 = md5(@file_get_contents($to));
324*4e3e87e4SAndreas Gohr                if ($fmd5 != $tmd5 || !file_exists($to)) {
325*4e3e87e4SAndreas Gohr                    if ($dryrun) {
326*4e3e87e4SAndreas Gohr                        // just check for writability
327*4e3e87e4SAndreas Gohr                        if ((file_exists($to) && !is_writable($to)) ||
328*4e3e87e4SAndreas Gohr                            (!file_exists($to) && is_dir(dirname($to)) && !is_writable(dirname($to)))
329*4e3e87e4SAndreas Gohr                        ) {
330*4e3e87e4SAndreas Gohr
331*4e3e87e4SAndreas Gohr                            $this->log('error', '<b>' . $this->getLang('tv_noperm') . '</b>', hsc("$dir/$file"));
332*4e3e87e4SAndreas Gohr                            $ok = false;
333*4e3e87e4SAndreas Gohr                        } else {
334*4e3e87e4SAndreas Gohr                            $this->log('info', $this->getLang('tv_upd'), hsc("$dir/$file"));
335*4e3e87e4SAndreas Gohr                        }
336*4e3e87e4SAndreas Gohr                    } else {
337*4e3e87e4SAndreas Gohr                        // check dir
338*4e3e87e4SAndreas Gohr                        if (io_mkdir_p(dirname($to))) {
339*4e3e87e4SAndreas Gohr                            // remove existing (avoid case sensitivity problems)
340*4e3e87e4SAndreas Gohr                            if (file_exists($to) && !@unlink($to)) {
341*4e3e87e4SAndreas Gohr                                $this->log('error', '<b>' . $this->getLang('tv_nodel') . '</b>', hsc("$dir/$file"));
342*4e3e87e4SAndreas Gohr                                $ok = false;
343*4e3e87e4SAndreas Gohr                            }
344*4e3e87e4SAndreas Gohr                            // copy
345*4e3e87e4SAndreas Gohr                            if (!copy($from, $to)) {
346*4e3e87e4SAndreas Gohr                                $this->log('error', '<b>' . $this->getLang('tv_nocopy') . '</b>', hsc("$dir/$file"));
347*4e3e87e4SAndreas Gohr                                $ok = false;
348*4e3e87e4SAndreas Gohr                            } else {
349*4e3e87e4SAndreas Gohr                                $this->log('info', $this->getLang('tv_done'), hsc("$dir/$file"));
350*4e3e87e4SAndreas Gohr                            }
351*4e3e87e4SAndreas Gohr                        } else {
352*4e3e87e4SAndreas Gohr                            $this->log('error', '<b>' . $this->getLang('tv_nodir') . '</b>', hsc("$dir"));
353*4e3e87e4SAndreas Gohr                            $ok = false;
354*4e3e87e4SAndreas Gohr                        }
355*4e3e87e4SAndreas Gohr                    }
356*4e3e87e4SAndreas Gohr                }
357*4e3e87e4SAndreas Gohr            }
358*4e3e87e4SAndreas Gohr        }
359*4e3e87e4SAndreas Gohr        closedir($dh);
360*4e3e87e4SAndreas Gohr        return $ok;
361*4e3e87e4SAndreas Gohr    }
362*4e3e87e4SAndreas Gohr
363*4e3e87e4SAndreas Gohr    // region utilities
364*4e3e87e4SAndreas Gohr
365*4e3e87e4SAndreas Gohr    /**
366*4e3e87e4SAndreas Gohr     * Figure out the release date from the version string
367*4e3e87e4SAndreas Gohr     *
368*4e3e87e4SAndreas Gohr     * @param $version
369*4e3e87e4SAndreas Gohr     * @return int|string returns 0 if the version can't be read
370*4e3e87e4SAndreas Gohr     */
371*4e3e87e4SAndreas Gohr    protected function dateFromVersion($version)
372*4e3e87e4SAndreas Gohr    {
373*4e3e87e4SAndreas Gohr        if (preg_match('/(^|\D)(\d\d\d\d-\d\d-\d\d)(\D|$)/i', $version, $m)) {
374*4e3e87e4SAndreas Gohr            return $m[2];
375*4e3e87e4SAndreas Gohr        }
376*4e3e87e4SAndreas Gohr        return 0;
377*4e3e87e4SAndreas Gohr    }
378*4e3e87e4SAndreas Gohr
379*4e3e87e4SAndreas Gohr    /**
380*4e3e87e4SAndreas Gohr     * Recursive delete
381*4e3e87e4SAndreas Gohr     *
382*4e3e87e4SAndreas Gohr     * @author Jon Hassall
383*4e3e87e4SAndreas Gohr     * @link   http://de.php.net/manual/en/function.unlink.php#87045
384*4e3e87e4SAndreas Gohr     */
385*4e3e87e4SAndreas Gohr    protected function recursiveDelete($dir)
386*4e3e87e4SAndreas Gohr    {
387*4e3e87e4SAndreas Gohr        if (!$dh = @opendir($dir)) {
388*4e3e87e4SAndreas Gohr            return false;
389*4e3e87e4SAndreas Gohr        }
390*4e3e87e4SAndreas Gohr        while (false !== ($obj = readdir($dh))) {
391*4e3e87e4SAndreas Gohr            if ($obj == '.' || $obj == '..') continue;
392*4e3e87e4SAndreas Gohr
393*4e3e87e4SAndreas Gohr            if (!@unlink($dir . '/' . $obj)) {
394*4e3e87e4SAndreas Gohr                $this->recursiveDelete($dir . '/' . $obj);
395*4e3e87e4SAndreas Gohr            }
396*4e3e87e4SAndreas Gohr        }
397*4e3e87e4SAndreas Gohr        closedir($dh);
398*4e3e87e4SAndreas Gohr        return @rmdir($dir);
399*4e3e87e4SAndreas Gohr    }
400*4e3e87e4SAndreas Gohr
401*4e3e87e4SAndreas Gohr    /**
402*4e3e87e4SAndreas Gohr     * Log a message
403*4e3e87e4SAndreas Gohr     *
404*4e3e87e4SAndreas Gohr     * @param string ...$level, $msg
405*4e3e87e4SAndreas Gohr     */
406*4e3e87e4SAndreas Gohr    protected function log()
407*4e3e87e4SAndreas Gohr    {
408*4e3e87e4SAndreas Gohr        $args = func_get_args();
409*4e3e87e4SAndreas Gohr        $level = array_shift($args);
410*4e3e87e4SAndreas Gohr        $msg = array_shift($args);
411*4e3e87e4SAndreas Gohr        $msg = vsprintf($msg, $args);
412*4e3e87e4SAndreas Gohr        if ($this->logger) $this->logger->log($level, $msg);
413*4e3e87e4SAndreas Gohr    }
414*4e3e87e4SAndreas Gohr
415*4e3e87e4SAndreas Gohr    // endregion
416*4e3e87e4SAndreas Gohr}
417