<?php
declare(strict_types = 1);

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

/**
 * Gestion des utilisateurs.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class GalleryUsers extends Gallery
{
	/**
	 * Modification de l'avatar.
	 *
	 * @return int
	 */
	public static function changeAvatar(): int
	{
		Upload::contentLength();

		if (empty($_POST['action']))
		{
			return 1;
		}

		switch ($_POST['action'])
		{
			case 'change' :
				if ($temp_dir_path = App::getTempDirSys())
				{
					$filepath = Upload::filepath($_FILES['change'], $temp_dir_path);
					if (is_array($filepath))
					{
						Report::warning($filepath['text']);
						return 1;
					}

					// Vérifications du fichier.
					$r = Avatar::check($filepath);
					if ($r['code'] > 0)
					{
						Report::warning($r['message']);
						return 1;
					}

					if (Avatar::change(Auth::$id, $filepath))
					{
						Auth::$infos['user_avatar'] = 1;
						Report::success(__('L\'avatar a été changé.'));
						break;
					}
				}
				return -1;

			case 'delete' :
				if (Avatar::delete(Auth::$id))
				{
					Auth::$infos['user_avatar'] = 0;
					Report::success(__('L\'avatar a été supprimé.'));
				}
				break;

			default :
				return 1;
		}

		$avatar_url = Avatar::getURL(Auth::$id, (bool) Auth::$infos['user_avatar']);
		$avatar_url_thumb = Avatar::getURL(Auth::$id, (bool) Auth::$infos['user_avatar'], TRUE);
		Template::set('user', ['avatar_source' => $avatar_url]);
		Template::set('member',
		[
			'avatar' =>
			[
				'url' => $avatar_url,
				'url_thumb' => $avatar_url_thumb,
			]
		]);

		return 1;
	}

	/**
	 * Modification des options.
	 *
	 * @return int
	 */
	public static function changeOptions(): int
	{
		if (isset($_POST['edit']))
		{
			$r = User::edit(Auth::$id, $_POST,
			[
				'lang' => ['activated' => 1, 'required' => 0],
				'tz' => ['activated' => 1, 'required' => 0]
			]);

			if ($r === FALSE)
			{
				return -1;
			}
			if ($r === TRUE)
			{
				// Log d'activité.
				App::logActivity('profile_change');

				Auth::$lang = $_POST['lang'];
				Report::success();
			}
		}

		return 1;
	}

	/**
	 * Modification du mot de passe.
	 *
	 * @return int
	 */
	public static function changePassword(): int
	{
		Template::set('field_error', '');

		if (empty($_POST['edit']))
		{
			return 1;
		}

		// Vérification du mot de passe actuel.
		if (!isset($_POST['password_current']) || strlen($_POST['password_current']) > 64
		|| !Security::passwordVerify($_POST['password_current'], Auth::$infos['user_password']))
		{
			Report::warning(__('Mot de passe incorrect.'));
			Template::set('field_error', 'password_current');
			$_POST = [];
			return 1;
		}

		// Confirmation du nouveau mot de passe.
		if (!isset($_POST['password']) || !isset($_POST['password_confirm'])
		|| $_POST['password_confirm'] != $_POST['password'])
		{
			Report::warning(L10N::getText('password_confirm_error'));
			Template::set('field_error', 'password_confirm');
			return 1;
		}

		$r = User::edit(Auth::$id, $_POST, ['password' => ['activated' => 1, 'required' => 1]]);
		if ($r === FALSE)
		{
			return -1;
		}
		else if (is_array($r))
		{
			Report::warning($r['message']);
			Template::set('field_error', $r['param']);
		}
		else if ($r === TRUE)
		{
			// Log d'activité.
			App::logActivity('profile_change');

			Report::success();
			$_POST = [];
		}

		return 1;
	}

	/**
	 * Modification d'un profil utilisateur.
	 *
	 * @return int
	 */
	public static function changeProfile(): int
	{
		Template::set('field_error', '');

		if (empty($_POST['edit']))
		{
			return 1;
		}

		App::checkboxes($_POST);

		// Paramètres.
		$params = Config::$params['users_profile_params'];
		unset($params['login']);
		unset($params['password']);

		$r = User::edit(Auth::$id, $_POST, $params);
		if ($r === FALSE)
		{
			return -1;
		}
		else if (is_array($r))
		{
			Report::warning($r['message']);
			Template::set('field_error', $r['param']);
		}
		else if ($r === TRUE)
		{
			// Log d'activité.
			App::logActivity('profile_change');

			Template::set('user',
			[
				'nickname' => User::getNickname(
					Auth::$infos['user_login'],
					$_POST['nickname'] ?? Auth::$infos['user_nickname']
				)
			]);
			Report::success();
		}

		return 1;
	}

	/**
	 * Création d'une catégorie ou d'un album.
	 *
	 * @return int
	 */
	public static function createCategory(): int
	{
		// Vérifications des permissions de l'utilisateur.
		if (!Config::$params['users'] || !Auth::$connected
		|| !Auth::$groupPerms['create_albums'])
		{
			return 0;
		}

		// Vérifications des données.
		if (!isset($_POST['new_category'])
		 || !Utility::checkPost('cat_id', '`^\d{1,12}$`')
		 || !in_array($_POST['type'] ?? '', ['album', 'category'])
		 || !isset($_POST['title']) || !is_string($_POST['title'])
		 || !isset($_POST['desc']) || !is_string($_POST['desc']))
		{
			return 1;
		}

		// Autorise-t-on la création de catégories ?
		if (!Auth::$groupPerms['create_albums_categories'])
		{
			$_POST['type'] = 'album';
		}

		// Vérification du titre.
		if (mb_strlen($title = Utility::trimAll($_POST['title'])) < 1)
		{
			Report::warning(__('Le titre doit comporter au moins 1 caractère.'));
			return 1;
		}

		// Vérification des permissions de création d'albums dans la catégorie parente.
		if (!Auth::$isAdmin)
		{
			if ($_POST['cat_id'] == 1 && !Auth::$groupPerms['create_albums_gallery_root'])
			{
				return 1;
			}
			if ($_POST['cat_id'] > 1)
			{
				$sql = 'SELECT cat_parents
						 FROM {categories} AS cat
						WHERE cat_id = :cat_id
						  AND cat_status = "1"
						  AND cat_creatable = "1"
						  AND ' . SQL::catPerms() . '
						  AND ' . SQL::catPassword();
				if (Auth::$groupPerms['create_albums_owner'])
				{
					$sql .= ' AND user_id = :user_id';
					DB::params(['user_id' => Auth::$id]);
				}
				DB::params(['cat_id' => $_POST['cat_id']]);
				if (!DB::execute($sql))
				{
					return -1;
				}
				if (!$cat_parents = DB::fetchVal())
				{
					return 1;
				}

				// Vérification de la permission de création d'albums
				// pour les catégories parentes.
				if ($cat_parents != '1.')
				{
					$cat_parents = DB::inInt(explode('.', substr($cat_parents, 2, -1)));
					$sql = "SELECT cat_id,
								   cat_creatable
							  FROM {categories}
							 WHERE cat_id IN ($cat_parents)";
					if (!DB::execute($sql))
					{
						return -1;
					}
					if (in_array('0', DB::fetchAll('cat_id', 'cat_creatable')))
					{
						return 1;
					}
				}
			}
		}

		// Création de la catégorie.
		$cat_id = Category::create(
			$_POST['type'],
			$title,
			(string) $_POST['desc'],
			1,
			(int) $_POST['cat_id'],
			Auth::$id
		);
		switch ($cat_id)
		{
			case -1 :
			case 0 :
				Report::error();
				break;

			case 1 :
				Report::warning(
					__('Une catégorie ou un album avec le même titre existe déjà.')
				);
				break;

			default :
				App::logActivity('cat_add', '',
				[
					'parent_id' => $_POST['cat_id'],
					'type' => $_POST['type'],
					'title' => $_POST['title']
				]);
				if ($_POST['type'] == 'album')
				{
					if (Auth::$groupPerms['upload'])
					{
						Template::set('create_success',
						[
							'title' => $title,
							'message' => __('L\'album %s a été créé.'),
							'upload_link' => App::getURL('user-upload/album/' . $cat_id)
						]);
					}
					else
					{
						Report::success(__('L\'album a été créé.'));
					}
				}
				else
				{
					Report::success(__('La catégorie a été créée.'));
				}
				$_POST = [];
		}

		return 1;
	}

	/**
	 * Génère la liste des albums ou des catégories dans lesquels
	 * il est possible d'envoyer des fichiers ou de créer des albums.
	 *
	 * @param string $type
	 *
	 * @return void
	 */
	public static function getCatList(string $type): array
	{
		// Liste des catégories.
		$perms = ' AND cat_status = "1"';
		if ($type == 'categories')
		{
			$perms .= ' AND cat_filemtime IS NULL';
		}
		if (!self::$categories)
		{
			self::$categories = Category::getList(
				$makelist,
				function(){},
				0,
				SQL::catPerms() . $perms,
				SQL::catPassword('select')
			);
		}

		$create_list = function(int $parent_id = 1, int $n = 1) use (&$create_list, $type)
		{
			static $list = [];
			static $creatable_parents = [];

			if ($n == 1)
			{
				$list[1] =
				[
					'id' => 1,
					'level' => 0,
					'selectable' =>
						Auth::$isAdmin || Auth::$groupPerms['create_albums_gallery_root'],
					'title' => __('galerie')
				];
			}

			if (!is_array(self::$categories))
			{
				return $list;
			}

			$i = 0;
			foreach (self::$categories as $id => &$infos)
			{
				if ($infos['password_auth'] === NULL
				 || $infos['parent_id'] != $parent_id || $id == 1)
				{
					continue;
				}

				// On ignore les catégories désactivées.
				if ($infos['cat_status'] != '1')
				{
					continue;
				}

				$selectable = TRUE;
				if ($type == 'albums')
				{
					// L'utilisateur doit-il être propriétaire de l'album ?
					if ($infos['cat_filemtime'] !== NULL
					&& Auth::$groupPerms['upload_owner']
					&& $infos['user_id'] != Auth::$infos['user_id'])
					{
						continue;
					}

					// L'autorisation d'ajout de fichiers doit être activée.
					if ($infos['cat_uploadable'] != '1')
					{
						continue;
					}
				}
				else
				{
					// Catégorie sélectionnable ?
					if (!Auth::$isAdmin)
					{
						if (Auth::$groupPerms['create_albums_owner'])
						{
							$selectable = $infos['user_id'] == Auth::$id;
						}
						if ($infos['cat_creatable'] != '1')
						{
							$selectable = FALSE;
						}
						else
						{
							foreach (explode('.', substr($infos['cat_parents'], 0, -1)) as $p)
							{
								if ($p > 1 && $creatable_parents[$p] != '1')
								{
									$selectable = FALSE;
									break;
								}
							}
						}
					}
				}
				$creatable_parents[$id] = $infos['cat_creatable'];

				// Ajoute la catégorie à la liste.
				$list[$id] =
				[
					'id' => $id,
					'level' => $n,
					'parent' => $parent_id,
					'selectable' => $selectable,
					'title' => $infos['cat_name'],
					'type' => $infos['cat_filemtime'] === NULL ? 1 : 0
				];

				// Si c'est une catégorie, on la parcours.
				if ($infos['cat_filemtime'] === NULL)
				{
					if (!$create_list((int) $id, $n + 1))
					{
						continue;
					}
				}

				$i++;
			}

			if ($i == 0 && $type == 'albums')
			{
				unset($list[$parent_id]);
			}

			return $n == 1 ? $list : $i;
		};

		$list = $create_list();
		return ['k' => array_keys($list), 'v' => array_values($list)];
	}

	/**
	 * Récupère les informations de l'utilisateur.
	 *
	 * @param int $user_id
	 *
	 * @return int
	 */
	public static function getInfos(int $user_id = 0): int
	{
		if (!Config::$params['users'])
		{
			return 0;
		}

		if (!$user_id)
		{
			$user_id = Auth::$id;
		}

		$sql = 'SELECT u.*,
					   g.*
				  FROM {users} AS u
			 LEFT JOIN {groups} AS g USING (group_id)
				 WHERE user_id = ?
				   AND user_id != 2
				   AND user_status = "1"';
		if (!DB::execute($sql, $user_id))
		{
			return -1;
		}
		if (!$i = DB::fetchRow())
		{
			return 0;
		}

		$stats = [];
		if ($_GET['section'] == 'user')
		{
			$stats =
			[
				'comments' => 0,
				'date_created' => L10N::dt(__('%A %d %B %Y'), $i['user_crtdt']),
				'date_last_visite' => $i['user_lastvstdt']
					? L10N::dt(__('%A %d %B %Y'), $i['user_lastvstdt'])
					: NULL,
				'favorites' => 0,
				'images' => 0,
				'videos' => 0
			];

			// Nombre de favoris.
			if (Config::$params['favorites'])
			{
				$sql = 'SELECT COUNT(*)
						  FROM {favorites} AS fav
					 LEFT JOIN {items} AS i
							ON fav.item_id = i.item_id
					 LEFT JOIN {categories} AS cat
							ON i.cat_id = cat.cat_id
						 WHERE item_status = "1"
						   AND fav.user_id = :user_id
						   AND ' . SQL::catPerms() . '
						   AND ' . SQL::catPassword();
				DB::params(['user_id' => $user_id]);
				if (!DB::execute($sql))
				{
					return -1;
				}
				$stats['favorites'] = DB::fetchVal();
			}

			// Nombre d'images.
			$sql = 'SELECT cat_a_images FROM {categories} WHERE cat_id = 1';
			if (DB::execute($sql) && DB::fetchVal() > 0)
			{
				$sql = 'SELECT COUNT(*)
						  FROM {items} AS i
					 LEFT JOIN {users} AS u
							ON i.user_id = u.user_id
					 LEFT JOIN {categories} AS cat
							ON i.cat_id = cat.cat_id
						 WHERE item_status = "1"
						   AND item_type IN (' . DB::inInt(Item::IMAGE_TYPES) . ')
						   AND i.user_id = :user_id
						   AND ' . SQL::catPerms() . '
						   AND ' . SQL::catPassword();
				DB::params(['user_id' => $user_id]);
				if (!DB::execute($sql))
				{
					return -1;
				}
				$stats['images'] = DB::fetchVal();
			}

			// Nombre de vidéos.
			$sql = 'SELECT cat_a_videos FROM {categories} WHERE cat_id = 1';
			if (DB::execute($sql) && DB::fetchVal() > 0)
			{
				$sql = 'SELECT COUNT(*)
						  FROM {items} AS i
					 LEFT JOIN {users} AS u
							ON i.user_id = u.user_id
					 LEFT JOIN {categories} AS cat
							ON i.cat_id = cat.cat_id
						 WHERE item_status = "1"
						   AND item_type IN (' . DB::inInt(Item::VIDEO_TYPES) . ')
						   AND i.user_id = :user_id
						   AND ' . SQL::catPerms() . '
						   AND ' . SQL::catPassword();
				DB::params(['user_id' => $user_id]);
				if (!DB::execute($sql))
				{
					return -1;
				}
				$stats['videos'] = DB::fetchVal();
			}

			// Nombre de commentaires.
			if (Config::$params['comments'] && Auth::$groupPerms['comments_read'])
			{
				$sql = 'SELECT COUNT(*)
						  FROM {comments} AS com
					 LEFT JOIN {items} AS i
							ON com.item_id = i.item_id
					 LEFT JOIN {categories} AS cat
							ON i.cat_id = cat.cat_id
						 WHERE item_status = "1"
						   AND com_status = "1"
						   AND com.user_id = :user_id
						   AND ' . SQL::catPerms() . '
						   AND ' . SQL::catPassword();
				DB::params(['user_id' => $user_id]);
				if (!DB::execute($sql))
				{
					return -1;
				}
				$stats['comments'] = DB::fetchVal();
			}
		}

		Template::set('profile_params', User::getProfileParams());
		Template::set('member',
		[
			'admin' => (bool) $i['group_admin'],
			'id' => (int) $i['user_id'],
			'nickname' => User::getNickname($i['user_login'], $i['user_nickname']),

			// Avatar.
			'avatar' =>
			[
				'status' => (bool) $i['user_avatar'],
				'url' => Avatar::getURL((int) $i['user_id'], (bool) $i['user_avatar'], FALSE),
				'url_thumb' => Avatar::getURL((int) $i['user_id'], (bool) $i['user_avatar'])
			],

			// Groupe.
			'group' =>
			[
				'id' => (int) $i['group_id'],
				'link' => App::getURL('members/group/' . $i['group_id']),
				'name' => L10N::getTextGroupName((int) $i['group_id'], $i['group_name']),
				'title' => L10N::getTextGroupTitle(
					(int) $i['group_id'], Utility::isEmpty($i['group_title'])
						? $i['group_name']
						: $i['group_title']
				)
			],

			// Informations personnelles.
			'infos' =>
			[
				'name' => [
					'text' => __('Nom'),
					'value' => $i['user_name']
				],
				'firstname' => [
					'text' => __('Prénom'),
					'value' => $i['user_firstname']
				],
				'gender' => [
					'text' => __('Genre'),
					'value' => L10N::getTextUserGender($i['user_gender'])
				],
				'birthdate' => [
					'text' => __('Date de naissance'),
					'value' => $i['user_birthdate']
						? L10N::dt(__('%A %d %B %Y'), $i['user_birthdate'])
						: '',
				],
				'location' => [
					'text' => __('Localisation'),
					'value' => $i['user_location']
				],
				'website' => [
					'text' => __('Site Web'),
					'value' => $i['user_website']
				],
				'description' => [
					'text' => __('Description'),
					'value' => $i['user_description']
				],
				'custom_1' => [
					'text' => Template::$data['profile_params']['custom_1']['name'],
					'value' => $i['user_custom_1']
				],
				'custom_2' => [
					'text' => Template::$data['profile_params']['custom_2']['name'],
					'value' => $i['user_custom_2']
				],
				'custom_3' => [
					'text' => Template::$data['profile_params']['custom_3']['name'],
					'value' => $i['user_custom_3']
				],
				'custom_4' => [
					'text' => Template::$data['profile_params']['custom_4']['name'],
					'value' => $i['user_custom_4']
				],
				'custom_5' => [
					'text' => Template::$data['profile_params']['custom_5']['name'],
					'value' => $i['user_custom_5']
				]
			],

			// Statistiques.
			'stats' => $stats
		]);

		// Données de template : infos de profil.
		if ($_GET['section'] == 'user-profile' || $_GET['section'] == 'user-options')
		{
			Template::set('profile', User::getProfileEdit($i, $_POST));
		}

		return 1;
	}

	/**
	 * Récupération de la liste des membres.
	 *
	 * @return int
	 */
	public static function getMembers(): int
	{
		// Permission d'accès.
		if (!Auth::$groupPerms['members_profile'])
		{
			return 0;
		}

		$config = Config::$params['pages_params']['members'];

		// Groupe.
		if (isset($_GET['group_id']))
		{
			$sql = 'SELECT group_name FROM {groups} WHERE group_id = ?';
			if (DB::execute($sql, $_GET['group_id']))
			{
				$group_name = L10N::getTextGroupName((int) $_GET['group_id'], DB::fetchVal());
			}
		}

		// Clause WHERE.
		$params = [];
		$sql_where = '(user_status = "1" OR (user_status = "0" AND user_lastvstdt IS NOT NULL))
			AND user_id != 2';
		if (isset($_GET['group_id']))
		{
			$sql_where .= ' AND group_id = :group_id';
			$params = ['group_id' => $_GET['group_id']];
		}

		// Nombre d'utilisateurs.
		DB::params($params);
		if (!DB::execute("SELECT COUNT(*) FROM {users} WHERE $sql_where"))
		{
			return -1;
		}
		$nb_users = DB::fetchVal();
		if (!$nb_users && isset($_GET['group_id']))
		{
			App::redirect('members');
		}

		// Clause ORDER BY.
		$sql_order_by = 'LOWER(user_login) ASC';
		if (preg_match('`^(user\_(?:crtdt|lastvstdt))\s(ASC|DESC)$`', $config['order_by'], $m))
		{
			$sql_order_by = $m[1] . ' ' . $m[2];
		}
		SQL::nicknameOrderBy($sql_nickname, $sql_order_by);

		// Clause LIMIT.
		$nb_per_page = (int) ($config['nb_per_page'] ?? 10);
		$sql_limit_start = $nb_per_page * ($_GET['page'] - 1);

		// Récupération des utilisateurs.
		$sql = "SELECT user_id,
					   user_avatar,
					   user_login,
					   user_nickname,
					   user_crtdt,
					   user_lastvstdt,
					   user_status,
					   $sql_nickname,
					   g.group_id,
					   g.group_title
				  FROM {users} AS u
			 LEFT JOIN {groups} AS g USING (group_id)
				 WHERE $sql_where
			  ORDER BY $sql_order_by
			     LIMIT $sql_limit_start,$nb_per_page";
		DB::params($params);
		if (!DB::execute($sql))
		{
			return -1;
		}
		$users = DB::fetchAll('user_id');
		if (!$users && $_GET['page'] > 1)
		{
			App::redirect($_GET['q_pageless']);
		}
		$members = [];
		foreach ($users as &$i)
		{
			$members[] =
			[
				'avatar_src' => Avatar::getURL(
					(int) $i['user_id'], (bool) $i['user_avatar'], TRUE
				),
				'date_created' => L10N::dt(__('%A %d %B %Y'), $i['user_crtdt']),
				'date_lastvisited' => $i['user_lastvstdt']
					? L10N::dt(__('%A %d %B %Y'), $i['user_lastvstdt'])
					: '/',
				'id' => (int) $i['user_id'],
				'link' => Auth::$groupPerms['members_profile'] && $i['user_status'] == 1
					? App::getURL('user/' . $i['user_id'])
					: NULL,
				'login' => User::getNickname($i['user_login'], $i['user_nickname']),
				'group' =>
				[
					'link' => App::getURL('members/group/' . $i['group_id']),
					'title' => L10N::getTextGroupTitle((int) $i['group_id'], $i['group_title'])
				]
			];
		}
		Template::set('members',
		[
			'count' => $nb_users,
			'group' =>
			[
				'name' => $group_name ?? NULL
			],
			'list' => $members,
			'show' =>
			[
				'date_created' => (bool) $config['show_crtdt'],
				'date_lastvisited' => (bool) $config['show_lastvstdt'],
				'group_title' => (bool) $config['show_title']
			]
		]);

		// Pagination.
		$pages_count = ceil($nb_users / $nb_per_page);
		self::_pagination($pages_count);

		return 1;
	}

	/**
	 * Déconnexion du compte d'un utilisateur ou
	 * d'une catégorie vérouillée par mot de passe.
	 *
	 * @param int $cat_id
	 *
	 * @return mixed
	 */
	public static function logout(int $cat_id = 0)
	{
		// Quelques vérifications.
		if (!isset($_POST['logout'])
		|| !Auth::getCookieSessionToken()
		|| (!$cat_id && (!Auth::$connected || !Config::$params['users'])))
		{
			return 1;
		}

		// Catégorie.
		if ($cat_id > 0)
		{
			// Récupération des informations utiles.
			$sql = 'SELECT cat.cat_id,
						   CASE WHEN cat_filemtime IS NULL
								THEN "category"
								ELSE "album"
								 END AS cat_type,
						   ' . SQL::catPassword('select') . ',
						   p.cat_id AS p_cat_id
					  FROM {categories} AS cat
				 LEFT JOIN {passwords} AS p USING (password_id)
					 WHERE cat.cat_id = :cat_id
					   AND ' . SQL::catPerms();
			DB::params(['cat_id' => $cat_id]);
			if (!DB::execute($sql))
			{
				return -1;
			}
			$i = DB::fetchRow();

			// La catégorie n'existe pas ou n'est pas protégée par un mot de passe.
			if (!$i || $i['password_auth'] == 1)
			{
				return 0;
			}

			// La catégorie est protégée par un mot de passe,
			// mais l'utilisateur n'est pas connecté.
			if (!$i['password_auth'])
			{
				return 'password/' . $i['cat_type'] . '/' . $i['cat_id'];
			}

			// Suppression de l'association session - catégorie.
			$sql = 'SELECT session_id
					  FROM {sessions}
					 WHERE session_token = ?
					   AND session_expire > NOW()';
			$sql = "DELETE
					  FROM {sessions_categories}
					 WHERE session_id = ($sql)
					   AND cat_id = ?";
			$params = [Auth::getCookieSessionToken(), $i['p_cat_id']];
			if (!DB::execute($sql, $params))
			{
				return -1;
			}
			if (DB::rowCount())
			{
				// On génère un nouveau jeton de session afin d'interdire
				// l'accès aux fichiers qui avaient été déverrouillés.
				Auth::$session->add('session_token', Auth::getNewSessionToken()['token']);

				return 'password/' . $i['cat_type'] . '/' . $i['cat_id'];
			}
		}

		// Compte utilisateur.
		else if (Auth::$connected)
		{
			if (!Auth::logout())
			{
				return -1;
			}

			return '';
		}

		return 1;
	}

	/**
	 * Procédure pour l'oubli du mot de passe.
	 *
	 * @return int
	 */
	public static function passwordForgot(): int
	{
		if (Auth::$connected)
		{
			App::redirect();
		}

		$forgot = [];

		if (User::passwordForgot($_POST, $forgot))
		{
			if (isset($forgot['warning']))
			{
				Report::warning($forgot['warning']);
			}
		}
		else
		{
			Report::error();
		}

		if ($forgot['step'] == 'confirm')
		{
			Report::success($forgot['text']);
		}
		Template::set('forgot_password', $forgot);

		return 1;
	}

	/**
	 * Création d'un nouveau compte utilisateur.
	 *
	 * @return int
	 */
	public static function register(): int
	{
		foreach (['admin', 'email', 'password'] as $method)
		{
			${'valid_' . $method} = Config::$params['users_registration_valid_' . $method];
		}

		// Informations de profil.
		$params = User::getProfileParams();

		// Pour les inscriptions, on ne prend en compte
		// que les informations de profil qui sont obligatoires.
		foreach ($params as $name => &$i)
		{
			if (!$i['activated'])
			{
				$i['required'] = 0;
			}
			if (!$i['required'] && $name != 'lang')
			{
				$i['activated'] = 0;
			}
		}

		// Si une validation par courriel ou par un administrateur est
		// activée, alors on rend le paramètre 'email' obligatoire.
		if ($valid_email || $valid_admin)
		{
			$params['email']['activated'] = 1;
			$params['email']['required'] = 1;
		}

		// Informations de template.
		Template::set('field_error', '');
		Template::set('register',
		[
			'fields' => $params,
			'fields_infos' => array_sum(array_column($params, 'required')),
			'gender' =>
			[
				[
					'value' => '?',
					'text' => '?'
				],
				[
					'value' => 'M',
					'text' => L10N::getTextUserGender('M')
				],
				[
					'value' => 'F',
					'text' => L10N::getTextUserGender('F')
				]
			],
			'password' =>
			[
				'minlength' => User::getPasswordMinLength()
			]
		]);

		if (!Config::$params['users'] || !Config::$params['users_registration'])
		{
			return 0;
		}

		if ($valid_password)
		{
			Template::set('register',
			[
				'password_validation' => TRUE,
				'password_validation_text' =>
					Config::$params['users_registration_password_text']
			]);
		}

		// Si l'utilisateur est déjà connecté, on redirige vers la page d'accueil.
		if (Auth::$connected)
		{
			App::redirect();
			return 1;
		}

		// Si le formulaire n'a pas été envoyé, on arrête là.
		if (empty($_POST['register']))
		{
			return 1;
		}

		// Vérification par un faux champ de formulaire.
		if (!isset($_POST['f_email']) || $_POST['f_email'] !== '')
		{
			Report::warning('Bot detected.');
			return 1;
		}

		// Vérification par listes noires.
		$login = $_POST['login'] ?? '';
		$email = $_POST['email'] ?? '';
		$r = App::blacklists('register', $login, $email, '',
			['login' => $login, 'email' => $email]);
		if (is_array($r))
		{
			$field = '';
			if ($r['list'] == 'emails')
			{
				$field = 'email';
			}
			if ($r['list'] == 'names')
			{
				$field = 'login';
			}
			Report::warning($r['text']);
			Template::set('field_error', $field);

			return 1;
		}

		// Validation de l'inscription par mot de passe.
		if ($valid_password)
		{
			if (isset($_POST['password_validation']))
			{
				$callback = function(string $new_password): void
				{
					DB::execute(
						'UPDATE {config} SET conf_value = ? WHERE conf_name = ?',
						[$new_password, 'users_registration_password']
					);
				};
				if (!Security::passwordVerify($_POST['password_validation'],
				Config::$params['users_registration_password'], $callback))
				{
					// Log d'activité.
					App::logActivity('register_rejected_password', '', [
						'login' => $_POST['login'], 'email' => $_POST['email'] ?? ''
					]);

					Report::warning(__('Mot de passe de validation incorrect.'));
					Template::set('field_error', 'password_validation');
					return 1;
				}
			}
			else
			{
				return 1;
			}
		}

		// Confirmation du mot de passe.
		if (!isset($_POST['password_confirm']) || !isset($_POST['password']))
		{
			return 1;
		}
		if ($_POST['password_confirm'] != $_POST['password'])
		{
			Report::warning(L10N::getText('password_confirm_error'));
			Template::set('field_error', 'password_confirm');
			return 1;
		}

		// Groupe par défaut.
		$_POST['group_id'] = 3;

		// Langue.
		$_POST['lang'] = Auth::$lang;

		// État.
		if ($valid_email)
		{
			$_POST['status'] = '-2';
		}
		else if ($valid_admin)
		{
			$_POST['status'] = '-1';
		}
		else
		{
			$_POST['status'] = '1';
		}

		// On démarre une transaction.
		if (!DB::beginTransaction())
		{
			return -1;
		}

		// Création du profil.
		$r = User::create($_POST, $params);

		// Erreur.
		if ($r === FALSE)
		{
			return -1;
		}

		// Formulaire incomplet ou incorrect.
		if (is_array($r))
		{
			Report::warning($r['message']);
			Template::set('field_error', $r['param']);

			return 1;
		}

		if (!is_int($r))
		{
			return -1;
		}

		// Initialisation du courriel.
		$mail = new Mail();

		// Validation par courriel.
		if ($valid_email)
		{
			$sql = 'UPDATE {users} SET user_rkey = ? WHERE user_id = ?';
			if (!DB::execute($sql, [$rkey = Security::key(20), $r]))
			{
				return -1;
			}

			// Sujet.
			$subject = __('Demande de confirmation de votre inscription à la galerie');

			// Message.
			$message = __('Pour valider votre inscription à la galerie "%s",'
				. ' il vous suffit de suivre le lien suivant :') . "\n\n%s\n\n";
			$message .= __('Notez que ce lien n\'est valide que durant 48 heures.'
				. ' Passé ce délai, vous devrez vous réinscrire.');
			$message = sprintf(
				$message,
				Config::$params['gallery_title'],
				GALLERY_HOST . App::getURL('validation/' . $rkey)
			);

			// Préparation du courriel.
			$mail->messages[] =
			[
				'to' => htmlspecialchars($_POST['email']),
				'subject' => '[' . Config::$params['gallery_title'] . '] ' . $subject,
				'message' => $message
			];

			Report::success(__('Pour valider votre inscription, '
				. 'vous devez suivre la procédure indiquée dans '
				. 'le courriel qui vient de vous être envoyé.'));
		}

		// Validation par un administrateur.
		else if ($valid_admin)
		{
			Report::success(__('Votre inscription est en attente de'
				. ' validation par un administrateur. Vous serez prévenu par'
				. ' courriel dès que celle-ci sera validée.'));
		}

		// Si aucune autre validation, on connecte automatiquement l'utilisateur.
		else if (Auth::form($_POST['login'], $_POST['password']))
		{
			Report::success(__('Votre inscription a été validée.'
				. ' Vous êtes maintenant identifié.'));
			Template::set('user',
			[
				'admin' => FALSE,
				'auth' => TRUE,
				'avatar_source' => Avatar::getURL(),
				'id' => Auth::$id,
				'nickname' => $_POST['login']
			]);
		}

		// Log d'activité.
		App::logActivity('register', '', [
			'login' => $_POST['login'], 'email' => $_POST['email'] ?? ''
		]);

		// Notification aux administrateurs.
		if (!$valid_email)
		{
			$mail->notify('registration', [], 0,
			[
				'user_id' => $r,
				'user_name' => $_POST['login']
			]);
		}

		// On valide la transaction.
		if (!DB::commitTransaction())
		{
			return -1;
		}

		// Envoi du courriel.
		return !$mail->send() && $valid_email ? -1 : 1;
	}

	/**
	 * Validation des inscriptions par courriel.
	 *
	 * @return void
	 */
	public static function registerValidation(): void
	{
		$valid_admin = Config::$params['users_registration_valid_admin'];

		// Récupération des informations de l'utilisateur.
		$sql = 'SELECT user_id, user_login, user_nickname FROM {users} WHERE user_rkey = ?';
		if (!DB::execute($sql, $_GET['key'] ?? ''))
		{
			Report::error();
			return;
		}
		$user_infos = DB::fetchRow();

		// Mise à jour de la base de données.
		$sql = 'UPDATE {users}
				   SET user_status = :status,
				       user_rkey = NULL
				 WHERE user_rkey = :key
				   AND user_crtdt > DATE_ADD(NOW(), INTERVAL -2 DAY)
				   AND user_status = "-2"';
		$params =
		[
			'key' => $_GET['key'] ?? '',
			'status' => $valid_admin ? '-1' : '1'
		];
		if (!DB::execute($sql, $params))
		{
			Report::error();
			return;
		}

		// Code invalide.
		if (!DB::rowCount())
		{
			Report::warning(__('Code invalide ou périmé.'));
			return;
		}

		// Notification d'inscription aux administrateurs.
		$mail = new Mail();
		$mail->notify('registration', [], 0,
		[
			'user_id' => $user_infos['user_id'],
			'user_name' => User::getNickname(
				$user_infos['user_login'],
				$user_infos['user_nickname']
			)
		]);
		$mail->send();

		// Validation supplémentaire par un administrateur.
		if ($valid_admin)
		{
			Report::success(__('Votre compte est désormais en attente de'
				. ' validation par un administrateur. Vous serez prévenu par'
				. ' courriel dès que votre inscription sera validée.'));
			return;
		}

		// Compte validé.
		Report::success(__('Votre compte a été validé.'
			. ' Vous pouvez maintenant vous connecter.'));
	}

	/**
	 * Ajoute des fichiers envoyés par le navigateur.
	 *
	 * @return int
	 */
	public static function upload(): int
	{
		if (!Config::$params['users'] || !Auth::$connected || !Auth::$groupPerms['upload'])
		{
			return 0;
		}

		if (!isset($_POST['upload'])
		 || !Utility::checkPost('cat_id', '`^\d{1,12}$`')
		 || !Utility::checkPost('temp_dir', '`^[a-z\d]{40}$`i'))
		{
			return 1;
		}

		// Avertissements et erreurs.
		foreach (['error', 'warning'] as $type)
		{
			if (isset($_POST[$type]) && is_array($_POST[$type]))
			{
				foreach ($_POST[$type] as &$msg)
				{
					Report::warning(urldecode($msg));
				}
			}
		}

		// Répertoire temporaire.
		$temp_dir = GALLERY_ROOT . '/cache/temp/' . $_POST['temp_dir'];
		if (!file_exists($temp_dir))
		{
			return 1;
		}

		// Nouveaux fichiers.
		if (isset($_POST['success']) && is_array($_POST['success'])
		 && count($_POST['success']) <= (int) Config::$params['upload_maxtotalfiles'])
		{
			$i = Category::getInfosUpload((int) $_POST['cat_id'], FALSE);
			if ($i === FALSE)
			{
				return -1;
			}
			if ($i === [])
			{
				return 0;
			}
			$cat_path = $i['cat_path'];

			$add_files = array_map('rawurldecode', $_POST['success']);
		}

		// Y a-t-il des fichiers à ajouter ?
		if (!empty($add_files))
		{
			// Ajout de fichiers directement à la galerie.
			if (Auth::$groupPerms['upload_mode'])
			{
				$dest_path = CONF_ALBUMS_PATH . '/' . $cat_path;
				$add_files = Upload::itemMove($add_files, $temp_dir, $dest_path);

				// Initialisation du scan.
				$scan = new Scan();
				if (!$scan->getInit)
				{
					return -1;
				}

				// Options de scan.
				$scan->setHttp = TRUE;
				$scan->setHttpAlbum = &$i;
				$scan->setHttpFiles = array_flip($add_files);
				$scan->setHttpOriginalDir = $temp_dir . '/original';
				$scan->setUserId = Auth::$id;
				$scan->setUserName = Auth::$nickname;

				// Lancement du scan de l'album.
				if ($scan->start($cat_path) === FALSE)
				{
					return -1;
				}

				// 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.'));
				}

				// Erreurs et fichiers rejetés.
				$report['files_errors'] = array_merge(
					$report['files_errors'],
					$report['files_rejected']
				);
				if ($report['files_errors'])
				{
					foreach ($report['files_errors'] as &$e)
					{
						Report::warning($i['file'] . ' : ' . ucfirst($e['message']) . '.');
						$file_path = $dest_path . '/' . $e['file'];
						if (file_exists($file_path))
						{
							File::unlink($file_path);
						}
					}
				}

				// Log d'activité.
				App::logActivity('upload_files', '',
				[
					'cat_id' => $_POST['cat_id'],
					'files_added' => $nb_files,
					'files_errors' => count($report['files_errors']),
					'files_rejected' => count($report['files_rejected'])
				]);
			}

			// Mise en attente de validation des fichiers.
			else
			{
				$files = scandir($temp_dir);
				$pending_dir = GALLERY_ROOT . '/pending';
				$video = FALSE;

				// On déplace les fichiers vers le répertoire d'attente.
				$add_files_temp = $add_files;
				$add_files = [];
				foreach ($add_files_temp as &$file)
				{
					if (in_array($file, $files))
					{
						$pending_file = File::getSecureFilename($file, $pending_dir);

						$oldname = $temp_dir . '/' . $file;
						$newname = $pending_dir . '/' . $pending_file;
						if (File::rename($oldname, $newname))
						{
							$add_files[$pending_file] = $file;
						}
					}
				}

				// Requête préparée d'insertion des fichiers dans la table {items_pending}.
				$sql = 'INSERT INTO {items_pending} (
						cat_id, user_id, pending_file, pending_type, pending_filesize,
						pending_exif, pending_iptc, pending_xmp,
						pending_name, pending_height, pending_width, pending_adddt, pending_ip
					) VALUES (
						:cat_id, :user_id, :pending_file, :pending_type, :pending_filesize,
						:pending_exif, :pending_iptc, :pending_xmp,
						:pending_name, :pending_height, :pending_width, NOW(), :pending_ip
					)';

				// Paramètres de la requête préparée.
				$params = [];
				foreach ($add_files as $file => &$original)
				{
					if ($file === NULL)
					{
						continue;
					}

					$pending_file = $pending_dir . '/' . $file;

					// Si le fichier est une image et qu'elle a été
					// redimensionnée avec les paramètres de configuration
					// "upload_resize_maxwidth" et "upload_resize_maxheight",
					// on récupère ses métadonnées et ses dimensions.
					$pending_exif = NULL;
					$pending_iptc = NULL;
					$pending_xmp = NULL;
					$height = 0;
					$width = 0;
					$ext = strtolower(preg_replace('`^.+\.([a-z0-9]{2,4})$`i', '$1', $file));
					if (in_array($ext, Image::EXT_ARRAY))
					{
						$file_original = $temp_dir . '/original/' . $original;
						if (file_exists($file_original))
						{
							$metadata = new Metadata($file_original);
							$pending_exif = Utility::jsonEncode($metadata->getExifData());
							$pending_iptc = Utility::jsonEncode($metadata->getIptcData());
							$pending_xmp = $metadata->getXmpData();

							// Orientation.
							if (file_exists($file_original . '.rotate'))
							{
								File::putContents($pending_file . '.rotate');
							}
						}
						if ($i = Image::getTypeSize($pending_file))
						{
							$height = (int) $i['height'];
							$width = (int) $i['width'];
						}
					}

					$type = Item::getTypeCode($pending_file);
					if (Item::isVideo($type))
					{
						$video = TRUE;
					}

					$params[] =
					[
						'cat_id' => (int) $_POST['cat_id'],
						'user_id' => Auth::$id,
						'pending_file' => $file,
						'pending_type' => $type,
						'pending_filesize' => (int) filesize($pending_file),
						'pending_exif' => $pending_exif,
						'pending_iptc' => $pending_iptc,
						'pending_xmp' => $pending_xmp,
						'pending_name' => Item::getTitle($file),
						'pending_height' => $height,
						'pending_width' => $width,
						'pending_ip' => $_SERVER['REMOTE_ADDR']
					];
				}
				if (!$params)
				{
					return 1;
				}
				if (!DB::execute($sql, $params))
				{
					foreach ($add_files as $file => &$original)
					{
						if (file_exists($pending_dir . '/' . $file))
						{
							unlink($pending_dir . '/' . $file);
						}
					}
					return -1;
				}

				// Notification par courriel.
				$sql = 'SELECT cat_name FROM {categories} WHERE cat_id = ?';
				DB::execute($sql, $_POST['cat_id']);
				$mail = new Mail();
				$mail->notify('items-pending', [$cat_path], Auth::$id,
				[
					'cat_id' => $_POST['cat_id'],
					'cat_name' => (string) DB::fetchVal(),
					'user_id' => Auth::$id,
					'user_name' => User::getNickname(
						Auth::$infos['user_login'], Auth::$infos['user_nickname']
					)
				]);
				$mail->send();

				// Capture vidéo.
				if ($video)
				{
					Config::changeDBParams(['video_captures' => 1]);
				}

				// Log d'activité.
				App::logActivity('upload_files', '',
				[
					'cat_id' => $_POST['cat_id'],
					'files_added' => count($params)
				]);

				// Rapport.
				$message = count($params) > 1
					? __('%s fichiers ont été mis en attente de validation.')
					: __('%s fichier a été mis en attente de validation.');
				Report::success(sprintf($message, count($params)));
			}
		}

		File::rmdir($temp_dir);
		return 1;
	}
}
?>