<?php
declare(strict_types = 1);

/**
 * Classe mère pour toute l'administration.
 *
 */
class Admin
{
	/**
	 * Instance de la classe Search.
	 *
	 * @var object
	 */
	public static $search;

	/**
	 * Qualité des vignettes.
	 *
	 * @var string
	 */
	public static $thumbQuality = CONF_THUMBS_QUALITY . '';

	/**
	 * Taille des vignettes.
	 *
	 * @var string
	 */
	public static $thumbSize = '400';



	/**
	 * Gestion des options d'affichage des objets.
	 *
	 * @param string $section
	 *
	 * @return void
	 */
	public static function displayOptions(string $section): void
	{
		// Partie 1 : Récupèration des options d'affichage.
		$value = function($section, $name, $default, $values)
		{
			// Listes.
			if ($values)
			{
				Auth::$infos['user_prefs'][$section][$name] =
					(isset(Auth::$infos['user_prefs'][$section][$name])
					&& in_array(Auth::$infos['user_prefs'][$section][$name], $values))
					? Auth::$infos['user_prefs'][$section][$name]
					: $default;
			}

			// Nombres.
			else if (is_int($default))
			{
				$val = (isset(Auth::$infos['user_prefs'][$section][$name]))
					? (int) Auth::$infos['user_prefs'][$section][$name]
					: $default;
				if ($val < 1)
				{
					$val = $default;
				}
				Auth::$infos['user_prefs'][$section][$name] = $val;
			}

			// Chaîne.
			else
			{
				$val = (isset(Auth::$infos['user_prefs'][$section][$name]))
					? Auth::$infos['user_prefs'][$section][$name]
					: $default;
				Auth::$infos['user_prefs'][$section][$name] = $val;
			}
		};

		// Paramètres pour chaque section.
		$order_by_order =
		[
			'default' => 'DESC',
			'regexp' => 'ASC|DESC',
			'values' =>
			[
				'ASC' => __('croissant'),
				'DESC' => __('décroissant')
			]
		];
		$thumbs_size = 
		[
			'default' => 'large',
			'regexp' => 'normal|large',
			'values' =>
			[
				'normal' => __('Petite'),
				'large' => __('Grande')
			]
		];
		switch ($section)
		{
			// Albums et catégories.
			case 'album' :
			case 'category' :
			case 'pending' :
				if ($section == 'pending')
				{
					$order_by_column =
					[
						'default' => 'adddt',
						'regexp' => 'adddt|filesize|name|size',
						'values' =>
						[
							'name' => __('Titre'),
							'adddt' => __('Date d\'ajout'),
							'filesize' => __('Poids')
						]
					];
				}
				else if ($section == 'album')
				{
					$order_by_column =
					[
						'default' => 'adddt',
						'regexp' => 'adddt|crtdt|filesize|name|path|position|pubdt|size',
						'values' =>
						[
							'name' => __('Titre'),
							'adddt' => __('Date d\'ajout'),
							'pubdt' => __('Date de publication'),
							'crtdt' => __('Date de création'),
							'filesize' => __('Poids'),
							'size' => __('Dimensions'),
							'path' => __('Nom de fichier'),
							'position' => __('Tri manuel')
						]
					];
				}
				else
				{
					$order_by_column =
					[
						'default' => 'crtdt',
						'regexp' => 'crtdt|name|path|position',
						'values' =>
						[
							'name' => __('Titre'),
							'crtdt' => __('Date de création'),
							'path' => __('Nom de répertoire'),
							'position' => __('Tri manuel')
						]
					];
				}
				$options =
				[
					'display' =>
					[
						'default' => 'list',
						'regexp' => 'grid|list',
						'values' => ''
					],
					'grid_lines' =>
					[
						'default' => 3,
						'regexp' => '[1-9]|[1-9]\d{1,2}',
						'values' => ''
					],
					'grid_order_by_column' => $order_by_column,
					'grid_order_by_order' => $order_by_order,
					'grid_tb_size' =>
					[
						'default' => 120,
						'regexp' => implode('|', CONF_THUMBS_GRID_SIZES),
						'values' => ''
					],
					'nb_per_page' =>
					[
						'default' => 10,
						'regexp' => '[1-9]|[1-9]\d{1,2}',
						'values' => ''
					],
					'order_by_column' => $order_by_column,
					'order_by_order' => $order_by_order,
					'thumbs_size' => $thumbs_size
				];
				break;

			// Commentaires.
			case 'comments' :
				$options =
				[
					'nb_per_page' =>
					[
						'default' => 10,
						'regexp' => '[1-9]|[1-9]\d{1,2}',
						'values' => ''
					],
					'order_by_column' =>
					[
						'default' => 'crtdt',
						'regexp' => 'crtdt|lastupddt',
						'values' =>
						[
							'crtdt' => __('Date d\'ajout'),
							'lastupddt' => __('Date de dernière modification')
						]
					],
					'order_by_order' => $order_by_order,
					'thumbs_size' => $thumbs_size
				];
				break;

			// Tags et appareils photos.
			case 'cameras_brands' :
			case 'cameras_models' :
			case 'tags' :
				$order_by_order['default'] = 'ASC';
				$options =
				[
					'nb_per_page' =>
					[
						'default' => 20,
						'regexp' => '[1-9]|[1-9]\d{1,2}',
						'values' => ''
					],
					'order_by_column' =>
					[
						'default' => 'name',
						'regexp' => 'count|name',
						'values' =>
						[
							'name' => __('Nom'),
							'count' => __('Nombre de fichiers liés')
						]
					],
					'order_by_order' => $order_by_order
				];
				break;

			// Logs d'activité des utilisateurs.
			case 'logs' :
				$options =
				[
					'nb_per_page' =>
					[
						'default' => 20,
						'regexp' => '[1-9]|[1-9]\d{1,2}',
						'values' => ''
					],
					'order_by_column' =>
					[
						'default' => 'date',
						'regexp' => 'date',
						'values' =>
						[
							'date' => __('Date')
						]
					],
					'order_by_order' => $order_by_order
				];
				break;

			// Utilisateurs.
			case 'users' :
				$options =
				[
					'nb_per_page' =>
					[
						'default' => 10,
						'regexp' => '[1-9]|[1-9]\d{1,2}',
						'values' => ''
					],
					'order_by_column' =>
					[
						'default' => 'crtdt',
						'regexp' => 'crtdt|lastvstdt|login',
						'values' =>
						[
							'login' => __('Nom d\'utilisateur'),
							'crtdt' => __('Date d\'inscription'),
							'lastvstdt' => __('Date de dernière visite')
						]
					],
					'order_by_order' => $order_by_order,
					'thumbs_size' => $thumbs_size
				];
				break;

			// Votes.
			case 'votes' :
				$options =
				[
					'nb_per_page' =>
					[
						'default' => 20,
						'regexp' => '[1-9]|[1-9]\d{1,2}',
						'values' => ''
					],
					'order_by_column' =>
					[
						'default' => 'date',
						'regexp' => 'date|rating',
						'values' =>
						[
							'date' => __('Date'),
							'rating' => __('Note')
						]
					],
					'order_by_order' => $order_by_order
				];
				break;
		}

		foreach ($options as $name => &$p)
		{
			$values = is_array($p['values']) ? array_keys($p['values']) : $p['values'];
			$value($section, $name, $p['default'], $values);
		}


		// Partie 2 : Modification des options d'affichage.
		if (!isset($_POST['options']))
		{
			goto tpl;
		}

		$change = FALSE;

		foreach ($options as $name => &$p)
		{
			if (Utility::checkPost($name, '`^(?:' . $p['regexp'] . ')$`i')
			&& (!isset(Auth::$infos['user_prefs'][$section][$name]) ||
			$_POST[$name] != Auth::$infos['user_prefs'][$section][$name]))
			{
				Auth::$infos['user_prefs'][$section][$name] = $_POST[$name];
				$change = TRUE;
			}
		}

		// Type d'affichage : une seule option pour les catégories et les albums.
		if (in_array($section, ['album', 'category']) && isset($_POST['display'])
		 && in_array($_POST['display'], ['grid', 'list']))
		{
			if ($section == 'album')
			{
				Auth::$infos['user_prefs']['category']['display'] = $_POST['display'];
			}
			else
			{
				Auth::$infos['user_prefs']['album']['display'] = $_POST['display'];
			}
		}

		// On met à jour les préférences de l'utilisateur.
		if (!$change)
		{
			goto tpl;
		}
		$params =
		[
			Utility::jsonEncode(Auth::$infos['user_prefs']),
			(int) Auth::$infos['user_id']
		];
		DB::execute('UPDATE {users} SET user_prefs = ? WHERE user_id = ?', $params);


		// Partie 3 : Données de template.
		tpl:
		foreach ($options as $name => &$p)
		{
			if (is_array($p['values']))
			{
				$o = [];
				foreach ($p['values'] as $value => &$text)
				{
					$o[] =
					[
						'value' => $value,
						'text' => $text,
						'selected' => (Auth::$infos['user_prefs']
							[$section][$name] == $value)
					];
				}
			}
			else
			{
				$o = Auth::$infos['user_prefs'][$section][$name];
			}
			Template::set('options', [$name => $o]);
		}
	}

