<?php
define('IN_ADMIN', true);
require("../includes/common.php");

if(!$islogin) exit("<script language='javascript'>window.location.href='./login.php';</script>");
$title='系统更新';
include './head.php';

$act = isset($_GET['act']) ? $_GET['act'] : 'check';

// 允许自定义临时目录
$temp_dir = SYSTEM_ROOT.'cache/';
if(!is_dir($temp_dir)) @mkdir($temp_dir, 0777, true);

function build_update_api_url($url) {
	if (preg_match('/^https?:\/\//i', $url)) return $url;
	if (substr($url, 0, 2) === '..') {
		$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https://" : "http://";
		$current_dir_url = $protocol . $_SERVER['HTTP_HOST'] . dirname($_SERVER['REQUEST_URI']);
		return $current_dir_url . '/' . $url;
	}
	return $url;
}

function http_get($url, &$error = null, $timeout = 12) {
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
	curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($ch, CURLOPT_USERAGENT, 'CronLiteUpdateClient/1.0');
	$data = curl_exec($ch);
	$errno = curl_errno($ch);
	$err = curl_error($ch);
	$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_close($ch);
	if ($data === false || $errno) {
		$error = $errno ? ($errno . ':' . $err) : 'curl_exec_failed';
		return false;
	}
	if ($httpcode >= 400) {
		$error = 'http_' . $httpcode;
		return false;
	}
	return $data;
}

function download_file($url, $save_path, &$error = null, $timeout = 20) {
	$fp = @fopen($save_path, 'wb');
	if (!$fp) {
		$error = 'cannot_write_file';
		return false;
	}
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_FILE, $fp);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
	curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($ch, CURLOPT_USERAGENT, 'CronLiteUpdateClient/1.0');
	$ok = curl_exec($ch);
	$errno = curl_errno($ch);
	$err = curl_error($ch);
	$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_close($ch);
	fclose($fp);
	if ($ok === false || $errno) {
		@unlink($save_path);
		$error = $errno ? ($errno . ':' . $err) : 'curl_exec_failed';
		return false;
	}
	if ($httpcode >= 400) {
		@unlink($save_path);
		$error = 'http_' . $httpcode;
		return false;
	}
	if (!file_exists($save_path) || filesize($save_path) <= 0) {
		@unlink($save_path);
		$error = 'empty_file';
		return false;
	}
	return true;
}

function safe_extract_zip($zip_path, $dest_dir, &$error = null) {
	$zip = new ZipArchive;
	if ($zip->open($zip_path) !== TRUE) {
		$error = 'open_zip_failed';
		return false;
	}
	for ($i = 0; $i < $zip->numFiles; $i++) {
		$name = $zip->getNameIndex($i);
		if ($name === false || $name === '') continue;
		$name = str_replace('\\', '/', $name);
		if (strpos($name, "\0") !== false) {
			$zip->close();
			$error = 'invalid_name';
			return false;
		}
		if (preg_match('/^[a-zA-Z]:\//', $name) || strpos($name, '../') !== false || strpos($name, '/..') !== false || substr($name, 0, 1) === '/') {
			$zip->close();
			$error = 'zip_path_traversal';
			return false;
		}
		$target = rtrim($dest_dir, '/\\') . '/' . $name;
		if (substr($name, -1) === '/') {
			if (!is_dir($target)) @mkdir($target, 0777, true);
			continue;
		}
		$dir = dirname($target);
		if (!is_dir($dir)) @mkdir($dir, 0777, true);
		$stream = $zip->getStream($name);
		if (!$stream) {
			$zip->close();
			$error = 'read_zip_entry_failed';
			return false;
		}
		$out = @fopen($target, 'wb');
		if (!$out) {
			fclose($stream);
			$zip->close();
			$error = 'write_file_failed';
			return false;
		}
		while (!feof($stream)) {
			$buf = fread($stream, 8192);
			if ($buf === false) break;
			fwrite($out, $buf);
		}
		fclose($out);
		fclose($stream);
	}
	$zip->close();
	return true;
}

// 检查 zip 扩展
if (!class_exists('ZipArchive')) {
	echo '<div class="container">';
	echo '<div class="card"><div class="card-body">';
	echo '<div class="alert alert-danger"><i class="fa fa-exclamation-circle"></i> 您的服务器不支持 ZipArchive 扩展，无法使用在线更新功能。</div>';
	echo '</div></div></div></body></html>';
	exit;
}

