<?php
declare(strict_types = 1);

require_once(__DIR__ . '/AdminAlbums.class.php');

/**
 * Gestion des fichiers.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class AdminItems extends AdminAlbums
{
	/**
	 * Informations en base de données du fichier courant.
	 *
	 * @var array
	 */
	public static $infos = [];



	/**
	 * Actions sur la sélection de fichiers.
	 *
	 * @return void
	 */
	public static function actions(): void
	{
		if (isset($_POST['cancel']) || !isset($_POST['selection']))
		{
			return;
		}
		switch ($action = self::_getSelectedIds($selected_ids))
		{
			case 'activate' :
			case 'activate_reset_item_pubdt' :
				$r = Item::status($selected_ids, 1, $action == 'activate_reset_item_pubdt');
				if ($r < 0)
				{
					Report::error();
				}
				else if ($r < 1)
				{
					Report::info(__('Aucun fichier n\'a été activé.'));
				}
				else
				{
					Report::success($r > 1
						? sprintf(__('%s fichiers ont été activés.'), $r)
						: __('1 fichier a été activé.'));
				}
				break;

			case 'deactivate' :
				$r = Item::status($selected_ids, 0);
				if ($r < 0)
				{
					Report::error();
				}
				else if ($r < 1)
				{
					Report::info(__('Aucun fichier n\'a été désactivé.'));
				}
				else
				{
					Report::success($r > 1
						? sprintf(__('%s fichiers ont été désactivés.'), $r)
						: __('1 fichier a été désactivé.'));
				}
				break;

			case 'delete' :
			case 'delete_resized' :
				$r = $_POST['action'] == 'delete'
					? Item::delete($selected_ids)
					: Item::deleteResized($selected_ids);
				if ($r < 0)
				{
					Report::error();
				}
				else if ($r < 1)
				{
					Report::info(__('Aucun fichier n\'a été supprimé.'));
				}
				else
				{
					Report::success($r > 1
						? sprintf(__('%s fichiers ont été supprimés.'), $r)
						: __('1 fichier a été supprimé.'));
				}
				break;

			case 'hits' :
			case 'owner' :
				if ($action == 'hits')
				{
					if (!preg_match('`^\d{1,12}$`', $_POST['hits']))
					{
						break;
					}
					$r = Item::views($selected_ids, (int) $_POST['hits']);
				}
				if ($action == 'owner')
				{
					$r = Item::owner($selected_ids, (int) $_POST['user_id']);
				}
				if ($r < 0)
				{
					Report::error();
				}
				else if ($r < 1)
				{
					Report::info(__('Aucun fichier n\'a été modifié.'));
				}
				else
				{
					Report::success($r > 1
						? sprintf(__('%s fichiers ont été modifiés.'), $r)
						: __('1 fichier a été modifié.'));
				}
				break;

			case 'move' :
				$r = Item::move($selected_ids, (int) $_POST['cat_id']);
				if ($r < 0)
				{
					Report::error();
				}
				else if ($r < 1)
				{
					Report::info(__('Aucun fichier n\'a été déplacé.'));
				}
				else
				{
					Report::success($r > 1
						? sprintf(__('%s fichiers ont été déplacés.'), $r)
						: __('1 fichier a été déplacé.'));
				}
				break;

			case 'update' :
				$r = Item::update($selected_ids);
				if ($r < 0)
				{
					Report::error();
				}
				else if ($r < 1)
				{
					Report::info(__('Aucun fichier n\'a été mis à jour.'));
				}
				else
				{
					Report::success($r > 1
						? sprintf(__('%s fichiers ont été mis à jour.'), $r)
						: __('1 fichier a été mis à jour.'));
				}
				break;
		}
	}

	/**
	 * Change les propriétés d'un fichier
	 * (état, album, nombre de vues).
	 *
	 * @return void
	 */
	public static function change(): void
	{
		$id = (int) $_GET['item_id'];

		if (!isset($_POST[$id]) || !isset($_POST['save']))
		{
			return;
		}

		Report::noChange();

		$r = function($r)
		{
			if ($r < 0)
			{
				Report::error();
			}
			if ($r > 0)
			{
				Report::success();
			}
		};

		if (isset($_POST[$id]['hits']) && preg_match('`^\d{1,12}$`', $_POST[$id]['hits']))
		{
			$r(Item::views([$id], (int) $_POST[$id]['hits']));
		}

		if (isset($_POST[$id]['status']))
		{
			$r(Item::status([$id], (int) $_POST[$id]['status']),
				!empty($_POST[$id]['reset_item_pubdt']));
		}

		if (isset($_POST[$id]['category_parent']))
		{
			$r(Item::move([$id], (int) $_POST[$id]['category_parent']));
		}

		if (isset($_POST[$id]['owner']))
		{
			$r(Item::owner([$id], (int) $_POST[$id]['owner']));
		}
	}

	/**
	 * Rognage d'une vignette.
	 *
	 * @return void
	 */
	public static function cropThumb(): void
	{
		if (!isset($_POST['crop']) || !Utility::checkPost('crop_selection',
		'`^\{"x":\d{1,5},"y":\d{1,5},"w":\d{1,5},"h":\d{1,5}\}$`'))
		{
			return;
		}

		// Récupération des informations utiles de l'image.
		$sql = 'SELECT item_tb_params, item_width, item_height FROM {items} WHERE item_id = ?';
		if (!DB::execute($sql, $_GET['item_id']))
		{
			Report::error();
		}
		if (!$i = DB::fetchRow())
		{
			return;
		}

		// Si la sélection couvre toute l'image,
		// on supprime les paramètres de sélection.
		$selection = json_decode($_POST['crop_selection'], TRUE);
		if ($selection['x'] == 0 && $selection['y'] == 0
		&& $selection['h'] == $i['item_height'] && $selection['w'] == $i['item_width'])
		{
			$_POST['crop_selection'] = NULL;
		}

		// Si les paramètres de sélection sont identiques,
		// inutile d'aller plus loin.
		if ($i['item_tb_params'] === $_POST['crop_selection'])
		{
			Report::noChange();
			return;
		}

		// Mise à jour de la base de données.
		$sql = 'UPDATE {items} SET item_tb_params = :crop WHERE item_id = :id';
		$params = ['crop' => $_POST['crop_selection'], 'id' => $_GET['item_id']];
		if (!DB::execute($sql, $params))
		{
			Report::error();
			return;
		}

		Report::success();
	}

	/**
	 * Supprime un fichier.
	 *
	 * @return void
	 */
	public static function delete(): void
	{
		if (isset($_POST['delete']) && Item::delete([(int) $_GET['item_id']]) == -1)
		{
			Report::error();
		}
	}

	/**
	 * Édition des informations.
	 *
	 * @return void
	 */
	public static function edit(): void
	{
		if (!$_POST || !isset($_POST['save']))
		{
			return;
		}

		App::checkboxes($_POST);

		$r = Item::edit($_POST);
		if ($r < 0)
		{
			Report::error();
			return;
		}

		// Vignette des catégories parentes.
		if (isset($_POST['thumb']) && isset($_POST['thumb']['_checkboxes'])
		&& is_array($_POST['thumb']['_checkboxes']))
		{
			$cat_thumbs =
			[
				'item' => [$_GET['item_id'] => []],
				'random' => [$_GET['item_id'] => []]
			];
			foreach ($_POST['thumb']['_checkboxes'] as &$cat_id)
			{
				if (array_key_exists($cat_id, $_POST['thumb']))
				{
					$method = $_POST['thumb'][$cat_id] ? 'item' : 'random';
					$cat_thumbs[$method][$_GET['item_id']][] = $cat_id;
				}
			}
			switch (Category::thumb($cat_thumbs))
			{
				case -1 :
					Report::error();
					return;

				case 0 :
					break;

				default :
					$r = 1;
			}
		}

		if ($r < 1)
		{
			if (!in_array($_GET['section'], ['item', 'item-edit']))
			{
				Report::info(__('Aucun fichier n\'a été modifié.'));
			}
		}
		else
		{
			if (!in_array($_GET['section'], ['item', 'item-edit']))
			{
				Report::success($r > 1
					? sprintf(__('%s fichiers ont été modifiés.'), $r)
					: __('1 fichier a été modifié.'));
			}
			else
			{
				Report::success();
			}
		}

		$_POST = [];
	}

	/**
	 * Édite les coordonnées de géolocalisation.
	 *
	 * @return void
	 */
	public static function editGeolocation(): void
	{
		$id = (int) $_GET['item_id'];

		if (!isset($_POST[$id]) || !isset($_POST['save'])
		 || !isset($_POST[$id]['latitude']) || is_array($_POST[$id]['latitude'])
		 || !isset($_POST[$id]['longitude']) || is_array($_POST[$id]['longitude']))
		{
			return;
		}

		$lat = Utility::trimAll((string) $_POST[$id]['latitude']);
		$long = Utility::trimAll((string) $_POST[$id]['longitude']);
		if ($lat !== '' || $long !== '')
		{
			if (is_string($coords = Geolocation::checkCoordinates($lat, $long)))
			{
				$_POST = [];
				Report::warning($coords);
				return;
			}
		}

		$r = Geolocation::editItems([$id => $_POST[$id]]);
		if ($r < 0)
		{
			Report::error();
			return;
		}

		if ($r < 1)
		{
			Report::noChange();
		}
		else
		{
			Report::success();
		}

		$_POST = [];
	}

	/**
	 * Récupération des fichiers de l'album courant pour générer une liste "Parcourir".
	 *
	 * @return void
	 */
	public static function getBrowseItems(): void
	{
		// Clause ORDER BY.
		$sql_order_by = self::getOrderBy(
			Auth::$infos['user_prefs']['album']['order_by_column'],
			Auth::$infos['user_prefs']['album']['order_by_order']
		);

		// Récupération des fichiers.
		$sql = "SELECT item_id,
					   item_type,
					   item_name
				  FROM {items} AS i
				 WHERE cat_id = ?
			  ORDER BY $sql_order_by";
		if (!DB::execute($sql, Template::$data['item']['cat_id']))
		{
			return;
		}
		$db_items = DB::fetchAll('item_id');

		// Formatage des informations pour le template.
		foreach ($db_items as &$i)
		{
			$items[$i['item_id']] =
			[
				'current' => $i['item_id'] == $_GET['item_id'],
				'link' => App::getURL($_GET['section'] . '/' . $i['item_id']),
				'title' => $i['item_name'],
				'type' => Item::isImage($i['item_type']) ? 'image' : 'video'
			];
		}
		Template::set('items_browse', $items);
	}

	/**
	 * Retourne les informations formatées d'un fichier.
	 *
	 * @param array $i
	 *   Informations brutes du fichier.
	 * @param array $parents_infos
	 *   Informations brutes des catégories parentes.
	 *
	 * @return array
	 */
	public static function getFormatedInfos(array &$i, array &$parents_infos = []): array
	{
		$ext = strtoupper(preg_replace('`^.*\.([^.]+)$`', '$1', $i['item_path']));
		$ext = $ext == 'JPG' ? 'JPEG' : $ext;
		$type = Item::isImage($i['item_type']) ? 'image' : 'video';
		$hits = (int) ($_POST[$i['item_id']]['item_hits'] ?? $i['item_hits']);

		$is_geolocation_post_lat = (isset($_POST[$i['item_id']]['latitude'])
			&& !Utility::isEmpty($_POST[$i['item_id']]['latitude']));
		$is_geolocation_post_long = (isset($_POST[$i['item_id']]['longitude'])
			&& !Utility::isEmpty($_POST[$i['item_id']]['longitude']));

		$file = App::getFileSource($i['item_path']);
		if ($type == 'video')
		{
			$file = str_replace('&file=', '&rand=' . rand() . '&file=', $file);
		}

		// Informations utiles des catégories parentes.
		$cat_parents = [];
		foreach ($parents_infos as &$p)
		{
			$cat_parents[] =
			[
				'cat_id' => (int) $p['cat_id'],
				'thumb_id' => (int) $p['thumb_id'],
				'cat_name' => $p['cat_name'],
				'cat_type' => $p['cat_type']
			];
		}

		// Orientation.
		if (in_array($i['item_orientation'], [5, 6, 7, 8]))
		{
			$width = $i['item_width'];
			$i['item_width'] = $i['item_height'];
			$i['item_height'] = $width;
		}

		return
		[
			// Paramètres de base.
			'file' => $file,
			'gallery_link' => $i['item_status'] == '1'
				? App::getURLGallery('item/' . $i['item_id'])
				: NULL,
			'id' => $i['item_id'],
			'link' => App::getURL('item-edit/' . $i['item_id']),
			'type' => $type,
			'type_mime' => Item::getTypeMime($i['item_type']),
			'type_text' => Item::getTypeText($i['item_type']),

			// Dimensions.
			'width' => $i['item_width'],
			'height' => $i['item_height'],

			// Paramètres vidéo.
			'vertical' => (int) ($type == 'video' && $i['item_height'] >= $i['item_width']),
			'duration' => $type == 'video' ? App::formatDuration($i['item_duration']) : '/',
			'duration_text' => $i['item_duration']
				? App::formatDuration($i['item_duration'])
				: '00:00',

			// Catégories parentes.
			'cat_id' => $i['cat_id'],
			'cat_parents' => $cat_parents,

			// Utilisateur.
			'user_id' => $i['user_id'],
			'user_link' => App::getURL('user/' . $i['user_id']),
			'user_nickname' => User::getNickname($i['user_login'], $i['user_nickname']),

			// Dates.
			'adddt_formated' => L10N::dt(__('%A %d %B %Y à %H:%M:%S'), $i['item_adddt']),
			'pubdt_formated' => $i['item_pubdt']
				? L10N::dt(__('%A %d %B %Y à %H:%M:%S'), $i['item_pubdt'])
				: '/',

			// Géolocalisation.
			'geolocation' =>
			[
				'latitude' => $is_geolocation_post_lat
					? Geolocation::getDecimalCoordinate($_POST[$i['item_id']]['latitude'])
					: $i['item_lat'],
				'latitude_map' => $is_geolocation_post_lat
					? Geolocation::getDecimalCoordinate($_POST[$i['item_id']]['latitude'])
					: ($i['item_lat']
						?? Config::$params['pages_params']['worldmap']['center_lat']),
				'longitude' => $is_geolocation_post_long
					? Geolocation::getDecimalCoordinate($_POST[$i['item_id']]['longitude'])
					: $i['item_long'],
				'longitude_map' => $is_geolocation_post_long
					? Geolocation::getDecimalCoordinate($_POST[$i['item_id']]['longitude'])
					: ($i['item_long']
						?? Config::$params['pages_params']['worldmap']['center_long']),
				'marker' => (int) ($i['item_lat'] !== NULL && $i['item_long'] !== NULL)
			],

			// État.
			'activated' => $i['item_status'] == '1',
			'deactivated' => $i['item_status'] != '1',
			'status_text' => L10N::getTextStatus((int) Item::getStatus($i['item_status'])),

			// Vignette.
			'thumb_src' => function() use ($i)
			{
				$thumb_src = App::getThumbSource('item',
					$i, self::$thumbSize, self::$thumbQuality);
				return htmlspecialchars($thumb_src);
			},
			'thumb_size' => function(int $max_width, int $max_height) use ($i): array
			{
				if (!$i['item_width'] || !$i['item_height'])
				{
					$i['item_width'] = 400;
					$i['item_height'] = 227;
				}
				return Image::getResizedSize((int) $i['item_width'], (int) $i['item_height'],
					$max_width, $max_height, (int) $i['item_orientation']);
			},

			// Informations.
			'description' => $_POST[$i['item_id']]['item_desc'] ?? (string) $i['item_desc'],
			'filename' => $_POST[$i['item_id']]['item_path'] ?? basename($i['item_path']),
			'title' => $_POST[$i['item_id']]['item_name'] ?? $i['item_name'],
			'url' => $_POST[$i['item_id']]['item_url'] ?? $i['item_url'],

			// Date de création.
			'crtdt' => $i['item_crtdt'],
			'crtdt_formated' => $i['item_crtdt']
				? L10N::dt(__('%A %d %B %Y à %H:%M:%S'), $i['item_crtdt'])
				: '/',
			'crtdt_year' => self::_getDate('item_crtdt', 0, $i),
			'crtdt_month' => self::_getDate('item_crtdt', 1, $i),
			'crtdt_day' => self::_getDate('item_crtdt', 2, $i),
			'crtdt_hour' => self::_getDate('item_crtdt', 3, $i),
			'crtdt_minute' => self::_getDate('item_crtdt', 4, $i),
			'crtdt_second' => self::_getDate('item_crtdt', 5, $i),

			// Date de publication.
			'pubdt' => $i['item_pubdt'],
			'pubdt_formated' => $i['item_pubdt']
				? L10N::dt(__('%A %d %B %Y à %H:%M:%S'), $i['item_pubdt'])
				: '/',
			'pubdt_year' => self::_getDate('item_pubdt', 0, $i),
			'pubdt_month' => self::_getDate('item_pubdt', 1, $i),
			'pubdt_day' => self::_getDate('item_pubdt', 2, $i),
			'pubdt_hour' => self::_getDate('item_pubdt', 3, $i),
			'pubdt_minute' => self::_getDate('item_pubdt', 4, $i),
			'pubdt_second' => self::_getDate('item_pubdt', 5, $i),

			// Date d'expiration.
			'expdt' => $i['item_expdt'],
			'expdt_formated' => $i['item_expdt']
				? L10N::dt(__('%A %d %B %Y à %H:%M:%S'), $i['item_expdt'])
				: '/',
			'expdt_year' => self::_getDate('item_expdt', 0, $i),
			'expdt_month' => self::_getDate('item_expdt', 1, $i),
			'expdt_day' => self::_getDate('item_expdt', 2, $i),
			'expdt_hour' => self::_getDate('item_expdt', 3, $i),
			'expdt_minute' => self::_getDate('item_expdt', 4, $i),
			'expdt_second' => self::_getDate('item_expdt', 5, $i),

			// Statistiques.
			'filesize_formated' => $i['item_filesize']
				? L10N::formatFilesize($i['item_filesize'])
				: 0,
			'comments_formated' => L10N::formatNumber((int) $i['item_comments']),
			'comments_link' => $i['item_comments'] > 0
				? App::getURL('comments/item/' . $i['item_id'])
				: NULL,
			'favorites_formated' => L10N::formatNumber((int) $i['item_favorites']),
			'hits' => $hits,
			'hits_formated' => L10N::formatNumber($hits),
			'rating_formated' => L10N::formatRating($i['item_rating']),
			'votes_formated' => L10N::formatNumber((int) $i['item_votes']),
			'votes_link' => $i['item_votes'] > 0
				? App::getURL('votes/item/' . $i['item_id'])
				: NULL,

			// Réglages : permissions.
			'commentable' => self::_getPermission($i, 'commentable'),
			'downloadable' => self::_getPermission($i, 'downloadable'),
			'votable' => self::_getPermission($i, 'votable'),
			'parent_commentable' => (int) ($i['parent_commentable'] ?? 0),
			'parent_downloadable' => (int) ($i['parent_downloadable'] ?? 0),
			'parent_votable' => (int) ($i['parent_votable'] ?? 0),

			// Mot de passe.
			'password' => $i['password_id'] === NULL ? '' : '**********',
			'password_parent' => self::_getPasswordParent('', $i, $parents_infos, TRUE),
			'password_parent_link' => self::_getPasswordParent('link', $i, $parents_infos, TRUE),
			'password_parent_name' => self::_getPasswordParent('name', $i, $parents_infos, TRUE),
			'password_parent_type' => self::_getPasswordParent('type', $i, $parents_infos, TRUE),

			// Tags.
			'tags' => self::_getTags($i)
		];
	}

	/**
	 * Récupération des informations du fichier courant.
	 *
	 * @return void
	 */
	public static function getInfos(): void
	{
		$sql = 'SELECT i.*,
					   cat.cat_id,
					   cat.password_id,
					   cat.cat_parents,
					   cat.cat_name,
					   cat.thumb_id AS cat_thumb_id,
					   "album" AS cat_type,
					   u.user_login,
					   u.user_nickname,
					   u.user_status,
					   p.cat_id AS p_cat_id
				  FROM {items} AS i
		     LEFT JOIN {categories} AS cat
				    ON i.cat_id = cat.cat_id
		     LEFT JOIN {users} AS u
				    ON i.user_id = u.user_id
			 LEFT JOIN {passwords} AS p
				    ON cat.password_id = p.password_id
			     WHERE i.item_id = ?';
		if (!DB::execute($sql, $_GET['item_id']))
		{
			return;
		}
		if (!isset((self::$infos = DB::fetchRow())['item_id']))
		{
			App::redirect('category/1');
			return;
		}

		// Récupération des tags de chaque fichier.
		$sql = 'SELECT tag_name
				  FROM {tags}
			 LEFT JOIN {tags_items} USING (tag_id)
				 WHERE item_id = ?';
		if (!DB::execute($sql, $_GET['item_id']))
		{
			return;
		}
		foreach (DB::fetchAll() as &$i)
		{
			self::$infos['tags'][] = $i['tag_name'];
		}

		// Fil d'Ariane.
		$parents_infos = Template::breadcrumb(self::$infos);

		$parents_infos[self::$infos['cat_id']] =
		[
			'cat_id' => self::$infos['cat_id'],
			'thumb_id' => self::$infos['cat_thumb_id'],
			'cat_name' => self::$infos['cat_name'],
			'cat_type' => self::$infos['cat_type']
		];

		// Lien vers la galerie.
		Template::set('gallery_link', self::$infos['item_status'] == '1'
			? App::getURLGallery('item/' . self::$infos['item_id'])
			: NULL);

		// Réglages.
		self::$infos['cat_parents'] .= self::$infos['cat_id'] . Parents::SEPARATOR;
		self::$infos = [self::$infos];
		Parents::settings(self::$infos);

		// Formatage des informations.
		Template::set('item', self::getFormatedInfos(self::$infos[0], $parents_infos));

		self::$infos = self::$infos[0];

		// Géolocalisation.
		Template::set('geolocation',
		[
			'layer' => Config::$params['geolocation_default_layer'],
			'zoom' => Config::$params['pages_params']['worldmap']['zoom']
		]);
	}

	/**
	 * Récupération des fichiers de la page courante.
	 *
	 * @return void
	 */
	public static function getItems(): void
	{
		$user_prefs = Auth::$infos['user_prefs']['album'];

		if ($user_prefs['display'] == 'grid')
		{
			if ($_GET['q_pageless'] != $_GET['q'])
			{
				App::redirect($_GET['q_pageless']);
			}
			return;
		}

		// Nombre de fichiers par page.
		$nb_per_page = $user_prefs['nb_per_page'];

		// Clause WHERE.
		$sql_where = "1=1";
		$params = [];
		if (isset($_GET['filter']))
		{
			if (($_GET['filter'] == 'search' && $where = self::$search->sql())
			|| $where = SQL::itemsWhere($_GET['filter'], $_GET['filter_value']))
			{
				$sql_where .= ' AND ' . $where['sql'];
				$params = $where['params'];
			}
			else if ($_GET['filter'] == 'search')
			{
				App::redirect(preg_replace('`/search/[^/]+`', '', $_GET['q_pageless']));
			}
		}

		// Limitation à la catégorie courante.
		if (Template::$data['category']['id'] > 1)
		{
			$sql_where .= ' AND item_path LIKE :path';
			$params['path'] = DB::likeEscape(Template::$data['category']['path']) . '/%';
		}

		// Clause LIMIT.
		$sql_limit = ($nb_per_page * ($_GET['page'] - 1)) . ",$nb_per_page";

		// Clause ORDER BY.
		$sql_order_by = self::getOrderBy(
			$user_prefs['order_by_column'],
			$user_prefs['order_by_order']
		);

		// Clause FROM.
		$sql_from = SQL::itemsFrom($_GET['filter'] ?? '');

		// Nombre de pages.
		if (isset($_GET['filter']))
		{
			$sql = "SELECT COUNT(*) FROM {items} AS i $sql_from WHERE $sql_where";
			if (DB::execute($sql, $params))
			{
				Template::$data['objects_count'] = DB::fetchVal();
			}
		}
		Template::set('nb_pages', ceil(Template::$data['objects_count'] / $nb_per_page));

		// Récupération des fichiers.
		$sql = "SELECT i.*,
					   i.item_tb_params AS tb_params,
					   cat.password_id,
					   cat.cat_parents,
					   u.user_login,
					   u.user_nickname,
					   u.user_status,
					   p.cat_id AS p_cat_id
				  FROM {items} AS i
					   $sql_from
			 LEFT JOIN {categories} AS cat
					ON i.cat_id = cat.cat_id
			 LEFT JOIN {users} AS u
					ON i.user_id = u.user_id
			 LEFT JOIN {passwords} AS p
				    ON cat.password_id = p.password_id
				 WHERE $sql_where
			  ORDER BY $sql_order_by
			     LIMIT $sql_limit";
		if (!DB::execute($sql, $params))
		{
			return;
		}
		$items = DB::fetchAll('item_id');
		if (!self::_objectsNoResult(count($items)))
		{
			return;
		}

		// Récupération des tags de chaque fichier.
		$sql = 'SELECT item_id,
					   tag_name
				  FROM {tags}
			 LEFT JOIN {tags_items} USING (tag_id)
				 WHERE item_id IN (' . DB::inInt(array_keys($items)) . ')';
		if (!DB::execute($sql))
		{
			return;
		}
		$tags_infos = DB::fetchAll();
		foreach ($tags_infos as &$i)
		{
			$items[$i['item_id']]['tags'][] = $i['tag_name'];
		}

		// Réglages.
		Parents::settings($items);

		// Formatage des informations pour le template.
		$formated_infos = [];
		foreach ($items as &$i)
		{
			$formated_infos[] = self::getFormatedInfos($i);
		}
		Template::set('items', $formated_infos);
	}

	/**
	 * Retourne la clause ORDER BY pour la récupération des fichiers.
	 *
	 * @param string $column
	 * @param string $order
	 *
	 * @return string
	 */
	public static function getOrderBy(string $column, string $order): string
	{
		$order_by = SQL::itemsOrderBy('', sprintf('item_%s %s', $column, $order));
		$order_by .= ', item_id ' . $order;

		return $order_by;
	}

	/**
	 * Récupération des fichiers de l'album courant
	 * pour la page de tri manuel des fichiers.
	 *
	 * @return void
	 */
	public static function getSortItems(): void
	{
		$sql = 'SELECT item_id,
					   item_name,
					   item_type,
					   item_path,
					   item_width,
					   item_height,
					   item_duration,
					   item_orientation,
					   item_adddt,
					   item_status
				  FROM {items}
				 WHERE cat_id = ?
			  ORDER BY item_position ASC';
		if (!DB::execute($sql, $_GET['album_id']))
		{
			return;
		}

		$items = [];
		foreach (DB::fetchAll() as &$i)
		{
			$items[] =
			[
				'activated' => $i['item_status'] == '1',
				'duration_text' => $i['item_duration']
					? App::formatDuration($i['item_duration'])
					: '00:00',
				'id' => (int) $i['item_id'],
				'title' => $i['item_name'],
				'thumb_src' => function() use ($i)
				{
					$thumb_src = App::getThumbSource(
						'item', $i, self::$thumbSize, self::$thumbQuality
					);
					return htmlspecialchars($thumb_src);
				},
				'type' => Item::isImage($i['item_type']) ? 'image' : 'video'
			];
		}
		Template::set('items', $items);
	}

	/**
	 * Remplace un fichier par un nouveau envoyé par le navigateur.
	 *
	 * @return void
	 */
	public static function replace(): void
	{
		Upload::contentLength();

		Template::set('upload', ['errors' => [], 'warnings' => []]);

		if (empty($_POST['temp_dir']) || empty($_POST['upload']))
		{
			return;
		}

		$errors = [];
		$warnings = [];

		// Avertissements.
		if (isset($_POST['warning']) && is_array($_POST['warning']))
		{
			foreach ($_POST['warning'] as $msg)
			{
				$warnings[] = urldecode($msg);
			}
		}

		// Erreurs.
		if (isset($_POST['error']) && is_array($_POST['error']))
		{
			foreach ($_POST['error'] as $msg)
			{
				$errors[] = urldecode($msg);
			}
		}

		// Récupération des informations utiles du fichier.
		$sql = 'SELECT item_type, item_path FROM {items} WHERE item_id = ?';
		if (!DB::execute($sql, $_GET['item_id']) || !$i = DB::fetchRow())
		{
			Report::error();
			goto end;
		}

		// Méthode POST.
		if (!empty($_FILES) && is_array($_FILES) && isset($_FILES['replace']))
		{
			// Répertoire temporaire.
			$temp_dir = GALLERY_ROOT . '/cache/temp/' . Security::key(40);
			if (!is_dir($temp_dir))
			{
				File::mkdir($temp_dir);
			}

			// Chemin du fichier envoyé.
			if (is_array($file_src = Upload::filepath($_FILES['replace'], $temp_dir)))
			{
				Report::warning($file_src['text']);
				goto end;
			}
		}

		// Méthode JavaScript.
		else if (isset($_POST['success']) && is_array($_POST['success']))
		{
			if (!file_exists($temp_dir = GALLERY_ROOT . '/cache/temp/' . $_POST['temp_dir'])
			|| !$new_file = urldecode($_POST['success'][0]))
			{
				goto end;
			}
			$file_src = $temp_dir . '/' . $new_file;
		}

		else
		{
			goto end;
		}

		// Vérification du fichier.
		$verify = Upload::itemVerify($file_src);
		if ($verify['error'])
		{
			Report::warning($verify['message']);
			goto end;
		}

		// Vérification de l'image.
		if (Item::isImage($i['item_type']))
		{
			// Vérification du type de fichier.
			if ($verify['infos']['type'] != 'image')
			{
				Report::warning(__('Le fichier n\'est pas une image valide.'));
				goto end;
			}

			// Vérification des dimensions de l'image.
			if ($verify['error'] == 'size')
			{
				Report::warning($verify['message']);
				goto end;
			}
		}

		// Vérification de la vidéo.
		else
		{
			// Vérification du type de fichier.
			if ($verify['infos']['type'] != 'video')
			{
				Report::warning(__('Le fichier n\'est pas une vidéo valide.'));
				goto end;
			}
		}

		// Vérification du type MIME.
		if (Item::getTypeMime($i['item_type']) != File::getMimeType($file_src))
		{
			Report::warning(__('Type de fichier non valide.'));
			goto end;
		}

		// On déplace le fichier en écrasant l'original.
		if (!File::rename($file_src, CONF_ALBUMS_PATH . '/' . $i['item_path']))
		{
			Report::error(__('Impossible de déplacer le fichier.'));
			goto end;
		}

		// On met à jour les informations du fichier.
		$r = Item::update([$_GET['item_id']]);
		if ($r < 0)
		{
			Report::error();
			goto end;
		}
		else if ($r < 1)
		{
			Report::noChange();
			goto end;
		}
		else
		{
			Report::success(__('Le fichier a été remplacé.'));
		}

		// On supprime la capture de la vidéo.
		if (Item::isVideo($i['item_type']))
		{
			$sql = 'UPDATE {items} SET item_duration = NULL WHERE item_id = ?';
			if (DB::execute($sql, $_GET['item_id']))
			{
				if ($files = scandir($dir = GALLERY_ROOT . '/cache/captures/'))
				{
					foreach ($files as &$f)
					{
						$p = explode('-', $f);
						if ($p[0] == $_GET['item_id'])
						{
							File::unlink($dir . $f);
						}
					}
				}
			}

			// On indique de créer une nouvelle capture.
			Config::changeDBParams(['video_captures' => 1]);
		}

		// On supprime les images redimensionnées du fichier.
		Item::deleteResized([$_GET['item_id']]);

		end:
		Template::set('upload', ['errors' => $errors, 'warnings' => $warnings]);
		if (isset($temp_dir) && file_exists($temp_dir))
		{
			File::rmdir($temp_dir);
		}
	}

	/**
	 * Définit les paramètres de template pour la section.
	 *
	 * @param string $tpl_filename
	 *
	 * @return void
	 */
	public static function tplSection(string $tpl_filename): void
	{
		Template::set('page', ['link' => App::getURL('category/1'), 'title' => __('Albums')]);
		Template::set('section', 'albums');
		Template::set('template',
		[
			'content' => $tpl_filename . '.tpl.php',
			'file' => 'item.tpl.php'
		]);
	}

	/**
	 * Ajoute les fichiers envoyés par le navigateur à l'album courant.
	 *
	 * @return void
	 */
	public static function upload(): void
	{
		Upload::contentLength();

		Template::set('upload', ['errors' => [], 'warnings' => []]);

		if (isset($_GET['search']) || empty($_POST['upload']))
		{
			return;
		}

		$add_files = [];
		$errors = [];
		$warnings = [];

		// Avertissements.
		if (isset($_POST['warning']) && is_array($_POST['warning']))
		{
			foreach ($_POST['warning'] as $msg)
			{
				$warnings[] = urldecode($msg);
			}
		}

		// Erreurs.
		if (isset($_POST['error']) && is_array($_POST['error']))
		{
			foreach ($_POST['error'] as $msg)
			{
				$errors[] = urldecode($msg);
			}
		}

		// Récupération des informations de l'album.
		$sql = 'SELECT cat_id,
					   cat_name,
					   cat_path
				  FROM {categories}
				 WHERE cat_id = ?
				   AND cat_filemtime IS NOT NULL';
		if (!DB::execute($sql, $_GET['album_id']) || !$cat_infos = DB::fetchRow())
		{
			Report::error();
			return;
		}
		$cat_path = $cat_infos['cat_path'];
		$dest_path = CONF_ALBUMS_PATH . '/' . $cat_path;

		// Méthode POST.
		if (!empty($_FILES) && is_array($_FILES))
		{
			// Répertoire temporaire.
			$temp_dir = GALLERY_ROOT . '/cache/temp/' . Security::key(40);
			if (!is_dir($temp_dir))
			{
				File::mkdir($temp_dir);
			}

			// On analyse chaque fichier.
			$add_files = [];
			for ($i = 0; $i < 4; $i++)
			{
				$file = $_FILES['file_' . $i] ?? NULL;
				if (is_array($file) && !empty($file['name']))
				{
					// Chemin du fichier.
					$filepath = Upload::filepath($file, $temp_dir);
					if (is_array($filepath))
					{
						$warnings[] = $file['name'] . ' : ' . $filepath['text'];
						continue;
					}

					// Vérification du fichier.
					$verify = Upload::itemVerify($filepath);
					if ($verify['error'])
					{
						$warnings[] = $file['name'] . ' : ' . $verify['message'];
						continue;
					}

					// Redimensionnement de l'image.
					if ($verify['infos']['type'] == 'image'
					&& $error_msg = Upload::itemResize($filepath))
					{
						$errors[] = $file['name'] . ' : ' . $error_msg;
						continue;
					}

					$add_files[] = basename($filepath);
				}
			}
		}

		// Méthode JavaScript.
		else if (isset($_POST['success']) && is_array($_POST['success']))
		{
			// Répertoire temporaire.
			if (empty($_POST['temp_dir']))
			{
				return;
			}
			$temp_dir = GALLERY_ROOT . '/cache/temp/' . $_POST['temp_dir'];
			if (!file_exists($temp_dir))
			{
				return;
			}

			// Fichiers envoyés avec succès.
			$add_files = array_map('rawurldecode', $_POST['success']);
		}

		else
		{
			goto end;
		}

		// Si aucun fichier à ajouter, inutile d'aller plus loin.
		if (empty($add_files))
		{
			goto end;
		}

		// On déplace les fichiers du répertoire temporaire
		// vers l'album de destination.
		$add_files = Upload::itemMove($add_files, $temp_dir, $dest_path);

		// Initialisation du scan.
		$scan = new Scan();
		if (!$scan->getInit)
		{
			$errors[] = __('Initialisation du scan échouée.');
			goto end;
		}

		// Options de scan.
		$scan->setHttp = TRUE;
		$scan->setHttpAlbum = $cat_infos;
		$scan->setHttpFiles = array_flip($add_files);
		$scan->setHttpOriginalDir = $temp_dir . '/original';
		$scan->setStatus = (int) isset($_POST['activate_files']);
		$scan->setReportAllFiles = FALSE;
		$scan->setUserId = Auth::$id;
		$scan->setUserName = Auth::$nickname;

		// Lancement du scan de l'album.
		if ($scan->start($cat_path) === FALSE)
		{
			$errors[] = __('Une erreur s\'est produite.');
			goto end;
		}

		// Rapport de scan.
		$report =& $scan->getReport;

		// Fichiers ajoutés.
		$nb_files = $report['images_added'] + $report['videos_added'];
		if ($nb_files > 0)
		{
			$message = ($nb_files > 1)
				? __('%s fichiers ont été ajoutés à l\'album.')
				: __('%s fichier a été ajouté à l\'album.');
			Report::success(sprintf($message, $nb_files));
		}
		else
		{
			Report::info(__('Aucun fichier n\'a été ajouté à l\'album.'));
		}

		$dir_path = CONF_ALBUMS_PATH . '/' . $cat_path . '/';

		// Erreurs.
		if ($report['files_errors'])
		{
			foreach ($report['files_errors'] as &$i)
			{
				$errors[] = $i['file'] . ' : ' . ucfirst($i['message']);
				$file_path = $dir_path . $i['file'];
				if (file_exists($file_path))
				{
					File::unlink($file_path);
				}
			}
		}

		// Fichiers rejetés.
		if ($report['files_rejected'])
		{
			foreach ($report['files_rejected'] as &$i)
			{
				$warnings[] = $i['file'] . ' : ' . ucfirst($i['message']);
				$file_path = $dir_path . $i['file'];
				if (file_exists($file_path))
				{
					File::unlink($file_path);
				}
			}
		}

		if (CONF_DEV_MODE && Maintenance::dbStats() !== 0)
		{
			trigger_error('Gallery stats error.', E_USER_WARNING);
		}

		end:
		Template::set('upload', ['errors' => $errors, 'warnings' => $warnings]);
		if (isset($temp_dir))
		{
			File::rmdir($temp_dir);
		}
	}



	/**
	 * Retourne la partie $part de la date $date.
	 *
	 * @param string $date
	 * @param int $part
	 * @param array $i
	 *
	 * @return string
	 */
	private static function _getDate(string $date, int $part, array &$i): string
	{
		$parts = ['year', 'month', 'day', 'hour', 'minute', 'second'];
		$p = $_POST[$i['item_id']][$date][$parts[$part]] ?? NULL;
		if ($p !== NULL)
		{
			return $p;
		}

		if (!$i[$date])
		{
			return '';
		}

		$date = explode('-', str_replace([' ', ':'], '-', $i[$date]));

		if (!isset($date[$part]))
		{
			return '';
		}

		return (string) (int) $date[$part];
	}

	/**
	 * Retourne la permission $perm.
	 *
	 * @param array $i
	 * @param string $perm
	 *
	 * @return int
	 */
	private static function _getPermission(array &$i, string $perm): int
	{
		if (isset($_POST[$i['item_id']]['settings']))
		{
			return (int) isset($_POST[$i['item_id']][$perm]);
		}

		return empty($i['parent_' . $perm]) ? 0 : (int) $i['item_' . $perm];
	}

	/**
	 * Retourne les tags d'un fichier séparés par une virgule.
	 *
	 * @param array $i
	 *
	 * @return string
	 */
	private static function _getTags(array &$i): string
	{
		if (isset($_POST[$i['item_id']]['tags']))
		{
			return $_POST[$i['item_id']]['tags'];
		}
		if (!isset($i['tags']) || !is_array($i['tags']))
		{
			return '';
		}
		usort($i['tags'], 'Utility::alphaSort');
		return implode(', ', $i['tags']);
	}
}
?>