	/**
	 * Gestion des filtres.
	 *
	 * @return void
	 */
	public static function filters(): void
	{
		App::filtersGET();

		// Moteur de recherche.
		self::search();

		// Filtre.
		if (isset($_GET['filter']) && !isset($_GET['search']))
		{
			if (!Template::filter((int) $_GET['filter_value'], $_GET['q_filterless']))
			{
				App::redirect($_GET['q_filterless']);
			}
		}
	}

	/**
	 * Gestion du moteur de recherche.
	 *
	 * @return void
	 */
	public static function search(): void
	{
		// Sections avec moteur de recherche.
		if (!in_array($_GET['section'], ['album', 'cameras-brands', 'cameras-models',
		'category', 'category-items', 'comments', 'logs', 'tags', 'users', 'votes']))
		{
			return;
		}

		// Initialisation.
		self::$search = new Search();

		// Récupération des paramètres de recherche.
		if (isset($_GET['search']))
		{
			Template::set('search', ['start' => 1]);
			if (!self::$search->sql($_GET['search']))
			{
				unset($_GET['filter']);
				unset($_GET['search']);
				Template::set('search', ['invalid' => 1]);
			}
		}

		// Traitement du formulaire.
		if (isset($_POST['search_query']) && mb_strlen($_POST['search_query']) <= 68
		&& !Utility::isEmpty($_POST['search_query']))
		{
			$options = $_POST['search_options'];

			if (in_array($_GET['section'], ['album', 'category', 'category-items']))
			{
				if ($_POST['search_options']['type'] == 'items')
				{
					$options = array_merge($options, $options['items']);
					$options['status_column'] = 'item_status';
					$options['user_table'] = 'i';
				}
				else
				{
					$options = array_merge($options, $options['categories']);
					$options['status_column'] = 'cat_status';
					$options['user_table'] = 'cat';
				}
				unset($options['categories']);
				unset($options['items']);
			}
			if ($_GET['section'] == 'comments')
			{
				$options['status_column'] = 'com_status';
			}
			if ($_GET['section'] == 'users')
			{
				$options['status_column'] = 'user_status';
				if (in_array('user_login', $options['columns']))
				{
					$options['columns'][] = 'user_nickname';
				}
			}

			if ($search_id = self::$search->insert($_POST['search_query'], $options))
			{
				switch ($_GET['section'])
				{
					case 'album' :
					case 'category' :
					case 'category-items' :
						$section = $_GET['section'] == 'category-items'
							? 'category'
							: $_GET['section'];
						App::redirect(
							$section . '/' . $_GET['value_1'] . '/search/' . $search_id
						);
						break;

					case 'users' :
						$group_id = isset($_GET['group_id']) ? '/group/' . $_GET['group_id'] : '';
						App::redirect('users' . $group_id . '/search/' . $search_id);
						break;

					default :
						App::redirect($_GET['section'] . '/search/' . $search_id);
				}
			}
		}

		// Template : paramètres communs.
		Template::set('search', [
			'all_words' => self::$search->options['all_words'] ?? TRUE,
			'exit_link' => App::getURL(
				preg_replace('`/search/[\da-z]+`i', '', $_GET['q_pageless'])
			),
			'query' => self::$search->query
		]);

		// Template : fonctions.
		$functions =
		[
			'columns' => function(array $columns): array
			{
				foreach ($columns as $col => &$val)
				{
					$val = isset(self::$search->options['columns'])
						? in_array($col, self::$search->options['columns'])
						: $val;
				}

				return $columns;
			},
			'date' => function(): array
			{
				$params =
				[
					'date_end_day' => '',
					'date_end_month' => '',
					'date_end_year' => '',
					'date_start_day' => '',
					'date_start_month' => '',
					'date_start_year' => ''
				];
				foreach ($params as $p => &$val)
				{
					$val = self::$search->options[$p] ?? $val;
				}

				return $params;
			},
			'select' => function(string $name, array $params): array
			{
				$selected = FALSE;
				foreach ($params as &$i)
				{
					if (isset(self::$search->options[$name])
					&& self::$search->options[$name] == $i['value'])
					{
						$i['selected'] = $selected = TRUE;
					}
					else
					{
						$i['selected'] = FALSE;
					}
				}
				if (!$selected)
				{
					foreach ($params as &$i)
					{
						$i['selected'] = !empty($i['default']);
					}
				}

				return $params;
			}
		];

		// Template : paramètres de chaque section.
		switch ($_GET['section'])
		{
			case 'album' :
			case 'category' :
			case 'category-items' :
				require_once(__DIR__ . '/AdminAlbums.class.php');
				AdminAlbums::tplSearch($functions);
				break;

			case 'cameras-brands' :
			case 'cameras-models' :
				require_once(__DIR__ . '/AdminCameras.class.php');
				AdminCameras::tplSearch($functions);
				break;

			case 'comments' :
				require_once(__DIR__ . '/AdminComments.class.php');
				AdminComments::tplSearch($functions);
				break;

			case 'logs' :
				require_once(__DIR__ . '/AdminLogs.class.php');
				AdminLogs::tplSearch($functions);
				break;

			case 'tags' :
				require_once(__DIR__ . '/AdminTags.class.php');
				AdminTags::tplSearch($functions);
				break;

			case 'users' :
				require_once(__DIR__ . '/AdminUsers.class.php');
				AdminUsers::tplSearch($functions);
				break;

			case 'votes' :
				require_once(__DIR__ . '/AdminVotes.class.php');
				AdminVotes::tplSearch($functions);
				break;
		}
	}

