*/
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
require_once DOKU_PLUGIN.'admin.php';
require_once DOKU_PLUGIN.'elwikiupgrade/VerboseTarLib.class.php';
require_once DOKU_PLUGIN.'elwikiupgrade/HTTPClient.php';
use dokuwiki\plugin\elwikiupgrade\DokuHTTPClient;
class admin_plugin_elwikiupgrade extends DokuWiki_Admin_Plugin {
private $tgzurl;
private $tgzfile;
private $tgzdir;
private $tgzversion;
private $pluginversion;
protected $haderrors = false;
public function __construct() {
global $conf;
$branch = 'stable';
$this->tgzurl = "https://download.einsatzleiterwiki.de/elwikiupgrade/einsatzleiterwiki-upgrade.tar.gz";
$this->tgzfile = $conf['tmpdir'].'/dokuwiki-elwikiupgrade.tgz';
$this->tgzdir = $conf['tmpdir'].'/dokuwiki-elwikiupgrade/';
$this->tgzversion = "https://download.einsatzleiterwiki.de/elwikiupgrade/VERSION";
$this->pluginversion = "https://download.einsatzleiterwiki.de/elwikiupgrade/plugin.info.txt";
}
public function getMenuSort() {
return 555;
}
public function handle() {
if($_REQUEST['step'] && !checkSecurityToken()) {
unset($_REQUEST['step']);
}
}
public function html() {
$abrt = false;
$next = false;
echo '
');
// enable auto scroll
?>
_stepit($abrt, $next);
// disable auto scroll
?>
');
$careful = '';
if($this->haderrors) {
echo '
'.$this->getLang('careful').'
';
$careful = 'careful';
}
$action = script();
echo '
';
$this->_progress($next);
}
/**
* Display a progress bar of all steps
*
* @param string $next the next step
*/
private function _progress($next) {
$steps = array('version', 'download', 'unpack', 'check', 'elwikiupgrade');
$active = true;
$count = 0;
echo '
';
foreach($steps as $step) {
$count++;
if($step == $next) $active = false;
if($active) {
echo '- ';
echo '✔';
} else {
echo '
- ';
echo ''.$count.'';
}
echo ''.$this->getLang('step_'.$step).'';
echo '
';
}
echo '
';
}
/**
* Decides the current step and executes it
*
* @param bool $abrt
* @param bool $next
*/
private function _stepit(&$abrt, &$next) {
if(isset($_REQUEST['step']) && is_array($_REQUEST['step'])) {
$step = array_shift(array_keys($_REQUEST['step']));
} else {
$step = '';
}
if($step == 'cancel' || $step == 'done') {
# cleanup
@unlink($this->tgzfile);
$this->_rdel($this->tgzdir);
if($step == 'cancel') $step = '';
}
if($step) {
$abrt = true;
$next = false;
if($step == 'version') {
$this->_step_version();
$next = 'download';
} elseif ($step == 'done') {
$this->_step_done();
$next = '';
$abrt = '';
} elseif(!file_exists($this->tgzfile)) {
if($this->_step_download()) $next = 'unpack';
} elseif(!is_dir($this->tgzdir)) {
if($this->_step_unpack()) $next = 'check';
} elseif($step != 'elwikiupgrade') {
if($this->_step_check()) $next = 'elwikiupgrade';
} elseif($step == 'elwikiupgrade') {
if($this->_step_copy()) {
$next = 'done';
$abrt = '';
}
} else {
echo 'uhm. what happened? where am I? This should not happen';
}
} else {
# first time run, show intro
echo $this->locale_xhtml('step0');
$abrt = false;
$next = 'version';
}
}
/**
* Output the given arguments using vsprintf and flush buffers
*/
public static function _say() {
$args = func_get_args();
echo '
';
echo vsprintf(array_shift($args)."
\n", $args);
flush();
ob_flush();
}
/**
* Print a warning using the given arguments with vsprintf and flush buffers
*/
public function _warn() {
$this->haderrors = true;
$args = func_get_args();
echo '
';
echo vsprintf(array_shift($args)."
\n", $args);
flush();
ob_flush();
}
/**
* Recursive delete
*
* @author Jon Hassall
* @link http://de.php.net/manual/en/function.unlink.php#87045
*/
private function _rdel($dir) {
if(!$dh = @opendir($dir)) {
return false;
}
while(false !== ($obj = readdir($dh))) {
if($obj == '.' || $obj == '..') continue;
if(!@unlink($dir.'/'.$obj)) {
$this->_rdel($dir.'/'.$obj);
}
}
closedir($dh);
return @rmdir($dir);
}
/**
* Check various versions
*
* @return bool
*/
private function _step_version() {
$ok = true;
// we need SSL - only newer HTTPClients check that themselves
if(!in_array('ssl', stream_get_transports())) {
$this->_warn($this->getLang('vs_ssl'));
$ok = false;
}
// get the available version
$http = new DokuHTTPClient();
$tgzversion = $http->get($this->tgzversion);
if(!$tgzversion) {
$this->_warn($this->getLang('vs_tgzno').' '.hsc($http->error));
$ok = false;
}
$tgzversionnum = $this->dateFromVersion($tgzversion);
if($tgzversionnum === 0) {
$this->_warn($this->getLang('vs_tgzno'));
$ok = false;
} else {
self::_say($this->getLang('vs_tgz'), $tgzversion);
}
// get the current version
$version = getVersion();
$versionnum = $this->dateFromVersion($version);
self::_say($this->getLang('vs_local'), $version);
// compare versions
if(!$versionnum) {
$this->_warn($this->getLang('vs_localno'));
$ok = false;
} else if($tgzversionnum) {
if($tgzversionnum < $versionnum) {
$this->_warn($this->getLang('vs_newer'));
$ok = false;
} elseif($tgzversionnum == $versionnum && $tgzversion == $version) {
$this->_warn($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->_warn($this->getLang('vs_plugin'), $plugininfo['date']);
$ok = false;
}
}
// check if PHP is up to date
$minphp = '5.6';
if(version_compare(phpversion(), $minphp, '<')) {
$this->_warn($this->getLang('vs_php'), $minphp, phpversion());
$ok = false;
}
return $ok;
}
/**
* Redirect to the start page
*/
private function _step_done() {
if (function_exists('opcache_reset')) {
opcache_reset();
}
echo $this->getLang('finish');
echo "";
}
/**
* Download the tarball
*
* @return bool
*/
private function _step_download() {
self::_say($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->_warn($http->error);
$this->_warn($this->getLang('dl_fail'));
return false;
}
if(!io_saveFile($this->tgzfile, $data)) {
$this->_warn($this->getLang('dl_fail'));
return false;
}
self::_say($this->getLang('dl_done'), filesize_h(strlen($data)));
return true;
}
/**
* Unpack the tarball
*
* @return bool
*/
private function _step_unpack() {
self::_say('
'.$this->getLang('pk_extract').'');
@set_time_limit(300);
@ignore_user_abort();
try {
$tar = new VerboseTar();
$tar->open($this->tgzfile);
$tar->extract($this->tgzdir, 1);
$tar->close();
} catch (Exception $e) {
$this->_warn($e->getMessage());
$this->_warn($this->getLang('pk_fail'));
return false;
}
self::_say($this->getLang('pk_done'));
self::_say(
$this->getLang('pk_version'),
hsc(file_get_contents($this->tgzdir.'/VERSION')),
getVersion()
);
return true;
}
/**
* Check permissions of files to change
*
* @return bool
*/
private function _step_check() {
self::_say($this->getLang('ck_start'));
$ok = $this->_traverse('', true);
if($ok) {
self::_say('
'.$this->getLang('ck_done').'');
} else {
$this->_warn('
'.$this->getLang('ck_fail').'');
}
return $ok;
}
/**
* Copy over new files
*
* @return bool
*/
private function _step_copy() {
self::_say($this->getLang('cp_start'));
$ok = $this->_traverse('', false);
if($ok) {
self::_say('
'.$this->getLang('cp_done').'');
$this->_rmold();
self::_say('
'.$this->getLang('finish').'');
} else {
$this->_warn('
'.$this->getLang('cp_fail').'');
}
return $ok;
}
/**
* Delete outdated files
*/
private function _rmold() {
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 an case sensitive match
if(basename(realpath($file)) != basename($file)) {
self::_say($this->getLang('rm_mismatch'), hsc($line));
continue;
}
if((is_dir($file) && $this->_rdel($file)) ||
@unlink($file)
) {
self::_say($this->getLang('rm_done'), hsc($line));
} else {
$this->_warn($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');
}
/**
* 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 _traverse($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->_warn('
'.$this->getLang('tv_noperm').'', hsc("$dir/$file"));
$ok = false;
}
}
}
// recursion
if(!$this->_traverse("$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->_warn('
'.$this->getLang('tv_noperm').'', hsc("$dir/$file"));
$ok = false;
} else {
self::_say($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->_warn('
'.$this->getLang('tv_nodel').'', hsc("$dir/$file"));
$ok = false;
}
// copy
if(!copy($from, $to)) {
$this->_warn('
'.$this->getLang('tv_nocopy').'', hsc("$dir/$file"));
$ok = false;
} else {
self::_say($this->getLang('tv_done'), hsc("$dir/$file"));
}
} else {
$this->_warn('
'.$this->getLang('tv_nodir').'', hsc("$dir"));
$ok = false;
}
}
}
}
}
closedir($dh);
return $ok;
}
/**
* Figure out the release date from the version string
*
* @param $version
* @return int|string returns 0 if the version can't be read
*/
public function dateFromVersion($version) {
if(preg_match('/(^|[^\d])(\d\d\d\d-\d\d-\d\d)([^\d]|$)/i', $version, $m)) {
return $m[2];
}
return 0;
}
}
// vim:ts=4:sw=4:et:enc=utf-8: