<?php
declare(strict_types = 1);

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

/**
 * Gestion des albums.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class AdminAlbums extends Admin
{
	/**
	 * Informations en base de données de la catégorie courante.
	 *
	 * @var array
	 */
	public static $catInfos = [];



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

		// Identifiant de l'objet.
		$id = $_GET['item_id'] ?? $_GET['category_id'];

		// Récupération des informations utiles de l'image.
		if (isset($_GET['item_id']))
		{
			$sql = 'SELECT item_tb_params AS tb_params,
						   item_width,
						   item_height
					  FROM {items}
					 WHERE item_id = ?';
		}
		else
		{
			$sql = 'SELECT cat_tb_params AS tb_params,
						   item_width,
						   item_height
					  FROM {categories} AS cat
				 LEFT JOIN {items} AS i
					    ON cat.thumb_id = i.item_id
					 WHERE cat.cat_id = ?';
		}
		if (!DB::execute($sql, $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['tb_params'] === $_POST['crop_selection'])
		{
			Report::noChange();
			return;
		}

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

		Report::success();
	}

	/**
	 * Construit la liste de toutes les catégories de la galerie.
	 *
	 * @param bool $move_only_cat
	 *
	 * @return void
	 */
	public static function getCategoriesLists(bool $move_only_cat = FALSE): void
	{
		$category_one_sections = ['album-sort', 'category-sort', 'category-items'];
		$get_url = function($page) use ($category_one_sections)
		{
			switch ($_GET['section'])
			{
				case 'album-delete' :
				case 'album-edit' :
				case 'album-geolocation' :
				case 'album-sort' :
				case 'album-thumb' :
				case 'album-thumb-new' :
				case 'category-delete' :
				case 'category-edit' :
				case 'category-geolocation' :
				case 'category-items' :
				case 'category-sort' :
				case 'category-thumb' :
				case 'category-thumb-new' :
					if ($page == 'category/1' &&
					!in_array($_GET['section'], $category_one_sections))
					{
						break;
					}
					$section = preg_replace('`^(album|category)\-`', '', $_GET['section']);
					$page = str_replace('album/', 'album-' . $section . '/', $page);
					$page = str_replace('category/', 'category-' . $section . '/', $page);
					$page = str_replace('album-items', 'album', $page);
					break;
			}
			$filter = isset($_GET['filter'])
				? '/' . $_GET['filter'] . ($_GET['filter_value'] !== ''
					? '/' . $_GET['filter_value']
					: '')
				: '';

			return App::getURL($page . $filter);
		};
		$categories = Category::getList($makelist, $get_url, $_GET['category_id'] ?? 1);
		if (!$makelist)
		{
			return;
		}
		$list = $makelist($categories, 0, 0, $move_only_cat);
		Template::set('categories_move', $list);

		// Réduction de la liste.
		$categories_browse = NULL;
		$reduce_from_items = function() use (&$categories, &$categories_browse, &$makelist)
		{
			if (!(($_GET['filter'] == 'search' && $where = self::$search->sql())
			|| $where = SQL::itemsWhere($_GET['filter'], $_GET['filter_value'])))
			{
				return;
			}

			$sql_from = SQL::itemsFrom($_GET['filter'] ?? '');
			$sql = "SELECT cat.cat_id,
						   COUNT(i.item_id) AS count
					  FROM {items} AS i
						   $sql_from
				 LEFT JOIN {categories} AS cat
						ON i.cat_id = cat.cat_id
					 WHERE {$where['sql']}
				  GROUP BY cat.cat_id";
			if (!DB::execute($sql, $where['params']))
			{
				return;
			}
			$infos = DB::fetchAll('cat_id', 'count');
			Category::reduceList($categories, $infos);
			$categories_browse = $makelist($categories);
		};
		$reduce_from_categories = function() use (&$categories, &$categories_browse, &$makelist)
		{
			if (!$where = self::sqlWhereCategories())
			{
				return;
			}
			$sql = "SELECT parent_id AS cat_id,
						   COUNT(cat_id) AS count
					  FROM {categories} AS cat
					 WHERE {$where['sql']}
				  GROUP BY parent_id";
			if (!DB::execute($sql, $where['params']))
			{
				return;
			}
			$infos = DB::fetchAll('cat_id', 'count');
			$parents_id = array_keys($infos);

			// Réduction de la liste.
			$list_ids = [];
			foreach ($categories as $id => &$i)
			{
				if ($i['cat_filemtime'] === NULL && in_array($id, $parents_id))
				{
					foreach (explode(Parents::SEPARATOR, $i['cat_parents'] . $id) as $pid)
					{
						if (!in_array($pid, $list_ids))
						{
							$list_ids[] = $pid;
						}
					}
				}
			}
			foreach ($categories as $id => &$i)
			{
				if (!in_array($id, $list_ids))
				{
					unset($categories[$id]);
					continue;
				}
				if (!isset($infos[$i['cat_id']]))
				{
					continue;
				}
				if (isset($i['count']))
				{
					$i['count'] += (int) $infos[$i['cat_id']];
				}
				else
				{
					$i['count'] = (int) $infos[$i['cat_id']];
				}
				if ($i['cat_id'] > 1)
				{
					$parents = explode(Parents::SEPARATOR, substr($i['cat_parents'], 0, -1));
					foreach ($parents as &$id)
					{
						if (isset($categories[$id]['count']))
						{
							$categories[$id]['count'] += (int) $infos[$i['cat_id']];
						}
						else
						{
							$categories[$id]['count'] = (int) $infos[$i['cat_id']];
						}
					}
				}
			}

			$categories_browse = $makelist($categories);
		};
		if (isset($_GET['filter']))
		{
			if (isset($_GET['search']))
			{
				if (self::$search->options['type'] == 'items')
				{
					$reduce_from_items();
				}
				else
				{
					$reduce_from_categories();
				}
			}
			else
			{
				$reduce_from_items();
			}
		}
		else
		{
			$categories_browse = $move_only_cat ? $makelist($categories) : $list;
		}
		self::_categoriesBrowse($categories_browse);
	}

	/**
	 * Récupère les paramètres de rognage d'une image.
	 *
	 * @params string $type
	 *   Type : 'cat' ou 'item'.
	 *
	 * @return void
	 */
	public static function getCropInfos(string $type): void
	{
		if ($type == 'cat')
		{
			$i =& AdminAlbums::$catInfos;
			$tb_params = $i['cat_tb_params'];
		}
		else
		{
			$i =& AdminItems::$infos;
			$tb_params = $i['item_tb_params'];
		}

		$selection = json_decode($tb_params ?? '', TRUE);

		$image = '';
		$height = $width = 0;

		if (Item::isImage($i['item_type']))
		{
			$image = $i['item_path'];

			if (in_array($i['item_orientation'], [5, 6, 7, 8]))
			{
				$width = $i['item_height'];
				$height = $i['item_width'];
			}
			else
			{
				$width = $i['item_width'];
				$height = $i['item_height'];
			}
		}
		else
		{
			if (Item::isVideo($i['item_type']))
			{
				$filename = Video::getCapture((int) $i['item_id'], $i['item_adddt']);
				$image = '/cache/captures/' . $filename;
			}
			else if ($type == 'cat')
			{
				$filename = App::hashFilename((string) $i['cat_id'], ['ext']);
				$image = '/cache/external/' . $filename;
			}
			if (file_exists(GALLERY_ROOT . $image))
			{
				$image_size = Image::getTypeSize(GALLERY_ROOT . $image);
			}
			$width = $image_size['width'] ?? 0;
			$height = $image_size['height'] ?? 0;

			// On inverse les dimensions de l'image en fonction
			// de la valeur du paramètre EXIF "orientation".
			if ($type == 'cat')
			{
				$metadata = new Metadata(GALLERY_ROOT . $image);
				$orientation = (int) $metadata->getExifValue('orientation');
				if (in_array($orientation, [5, 6, 7, 8]))
				{
					$width_temp = $width;
					$width = $height;
					$height = $width_temp;
				}
			}
		}

		Template::set('crop',
		[
			'image' => App::getFileSource($image),
			'width' => $width,
			'height' => $height,
			'selection_x' => $selection['x'] ?? 0,
			'selection_y' => $selection['y'] ?? 0,
			'selection_w' => $selection['w'] ?? $width,
			'selection_h' => $selection['h'] ?? $height
		]);
	}

	/**
	 * Récupère tous les tags de la galerie.
	 *
	 * @return array
	 */
	public static function getTagsList(): array
	{
		Template::set('tags', []);
		if (!DB::execute('SELECT tag_id, tag_name FROM {tags} ORDER BY LOWER(tag_name) ASC'))
		{
			return [];
		}
		if (!$tags = DB::fetchAll('tag_id', 'tag_name'))
		{
			return [];
		}

		Template::set('tags', $tags = array_values($tags));

		return $tags;
	}

	/**
	 * Retourne l'URL de la catégorie 1 de la section courante.
	 *
	 * @return void
	 */
	public static function getURLSectionCatOne(): string
	{
		$section = $_GET['section'] == 'album' ? 'category' : $_GET['section'];
		$url = preg_replace('`^[^/]+/\d+`', $section . '/1', $_GET['q_pageless']);

		return $url;
	}

	/**
	 * Retourne la clause WHERE correspondant
	 * aux critères de recherche des catégories à récupérer.
	 *
	 * @return array
	 */
	public static function sqlWhereCategories(): array
	{
		$search = self::$search->sql();
		if (!$search)
		{
			App::redirect(preg_replace('`/search/[^/]+`', '', $_GET['q_pageless']));
			return [];
		}

		return $search;
	}

	/**
	 * Informations de template pour les filtres.
	 *
	 * @return void
	 */
	public static function tplFilter(): void
	{
		Template::set('filter', [
			'exit_link' => App::getURL($_GET['q_filterless']),
			'exit_text' => __('Montrer tous les fichiers')
		]);
		switch ($_GET['filter'])
		{
			// Appareils photos.
			case 'camera-brand' :
				$sql = 'SELECT camera_brand_name FROM {cameras_brands} WHERE camera_brand_id = ?';
				DB::execute($sql, (int) $_GET['filter_value']);
				if (!$brand_name = DB::fetchVal())
				{
					App::redirect($_GET['q_filterless']);
				}
				Template::set('filter', [
					'text' => __('Photos prises par un appareil photos de marque %s'),
					'text_format_text' => $brand_name
				]);
				break;

			case 'camera-model' :
				$sql = 'SELECT camera_model_name FROM {cameras_models} WHERE camera_model_id = ?';
				DB::execute($sql, (int) $_GET['filter_value']);
				if (!$model_name = DB::fetchVal())
				{
					App::redirect($_GET['q_filterless']);
				}
				Template::set('filter', [
					'text' => __('Photos prises avec le modèle d\'appareil photos %s'),
					'text_format_text' => $model_name
				]);
				break;

			// Tags.
			case 'tag' :
				$sql = 'SELECT tag_name FROM {tags} WHERE tag_id = ?';
				DB::execute($sql, (int) $_GET['filter_value']);
				if (!$tag_name = DB::fetchVal())
				{
					App::redirect($_GET['q_filterless']);
				}
				Template::set('filter', [
					'text' => __('Fichiers liés au tag %s'),
					'text_format_text' => $tag_name
				]);
				break;

			// Fichiers de l'utilisateur.
			case 'user-favorites' :
				$text = $text ?? __('Favoris de %s');
			case 'user-images' :
				$text = $text ?? __('Photos de %s');
			case 'user-items' :
				$text = $text ?? __('Fichiers de %s');
			case 'user-videos' :
				$text = $text ?? __('Vidéos de %s');
				$sql = 'SELECT user_login FROM {users} WHERE user_id = ?';
				DB::execute($sql, (int) $_GET['filter_value']);
				if (!$user_login = DB::fetchVal())
				{
					App::redirect($_GET['q_filterless']);
				}
				Template::set('filter', [
					'text' => $text,
					'text_format_link' => App::getURL(
						'user/' . (int) $_GET['filter_value']
					),
					'text_format_text' => $user_login
				]);
				break;
		}
	}

	/**
	 * Informations de template pour le moteur de recherche.
	 *
	 * @param array $functions
	 *
	 * @return void
	 */
	public static function tplSearch(array &$functions): void
	{
		// Type d'objet.
		$type_categories = isset(self::$search->options['type'])
			&& self::$search->options['type'] == 'categories';
		$type_items = isset(self::$search->options['type'])
			&& self::$search->options['type'] == 'items';

		// Colonnes.
		$columns_categories =
		[
			'cat_desc'  => TRUE,
			'cat_name'  => TRUE,
			'cat_path'  => TRUE,
			'cat_url'   => TRUE
		];
		$columns_items =
		[
			'item_desc' => TRUE,
			'item_id'   => FALSE,
			'item_name' => TRUE,
			'item_path' => TRUE,
			'item_url'  => TRUE
		];
		if ($type_items)
		{
			$columns = array_merge(
				$functions['columns']($columns_items),
				$columns_categories
			);
		}
		else
		{
			$columns = array_merge(
				$functions['columns']($columns_categories),
				$columns_items
			);
		}

		// Exclusion.
		$item_exclude_column = $functions['select']('item_exclude_column',
		[
			[
				'value' => 'comments',
				'text' => __('Commentaires')
			],
			[
				'value' => 'crtdt',
				'text' => __('Date de création')
			],
			[
				'value' => 'desc',
				'text' => __('Description')
			],
			[
				'value' => 'geoloc',
				'text' => __('Géolocalisation')
			],
			[
				'value' => 'hits',
				'text' => __('Vues')
			],
			[
				'value' => 'tags',
				'text' => __('Tags'),
				'default' => 1
			],
			[
				'value' => 'votes',
				'text' => __('Votes')
			]
		]);

		// Type de fichier.
		$item_type = $functions['select']('item_type',
		[
			[
				'value' => 'all',
				'text' => '*' . __('Tous'),
				'default' => 1
			],
			[
				'value' => 'image',
				'text' => __('Image (tous les formats)')
			],
			[
				'value' => 'video',
				'text' => __('Vidéo (tous les formats)')
			],
			[
				'value' => 'image_avif',
				'text' => Item::getTypeText(Item::TYPE_AVIF)
			],
			[
				'value' => 'image_gif',
				'text' => Item::getTypeText(Item::TYPE_GIF)
			],
			[
				'value' => 'image_gif_animated',
				'text' => Item::getTypeText(Item::TYPE_GIF_ANIMATED)
			],
			[
				'value' => 'image_jpeg',
				'text' => Item::getTypeText(Item::TYPE_JPEG)
			],
			[
				'value' => 'image_png',
				'text' => Item::getTypeText(Item::TYPE_PNG)
			],
			[
				'value' => 'image_png_animated',
				'text' => Item::getTypeText(Item::TYPE_PNG_ANIMATED)
			],
			[
				'value' => 'image_webp',
				'text' => Item::getTypeText(Item::TYPE_WEBP)
			],
			[
				'value' => 'image_webp_animated',
				'text' => Item::getTypeText(Item::TYPE_WEBP_ANIMATED)
			],
			[
				'value' => 'video_mp4',
				'text' => Item::getTypeText(Item::TYPE_MP4)
			],
			[
				'value' => 'video_webm',
				'text' => Item::getTypeText(Item::TYPE_WEBM)
			]
		]);

		// Format d'image.
		$item_format = $functions['select']('item_format',
		[
			[
				'value' => 'all',
				'text' => '*' . __('Tous'),
				'default' => 1
			],
			[
				'value' => 'square',
				'text' => __('Carré')
			],
			[
				'value' => 'panorama',
				'text' => __('Panorama')
			],
			[
				'value' => 'landscape',
				'text' => __('Paysage')
			],
			[
				'value' => 'portrait',
				'text' => __('Portrait')
			],
		]);

		// État.
		$status = $functions['select']('status',
		[
			[
				'value' => '*',
				'text' => '*' . __('Tous'),
				'default' => 1
			],
			[
				'value' => '1',
				'text' => __('Activé')
			],
			[
				'value' => '0',
				'text' => __('Désactivé')
			]
		]);

		// Unité de poids.
		$item_filesize_unit = $functions['select']('item_filesize_unit',
		[
			[
				'value' => 'b',
				'text' => __('octets')
			],
			[
				'value' => 'kb',
				'text' => __('Ko'),
				'default' => 1,
			],
			[
				'value' => 'mb',
				'text' => __('Mo')
			],
			[
				'value' => 'gb',
				'text' => __('Go')
			]
		]);

		Template::set('search', $functions['date']());
		Template::set('search',
		[
			'columns' => $columns,
			'date' => self::$search->options
				? isset(self::$search->options['date'])
				: FALSE,
			'date_column_cat' => $type_categories && isset(self::$search->options['date_column'])
				? self::$search->options['date_column']
				: 'cat_crtdt',
			'date_column_items' => $type_items && isset(self::$search->options['date_column'])
				? self::$search->options['date_column']
				: 'item_crtdt',
			'item_exclude' => self::$search->options
				? isset(self::$search->options['item_exclude'])
				: FALSE,
			'item_exclude_column' => $item_exclude_column,
			'item_format' => $item_format,
			'item_filesize' => self::$search->options
				? isset(self::$search->options['item_filesize'])
				: FALSE,
			'item_filesize_end' => self::$search->options['item_filesize_end'] ?? '',
			'item_filesize_start' => self::$search->options['item_filesize_start'] ?? '',
			'item_filesize_unit' => $item_filesize_unit,
			'item_size' => self::$search->options
				? isset(self::$search->options['item_size'])
				: FALSE,
			'item_size_height_end' => self::$search->options['item_size_height_end'] ?? '',
			'item_size_height_start' => self::$search->options['item_size_height_start'] ?? '',
			'item_size_width_end' => self::$search->options['item_size_width_end'] ?? '',
			'item_size_width_start' => self::$search->options['item_size_width_start'] ?? '',
			'item_tags' => self::$search->options
				? isset(self::$search->options['item_tags'])
				: TRUE,
			'item_type' => $item_type,
			'status' => $status,
			'type' =>
			[
				'categories' => $type_categories,
				'items' => self::$search->query === '' || $type_items
			],
			'user' => self::$search->options['user'] ?? 'all'
		]);
	}

	/**
	 * Nouvelle capture d'image d'une vidéo.
	 *
	 * @return void
	 */
	public static function videoCapture(): void
	{
		if (!isset($_POST['new_thumb'])
		 || empty($_POST['video_capture']) || !is_string($_POST['video_capture'])
		 || !Utility::checkPost('video_hash', '`^[0-9a-f]{32}$`')
		 || !Utility::checkPost('video_id', '`^\d{1,12}$`')
		 || !Utility::checkPost('video_duration', '`^\d{1,9}$`')
		 || !Utility::checkPost('video_height', '`^\d{1,9}$`')
		 || !Utility::checkPost('video_width', '`^\d{1,9}$`'))
		{
			return;
		}

		$data = base64_decode($_POST['video_capture']);
		$capture = GALLERY_ROOT . '/cache/captures/'
			. $_POST['video_id'] . '-' . $_POST['video_hash'] . '.jpg';

		if (File::putContents($capture, $data) !== FALSE)
		{
			// Suppression des images redimensionnées.
			Item::deleteResized([$_POST['video_id']], ['resize', 'thumbs']);

			// Enregistrement des paramètres de la vidéo en base de données.
			$duration = (int) $_POST['video_duration'];
			$width = (int) $_POST['video_width'];
			$height = (int) $_POST['video_height'];
			if ($width > 0 && $height > 0 && $duration > 0)
			{
				$sql = "UPDATE {items}
						   SET item_duration = :duration,
							   item_width = :width,
							   item_height = :height
						 WHERE item_id = :id";
				$params =
				[
					'duration' => $duration,
					'height' => $height,
					'id' => $_POST['video_id'],
					'width' => $width
				];
				if (!DB::execute($sql, $params))
				{
					Report::error();
					return;
				}
			}

			Report::success();
		}
		else
		{
			Report::error();
		}
	}



	/**
	 * Retourne le paramètre $param de la catégorie parente
	 * sur laquelle a été défini un mot de passe.
	 *
	 * @param string $param
	 *   Nom du paramètre à retourner.
	 * @param array $object_infos
	 *   Informations de l'objet.
	 * @param array $parents_infos
	 *   Informations des parents de l'objet.
	 * @param array $is_item
	 *   L'objet est-il un fichier ?
	 *
	 * @return mixed
	 */
	protected static function _getPasswordParent(string $param,
	array &$object_infos, array &$parents_infos, bool $is_item)
	{
		if ($object_infos['password_id'] === NULL)
		{
			return NULL;
		}

		$parent_password = $is_item || $object_infos['p_cat_id'] != $object_infos['cat_id'];

		$i = $parent_password && array_key_exists($object_infos['p_cat_id'], $parents_infos)
			? $parents_infos[$object_infos['p_cat_id']]
			: NULL;

		switch ($param)
		{
			case 'link' :
				return $i ? App::getURL($i['cat_type'] . '/' . $i['cat_id']) : '';

			case 'name' :
			case 'type' :
				return $i ? $i['cat_' . $param] : '';

			default :
				return $parent_password;
		}
	}
}
?>