	/**
	 * Modifie les paramètres d'éléments triables.
	 *
	 * @param string $items
	 *
	 * @return void
	 */
	public static function sortable(string $items): void
	{
		// Quelques vérifications
		if (!isset($_POST['items']) || !is_array($_POST['items']))
		{
			return;
		}

		// Ordre des éléments.
		$new_order = $items_order = Config::$params[$items . '_order'];
		if (isset($_POST['serial']) && is_string($_POST['serial'])
		&& preg_match('`^(?:i\[\]=\d{1,2}&)*(?:i\[\]=\d{1,2})$`', $_POST['serial']))
		{
			$serial = str_replace('i[]=', '', $_POST['serial']);
			$serial = explode('&', $serial);
			if (count($serial) == count($items_order))
			{
				$new_order = [];
				foreach ($serial as $position)
				{
					$new_order[] = $items_order[$position];
				}
			}
		}

		// Paramètres.
		$new_params = $items_params = Config::$params[$items . '_params'];
		foreach ($new_params as $name => &$p)
		{
			// Suppression de pages personnalisées.
			if ($items == 'pages')
			{
				if (substr($name, 0, 7) == 'custom_'
				&& !isset($_POST['items'][$name]))
				{
					unset($new_order[array_search($name, $new_order)]);
					unset($new_params[$name]);
					continue;
				}
			}

			// Format des paramètres EXIF.
			if ($items == 'exif')
			{
				if (isset($_POST['items'][$name]['format']) && isset($p['format'])
				&& $_POST['items'][$name]['format'] != $p['format'])
				{
					$p['format'] = $_POST['items'][$name]['format'];
				}
			}

			// Activation.
			if (isset($_POST['items'][$name]['activated']) && !$p['status'])
			{
				$p['status'] = 1;
			}

			// Désactivation.
			if (!isset($_POST['items'][$name]['activated']) && $p['status'])
			{
				$p['status'] = 0;
			}
		}

		// Mise à jour.
		switch (Config::changeDBParams([
			$items . '_order' => $new_order,
			$items . '_params' => $new_params
		]))
		{
			case -1 :
				Report::error();
				break;

			case 0 :
				Report::noChange();
				break;

			default :
				Report::success();
				break;
		}
	}

