<?php
/*
 * kzInstall.php is designed by J.P. Pourrez (https://kazimentou.fr)
 * version 2018-02-04
 * Updated 2018-02-06
 * Updated 2018-04-25
 * */

# List every URL for downloading and folders as targets.
const ROOT = 'PluXml/'; # must end with '/'
$URLS = array(
	'' => array(
		# 'http://telechargements.pluxml.org/download.php'
		'https://github.com/pluxml/PluXml/archive/5.6.zip'
	),
	ROOT.'plugins' => array(
		'https://kazimentou.fr/pluxml-plugins2/index.php?plugin=kzUploader&download',
		'https://www.echecs-annonay.fr/kazimentou/index.php?plugin=tinyMCE&download',
		# 'https://kazimentou.fr/pluxml-plugins2/index.php?plugin=tinyMCE&download',
		'https://kazimentou.fr/pluxml-plugins2/index.php?plugin=captchaImage&download',
		'https://kazimentou.fr/pluxml-plugins2/index.php?plugin=kzMailCommentAlert&download'
	),
	ROOT.'themes'	=> array(
		'https://kazimentou.fr/divers/PluXml/echecs.zip',
		# Theme PluxZero
		'http://ressources.pluxml.org/?telechargement/MDE3OS4wMTkuMTA4LnRoZW1lLXBsdXh6ZXJvL3B1YmxpYy9hcmNoaXZlLnppcHx0aGVtZS1wbHV4emVyb3x6aXAqYzA4ZTYy'
	)
);

const L_URL =			0;
const L_ZIP =			1;
const L_EXIT =			2;
const L_LIB =			3;
const L_BAD_ZIP =		4;
const L_FAIL =			5;
const L_UNWRITABLE =	6;
const L_TOO_MUCH_FILES= 7;
const L_FOLDER =		8;
$all_langs = array(
	'en' => array(
		"Downloading from %s",
		"Unzipping to folder : %s\n",
		"Please, press the F5 key within 60 seconds for reaching %s",
		"The following libraries are required :",
		"Bad Zip archive",
		"Downloading for %s fails",
		"The folder %s is unwritable",
		"The folder %s must have only the ". __FILE__ ." script",
		"Folder"
	),
	'fr' => array(
		"Téléchargement depuis %s",
		"Déployement dans le dossier : %s\n",
		"Vous avez 60 secondes pour appuyer sur la touche F5 pour accéder à %s",
		"Les bibliothèques suivantes sont nécessaires :",
		"Archive Zip incorrecte",
		"Échec au téléchargement depuis %s",
		"Aucun droit en écriture pour le dossier %s",
		"Le dossier %s doit contenir uniquement le script ".__FILE__,
		"Dossier"
	),
	'es' => array(
		"descargando desde %s",
		"descomprimir en la carpeta : %s\n",
		"Por favor, presione la tecla F5 dentro de 60 segundos para llegar a %s",
		"Se requieren las siguientes bibliotecas :",
		"mal archivo zip",
		"La descarga de %s falla",
		"No permiso de escritura para la carpeta %s",
		"The folder %s must have only this PHP script",
		"Targeta"
	),
	'de' => array(
		"Herunterladen von %s",
		"Entpacken in Ordner : %s\n",
		"Drücken Sie die Taste F5 innerhalb von 60 Sekunden, um %s zu erreichen",
		"Die folgenden Bibliotheken sind erforderlich :",
		"schlechtes Zip-Archiv",
		"Das Herunterladen für %s schlägt fehl",
		"Keine Schreibberechtigung für den %s-Ordner",
		"The folder %s must have only this PHP script",
		"Folder"
	)
);

define('PUBLIC_NETWORK', filter_input(
	INPUT_SERVER, 'SERVER_ADDR', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE
));

const PATTERN = '{*,.htaccess,.git*}';
const HOMEPAGE = '/install.php';
define('WORKDIR', __DIR__ );
define('REPORTFILE', WORKDIR.'/'.basename(__FILE__, '.php').'.json');
define('REPORT_URL', preg_replace('@\.php(?:\?[^/]*)?$@', '.json', $_SERVER['PHP_SELF']));
define('NEW_LOCATION', rtrim(dirname($_SERVER['PHP_SELF']), '/').HOMEPAGE);
const ID_PATTERN = 'U%02d';

function i18n($text, $value=false, $return=false) {
	global $all_langs;
	if($return) { return sprintf($all_langs[LANG][$text], $value); }
	printf($all_langs[LANG][$text]."\n", $value);
}

function error($msg) {
	header('Content-type: text/plain;charset=utf-8');
	echo $msg;
}

$dashboard = array(
	'begin'	=> false,
	'urls'	=> array(),
	'end'	=> false,
	'msgId'	=> 0
);