if ($act == 'do_update') {
	@ob_end_flush();
	@ob_implicit_flush(true);
	function render_msg($type, $text) {
		echo '<div class="update-message '.$type.'"><i class="fa fa-'.($type=='success'?'check-circle':($type=='error'?'times-circle':($type=='warning'?'exclamation-triangle':'info-circle'))).'"></i> '.htmlspecialchars($text, ENT_QUOTES, 'UTF-8').'</div>';
		@flush();
	}
	?>
	<div class="container">
		<div class="page-header-section">
			<div class="page-title">
				<div class="page-title-icon"><i class="fa fa-refresh"></i></div>
				<h1>系统更新</h1>
			</div>
		</div>

		<div class="card update-progress-card">
			<div class="card-header">
				<h3><i class="fa fa-download"></i> 更新进度</h3>
			</div>
			<div class="card-body">
				<div class="alert alert-warning">
					<i class="fa fa-exclamation-triangle"></i>
					<strong>注意：</strong>更新过程中请勿关闭页面。若更新包来源不可信请勿更新。
				</div>
				<div class="update-log">
	<?php
	$url = build_update_api_url(UPDATE_API . '?act=check_json&ver='.VERSION);
	$err = null;
	$res = http_get($url, $err, 12);
	if ($res === false) {
		render_msg('error', '获取更新信息失败：'.$err);
		echo '</div><div class="update-actions"><a class="btn btn-secondary" href="./update.php"><i class="fa fa-arrow-left"></i> 返回</a></div></div></div></div></body></html>';
		exit;
	}
	$data = json_decode($res, true);
	
	if (!$data || $data['code'] != 0) {
		$msg = ($data && isset($data['msg']) && $data['msg']) ? $data['msg'] : '接口请求错误';
		render_msg('error', '获取更新信息失败：'.$msg);
		echo '</div><div class="update-actions"><a class="btn btn-secondary" href="./update.php"><i class="fa fa-arrow-left"></i> 返回</a></div></div></div></div></body></html>';
		exit;
	}
	
	$local_ver = intval(VERSION);
	$remote_ver = isset($data['data']['version_code']) ? intval($data['data']['version_code']) : 0;
	if (!isset($data['data']) || $remote_ver <= $local_ver) {
		render_msg('success', '当前已是最新版本，无需更新。');
		echo '</div><div class="update-actions"><a class="btn btn-primary" href="./"><i class="fa fa-home"></i> 返回首页</a></div></div></div></div></body></html>';
		exit;
	}
	
	$update_info = $data['data'];
	$remote_file = $update_info['file_url'];
	$sql_file = $update_info['sql_url'];
	if (empty($remote_file) || !preg_match('/^https?:\/\//i', $remote_file)) {
		render_msg('error', '更新包地址无效');
		echo '</div><div class="update-actions"><a class="btn btn-secondary" href="./update.php"><i class="fa fa-arrow-left"></i> 返回</a></div></div></div></div></body></html>';
		exit;
	}

	render_msg('info', '当前版本：'.$local_ver.'，最新版本：'.$remote_ver);
	
	$local_zip = $temp_dir . 'update.zip';
	render_msg('info', '正在下载更新包...');
	$d_err = null;
	if (!download_file($remote_file, $local_zip, $d_err, 60)) {
		render_msg('error', '下载更新包失败：'.$d_err);
		echo '</div><div class="update-actions"><a class="btn btn-secondary" href="./update.php"><i class="fa fa-arrow-left"></i> 返回</a></div></div></div></div></body></html>';
		exit;
	}
	render_msg('success', '更新包下载完成');
	
	render_msg('info', '正在解压更新包...');
	$x_err = null;
	if (!safe_extract_zip($local_zip, ROOT, $x_err)) {
		@unlink($local_zip);
		render_msg('error', '解压更新包失败：'.$x_err);
		echo '</div><div class="update-actions"><a class="btn btn-secondary" href="./update.php"><i class="fa fa-arrow-left"></i> 返回</a></div></div></div></div></body></html>';
		exit;
	}
	@unlink($local_zip);
	render_msg('success', '更新包解压完成');
	
	if ($sql_file && preg_match('/^https?:\/\//i', $sql_file)) {
		render_msg('info', '正在执行数据库更新...');
		$local_sql = $temp_dir . 'update.sql';
		$s_err = null;
		if (download_file($sql_file, $local_sql, $s_err, 30)) {
			$sql_content = file_get_contents($local_sql);
			$sqls = explode(';', $sql_content);
			foreach ($sqls as $sql) {
				$sql = trim($sql);
				if ($sql === '') continue;
				$DB->exec($sql);
			}
			@unlink($local_sql);
			render_msg('success', '数据库更新完成');
		} else {
			render_msg('warning', '数据库更新下载失败：'.$s_err);
		}
	}
	
	render_msg('success', '更新成功！当前版本：'.$remote_ver);
	echo '</div><div class="update-actions"><a class="btn btn-primary" href="./"><i class="fa fa-home"></i> 返回首页</a> <a class="btn btn-secondary" href="./update.php"><i class="fa fa-refresh"></i> 重新检查</a></div>';
	echo '</div></div></div></div></body></html>';
} else {
	$url = build_update_api_url(UPDATE_API . '?act=check_json&ver='.VERSION);
	$err = null;
	$res = http_get($url, $err, 10);
	$info = null;
	if ($res !== false) {
		$tmp = json_decode($res, true);
		if ($tmp && isset($tmp['code']) && $tmp['code'] == 0 && isset($tmp['data'])) $info = $tmp['data'];
	}
	$local_ver = intval(VERSION);
	$remote_ver = $info && isset($info['version_code']) ? intval($info['version_code']) : 0;
	$has_update = $remote_ver > $local_ver;
	$version_name = isset($info['version_name']) ? $info['version_name'] : '';
	$changelog = isset($info['changelog']) ? $info['changelog'] : '';
	?>
	<div class="container">
		<!-- 页面标题 -->
		<div class="page-header-section">
			<div class="page-title">
				<div class="page-title-icon"><i class="fa fa-refresh"></i></div>
				<h1>系统更新</h1>
			</div>
		</div>
		<?php if ($res === false): ?>
		<div class="card">
			<div class="card-body">
				<div class="update-status error">
					<div class="status-icon">
						<i class="fa fa-times-circle"></i>
					</div>
					<div class="status-content">
						<h3>检查更新失败</h3>
						<p><?php echo htmlspecialchars($err, ENT_QUOTES, 'UTF-8'); ?></p>
						<a href="./update.php" class="btn btn-primary"><i class="fa fa-refresh"></i> 重新检查</a>
					</div>
				</div>
			</div>
		</div>
		<?php elseif (!$info): ?>
		<div class="card">
			<div class="card-body">
				<div class="update-status warning">
					<div class="status-icon">
						<i class="fa fa-exclamation-circle"></i>
					</div>
					<div class="status-content">
						<h3>更新接口返回异常</h3>
						<p>暂无可用更新或接口返回数据异常</p>
						<a href="./update.php" class="btn btn-primary"><i class="fa fa-refresh"></i> 重新检查</a>
					</div>
				</div>
			</div>
		</div>
		<?php else: ?>
		<!-- 更新状态 -->
		<div class="card">
			<div class="card-header">
				<h3><i class="fa fa-info-circle"></i> 更新状态</h3>
			</div>
			<div class="card-body">
				<?php if ($has_update): ?>
				<div class="update-status has-update">
					<div class="status-icon">
						<i class="fa fa-download"></i>
					</div>
					<div class="status-content">
						<h3>发现新版本</h3>
						<p>版本名称：<strong><?php echo htmlspecialchars($version_name, ENT_QUOTES, 'UTF-8'); ?></strong></p>
						<div class="status-actions">
							<a href="?act=do_update" class="btn btn-success btn-lg" onclick="return confirm('确定要执行更新吗？建议先备份网站文件和数据库。')">
								<i class="fa fa-download"></i> 立即更新
							</a>
							<a href="./update.php" class="btn btn-secondary">
								<i class="fa fa-refresh"></i> 重新检查
							</a>
						</div>
					</div>
				</div>
				<?php else: ?>
				<div class="update-status latest">
					<div class="status-icon">
						<i class="fa fa-check-circle"></i>
					</div>
					<div class="status-content">
						<h3>当前已是最新版本</h3>
						<p>您的系统版本 <?php echo htmlspecialchars(strval($local_ver), ENT_QUOTES, 'UTF-8'); ?> 是最新的</p>
						<div class="status-actions">
							<a href="./update.php" class="btn btn-primary">
								<i class="fa fa-refresh"></i> 重新检查
							</a>
						</div>
					</div>
				</div>
				<?php endif; ?>
			</div>
		</div>

		<!-- 更新日志 -->
		<?php if ($changelog): ?>
		<div class="card">
			<div class="card-header">
				<h3><i class="fa fa-file-text-o"></i> 更新说明</h3>
			</div>
			<div class="card-body">
				<div class="changelog-content">
					<?php 
					$lines = explode("\n", trim($changelog));
					$formatted_lines = [];
					foreach ($lines as $line) {
						$line = trim($line);
						if ($line !== '') {
							$formatted_lines[] = htmlspecialchars($line, ENT_QUOTES, 'UTF-8');
						}
					}
					echo implode('<br>', $formatted_lines);
					?>
				</div>
			</div>
		</div>
		<?php endif; ?>
		<?php endif; ?>
	</div>
	<?php
}
?>

<style>
/* 设置页面专用样式 */
.settings-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 24px;
    padding: 16px;
    background: var(--bg-card);
    border-radius: var(--border-radius-lg);
    border: 1px solid var(--border-color);
    box-shadow: var(--shadow);
}

.tab-item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    border-radius: var(--border-radius);
    color: var(--text-secondary);
    font-size: 14px;
    font-weight: 500;
    text-decoration: none;
    transition: all var(--transition-fast);
    background: var(--gray-50);
    border: 1px solid transparent;
}

.tab-item:hover {
    background: var(--gray-100);
    color: var(--text-primary);
    text-decoration: none;
}

.tab-item.active {
    background: var(--primary-gradient);
    color: #fff;
    box-shadow: 0 4px 12px rgba(13, 148, 136, 0.3);
}

.tab-item i {
    font-size: 14px;
}

/* 更新状态 */
.update-status {
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 32px;
}

.status-icon {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 36px;
    flex-shrink: 0;
}

.update-status.latest .status-icon {
    background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
    color: #059669;
}

.update-status.has-update .status-icon {
    background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
    color: #2563eb;
}

.update-status.error .status-icon {
    background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
    color: #dc2626;
}

.update-status.warning .status-icon {
    background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
    color: #d97706;
}

.status-content h3 {
    margin: 0 0 8px 0;
    font-size: 20px;
    color: var(--text-primary);
}