	/**
	 * Initialisation de l'interface d'administration.
	 *
	 * @return void
	 */
	public static function start(): void
	{
		// Changement de page.
		if (isset($_POST['page'])
		&& preg_match('`^\d{1,12}$`', $_POST['page']) && $_POST['page'] > 0)
		{
			App::redirect($_GET['q'] . '/page/' . $_POST['page']);
		}

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

		// Récupération de la configuration.
		if (!Config::getDBParams())
		{
			die('No database.');
		}

		// Gestion des paramètres GET.
		self::_request();

		// Initialisation du template.
		Template::init();

		// Authentification utilisateur.
		$sections_connection = ['forgot-password', 'login'];
		$auth = Auth::cookie();
		if ($_GET['section'] == 'user' && $_GET['user_id'] == Auth::$id && !empty($_POST))
		{
			L10N::locale($_POST['lang'] ?? '', $_POST['tz'] ?? '');
		}
		else
		{
			L10N::locale(!Auth::$connected && Config::$params['lang_switch']
				? Auth::$prefs->read('lang')
				: '');
		}
		Template::set('lang', str_replace('_', '-', Auth::$lang));
		if (!$auth)
		{
			// Si l'authentification a échouée et que l'utilisateur ne se trouve pas
			// sur une page de la console de connexion, on le redirige vers celle-ci.
			if (!in_array($_GET['section'], $sections_connection))
			{
				App::redirect('login');
				return;
			}
			require_once(__DIR__ . '/../' . str_replace('-', '_', $_GET['section']) . '.php');
			return;
		}

		// Si l'utilisateur est connecté mais qu'il demande une page
		// de la console de connexion, on le redirige vers le tableau de bord.
		if (in_array($_GET['section'], $sections_connection))
		{
			App::redirect('dashboard');
		}

		// On transmet au template les informations sur l'utilisateur connecté.
		Template::set('auth',
		[
			'avatar' =>
			[
				'thumb' =>
				[
					'url' => Avatar::getURL(
						Auth::$id, (bool) Auth::$infos['user_avatar']
					)
				]
			],
			'id' => Auth::$id,
			'nickname' => Auth::$nickname
		]);

		// Installation des nouvelles langues.
		L10N::langInstall();

		// Nom de section.
		$section = str_replace(['-', '/'], '_', $_GET['section'] == 'page'
			? $_GET['q_pageless']
			: $_GET['section']
		);
		Template::set('section_id', $section);

		// Aide contextuelle.
		$help_file = GALLERY_ROOT . '/locale/' . Auth::$lang
			. '/help/' . preg_replace('`\_\d+$`', '', $section) . '.html';
		Template::set('help', file_exists($help_file) ? $help_file : NULL);

		// Aide contextuelle : HTML.
		Template::set('allowed_attrs',
			implode(', ', array_keys(HTML::ALLOWED_ATTRS)));
		Template::set('allowed_tags',
			implode(', ', array_map(function($v){return "<$v>";}, HTML::ALLOWED_TAGS)));

		// Requêtes avec filtre.
		self::filters();

		// Captures vidéos (partie 1).
		if (!isset($_POST['upload']))
		{
			Template::set('video_captures', Video::captures());
		}

		// Fichiers à activer ou désactiver.
		Item::pubexp();

		// Opérations quotidiennes.
		App::dailyUpdate();

		// Traitement de la page courante.
		$page = str_replace('-', '_', $_GET['section']);
		if (in_array($_GET['section'], ['album', 'category']))
		{
			if (isset($_GET['search']))
			{
				$page = Admin::$search->options['type'] == 'items' ? 'album' : 'category';
			}
			else if (isset($_GET['filter']))
			{
				$page = 'album';
			}
		}
		require_once(__DIR__ . '/../' . $page . '.php');

		// Captures vidéos (partie 2).
		if (isset($_POST['upload']) || isset($_FILES['replace']))
		{
			Template::set('video_captures', Video::captures());
		}
	}