/* write $dashboard to file */
function report() {
	global $dashboard;
	$fp = fopen(REPORTFILE, 'w');
	fwrite($fp, json_encode($dashboard));
	fclose($fp);
}

function progressbar($resource, $total_download, $partiel_download, $total_upload, $partiel_upload) {
	global $dashboard;

	$key = curl_getinfo($resource, CURLINFO_PRIVATE);
	if(
		array_key_exists($key, $dashboard['urls']) and (
			$partiel_download - $dashboard['urls'][$key]['partiel'] > 8192 or
			$partiel_download == $total_download
		)
	) {
		$dashboard['urls'][$key]['total'] = $total_download;
		$dashboard['urls'][$key]['partiel'] = $partiel_download;
		$dashboard['msgId']++;
		report();
	}
}

function unzip($filename, $target) {
	if(!empty($filename)) {
		$zip = new ZipArchive();
		if($zip->open($filename) === true) {
			$target = trim($target, '/ \\');
			$tt = WORKDIR;
			if(!empty($target)) { $tt .= "/$target"; }
			$zip->extractTo($tt);
			$root = $zip->getNameIndex(0);
			$zip->close();
			unlink($filename); # Clean up !

			# At Github, $root change on every release.
			$root = substr($root, 0, strpos($root, '/'));
			$newName = preg_replace('@-?[\d.]*$@', '', $root); # for release from Github
			if($newName != $root) { rename("$tt/$root", "$tt/$newName"); }
			return true;
		} else {
			return false;
		}
	}
}

function install($URLS, &$dashboard) {
	$offset_time = filemtime(__FILE__);
	$dashboard['begin'] = time() - $offset_time;

	$chs = array();
	$fps = array();
	$filenames = array();
	$n = 0;

	$mh = curl_multi_init();
	foreach($URLS as $target=>$urls_list) {
		$filenames[$target] = array();
		foreach($urls_list as $url) {
			$fn = tempnam(sys_get_temp_dir(), 'KZX');
			$filenames[$target][] = $fn;
			$fps[$n] = fopen($fn, 'w');
			$key = sprintf(ID_PATTERN, $n);
			$dashboard['urls'][$key] = array( 'total' => 0, 'partiel' =>0 );

			$chs[$n] = curl_init($url);
			curl_setopt_array($chs[$n], array(
				CURLOPT_FOLLOWLOCATION		=> true,
				CURLOPT_HEADER				=> false,
				CURLOPT_USERAGENT			=> $_SERVER['HTTP_USER_AGENT'],
				CURLOPT_NOPROGRESS			=> false,
				CURLOPT_PROGRESSFUNCTION	=> 'progressbar',
				CURLOPT_FILE				=> $fps[$n],
				CURLOPT_PRIVATE				=> $key
			));
			curl_multi_add_handle($mh, $chs[$n]);
			$n++;
		}
	}

	report();

	$active = true;
	while($active) {
		usleep(200000);
		$cme = curl_multi_exec($mh, $active);
	}

	for($i=0; $i<$n; $i++) {
		curl_multi_remove_handle($mh, $chs[$i]);
		curl_close($chs[$i]);
		fclose($fps[$i]);
	}
	curl_multi_close($mh);

	# Some site doesn't introduce the full size of theirs files for downloading. For E.G. : github
	foreach(array_keys($dashboard['urls']) as $key) {
		if($dashboard['urls'][$key]['total'] == 0) {
			$dashboard['urls'][$key]['total'] = $dashboard['urls'][$key]['partiel'];
		}
	}

	foreach($filenames as $target=>$fileslist) {
		foreach($fileslist as $filename1) {
			unzip($filename1, $target);
			unlink($filename1);
		}
	}

	# final step
	$sources = glob(WORKDIR.'/'.ROOT.PATTERN, GLOB_BRACE + GLOB_NOESCAPE);
	$offset = strlen(WORKDIR.'/'.ROOT) - 1;
	foreach($sources as $src) {
		rename($src, WORKDIR.substr($src, $offset));
	}
	rmdir(WORKDIR.'/'.ROOT);

	$dashboard['end'] = time() - $offset_time;
	report();
}

/* =========== main ========== */

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Expires: Fri, 14 Jul 2000 12:00:00 GMT');
header('Pragma: no-cache');

if(!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
	foreach(explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $accept_lang) {
		$lg = substr($accept_lang, 0, 2);
		if(array_key_exists($lg, $all_langs)) {
			define('LANG', $lg);
			break;
		}
	}
}

if(!defined('LANG')) { define('LANG', 'en'); }

