<?php
declare(strict_types = 1);

/**
 * Mise à jour de l'application.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
require_once(__DIR__ . '/includes/prepend.php');

// Initialisation.
Template::init();
Upgrade::init($latest_version[0] ?? System::APP_VERSION);
if (Template::$data['upgrade_tpl'])
{
	Template::start();
	$tpl =& Template::$data;
}



/**
 * Opérations de mise à jour de l'application.
 */
class Upgrade
{
	/**
	 * La mise à jour a-t-elle réussie ?
	 *
	 * @var bool
	 */
	public static $success = FALSE;



	/**
	 * Paramètres de la table "config" à mettre à jour.
	 *
	 * @var array
	 */
	private static $_config;



	/**
	 * Initialisation.
	 *
	 * @param string $latest_version
	 *
	 * @return void
	 */
	public static function init(string $latest_version): void
	{
		Template::set('upgrade', TRUE);
		Template::set('upgrade_success', FALSE);
		Template::set('upgrade_tpl', FALSE);
		Template::set('upgrade_version', $latest_version);

		// Si le script n'est pas appelé depuis l'interface d'administration,
		// on crée une connexion à la base de données.
		if (DB::$PDO === NULL)
		{
			Template::set('upgrade_tpl', TRUE);

			// Connexion à la base de données.
			if (!DB::connect())
			{
				die('Impossible de se connecter à la base de données.'
					. '<br>Unable to connect to the database.');
			}

			// Récupération de la configuration.
			Config::getDBParams();

			// Authentification utilisateur.
			Auth::cookie();
			L10N::locale();

			// L'application doit-elle être mise à jour ?
			if (Config::$params['app_version'] == $latest_version)
			{
				Template::set('upgrade', FALSE);
				return;
			}
		}

		// Mise à jour.
		if (!Template::$data['upgrade_tpl'] || !empty($_POST['upgrade']))
		{
			self::_upgrade($latest_version);
		}
	}



	/**
	 * Met à jour les permissions des groupes.
	 *
	 * @return bool
	 */
	private static function _updateGroupsPerms(): bool
	{
		// Récupération des permissions des groupes.
		if (!DB::execute('SELECT group_id, group_perms FROM {groups}'))
		{
			return FALSE;
		}
		$groups = DB::fetchAll();
		$change = FALSE;
		foreach ($groups as &$i)
		{
			$perms = Utility::jsonDecode($i['group_perms']);
			foreach (Config::USERS_GROUP_PERMS_DEFAULT as $name => &$value)
			{
				if (!array_key_exists($name, $perms))
				{
					$perms[$name] = $value;
					$change = TRUE;
				}
			}
			ksort($perms);
			$i['group_perms'] = Utility::jsonEncode($perms);
		}
		if ($change)
		{
			$sql = 'UPDATE {groups} SET group_perms = :group_perms WHERE group_id = :group_id';
			if (!DB::execute($sql, $groups))
			{
				return FALSE;
			}
		}

		return TRUE;
	}