	/**
	 * Formats de fichiers disponibles pour les images redimensionnées.
	 *
	 * @return void
	 */
	public static function tplFileTypes(): void
	{
		$file_types =
		[
			'' => __('Format d\'origine'),
			'avif' => 'AVIF',
			'jpeg' => 'JPEG',
			'webp' => 'WEBP'
		];
		if (!function_exists('imageavif'))
		{
			unset($file_types['avif']);
		}
		Template::set('file_types', $file_types);
	}

	/**
	 * Télécharge et installe la dernière version disponible.
	 *
	 * @return void
	 */
	public static function update(): void
	{
		$step = (int) ($_GET['section_2'] ?? 0);

		$error = function($line)
		{
			Template::set('update',
			[
				'message_error' => "[$line] " . Report::getErrorDefaultMessage()
			]);
		};

		$no_new_version = function()
		{
			Template::set('update',
			[
				'message_info' => sprintf(
					__('Vous avez déjà la version la plus récente de %s.'), System::APP_NAME
				)
			]);
		};

		Template::set('update',
		[
			'new_version' => $new_version = Config::$params['new_version']['version'] ?? '?'
		]);

		if (!$step && version_compare((string) $new_version, System::APP_VERSION, '>'))
		{
			App::redirect('update/1');
			return;
		}

		if (!$step && !empty($_POST['check']))
		{
			if (($new_version = Update::getLatestVersion()) === FALSE)
			{
				$error(__LINE__);
				return;
			}
			if (is_array($new_version) && isset($new_version['version']))
			{
				$app_version = (string) $new_version['version'];
				if (preg_match(Update::VERSION_REGEXP, $app_version)
				&& version_compare(System::APP_VERSION, $app_version, '<'))
				{
					Config::changeDBParams(
					[
						'new_version' => $new_version,
						'new_version_check_date' => date('Y-m-d')
					]);
					App::redirect('update/1');
				}
			}
			$no_new_version();
			return;
		}

		if (!Update::isAllowedHTTPOrigin() || !is_array(Config::$params['new_version']))
		{
			if ($step > 0)
			{
				$no_new_version();
			}
			return;
		}

		$php_version = Config::$params['new_version']['php'] ?? '7.2';
		if (!version_compare(System::getPHPVersion(), (string) $php_version, '>='))
		{
			Template::set('update',
			[
				'message_info' => __('Version de PHP incompatible.')
			]);
			return;
		}

		// Étape 1 : téléchargement de la dernière version.
		if ($step == 1)
		{
			if (version_compare(System::APP_VERSION, (string) $new_version, '>='))
			{
				$no_new_version();
				return;
			}

			if (!empty($_POST['update']))
			{
				// Récupération des informations de la dernière version.
				if (($latest_version = Update::getLatestVersion()) === FALSE)
				{
					$error(__LINE__);
					return;
				}

				// On vérifie qu'il s'agit d'une nouvelle version.
				if (!version_compare(System::APP_VERSION,
				(string) $latest_version['version'], '<'))
				{
					$no_new_version();
					return;
				}

				if (!extension_loaded('zip'))
				{
					Template::set('update',
					[
						'message_info' => __('Extension Zip non activée.')
					]);
					return;
				}

				// Récupération de l'archive.
				if (!Update::getArchive($latest_version))
				{
					$error(__LINE__);
					return;
				}

				// Remplacement des fichiers.
				if (!Update::replaceFiles())
				{
					$error(__LINE__);
					return;
				}

				// Temporisation.
				// Avec certains hébergeurs, le remplacement de
				// fichiers prend tellement de temps que la
				// redirection vers l'étape 2 intervient trop vite.
				// C'est à dire que lors de la vérification de la
				// nouvelle version avec System::APP_VERSION c'est
				// le fichier de l'ancienne version qui est chargé !
				if (function_exists('sleep'))
				{
					sleep(3);
				}

				// Redirection (pour charger les nouveaux paramètres de configuration).
				header('Location: ' . GALLERY_HOST . CONF_GALLERY_PATH
					. '/' . CONF_ADMIN_DIR . '/?q=update/2');
				die;
			}
		}

		// Étape 2 : exécution du script de mise à jour.
		if ($step == 2)
		{
			if (Config::$params['app_version'] == System::APP_VERSION)
			{
				$no_new_version();
				return;
			}

			$upgrade_file = GALLERY_ROOT . '/upgrade.php';
			if (!file_exists($upgrade_file))
			{
				$error(__LINE__);
				return;
			}
			include_once($upgrade_file);

			// Échec ?
			if (!Upgrade::$success)
			{
				$error(__LINE__);
				return;
			}

			// Suppression de l'archive, du script de mise à jour
			// et du répertoire d'installation.
			$archive_file = GALLERY_ROOT . '/latestversion.zip';
			if (file_exists($archive_file))
			{
				File::unlink($archive_file);
			}
			if (file_exists($upgrade_file))
			{
				File::unlink($upgrade_file);
			}
			if (is_dir($install_dir = GALLERY_ROOT . '/install'))
			{
				File::rmdir($install_dir);
			}

			// Mise à jour réussie.
			Template::set('update',
			[
				'message_success' => __('Mise à jour réussie !')
			]);
		}
	}