.status-content p {
    margin: 0 0 16px 0;
    color: var(--text-secondary);
}

.status-actions {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

/* 更新日志 */
.changelog-content {
    font-size: 14px;
    line-height: 1.8;
    color: var(--text-primary);
    word-break: break-word;
    text-align: left;
    padding: 16px;
    background: var(--gray-50);
    border-radius: var(--border-radius);
    border: 1px solid var(--border-color);
}

/* 更新进度 */
.update-progress-card {
    max-width: 800px;
    margin: 0 auto;
}

.update-log {
    background: var(--gray-50);
    border-radius: var(--border-radius);
    padding: 20px;
    margin: 20px 0;
    max-height: 400px;
    overflow-y: auto;
}

.update-message {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border-color);
    font-size: 14px;
}

.update-message:last-child {
    border-bottom: none;
}

.update-message i {
    font-size: 16px;
    width: 20px;
    text-align: center;
}

.update-message.success {
    color: #059669;
}

.update-message.error {
    color: #dc2626;
}

.update-message.warning {
    color: #d97706;
}

.update-message.info {
    color: #2563eb;
}

.update-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
    padding-top: 20px;
    border-top: 1px solid var(--border-color);
}

/* 响应式 */
@media (max-width: 767px) {
    .settings-tabs {
        padding: 12px;
        gap: 6px;
    }
    
    .tab-item {
        padding: 8px 12px;
        font-size: 13px;
    }
    
    .update-status {
        flex-direction: column;
        text-align: center;
        padding: 24px;
    }
    
    .status-actions {
        justify-content: center;
    }
}
</style>

</body>
</html>