	/**
	 * Mise à jour.
	 *
	 * @param string $latest_version
	 *
	 * @return void
	 */
	private static function _upgrade(string $latest_version): void
	{
		// Nouveaux paramètres du fichier de configuration.
		$file_params =
		[
			'Base de données : utiliser les transactions ?' =>
			[
				'CONF_DB_TRAN' => 1
			],
			'Content-Security-Policy: frame-ancestors.' =>
			[
				'CONF_HTTP_CSP_FRAME_ANCESTORS' => ''
			],
			'Content-Security-Policy: hosts.' =>
			[
				'CONF_HTTP_CSP_HOSTS' => ''
			],
			'Liste blanche d\'adresses IP.' =>
			[
				'CONF_AUTH_ADMIN_IP' => ''
			],
			'Paramètres SMTP.' =>
			[
				'CONF_SMTP_MAIL' => 0,
				'CONF_SMTP_HOST' => '',
				'CONF_SMTP_PORT' => 25,
				'CONF_SMTP_AUTH' => 0,
				'CONF_SMTP_USER' => '',
				'CONF_SMTP_PASS' => '',
				'CONF_SMTP_TIME' => 4
			],
			'Poivre des mots de passe.' =>
			[
				'CONF_PASSWORD_PEPPER' => Security::key(62)
			],
			'SQLite (sauvegarde de la base de données).' =>
			[
				'CONF_SQLITE_BACKUP' => 1,
				'CONF_SQLITE_BACKUP_MAX' => 10
			],
		];
		foreach ($file_params as $comment => &$params)
		{
			if (!defined(array_keys($params)[0]))
			{
				if (!Config::addFileParams($params, $comment))
				{
					return;
				}
			}
		}
		if (!defined('CONF_DB_TRAN'))
		{
			define('CONF_DB_TRAN', 1);
		}

		// Début de la transaction.
		if (!DB::beginTransaction())
		{
			return;
		}

		self::$_config = Config::$params;
		$app_version = self::$_config['app_version'];

		// Correction de la colonne 'cat_parents'.
		if (version_compare($app_version, '3.0.12', '<'))
		{
			if (Maintenance::catParents() < 0)
			{
				return;
			}
		}

		// Modification de la colonne 'com_status'.
		if (version_compare($app_version, '3.0.13', '<'))
		{
			if (CONF_DB_TYPE != 'sqlite'
			&& !DB::execute('ALTER TABLE {comments} MODIFY com_status VARCHAR(2)'))
			{
				return;
			}
		}

		// Modification de la valeur de la colonne 'cat_creatable'.
		if (version_compare($app_version, '3.0.18', '<'))
		{
			if (!DB::execute('UPDATE {categories} SET cat_creatable = 1'))
			{
				return;
			}
		}

		// Ajout des colonnes 'item_pubdt' et 'item_expdt',
		// et renommage de la colonne 'cat_lastadddt' en 'cat_lastpubdt'.
		if (version_compare($app_version, '3.0.19', '<'))
		{
			if (!DB::execute('ALTER TABLE {items} ADD COLUMN item_pubdt DATETIME')
			 || !DB::execute('ALTER TABLE {items} ADD COLUMN item_expdt DATETIME')
			 || !DB::execute('UPDATE {items} SET item_pubdt = item_adddt'))
			{
				return;
			}

			if (CONF_DB_TYPE == 'sqlite' && version_compare(DB::$version, '3.25', '<'))
			{
				if (!DB::execute('ALTER TABLE {categories} ADD COLUMN cat_lastpubdt DATETIME')
				 || !DB::execute('UPDATE {categories} SET cat_lastpubdt = cat_lastadddt')
				 || !DB::execute('UPDATE {categories} SET cat_lastadddt = NULL'))
				{
					return;
				}
			}
			else
			{
				$sql = CONF_DB_TYPE == 'mysql'
					? 'ALTER TABLE {categories} CHANGE cat_lastadddt cat_lastpubdt DATETIME'
					: 'ALTER TABLE {categories} RENAME COLUMN cat_lastadddt TO cat_lastpubdt';
				if (!DB::execute($sql))
				{
					return;
				}
			}

			if (isset(self::$_config['categories_sql_order_by']))
			{
				self::$_config['categories_sql_order_by']
					= str_replace('cat_lastadddt', 'cat_lastpubdt',
					self::$_config['categories_sql_order_by']);
			}
		}

		// Ajout d'un INDEX sur la colonne 'item_pubdt'.
		if (version_compare($app_version, '3.0.24', '<'))
		{
			DB::execute('CREATE INDEX {items_pubdt_idx} ON {items} (item_pubdt)');
		}

		// Modification de la colonne 'item_status'.
		if (version_compare($app_version, '3.0.26', '<'))
		{
			if (CONF_DB_TYPE != 'sqlite'
			&& !DB::execute('ALTER TABLE {items} MODIFY item_status VARCHAR(2)'))
			{
				return;
			}
		}

		// Ajout d'un INDEX UNIQUE sur la table 'favorites'.
		if (version_compare($app_version, '3.0.27', '<'))
		{
			DB::execute('CREATE UNIQUE INDEX {favorites_uk1} ON {favorites} (user_id, item_id)');
		}

		// Nouveaux paramètres de configuration en base de données.
		$config_default = Config::getDBParamDefault();
		foreach ($config_default as $param => &$value)
		{
			if (!array_key_exists($param, self::$_config))
			{
				self::$_config[$param] = $value;
			}
		}
		foreach (['categories_stats', 'exif', 'iptc', 'pages', 'xmp'] as $conf)
		{
			$default_params = Config::getDBParamDefault()[$conf . '_params'];
			foreach ($default_params as $item => &$params)
			{
				if (array_key_exists($item, self::$_config[$conf . '_params']))
				{
					foreach ($params as $k => &$v)
					{
						if (!array_key_exists($k, self::$_config[$conf . '_params'][$item]))
						{
							self::$_config[$conf . '_params'][$item][$k] = $v;
						}
					}
				}
				else
				{
					self::$_config[$conf . '_params'][$item] = $params;
				}
				if (!in_array($item, self::$_config[$conf . '_order']))
				{
					self::$_config[$conf . '_order'][] = $item;
				}
			}
			if ($conf != 'pages')
			{
				foreach (self::$_config[$conf . '_params'] as $i => &$p)
				{
					if (!array_key_exists($i, $default_params))
					{
						unset(self::$_config[$conf . '_params'][$i]);
						continue;
					}
					foreach ($p as $name => &$value)
					{
						if (!array_key_exists($name, $default_params[$i]))
						{
							unset(self::$_config[$conf . '_params'][$i][$name]);
						}
					}
				}
			}
		}

		// Modification de la configuration en base de données.
		$history = ['date' => date('Y-m-d H:i:s'), 'version' => $latest_version];
		if (is_array(self::$_config['app_history']))
		{
			self::$_config['app_history'][] = $history;
		}
		else
		{
			self::$_config['app_history'] = [$history];
		}
		self::$_config['app_version'] = $latest_version;

		// Nouveaux paramètres "users_items_resize".
		if (version_compare($app_version, '3.0.18', '<'))
		{
			if (self::$_config['items_resize_height']
			 && self::$_config['items_resize_width'])
			{
				self::$_config['users_items_resize_height']
					= self::$_config['items_resize_height'];
				self::$_config['users_items_resize_type']
					= self::$_config['items_resize_type'];
				self::$_config['users_items_resize_width']
					= self::$_config['items_resize_width'];
			}
			else
			{
				self::$_config['users_items_resize'] = 0;
			}
		}

		// diaporama_hide_control_bars => diaporama_control_bars
		if (isset(self::$_config['diaporama_hide_control_bars']))
		{
			self::$_config['diaporama_control_bars'] = (int)
		   !self::$_config['diaporama_hide_control_bars'];
		   unset(self::$_config['diaporama_hide_control_bars']);
		}

		// Mise à jour de la table "config".
		ksort(self::$_config);
		$config_values = [];
		$config_params = [];
		foreach (self::$_config as $k => &$v)
		{
			if (is_array($v))
			{
				$v = Utility::jsonEncode($v);
			}
			$config_values[] = "('$k', ?)";
			$config_params[] = $v;
		}
		$config_values = implode(', ', $config_values);

		// Suppression du contenu de la table.
		// DELETE et non TRUNCATE car transaction !
		if (!DB::execute('DELETE FROM {config}'))
		{
			return;
		}

		// Insertion de la nouvelle configuration.
		$sql = "INSERT INTO {config} (conf_name, conf_value) VALUES $config_values";
		if (!DB::execute($sql, $config_params))
		{
			return;
		}

		// Ajout de permissions de groupe.
		if (!self::_updateGroupsPerms())
		{
			return;
		}

		// Exécution de la transaction.
		if (DB::inTransaction() && !DB::commitTransaction())
		{
			return;
		}

		// Création de la table 'selection' et suppression de
		// la contraine UNIQUE sur les colonnes '*_url' de certaines tables.
		// Code à exécuter hors transaction.
		if (version_compare($app_version, '3.0.20', '<'))
		{
			DB::execute(SQL::getSchema('selection'));
			$mysql = function($t, $i)
			{
				DB::execute("SHOW INDEXES FROM $t");
				if (isset(DB::fetchAll('Key_name')[$i]))
				{
					DB::execute("DROP INDEX $i ON $t");
				}
			};
			$pgsql = function($t, $i)
			{
				DB::execute("ALTER TABLE $t DROP CONSTRAINT IF EXISTS $i");
			};
			$sqlite = function($t, $i)
			{
				DB::rebuildTable($t);
			};
			foreach (['cameras_brands', 'cameras_models', 'tags'] as $t)
			{
				${CONF_DB_TYPE}(CONF_DB_PREF . $t, CONF_DB_PREF . "{$t}_uk2");
			}
		}

		self::$success = TRUE;
		Template::set('upgrade_success', TRUE);
	}
}
if (!Template::$data['upgrade_tpl'])
{
	return;
}
?>
<!DOCTYPE html>
<html lang="<?php echo Auth::$lang; ?>">