	/**
	 * Génère les paramètres de template pour
	 * la construction des listes de catégories.
	 *
	 * @param mixed $categories_browse
	 *
	 * @return void
	 */
	protected static function _categoriesBrowse($categories_browse): void
	{
		$browse_levels = 1;
		$browse_subcats = 0;
		if (is_array($categories_browse)
		/*&& count(array_column($categories_browse, 'id')) > 10*/)
		{
			foreach ($categories_browse as &$i)
			{
				if ($i['node'] == 'content')
				{
					if ($i['subcats'])
					{
						$browse_subcats = 1;
					}
					if ($i['level'] > $browse_levels)
					{
						$browse_levels = $i['level'];
					}
				}
			}
		}
		Template::set('categories_browse', $categories_browse);
		Template::set('categories_browse_levels', $browse_levels);
		Template::set('categories_browse_subcats', $browse_subcats);
	}

	/**
	 * Récupère de manière sûre les identifiants des objets sélectionnés
	 * dans un formulaire sur lesquels une action est à effectuer.
	 *
	 * @param array $selected_ids
	 *
	 * @return string
	 *   Retourne l'action à effectuer.
	 */
	protected static function _getSelectedIds(&$selected_ids): string
	{
		$action = (string) ($_POST['action'] ?? '');
		$selected_ids = array_map('intval', array_keys($_POST['selected'] ?? []));

		$post = [];
		foreach ($_POST as $k => &$v)
		{
			if (!preg_match('`^\d+$`', (string) $k))
			{
				$post[$k] = $v;
			}
		}
		$_POST = $post;

		return $action;
	}

