<?php
declare(strict_types = 1);

/**
 * Méthodes de gestion des informations de template.
 *
 * - Dans les fonctions anonymes destinées à être utilisées dans les
 *   fichiers de template, ne jamais utiliser Config::getDBParam(),
 *   mais Template::$data['config']().
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class Template
{
	/**
	 * Données de template en accès direct avant le chargement du template.
	 *
	 * @var array
	 */
	public static $data = [];



	/**
	 * Définit les informations utiles pour générer un fil d'Ariane
	 * vers un objet de la galerie (catégorie ou fichier).
	 *
	 * @param array $infos
	 *   Informations de l'objet.
	 * @param array $urlname
	 *   Doit-on former les liens avec le nom d'URL de l'objet ?
	 * @param string $section
	 *   Section principale.
	 * @param array $filter
	 *   Doit-on former les liens avec le filtre ?
	 *
	 * @return array
	 *   Informations des catégories parentes de l'objet.
	 */
	public static function breadcrumb(array &$infos,
	bool $urlname = FALSE, string $section = 'category', bool $filter = TRUE): array
	{
		// Filtre.
		$filter_item = $filter ? ($_GET['q_filter'] ?? '') : '';
		$filter_cat = isset($_GET['filter_cat_id'])
			? preg_replace('`/' . $_GET['filter_cat_id'] . '$`', '', $filter_item)
			: $filter_item;

		$urlname = $urlname ? '-' . __('galerie') : '';
		$breadcrumb[] =
		[
			'id' => 1,
			'name' => __('galerie'),
			'type' => $section,
			'url' => App::getURL($section . '/1' . $urlname . $filter_cat)
		];
		$parents = Category::getParentsIdArray($infos['cat_parents']);
		array_shift($parents);
		$parents_infos = [];
		if ($infos['cat_id'] > 1)
		{
			if (count($parents))
			{
				$parents_infos = Parents::getInfos($parents);
				foreach ($parents_infos as &$i)
				{
					$urlname = $urlname ? '-' . $i['cat_url'] : '';
					$breadcrumb[] =
					[
						'a_cat' => $i['cat_a_subs'],
						'id' => $i['cat_id'],
						'name' => $i['cat_name'],
						'order_by' => $i['cat_orderby'],
						'type' => 'category',
						'url' => App::getURL(
							$section . '/' . $i['cat_id'] . $urlname . $filter_cat
						)
					];
				}
			}
			$urlname = $urlname ? '-' . $infos['cat_url'] : '';
			$breadcrumb[] =
			[
				'id' => $infos['cat_id'],
				'name' => $infos['cat_name'],
				'type' => $infos['cat_type'],
				'url' => App::getURL(
					(strstr($section, 'category')
						? str_replace('category', $infos['cat_type'], $section)
						: $section)
					. '/' . $infos['cat_id'] . $urlname . $filter_cat
				)
			];
		}
		if (isset($infos['item_name']))
		{
			$urlname = $urlname ? '-' . $infos['item_url'] : '';
			$breadcrumb[] =
			[
				'id' => $infos['item_id'],
				'name' => $infos['item_name'],
				'type' => Item::isVideo($infos['item_type']) ? 'video' : 'image',
				'url' => App::getURL('item/' . $infos['item_id'] . $urlname . $filter_item)
			];
		}
		$breadcrumb[count($breadcrumb)-1]['current'] = TRUE;

		self::set('breadcrumb', $breadcrumb);
		return $parents_infos;
	}

	/**
	 * Informations du filtre en complément du fil d'Ariane.
	 *
	 * @param int $value
	 * @param string $exit_url
	 *
	 * @return bool
	 */
	public static function filter(int $value, string $exit_url): bool
	{
		self::set('filter',
		[
			'cat_link' => App::getURL(),
			'cat_name' => __('galerie'),
			'cat_text' => __('dans la %s'),
			'exit_link' => App::getURL(
				substr($exit_url, 0, 11) == 'category/1-' ? '' : $exit_url
			),
			'exit_text' => __('Tout montrer')
		]);

		if (isset(Template::$data['category_filter'])
		|| (isset(Template::$data['category']) && Template::$data['category']['id'] > 1))
		{
			$i = Template::$data['category_filter'] ?? Template::$data['category'];
			if ($i['id'] == $_GET['filter_cat_id'])
			{
				self::set('filter',
				[
					'cat_link' => App::getURL(
						$i['type'] . '/' . $i['id'] . '-' . $i['urlname']
					),
					'cat_name' => $i['title'],
					'cat_text' => ($i['type'] == 'category')
						? __('dans la catégorie %s')
						: __('dans l\'album %s')
				]);
			}
		}

		// Formatage d'une date.
		$format_date = function(): string
		{
			switch (strlen($_GET['filter_value']))
			{
				case 10 :
					$format = __('%A %d %B %Y');
					break;

				case 7 :
					$format = '%B %Y';
					break;

				case 4 :
					return $_GET['filter_value'];
			}
			return L10N::dt($format, $_GET['filter_value']);
		};

		// Types de fichiers présents dans la catégorie.
		$types = 'items';
		if (App::$scriptName == 'gallery')
		{
			$set_types = function(&$i)
			{
				if ($i['cat_a_images'] && !$i['cat_a_videos'])
				{
					return 'images';
				}
				if (!$i['cat_a_images'] && $i['cat_a_videos'])
				{
					return 'videos';
				}
				return 'items';
			};
			$i = GalleryCategory::$infos ? GalleryCategory::$infos : GalleryItems::$infos;
			if (GalleryCategory::$count)
			{
				$i['cat_a_images'] = GalleryCategory::$count['images'];
				$i['cat_a_videos'] = GalleryCategory::$count['videos'];
			}
			if (isset($_GET['filter_cat_id']) && $_GET['filter_cat_id'] != $i['cat_id'])
			{
				$sql = 'SELECT CASE WHEN cat_filemtime IS NULL
									THEN "category"
									ELSE "album"
									 END
						  FROM {categories}
						 WHERE cat_id = ?';
				if (DB::execute($sql, $_GET['filter_cat_id']))
				{
					$i['cat_type'] = DB::fetchVal();
				}
			}
			$types = $set_types($i);
		}

		switch ($_GET['filter'])
		{
			case 'action' :
				$action = str_replace('-', '_', $_GET['filter_value']);
				$action = AdminLogs::getActionParams($action);
				self::set('filter',
				[
					'object_text' => '%s',
					'object_name' => $action['text']
				]);
				return TRUE;

			case 'camera-brand' :
				$sql = 'SELECT camera_brand_name FROM {cameras_brands} WHERE camera_brand_id = ?';
				if (!DB::execute($sql, $value) || !$brand = DB::fetchVal())
				{
					return FALSE;
				}
				self::set('filter',
				[
					'object_text' => __('Photos prises avec un appareil de la marque %s'),
					'object_name' => Metadata::getExifCameraMake($brand)
				]);
				return TRUE;

			case 'camera-model' :
				$sql = 'SELECT camera_model_name FROM {cameras_models} WHERE camera_model_id = ?';
				if (!DB::execute($sql, $value) || !$model = DB::fetchVal())
				{
					return FALSE;
				}
				self::set('filter',
				[
					'object_text' => __('Photos prises avec le modèle %s'),
					'object_name' => isset(Metadata::MODELS[$model])
						? $model . ' (' . Metadata::MODELS[$model] . ')'
						: $model
				]);
				return TRUE;

			case 'comments' :
				switch ($types)
				{
					case 'images' :
						$text = __('Photos les plus commentées %s');
						break;
					case 'videos' :
						$text = __('Vidéos les plus commentées %s');
						break;
					default :
						$text = __('Photos et vidéos les plus commentées %s');
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => ''
				]);
				return TRUE;

			case 'date' :
				switch ($_GET['section'])
				{
					case 'comments' :
						$text = __('Commentaires du %s');
						break;
					case 'logs' :
						$text = __('Actions effectuées le %s');
						break;
					case 'votes' :
						$text = __('Votes du %s');
						break;
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => $format_date()
				]);
				return TRUE;

			case 'date-created' :
				switch ($types)
				{
					case 'images' :
						$text = strlen($_GET['filter_value']) > 7
							? __('Photos créées le %s')
							: __('Photos créées en %s');
						break;
					case 'videos' :
						$text = strlen($_GET['filter_value']) > 7
							? __('Vidéos créées le %s')
							: __('Vidéos créées en %s');
						break;
					default :
						$text = strlen($_GET['filter_value']) > 7
							? __('Photos et vidéos créées le %s')
							: __('Photos et vidéos créées en %s');
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => $format_date()
				]);
				return TRUE;

			case 'date-published' :
				switch ($types)
				{
					case 'images' :
						$text = strlen($_GET['filter_value']) > 7
							? __('Photos publiées le %s')
							: __('Photos publiées en %s');
						break;
					case 'videos' :
						$text = strlen($_GET['filter_value']) > 7
							? __('Vidéos publiées le %s')
							: __('Vidéos publiées en %s');
						break;
					default :
						$text = strlen($_GET['filter_value']) > 7
							? __('Photos et vidéos publiées le %s')
							: __('Photos et vidéos publiées en %s');
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => $format_date()
				]);
				return TRUE;

			case 'datetime' :
				self::set('filter',
				[
					'object_text' => __('Fichiers ajoutés le %s'),
					'object_name' => L10N::dt(
						__('%A %d %B %Y à %H:%M:%S'),
						date('Y-m-d H:i:s', (int) $_GET['filter_value'])
					)
				]);
				return TRUE;

			case 'favorites' :
				switch ($types)
				{
					case 'images' :
						$text = __('Photos favorites des utilisateurs');
						break;
					case 'videos' :
						$text = __('Vidéos favorites des utilisateurs');
						break;
					default :
						$text = __('Photos et vidéos favorites des utilisateurs');
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => ''
				]);
				return TRUE;

			case 'images' :
			case 'items' :
			case 'videos' :
				if ($_GET['filter'] == 'items')
				{
					switch ($types)
					{
						case 'images' :
							$text = __('Photos de %s');
							break;
						case 'videos' :
							$text = __('Vidéos de %s');
							break;
						default :
							$text = __('Photos et vidéos de %s');
					}
				}
				self::set('filter',
				[
					'cat_text' => (isset($i['cat_type']) && $_GET['filter_cat_id'] > 1)
						? ($i['cat_type'] == 'category'
							? __('la catégorie %s')
							: __('l\'album %s'))
						: __('la %s'),
					'object_text' => $_GET['filter'] == 'images'
						? __('Photos de %s')
						: ($_GET['filter'] == 'videos' ? __('Vidéos de %s') : $text),
					'object_name' => ''
				]);
				return TRUE;

			case 'ip' :
				switch ($_GET['section'])
				{
					case 'comments' :
						$text = __('Commentaires en provenance de l\'IP %s');
						break;
					case 'logs' :
						$text = __('Actions en provenance de l\'IP %s');
						break;
					case 'votes' :
						$text = __('Votes en provenance de l\'IP %s');
						break;
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => $_GET['filter_value']
				]);
				return TRUE;

			case 'item' :
				$sql = 'SELECT item_name FROM {items} WHERE item_id = ?';
				if (!DB::execute($sql, $value) || !$name = DB::fetchVal())
				{
					return FALSE;
				}
				switch ($_GET['section'])
				{
					case 'comments' :
						$text = __('Commentaires sur le fichier %s');
						break;
					case 'votes' :
						$text = __('Votes sur le fichier %s');
						break;
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_link' => App::getURL('item-edit/' . $value),
					'object_name' => $name
				]);
				return TRUE;

			case 'note' :
				self::set('filter',
				[
					'object_text' => __('Votes correspondant à la note %s'),
					'object_name' => $_GET['filter_value']
				]);
				return TRUE;

			case 'pending' :
				switch ($_GET['section'])
				{
					case 'comments' :
						$text = __('Commentaires en attente');
						break;
					case 'users' :
						$text = __('Utilisateurs en attente');
						break;
				}
				self::set('filter',
				[
					'object_text' => $text
				]);
				return TRUE;

			case 'recent-images' :
			case 'recent-items' :
			case 'recent-videos' :
				if ($_GET['filter'] == 'recent-items')
				{
					switch ($types)
					{
						case 'images' :
							$text = __('Photos de moins de %s');
							break;
						case 'videos' :
							$text = __('Vidéos de moins de %s');
							break;
						default :
							$text = __('Photos et vidéos de moins de %s');
					}
				}
				$days = (int) Config::$params['items_recent_days'];
				$text = strstr($_GET['filter'], 'images')
					? __('Photos de moins de %s')
					: (strstr($_GET['filter'], 'videos') ? __('Vidéos de moins de %s') : $text);
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => $days > 1
						? sprintf(__('%s jours'), $days)
						: sprintf(__('%s jour'), $days)
				]);
				return TRUE;

			case 'result' :
				self::set('filter',
				[
					'object_text' => __('Actions %s'),
					'object_name' => $_GET['filter_value'] == 'accepted'
						? __('acceptées')
						: __('rejetées')
				]);
				return TRUE;

			case 'search' :
				self::set('filter',
				[
					'exit_text' => __('Sortir de la recherche'),
					'object_text' => __('Recherche « %s »'),
					'object_name' => self::$data['search']['query'] ?? ''
				]);
				if (isset(self::$data['search']['invalid']))
				{
					self::set('filter',
					[
						'cat_name' => '',
						'cat_text' => '',
						'object_text' => __('Recherche périmée ou invalide')
					]);
				}
				return TRUE;

			case 'selection' :
				switch ($types)
				{
					case 'images' :
						$text = __('Photos de la sélection');
						break;
					case 'videos' :
						$text = __('Vidéos de la sélection');
						break;
					default :
						$text = __('Photos et vidéos de la sélection');
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => ''
				]);
				return TRUE;

			case 'tag' :
			case 'tags' :
				$tags = DB::inInt(explode(',', $_GET['filter_value']));
				$sql = "SELECT * FROM {tags} WHERE tag_id IN ($tags) ORDER BY tag_name ASC";
				if (!DB::execute($sql))
				{
					return FALSE;
				}
				switch ($types)
				{
					case 'images' :
						$text = $_GET['filter'] == 'tag'
							? __('Photos liées au tag %s')
							: __('Photos liées aux tags %s');
						break;
					case 'videos' :
						$text = $_GET['filter'] == 'tag'
							? __('Vidéos liées au tag %s')
							: __('Vidéos liées aux tags %s');
						break;
					default :
						$text = $_GET['filter'] == 'tag'
							? __('Photos et vidéos liées au tag %s')
							: __('Photos et vidéos liées aux tags %s');
				}
				$tags = [];
				foreach (DB::fetchAll('tag_id') as &$t)
				{
					$tags[] =
					[
						'link' => App::getURL(
							'category/1/tag/' . $t['tag_id'] . '-' . $t['tag_url']
						),
						'name' => $t['tag_name']
					];
				}
				self::set('filter',
				[
					'object_name' => $tags ? $tags[0]['name'] : '',
					'object_text' => $text,
					'tags' => $tags
				]);
				return (bool) $tags;

			case 'user' :
			case 'user-favorites' :
			case 'user-images' :
			case 'user-items' :
			case 'user-videos' :
				if ($_GET['section'] == 'logs')
				{
					unset(Template::$data['filter']);
					return TRUE;
				}
				$sql = 'SELECT user_login, user_nickname FROM {users} WHERE user_id = ?';
				if (!DB::execute($sql, $value) || !$user_infos = DB::fetchRow())
				{
					return FALSE;
				}
				switch ($_GET['filter'])
				{
					case 'user' :
						switch ($_GET['section'])
						{
							case 'comments' :
								$text = __('Commentaires de %s');
								break;
							case 'votes' :
								$text = __('Votes de %s');
								break;
						}
						break;
					case 'user-favorites' :
						$text = __('Favoris de %s');
						break;
					case 'user-images' :
						$text = __('Photos de %s');
						break;
					case 'user-items' :
						switch ($types)
						{
							case 'images' :
								$text = __('Photos de %s');
								break;
							case 'videos' :
								$text = __('Vidéos de %s');
								break;
							default :
								$text = __('Photos et vidéos de %s');
						}
						break;
					case 'user-videos' :
						$text = __('Vidéos de %s');
						break;
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_link' => App::getURL('user/' . $value),
					'object_name' => User::getNickname(
						$user_infos['user_login'], $user_infos['user_nickname']
					)
				]);
				return TRUE;

			case 'views' :
				switch ($types)
				{
					case 'images' :
						$text = __('Photos les plus vues');
						break;

					case 'videos' :
						$text = __('Vidéos les plus vues');
						break;

					default :
						$text = __('Photos et vidéos les plus vues');
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => ''
				]);
				return TRUE;

			case 'votes' :
				switch ($types)
				{
					case 'images' :
						$text = __('Photos les mieux notées');
						break;
					case 'videos' :
						$text = __('Vidéos les mieux notées');
						break;
					default :
						$text = __('Photos et vidéos les mieux notées');
				}
				self::set('filter',
				[
					'object_text' => $text,
					'object_name' => ''
				]);
				return TRUE;
		}
	}

	/**
	 * Formate un commentaire.
	 *
	 * @param string $str
	 *
	 * @return string
	 */
	public static function formatComment(string $str): string
	{
		$str = HTML::spaces($str);

		$limit = Config::$params['comments_url_to_link']
			? (int) Config::$params['comments_url_to_link_maxlength']
			: 0;
		if ($limit)
		{
			$str = HTML::linkify($str, $limit);
		}

		$str = self::formatEmoji($str);

		return nl2br($str, FALSE);
	}

	/**
	 * Formate une description.
	 *
	 * @param string $str
	 *
	 * @return string
	 */
	public static function formatDescription(string $str): string
	{
		switch ($_GET['section'])
		{
			case 'album' :
			case 'category' :
				if (GalleryCategory::$infos['cat_id'] > 1
				&& Config::$params['categories_description_model'])
				{
					return Description::model('cat', GalleryCategory::$infos);
				}
				break;

			case 'item' :
				if (Config::$params['items_description_model'])
				{
					return Description::model('item', GalleryItems::$infos);
				}
				break;
		}

		return self::formatText($str);
	}

	/**
	 * Formate les émojis en les entourant d'une balise <span>.
	 *
	 * @param string $str
	 *
	 * @return string
	 */
	public static function formatEmoji(string $str): string
	{
		return preg_replace_callback(
			'`' . Emoji::REGEXP . '`u',
			function($m)
			{
				/*
				$hex = strtoupper(dechex(hexdec(bin2hex(mb_convert_encoding(
					"$m[0]", 'UTF-32', 'UTF-8'
				)))));
				$text = __($hex);
				$title = ($text == $hex) ? "" : " title=\"$text\"";
				*/
				return "<span class=\"emoji\">$m[0]</span>";
			},
			$str
		);
	}

	/**
	 * Convertit les URL d'un texte en liens cliquables.
	 *
	 * @param string $str
	 * @param int $limit
	 *
	 * @return string
	 */
	public static function formatLinkify(string $str, int $limit = 50): string
	{
		$str = HTML::linkify($str, $limit);

		return nl2br($str, FALSE);
	}

	/**
	 * Formate du texte.
	 *
	 * @param string $str
	 * @param bool $nl2br
	 *
	 * @return string
	 */
	public static function formatText(string $str, bool $nl2br = TRUE): string
	{
		$str = HTML::spaces($str);
		$str = HTML::decode($str);
		if ($nl2br)
		{
			$str = HTML::nl2br($str);
		}
		$str = self::formatEmoji($str);
		$str = HTML::clean($str);

		return $str;
	}

	/**
	 * Initialisation : définit les données de template par défaut.
	 *
	 * @return void
	 */
	public static function init(): void
	{
		if (isset(self::$data['app']))
		{
			return;
		}

		$prefs = Auth::$prefs->value;

		self::$data =
		[
			'admin' =>
			[
				'link' => CONF_ADMIN_DIR == 'admin' && !empty(Config::$params['admin_link']),
				'path' => CONF_GALLERY_PATH . '/' . CONF_ADMIN_DIR,
				'style' =>
				[
					'path' => CONF_GALLERY_PATH . '/'
							. CONF_ADMIN_DIR . '/template/default/style/default'
				],
				'template' =>
				[
					'path' => CONF_GALLERY_PATH . '/' . CONF_ADMIN_DIR . '/template/default'
				]
			],
			'anticsrf' => function(): string
			{
				return htmlspecialchars(Security::getAntiCSRFToken());
			},
			'app' =>
			[
				'name' => System::APP_NAME,
				'version' => System::APP_VERSION,
				'website' => System::APP_WEBSITE
			],
			'config' => function(string $p = '')
			{
				$param = Config::$params[$p];
				return HTML::specialchars($param);
			},
			'cookie' =>
			[
				'session_expire' => (int) Auth::$session->read('session_expire')
			],
			'current_url' => App::getURL($_GET['q'] ?? ''),
			'current_url_pageless' => App::getURL($_GET['q_pageless'] ?? ''),
			'debug' => function()
			{
				return CONF_DEBUG_SQL
					|| (CONF_ERRORS_DISPLAY && ErrorHandler::$errors)
					|| count(l(NULL, FALSE));
			},
			'format_comment' => function($str): string
			{
				return Template::formatComment((string) $str);
			},
			'format_description' => function($str): string
			{
				return Template::formatDescription((string) $str);
			},
			'format_emoji' => function($str): string
			{
				return Template::formatEmoji((string) $str);
			},
			'format_linkify' => function($str, int $limit = 50): string
			{
				return Template::formatLinkify((string) $str, $limit);
			},
			'format_text' => function($str): string
			{
				return Template::formatText((string) $str);
			},
			'gallery' =>
			[
				'path' => CONF_GALLERY_PATH,
				'template' =>
				[
					'dir' => function(): string
					{
						return htmlspecialchars(self::$data['config']('theme_template'));
					},
					'path' => function(): string
					{
						$path = CONF_GALLERY_PATH . '/template/'
							 . self::$data['config']('theme_template');
						return htmlspecialchars($path);
					}
				],
				'title' => function($emoji = TRUE): string
				{
					$title = htmlspecialchars(Config::$params['gallery_title']);
					return $emoji ? Template::formatEmoji($title) : $title;
				}
			],
			'link' => function($p = ''): string
			{
				return htmlspecialchars(App::getURL($p));
			},
			'locale' => function($locale): string
			{
				$locale = explode(':', $locale);
				switch ($locale[0])
				{
					case 'month' :
						return htmlspecialchars(
							L10N::dt('%B', date('Y-' . $locale[1] . '-01'))
						);
				}
				return '';
			},
			'php2js' => function($var): string
			{
				if (is_array($var))
				{
					foreach ($var as &$val)
					{
						if (is_string($val))
						{
							$val = "'$val'";
						}
					}
					return '[' . implode(', ', $var) . ']';
				}
				return str_replace(
					["\\"  , "'"   , '"' , "\r", "\n", "\xE2\x80\xA8", "\xE2\x80\xA9"],
					['\\\\', '\\\'', '\"', '\r', '\n', '\u2028'      , '\u2029'      ],
					(string) $var
				);
			},
			'prefs' => function(string $param) use (&$prefs): string
			{
				return (string) ($prefs[$param] ?? '');
			},
			'print' => function(string $val): void
			{
				switch ($val)
				{
					case 'errors' :
						ErrorHandler::printErrors();
						break;

					case 'logs' :
						self::_printLogs();
						break;

					case 'sql' :
						self::_printSQL();
						break;
				}
			},
			'session' =>
			[
				'expire' => CONF_SESSION_EXPIRE
			],
			'strip_tags' => function(string $text, int $limit = 0, string $marker = '...'): string
			{
				$text = str_replace(['&lt;', '&gt;'], ['<', '>'], $text);
				$text = strip_tags((string) $text);
				return $limit
					? mb_strimwidth($text, 0, (int) $limit, (string) $marker)
					: $text;
			},
			'time' => function(): string
			{
				if (CONF_EXEC_TIME)
				{
					$time = microtime(TRUE) - START_TIME;
					$message = $time >= 2
						? __('Page générée en %.3f secondes')
						: __('Page générée en %.3f seconde');
					return htmlspecialchars(L10N::numeric(sprintf($message, $time)));
				}
				return '';
			},
			'upload_maxfilesize' => function(): int
			{
				$server_limit = Utility::getUploadMaxFilesize();
				$maxfilesize = (int) Config::$params['upload_maxfilesize'] * 1048576;
				if ($maxfilesize > $server_limit)
				{
					$maxfilesize = $server_limit;
				}
				return $maxfilesize;
			},
			'upload_slicesize' => function(): int
			{
				$value = 1048576 * 2;
				$server_limit = Utility::getUploadMaxFilesize();
				return $value > $server_limit ? $server_limit : $value;
			},
			'upload_totalsize' => function(): int
			{
				$value = 1048576 * 100;
				$maxfilesize = (int) Config::$params['upload_maxfilesize'] * 1048576;
				return $value < $maxfilesize ? $maxfilesize : $value;
			},
			'version_key' => CONF_DEV_MODE
				? time()
				: md5(System::APP_VERSION . '|' . (defined('CONF_KEY') ? CONF_KEY : ''))
		];
	}

	/**
	 * Définit la liste des langues disponibles.
	 *
	 * @param string $selected
	 *
	 * @return void
	 */
	public static function langs(string $selected): void
	{
		$params = Config::$params['lang_params'];
		$langs = [];
		foreach ($params['langs'] as $code => &$name)
		{
			$langs[] =
			[
				'code' => $code,
				'name' => $name,
				'selected' => $selected == $code
			];
		}
		self::set('langs', $langs);
	}

	/**
	 * Ajoute des données destinées aux fichiers de template.
	 *
	 * @param string $key
	 * @param mixed $value
	 *
	 * @return void
	 */
	public static function set(string $key, $value): void
	{
		if (isset(self::$data[$key]) && is_array(self::$data[$key]))
		{
			self::$data[$key] = array_replace_recursive(self::$data[$key], $value);
		}
		else
		{
			self::$data[$key] = $value;
		}
	}

	/**
	 * Sécurise les données de template.
	 *
	 * @return void
	 */
	public static function start(): void
	{
		ksort(self::$data);

		HTML::specialchars(self::$data);
		HTML::specialchars($_POST);
	}



	/**
	 * Imprime les logs PHP.
	 *
	 * @return void
	 */
	private static function _printLogs(): void
	{
		if (($logs = l(NULL, FALSE)) === [])
		{
			return;
		}

		echo '
			<style nonce="' . CSP_NONCE . '" type="text/css">
			.log
			{
				border: 1px solid gray;
				border-left: 3px solid gray;
				margin-top: 15px;
				max-width: 80%;
			}
			.log p
			{
				border-bottom: 1px solid #ddd;
				margin: 0;
				padding: 5px 8px;
				background: #f0f0f0;
			}
			.log pre
			{
				padding: 5px 10px;
			}
			</style>
		';
		foreach ($logs as $k => &$value)
		{
			$data = $value['data'];
			$trace = $value['trace'];
			$file = preg_replace('`^' . preg_quote(GALLERY_ROOT) . '`', '', $trace[0]['file']);
			$line = $trace[0]['line'];

			HTML::specialchars($data);
			if (is_array($data))
			{
				array_walk_recursive($data, function(&$v)
				{
					//if (is_callable($v) && !is_string($v))
					//if (is_object($v) && ($v instanceof Closure))
					if ($v instanceof Closure)
					{
						$v = 'Function()';
					}
				});
			}

			echo '<div class="log">';
			echo '<p>' . $file . ':' . $line . '</p>';
			echo '<pre>';
			if (is_array($data))
			{
				print_r($data);
			}
			else
			{
				var_dump($data);
			}
			echo '</pre>';
			echo '</div>';
		}
	}

	/**
	 * Construit et affiche un tableau des requêtes SQL.
	 *
	 * @return void
	 */
	private static function _printSQL(): void
	{
		if (!CONF_DEBUG_SQL)
		{
			return;
		}

		$queries =& DB::$queries;

		// Style CSS.
		echo '
			<style nonce="' . CSP_NONCE . '" type="text/css">
			#debug_sql
			{
				margin: 15px auto;
				background: white;
			}
			#debug_sql td,
			#debug_sql th
			{
				border: 1px solid silver;
				padding: 10px;
				vertical-align: top;
				max-width: 400px;
			}
			#debug_sql td.sql
			{
				font-family: "Courier New", sans-serif;
				width: 350px;
			}
			#debug_sql td.row_count,
			#debug_sql th.time
			{
				width: 80px;
			}
			#debug_sql .q
			{
				font-family: Verdana, Arial, Helvetica, sans-serif;
				color: #275F8F;
				font-weight: bold;
				margin-right: 10px;
			}
			#debug_sql .params
			{
				display: block;
				margin-top: 15px;
				color: #275F8F;
				font-weight: bold;
				font-family: Verdana, Arial, Helvetica, sans-serif;
			}
			#debug_sql .success
			{
				color: #648F27;
				font-weight: bold;
				text-transform: uppercase;
			}
			#debug_sql .failure
			{
				color: #D60E0E;
				font-weight: bold;
				text-transform: uppercase;
			}
			#debug_sql hr
			{
				border: 0;
				border-bottom: 1px solid silver;
			}
			#debug_sql td.num,
			#debug_sql td.line,
			#debug_sql td.row_count,
			#debug_sql td.time
			{
				text-align: right;
			}
			</style>
		';

		// Tableau.
		$total_time = 0;
		echo '<table id="debug_sql"><tr>
			<th class="num">#</th>
			<th class="file">' . __('Fichier') . '</th>
			<th class="line">' . __('Ligne') . '</th>
			<th class="sql">' . __('Requête SQL') . '</th>
			<th class="result">' . __('Résultat') . '</th>
			<th class="row_count">' . __('Nombre de lignes affectées') . '</th>
			<th class="time">' . __('Temps d\'exécution') . '</th>
		</tr>';

		for ($i = 0, $count_i = count($queries); $i < $count_i; $i++)
		{
			// Fichier.
			$file = $queries[$i]['file'];
			if (strpos($file, GALLERY_ROOT) === 0)
			{
				$file = substr($file, strlen(GALLERY_ROOT) + 1);
			}

			// Requête(s) SQL.
			if (is_array($queries[$i]['sql']))
			{
				$query = '';
				for ($n = 0, $count_n = count($queries[$i]['sql']); $n < $count_n; $n++)
				{
					if ($count_n > 1)
					{
						$query .= '<span class="q">[' . ($n + 1) . ']</span>';
					}
					if (is_array($queries[$i]['sql'][$n]))
					{
						$nb = count($queries[$i]['sql'][$n]['params']);
						$query .= nl2br(htmlentities($queries[$i]['sql'][$n]['sql']), FALSE);
						$query .= '<br>';
						$query .= '<span class="params">';
						$query .= sprintf(__('nombre de lignes : %s'), $nb);
						$query .= '</span>';
					}
					else
					{
						$query .= nl2br(htmlentities($queries[$i]['sql'][$n]), FALSE);
					}
					$query .= '<hr>';
				}
				$query = substr($query, 0, -6);
			}
			else
			{
				$query = nl2br(htmlentities($queries[$i]['sql']), FALSE);
			}

			// Résultat.
			$result = ($queries[$i]['exception'] === NULL)
				? '<span class="success">' . __('succès') . '</span>'
				: '<span class="failure">' . __('échec') . '</span>';

			// Exception.
			$exception = '';
			if (!empty($queries[$i]['exception']))
			{
				$exception .= '<br><br>';
				$exception .= nl2br(htmlentities($queries[$i]['exception']->getMessage()), FALSE);
			}

			// Nombre de lignes affectées.
			$row_count = $queries[$i]['row_count'];
			$row_count = $row_count < 0 ? '?' : $row_count;

			// Durée d'exécution.
			$time = L10N::numeric(sprintf('%0.3f ms', $queries[$i]['time'] * 1000));
			$total_time += $queries[$i]['time'];

			// Ligne du tableau.
			echo '<tr>
				<td class="num">' . ($i + 1) . '</td>
				<td class="file">' . $file . '</td>
				<td class="line">' . $queries[$i]['line'] . '</td>
				<td class="sql">' . $query . '</td>
				<td class="result">' . $result . $exception . '</td>
				<td class="row_count">' . $row_count . '</td>
				<td class="time">' . $time . '</td>
			</tr>';
		}

		// Durée totale d'exécution des requêtes.
		$total_time = L10N::numeric(sprintf('%0.3f ms', $total_time * 1000));
		echo '<tr id="total">
			<td class="num">*</td>
			<td class="file"></td>
			<td class="line"></td>
			<td class="sql"></td>
			<td class="result"></td>
			<td class="row_count"></td>
			<td class="time">' . $total_time . '</td>
		</tr>';

		echo '</table>';
	}
}
?>