123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- <?php
- /**
- * DokuWiki Plugin upgrade (Helper Component)
- *
- * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
- * @author Andreas Gohr <andi@splitbrain.org>
- */
- use dokuwiki\plugin\upgrade\HTTP\DokuHTTPClient;
- use splitbrain\PHPArchive\FileInfo;
- use splitbrain\PHPArchive\Tar;
- class helper_plugin_upgrade extends DokuWiki_Plugin
- {
- /** @var string download URL for the new DokuWiki release */
- public $tgzurl;
- /** @var string full path to where the file will be downloaded to */
- public $tgzfile;
- /** @var string full path to where the file will be extracted to */
- public $tgzdir;
- /** @var string URL to the VERSION file of the new DokuWiki release */
- public $tgzversion;
- /** @var string URL to the plugin.info.txt file of the upgrade plugin */
- public $pluginversion;
- /** @var admin_plugin_upgrade|cli_plugin_upgrade */
- protected $logger;
- public function __construct()
- {
- global $conf;
- $branch = 'stable';
- $this->tgzurl = "https://github.com/splitbrain/dokuwiki/archive/$branch.tar.gz";
- $this->tgzfile = $conf['tmpdir'] . '/dokuwiki-upgrade.tgz';
- $this->tgzdir = $conf['tmpdir'] . '/dokuwiki-upgrade/';
- $this->tgzversion = "https://raw.githubusercontent.com/splitbrain/dokuwiki/$branch/VERSION";
- $this->pluginversion = "https://raw.githubusercontent.com/splitbrain/dokuwiki-plugin-upgrade/master/plugin.info.txt";
- }
- /**
- * @param admin_plugin_upgrade|cli_plugin_upgrade $logger Logger object
- * @return void
- */
- public function setLogger($logger)
- {
- $this->logger = $logger;
- }
- // region Steps
- /**
- * Check various versions
- *
- * @return bool
- */
- public function checkVersions()
- {
- $ok = true;
- // we need SSL - only newer HTTPClients check that themselves
- if (!in_array('ssl', stream_get_transports())) {
- $this->log('error', $this->getLang('vs_ssl'));
- $ok = false;
- }
- // get the available version
- $http = new DokuHTTPClient();
- $tgzversion = trim($http->get($this->tgzversion));
- if (!$tgzversion) {
- $this->log('error', $this->getLang('vs_tgzno') . ' ' . hsc($http->error));
- $ok = false;
- }
- $tgzversionnum = $this->dateFromVersion($tgzversion);
- if ($tgzversionnum === 0) {
- $this->log('error', $this->getLang('vs_tgzno'));
- $ok = false;
- } else {
- $this->log('notice', $this->getLang('vs_tgz'), $tgzversion);
- }
- // get the current version
- $versiondata = getVersionData();
- $version = trim($versiondata['date']);
- $versionnum = $this->dateFromVersion($version);
- $this->log('notice', $this->getLang('vs_local'), $version);
- // compare versions
- if (!$versionnum) {
- $this->log('warning', $this->getLang('vs_localno'));
- $ok = false;
- } else if ($tgzversionnum) {
- if ($tgzversionnum < $versionnum) {
- $this->log('warning', $this->getLang('vs_newer'));
- $ok = false;
- } elseif ($tgzversionnum == $versionnum && $tgzversion == $version) {
- $this->log('warning', $this->getLang('vs_same'));
- $ok = false;
- }
- }
- // check plugin version
- $pluginversion = $http->get($this->pluginversion);
- if ($pluginversion) {
- $plugininfo = linesToHash(explode("\n", $pluginversion));
- $myinfo = $this->getInfo();
- if ($plugininfo['date'] > $myinfo['date']) {
- $this->log('warning', $this->getLang('vs_plugin'), $plugininfo['date']);
- $ok = false;
- }
- }
- // check if PHP is up to date
- $minphp = '7.2'; // FIXME get this from the composer file upstream
- if (version_compare(phpversion(), $minphp, '<')) {
- $this->log('error', $this->getLang('vs_php'), $minphp, phpversion());
- $ok = false;
- }
- return $ok;
- }
- /**
- * Download the tarball
- *
- * @return bool
- */
- public function downloadTarball()
- {
- $this->log('notice', $this->getLang('dl_from'), $this->tgzurl);
- @set_time_limit(300);
- @ignore_user_abort();
- $http = new DokuHTTPClient();
- $http->timeout = 300;
- $data = $http->get($this->tgzurl);
- if (!$data) {
- $this->log('error', $http->error);
- $this->log('error', $this->getLang('dl_fail'));
- return false;
- }
- io_mkdir_p(dirname($this->tgzfile));
- if (!file_put_contents($this->tgzfile, $data)) {
- $this->log('error', $this->getLang('dl_fail'));
- return false;
- }
- $this->log('success', $this->getLang('dl_done'), filesize_h(strlen($data)));
- return true;
- }
- /**
- * Unpack the tarball
- *
- * @return bool
- */
- public function extractTarball()
- {
- $this->log('notice', '<b>' . $this->getLang('pk_extract') . '</b>');
- @set_time_limit(300);
- @ignore_user_abort();
- try {
- $tar = new Tar();
- $tar->setCallback(function ($file) {
- /** @var FileInfo $file */
- $this->log('info', $file->getPath());
- });
- $tar->open($this->tgzfile);
- $tar->extract($this->tgzdir, 1);
- $tar->close();
- } catch (Exception $e) {
- $this->log('error', $e->getMessage());
- $this->log('error', $this->getLang('pk_fail'));
- return false;
- }
- $this->log('success', $this->getLang('pk_done'));
- $this->log(
- 'notice',
- $this->getLang('pk_version'),
- hsc(file_get_contents($this->tgzdir . '/VERSION')),
- getVersion()
- );
- return true;
- }
- /**
- * Check permissions of files to change
- *
- * @return bool
- */
- public function checkPermissions()
- {
- $this->log('notice', $this->getLang('ck_start'));
- $ok = $this->traverseCheckAndCopy('', true);
- if ($ok) {
- $this->log('success', '<b>' . $this->getLang('ck_done') . '</b>');
- } else {
- $this->log('error', '<b>' . $this->getLang('ck_fail') . '</b>');
- }
- return $ok;
- }
- /**
- * Copy over new files
- *
- * @return bool
- */
- public function copyFiles()
- {
- $this->log('notice', $this->getLang('cp_start'));
- $ok = $this->traverseCheckAndCopy('', false);
- if ($ok) {
- $this->log('success', '<b>' . $this->getLang('cp_done') . '</b>');
- } else {
- $this->log('error', '<b>' . $this->getLang('cp_fail') . '</b>');
- }
- return $ok;
- }
- /**
- * Delete outdated files
- */
- public function deleteObsoleteFiles()
- {
- global $conf;
- $list = file($this->tgzdir . 'data/deleted.files');
- foreach ($list as $line) {
- $line = trim(preg_replace('/#.*$/', '', $line));
- if (!$line) continue;
- $file = DOKU_INC . $line;
- if (!file_exists($file)) continue;
- // check that the given file is a case sensitive match
- if (basename(realpath($file)) != basename($file)) {
- $this->log('info', $this->getLang('rm_mismatch'), hsc($line));
- continue;
- }
- if ((is_dir($file) && $this->recursiveDelete($file)) ||
- @unlink($file)
- ) {
- $this->log('info', $this->getLang('rm_done'), hsc($line));
- } else {
- $this->log('error', $this->getLang('rm_fail'), hsc($line));
- }
- }
- // delete install
- @unlink(DOKU_INC . 'install.php');
- // make sure update message will be gone
- @touch(DOKU_INC . 'doku.php');
- @unlink($conf['cachedir'] . '/messages.txt');
- // clear opcache
- if (function_exists('opcache_reset')) {
- opcache_reset();
- }
- $this->log('success', '<b>' . $this->getLang('finish') . '</b>');
- return true;
- }
- /**
- * Remove the downloaded and extracted files
- *
- * @return bool
- */
- public function cleanUp()
- {
- @unlink($this->tgzfile);
- $this->recursiveDelete($this->tgzdir);
- return true;
- }
- // endregion
- /**
- * Traverse over the given dir and compare it to the DokuWiki dir
- *
- * Checks what files need an update, tests for writability and copies
- *
- * @param string $dir
- * @param bool $dryrun do not copy but only check permissions
- * @return bool
- */
- private function traverseCheckAndCopy($dir, $dryrun)
- {
- $base = $this->tgzdir;
- $ok = true;
- $dh = @opendir($base . '/' . $dir);
- if (!$dh) return false;
- while (($file = readdir($dh)) !== false) {
- if ($file == '.' || $file == '..') continue;
- $from = "$base/$dir/$file";
- $to = DOKU_INC . "$dir/$file";
- if (is_dir($from)) {
- if ($dryrun) {
- // just check for writability
- if (!is_dir($to)) {
- if (is_dir(dirname($to)) && !is_writable(dirname($to))) {
- $this->log('error', '<b>' . $this->getLang('tv_noperm') . '</b>', hsc("$dir/$file"));
- $ok = false;
- }
- }
- }
- // recursion
- if (!$this->traverseCheckAndCopy("$dir/$file", $dryrun)) {
- $ok = false;
- }
- } else {
- $fmd5 = md5(@file_get_contents($from));
- $tmd5 = md5(@file_get_contents($to));
- if ($fmd5 != $tmd5 || !file_exists($to)) {
- if ($dryrun) {
- // just check for writability
- if ((file_exists($to) && !is_writable($to)) ||
- (!file_exists($to) && is_dir(dirname($to)) && !is_writable(dirname($to)))
- ) {
- $this->log('error', '<b>' . $this->getLang('tv_noperm') . '</b>', hsc("$dir/$file"));
- $ok = false;
- } else {
- $this->log('info', $this->getLang('tv_upd'), hsc("$dir/$file"));
- }
- } else {
- // check dir
- if (io_mkdir_p(dirname($to))) {
- // remove existing (avoid case sensitivity problems)
- if (file_exists($to) && !@unlink($to)) {
- $this->log('error', '<b>' . $this->getLang('tv_nodel') . '</b>', hsc("$dir/$file"));
- $ok = false;
- }
- // copy
- if (!copy($from, $to)) {
- $this->log('error', '<b>' . $this->getLang('tv_nocopy') . '</b>', hsc("$dir/$file"));
- $ok = false;
- } else {
- $this->log('info', $this->getLang('tv_done'), hsc("$dir/$file"));
- }
- } else {
- $this->log('error', '<b>' . $this->getLang('tv_nodir') . '</b>', hsc("$dir"));
- $ok = false;
- }
- }
- }
- }
- }
- closedir($dh);
- return $ok;
- }
- // region utilities
- /**
- * Figure out the release date from the version string
- *
- * @param $version
- * @return int|string returns 0 if the version can't be read
- */
- protected function dateFromVersion($version)
- {
- if (preg_match('/(^|\D)(\d\d\d\d-\d\d-\d\d)(\D|$)/i', $version, $m)) {
- return $m[2];
- }
- return 0;
- }
- /**
- * Recursive delete
- *
- * @author Jon Hassall
- * @link http://de.php.net/manual/en/function.unlink.php#87045
- */
- protected function recursiveDelete($dir)
- {
- if (!$dh = @opendir($dir)) {
- return false;
- }
- while (false !== ($obj = readdir($dh))) {
- if ($obj == '.' || $obj == '..') continue;
- if (!@unlink($dir . '/' . $obj)) {
- $this->recursiveDelete($dir . '/' . $obj);
- }
- }
- closedir($dh);
- return @rmdir($dir);
- }
- /**
- * Log a message
- *
- * @param string ...$level, $msg
- */
- protected function log()
- {
- $args = func_get_args();
- $level = array_shift($args);
- $msg = array_shift($args);
- $msg = vsprintf($msg, $args);
- if ($this->logger) $this->logger->log($level, $msg);
- }
- // endregion
- }
|