	/**
	 * Traitement à effectuer si aucun objet n'a été récupéré.
	 *
	 * @param int $nb_objects
	 *   Nombre d'objets récupérés.
	 *
	 * @return bool
	 *   Retourne FALSE si la méthode appelante doit s'arrêter.
	 */
	protected static function _objectsNoResult(int $nb_objects): bool
	{
		if ($nb_objects > 0)
		{
			return TRUE;
		}

		// Section courante.
		switch ($_GET['section'])
		{
			case 'album' :
			case 'category' :
				$section = 'category/1';
				break;

			case 'items-pending' :
				$section = 'items-pending/1';
				break;

			default :
				$section = $_GET['section'];
		}

		// Action ayant entraîné une modification du nombre d'objets à récupérer :
		// on relance la méthode appelante en modifiant le numéro de page.
		if (isset($_POST['action']))
		{
			if ($_GET['page'] == 1)
			{
				return FALSE;
			}

			$last_page = Template::$data['nb_pages'] ?? 1;
			$_GET['q'] = preg_replace('`/page/' . $_GET['page'] . '$`',
				'/page/' . $last_page, $_GET['q']);
			$_GET['page'] = $last_page;
			$trace = debug_backtrace();
			call_user_func($trace[1]['class'] . '::' . $trace[1]['function']);
		}

		// Aucune action effectuée, mais page autre que la première :
		// on redirige vers la première page de la section.
		// Exemple :
		// 		Page demandée  : users/page/4
		//	 	Page redirigée : users
		else if ($_GET['page'] > 1)
		{
			App::redirect($_GET['q_pageless']);
		}

		// Aucune action effectuée et première page,
		// mais section spéciale (filtres, recherche) :
		// on redirige vers la même section sans filtre.
		// Exemple :
		// 		Page demandée  : votes/album/5/date/2012-07-23
		//	 	Page redirigée : votes/album/5
		else if (isset($_GET['filter'])
		&& $_GET['q'] != $section . '/' . $_GET['filter'] . '/' . $_GET['filter_value'])
		{
			if ($_GET['filter_value'] !== '')
			{
				App::redirect($section . '/' . $_GET['filter'] . '/' . $_GET['filter_value']);
			}
		}

		// Aucune action effectuée, première page, mais
		// section non principale (dans l'arborescence de la galerie) :
		// on redirige vers la section principale, sauf dans le cas
		// d'une recherche.
		// Exemple :
		// 		Page demandée  : votes/album/5
		//	 	Page redirigée : votes
		else if ($_GET['q'] != $section && !isset($_GET['search'])
		&& $section != 'category-items')
		{
			// Pour la section "Albums", on ne redirige que si
			// la catégorie n'existe pas car elle doit pouvoir
			// être affichée même si elle est vide.
			if ($section != 'category/1'
			|| ($section == 'category/1' && empty(Template::$data['category'])))
			{
				App::redirect($section);
			}
		}

		return FALSE;
	}