$missing_libs = array();
if(!function_exists('curl_init'))	{ $missing_libs[] = ' - Curl'; }
if(!class_exists('ZipArchive'))		{ $missing_libs[] = ' - ZipArchive'; }
if(!function_exists('json_encode'))	{ $missing_libs[] = ' - Json'; }
if(!function_exists('filter_has_var'))	{ $missing_libs[] = ' - Filter'; }
if(!empty($missing_libs)) {
	error(i18n(L_LIB).implode("\n", $missing_libs));
} elseif(!is_writable(__DIR__)) {
	error(i18n(L_UNWRITABLE, __DIR__));
} elseif(filter_has_var(INPUT_GET, 'install')) {
	# New install
	install($URLS, $dashboard);
} elseif(!empty($_GET) or !empty($_POST)) {
	# Forbidden request
	die;
} else {
	if(
		file_exists(__DIR__ . HOMEPAGE) and
		time() - filemtime(REPORTFILE) < 300
	) {
		# If a fresh install, jump to HOMEPAGE
		header('Location: '.NEW_LOCATION);
		# Auto-destruction if not in private range
		if(PUBLIC_NETWORK) { unlink(__FILE__); }
		exit;
	}

	# this script must be the only filename or folder in WORKDIR
	$sources = glob(WORKDIR.'/'.PATTERN, GLOB_BRACE + GLOB_NOESCAPE);
	if(count($sources) != 1 or $sources[0] != __FILE__) {
		error(i18n(L_TOO_MUCH_FILES, __DIR__));
		print_r($sources);
		exit;
	}
?>
<!DOCTYPE html>
<html lang="fr"><head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>kzInstall-2.0 by Kazimentou.fr</title>
	<link rel="shortcut icon" href="https://kazimentou.fr/favicon.ico">
	<style type="text/css">
		* { margin: 0; padding: 0; }
		body {background-color: #444; font-family: 'Noto Sans', Arial, Sans-Serif; }
		.container { max-width: 50rem; margin: 0 auto; padding: 0.3rem 0.8rem; background-color: #f0f0f0; }
		.container ul { padding-left: 1rem; }
		.container li p { display: flex; margin-bottom: 0; }
		.container progress { width: 100%; height: 0.5rem; }
		.container li p span:first-of-type { flex-grow: 1; margin-right: 1rem; overflow-x: hidden; }
		.container li p span:last-of-type { width: 6rem; text-align: right; background-color: #e8e8e8; }
		.container li p span.done:last-of-type { background-color: green; color: #fff; }
		iframe { display: none; }
	</style>
</head><body>
	<div class="container">
		<ul class="folders">
<?php
	$idCount = 0;
	foreach($URLS as $target=>$urls_list) {
		$caption = i18n(L_FOLDER, '', true);
		echo <<< EOT
			<li>
				<h2>$caption : /$target</h2>
				<ul>\n
EOT;
		foreach($urls_list as $url) {
			$id = sprintf(ID_PATTERN, $idCount);
			echo <<< EOT
					<li>
						<p><span>$url</span> <span id="size-$id">0</span></p>
						<progress id="prg-$id" value="0" max="100" />
					</li>\n
EOT;
			$idCount++;
		}
		echo <<< EOT
				</ul>
			</li>\n
EOT;
	}
?>
		</ul>
	</div>
	<iframe src="<?php echo $_SERVER['PHP_SELF']; ?>?install" height="10" width="100%">Votre navigateur ne gère pas les balises iframe</iframe>
	<script type="text/javascript">
		'use script';
		var failures = 5;
		const xhr = new XMLHttpRequest();
		var interval1 = null;

		xhr.onreadystatechange = function(event) {
			if (this.readyState === XMLHttpRequest.DONE) {
				if (this.status === 200) {
					if(this.responseText.trim().length == 0) {
						failures--;
						if(failures <= 0) {
							clearInterval(interval1);
							alert('Error on server');
						}
					} else {
						const datas = JSON.parse(this.responseText);
						if(datas != false) {
							for(var url in datas.urls) {
								const total = datas.urls[url].total;
								const partiel = datas.urls[url].partiel;
								if(total > 0 || partiel > 0) {
									const value = (total > 0) ? total : partiel;
									const sizeEl = document.getElementById('size-' + url)
									sizeEl.textContent = Number.parseFloat(value / 1024.0).toFixed(1) + ' Ko';
									if(total == partiel) {
										sizeEl.classList.add('done');
									}
									document.getElementById('prg-' + url).value = (total > 0) ? parseInt(100.0 * partiel / total) : 25;
									console.log('msg #' + datas.msgId);
								}
							}
							if(datas.end !== false) {
								clearInterval(interval1);
								alert('Downloaded in ' + (datas.end - datas.begin) + 's');
								const timer1 = setTimeout("window.location.href='<?php echo NEW_LOCATION; ?>'", 2000);
							}
						} else {
							failures--;
							if(failures <= 0) {
								alert('No downloading');
								return;
							}
						}
					}
				}
			}
		}

		interval1 = setInterval(function() {
			xhr.open('GET', "<?php echo REPORT_URL; ?>", true);
			xhr.send(null);
		}, 1000);

	</script>
</body></html>
<?php
}
?>