<head>
	<title><?php echo __('Mise à jour'); ?> - <?php echo $tpl['app']['name']; ?></title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width">
	<style nonce="<?php echo CSP_NONCE; ?>" type="text/css">
	h1,p,form,body,html
	{
		border: 0;
		margin: 0;
		padding: 0;
	}
	pre
	{
		font-family: "Courier New", serif;
		white-space: pre-wrap;
	}
	html
	{
		font-size: 100%;
	}
	body
	{
		color: black;
		font-family: Verdana, Arial, Helvetica, sans-serif;
		font-size: .8em;
		background: #fafafa;
		padding: 40px 15px;
	}
	a {
		color: black;
		text-decoration: none;
		border-bottom: 1px solid #884C2E;
		position: relative;
	}
	a:hover
	{
		border-width: 2px;
	}
	h1
	{
		font-size: 275%;
		font-weight: normal;
		border-bottom: 3px solid #8A7666;
		padding: 20px 15px 5px 30px;
		color: #555;
		font-family: Georgia, "Times New Roman", serif;
		letter-spacing: .02em;
		background: #F5F2EB url(admin/template/default/style/default/background.png);
	}
	p
	{
		margin-bottom: 15px;
		font-size: 110%;
	}
	form p
	{
		line-height: 1.7em;
	}
	strong
	{
		font-size: 110%;
		color: #333;
	}
	#upgrade
	{
		border: 1px solid #808080;
		max-width: 450px;
		margin: 0 auto;
		text-align: left;
		background: white;
		box-shadow: 0 0 14px #989898;
	}
	#content
	{
		padding: 35px 30px 1px;
		line-height: 1.6em;
	}
	footer
	{
		margin: 50px 0 20px;
		padding-top: 10px;
		text-align: right;
		border-top: 1px solid #D4D4D4;
	}
	.icon
	{
		display: flex;
		align-items: center;
		padding: 1px 0 0 26px;
		background-repeat: no-repeat;
		background-position: left center;
		min-height: 19px;
	}
	.icon span
	{
		font-weight: bold;
		color: #333;
	}
	.icon_ok
	{
		background-image: url(admin/template/default/style/default/icons/18x18/success.png);
	}
	.icon_error
	{
		background-image: url(admin/template/default/style/default/icons/18x18/error.png);
	}
	.icon_information
	{
		background-image: url(admin/template/default/style/default/icons/18x18/information.png);
	}
	input[type="submit"]
	{
		border: 1px solid silver;
		margin: 20px 0 5px;
		padding: 6px 9px;
		background: linear-gradient(0deg, #E5E5E5, #FFFFFF);
		white-space: nowrap;
		border-radius: 5px;
		outline: none;
		font-size: 120%;
	}
	input[type="submit"]:active
	{
		background: linear-gradient(0deg, #FFFFFF, #E5E5E5);
	}
	input[type="submit"]:focus
	{
		border-color: gray;
	}
	input[type="submit"]:hover
	{
		box-shadow: 0 0 4px silver;
	}
	</style>
</head>

<body>

	<div id="upgrade">
		<h1><?php echo __('Mise à jour'); ?></h1>
		<div id="content">
<?php if (!$tpl['upgrade']) : ?>
			<p class="icon icon_information"><span><?php printf(__('%s est déjà à jour.'), $tpl['app']['name']); ?></span></p>
<?php elseif (empty($_POST['upgrade'])) : ?>
			<form action="" method="post">
				<p><?php printf(__('Cliquez sur ce bouton pour mettre à jour l\'application vers la version %s.'), '<strong>' . $tpl['upgrade_version'] . '</strong>'); ?></p>
				<div>
					<input name="anticsrf" value="<?php echo $tpl['anticsrf'](); ?>" type="hidden">
					<input name="upgrade" value="<?php echo __('Démarrer la mise à jour'); ?>" type="submit">
				</div>
			</form>
<?php elseif ($tpl['upgrade_success']) : ?>
			<p class="icon icon_ok"><span><?php echo __('Mise à jour effectuée avec succès !'); ?></span></p>
			<p><?php printf(__('N\'oubliez pas de supprimer le fichier %s avant de poursuivre.'), '<strong>' . basename(__FILE__) . '</strong>'); ?></p>
<?php else : ?>
			<p class="icon icon_error"><span><?php echo __('La mise à jour a échouée.'); ?></span></p>
<?php endif; ?>
			<footer>
				<a href="<?php echo $tpl['gallery']['path']; ?>/"><?php echo __('Galerie'); ?></a>
			</footer>
		</div>
	</div>

	<?php $tpl['print']('logs'); ?>

	<?php $tpl['print']('errors'); ?>

</body>

</html>