	/**
	 * Gestion des paramètres GET.
	 *
	 * @return void
	 */
	private static function _request(): void
	{
		App::request
		([
			// Connexion.
			'forgot-password',
			'login',
			'logout',

			// Tableau de bord.
			'dashboard',

			// FTP.
			'scan',

			// Catégories.
			'{category}/{id}',
			'{category}/{id}/camera-(brand|model)/{id}',
			'{category}/{id}/datetime/{timestamp}',
			'{category}/{id}/selection',
			'{category}/{id}/search/{search}',
			'{category}/{id}/tag/{id}',
			'{category}/{id}/user-(favorites|images|items|videos)/{id}',
			'{category}-delete/{id}',
			'{category}-display/{id}',
			'{category}-edit/{id}',
			'{category}-geolocation/{id}',
			'{category}-search/{id}',
			'{category}-search-advanced/{id}',
			'{category}-sort/{id}',
			'{category}-thumb/{id}',
			'{category}-thumb-new/{id}',
			'album-upload/{id}',
			'category-add/{id}',
			'category-items/{id}',
			'category-items-display/{id}',
			'category-items-search/{id}',
			'category-items-search-advanced/{id}',

			// Fichiers.
			'item/{id}',
			'item-edit/{id}',
			'item-delete/{id}',
			'item-geolocation/{id}',
			'item-replace/{id}',
			'item-thumb/{id}',
			'item-thumb-new/{id}',

			// Fichiers en attente.
			'items-pending/{id}',
			'items-pending-display/{id}',

			// Commentaires.
			'comments',
			'comments/{category}/{id}',
			'comments/{category}/{id}/date/{date}',
			'comments/{category}/{id}/ip/{ip}',
			'comments/{category}/{id}/item/{id}',
			'comments/{category}/{id}/pending',
			'comments/{category}/{id}/search/{search}',
			'comments/{category}/{id}/user/{id}',
			'comments/date/{date}',
			'comments-display',
			'comments/ip/{ip}',
			'comments/item/{id}',
			'comments-options',
			'comments/pending',
			'comments-search',
			'comments-search-advanced',
			'comments/search/{search}',
			'comments/user/{id}',

			// Votes.
			'votes',
			'votes/{category}/{id}',
			'votes/{category}/{id}/date/{date}',
			'votes/{category}/{id}/ip/{ip}',
			'votes/{category}/{id}/item/{id}',
			'votes/{category}/{id}/note/[1-5]',
			'votes/{category}/{id}/search/{search}',
			'votes/{category}/{id}/user/{id}',
			'votes/date/{date}',
			'votes-display',
			'votes/ip/{ip}',
			'votes/item/{id}',
			'votes/note/[1-5]',
			'votes-search',
			'votes-search-advanced',
			'votes/search/{search}',
			'votes/user/{id}',

			// Tags.
			'tags',
			'tags-add',
			'tags-display',
			'tags-search',
			'tags-search-advanced',
			'tags/search/{search}',

			// Appareils photos.
			'cameras-brands',
			'cameras-brands-display',
			'cameras-brands-search',
			'cameras-brands/search/{search}',
			'cameras-models',
			'cameras-models-search',
			'cameras-models/search/{search}',
			'cameras-models-display',

			// Mise à jour de l'application.
			'update',
			'update/[1-2]',

			// Utilisateurs.
			'user/{id}',
			'user-avatar/{id}',
			'user-delete/{id}',
			'users',
			'users-add',
			'users-display',
			'users/group/{id}',
			'users/group/{id}/pending',
			'users/group/{id}/search/{search}',
			'users-options',
			'users/pending',
			'users-search',
			'users-search-advanced',
			'users/search/{search}',
			'users-sendmail',
			'users-sendmail/confirm',
			'users-sendmail/{id}',
			'users-sendmail/{ids}',

			// Groupes.
			'group/{id}',
			'group-categories/{id}',
			'group-delete/{id}',
			'group-features/{id}',
			'groups',
			'groups-add',

			// Pages.
			'new-page',
			'pages',
			'page/comments',
			'page/contact',
			'page/members',
			'page/custom/{id}',
			'page/worldmap',

			// Fonctionnalités.
			'categories-stats',
			'diaporama',
			'exif',
			'features',
			'features-diaporama',
			'iptc',
			'links',
			'xmp',

			// Options.
			'options-blacklists',
			'options-categories',
			'options-email',
			'options-gallery',
			'options-items',

			// Thèmes.
			'themes',

			// Maintenance.
			'maintenance',

			// Logs d'activité des utilisateurs.
			'logs',
			'logs/action/[-a-z]{1,40}',
			'logs/date/{date}',
			'logs-display',
			'logs/ip/{ip}',
			'logs/result/(accepted|rejected)',
			'logs-search',
			'logs-search-advanced',
			'logs/search/{search}',
			'logs/user/{id}',
			'logs/user/{id}/action/[-a-z]{1,40}',
			'logs/user/{id}/date/{date}',
			'logs/user/{id}/ip/{ip}',
			'logs/user/{id}/result/(accepted|rejected)',
			'logs/user/{id}/search/{search}',

			// Informations.
			'errors',
			'errors-forum',
			'system'
		]);

		// Valeurs par défaut.
		if (!isset($_GET['section']))
		{
			$_GET['q'] = $_GET['q_pageless'] = $_GET['section'] = 'dashboard';
		}

		if (isset($_GET['album_id']))
		{
			$_GET['category_id'] = $_GET['album_id'];
		}

		// Sections spéciales si JavaScript désactivé.
		$regexp = '`^([^/]+)\-(?:add|display|forum|search-advanced|search|upload)`';
		$_GET['section'] = preg_replace($regexp, '$1', $_GET['section']);
		$_GET['q_pageless'] = preg_replace($regexp, '$1', $_GET['q_pageless']);
	}
}
?>