<?php
declare(strict_types = 1);

/**
 * Gestion des groupes.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class Group
{
	/**
	 * Modification des permissions d'accès aux catégories.
	 *
	 * @param int $group_id
	 * @param string $list
	 * @param array $blacklist
	 * @param array $whitelist
	 *
	 * @return mixed
	 *   NULL si aucun changement effectué.
	 *   TRUE si la mise à jour a réussie.
	 *   FALSE en cas d'erreur.
	 */
	public static function changePermsCategories(int $group_id, string $list,
	array $blacklist = [], array $whitelist = [])
	{
		// Vérifications.
		if (!in_array($list, ['black', 'white']) || $group_id < 2)
		{
			return;
		}

		// Récupération des permissions du groupe.
		if (!is_array($i = self::getInfos($group_id)))
		{
			return $i;
		}
		$group_perms = $i['group_perms'];

		// Récupération des permissions d'accès aux catégories.
		$sql = 'SELECT cat_id, perm_list FROM {groups_permissions} WHERE group_id = ?';
		if (!DB::execute($sql, $group_id))
		{
			return FALSE;
		}
		$categories_perms = DB::fetchAll();

		// On identifie les modifications dans les permissions d'accès.
		$params_insert = [];
		$params_delete = [];
		foreach (['black', 'white'] as $perm_list)
		{
			// Liste actuelle en base de données.
			$categories_perms_list = [];
			foreach ($categories_perms as &$i)
			{
				if ($i['perm_list'] == $perm_list)
				{
					$categories_perms_list[] = $i['cat_id'];
				}
			}

			// Nouvelle liste.
			$list_update = ${$perm_list . 'list'};
			foreach ($list_update as $k => &$id)
			{
				// Il n'y a pas de catégorie avec un identifiant inférieur à 2.
				if ((!is_string($id) && !is_int($id)) || (int) $id < 2)
				{
					unset($list_update[$k]);
				}
			}
			$list_update =  array_unique($list_update);

			// Catégories ajoutées à la liste.
			if ($add = array_diff($list_update, $categories_perms_list))
			{
				foreach ($add as &$id)
				{
					$params_insert[] = [$group_id, $id, $perm_list];
				}
			}

			// Catégories retirées de la liste.
			if ($remove = array_diff($categories_perms_list, $list_update))
			{
				foreach ($remove as &$id)
				{
					$params_delete[] = [$group_id, $id, $perm_list];
				}
			}
		}

		// Liste noire ou liste blanche ?
		$new_list = FALSE;
		if ($list !== $group_perms['perm_list'])
		{
			$group_perms['perm_list'] = $new_list = $list;
		}

		// Aucun changement.
		if (!$params_insert && !$params_delete && !$new_list)
		{
			return;
		}

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

		// Catégories à ajouter aux listes.
		if ($params_insert)
		{
			$sql = 'INSERT INTO {groups_permissions}
					(group_id, cat_id, perm_list)
					VALUES (?, ?, ?)';
			if (!DB::execute($sql, $params_insert))
			{
				return FALSE;
			}
		}

		// Catégories à supprimer des listes.
		if ($params_delete)
		{
			$sql = 'DELETE
					  FROM {groups_permissions}
					 WHERE group_id = ?
					   AND cat_id = ?
					   AND perm_list = ?';
			if (!DB::execute($sql, $params_delete))
			{
				return FALSE;
			}
		}

		// Nouvelle liste de blocage.
		if ($new_list)
		{
			$sql = 'UPDATE {groups} SET group_perms = ? WHERE group_id = ?';
			$group_perms = Utility::jsonEncode($group_perms);
			if (!DB::execute($sql, [$group_perms, $group_id]))
			{
				return FALSE;
			}
		}

		// On valide la transaction.
		return DB::commitTransaction();
	}

	/**
	 * Modification des permissions d'accès aux fonctionnalités.
	 *
	 * @param int $group_id
	 * @param array $perms_update
	 *
	 * @return mixed
	 *   NULL si aucun changement effectué.
	 *   TRUE si la mise à jour a réussie.
	 *   FALSE en cas d'erreur.
	 */
	public static function changePermsFeatures(int $group_id, array $perms_update)
	{
		// Vérifications.
		if ($group_id < 2)
		{
			return;
		}

		// Récupération des permissions du groupe.
		if (!is_array($i = self::getInfos($group_id)))
		{
			return $i;
		}
		$group_perms = $i['group_perms'];
		$group_perms['admin'] = $i['group_admin'];

		// Permissions ne faisant pas partie du groupe 2.
		$group_2_no_perms =
		[
			'admin',
			'upload',
			'upload_mode',
			'upload_type'
		];

		// On détermine s'il y a eu des modifications.
		$new_perms = $group_perms;
		foreach ($new_perms as $p => &$v)
		{
			if (($group_id == 2 && in_array($p, $group_2_no_perms)) || $p == 'perm_list')
			{
				continue;
			}
			if ($p == 'upload_type')
			{
				if (in_array($perms_update[$p], ['all', 'images', 'videos']))
				{
					$v = $perms_update[$p];
				}
			}
			else
			{
				$v = (int) !empty($perms_update[$p]);
			}
		}

		// Pas de changement ?
		if ($group_perms == $new_perms)
		{
			return;
		}

		// Paramètres SQL.
		$group_admin = $new_perms['admin'];
		unset($new_perms['admin']);
		$params = [Utility::jsonEncode($new_perms), $group_admin, $group_id];

		// On enregistre les nouvelles permissions.
		$sql = 'UPDATE {groups} SET group_perms = ?, group_admin = ? WHERE group_id = ?';
		return DB::execute($sql, $params);
	}

	/**
	 * Création d'un nouveau groupe.
	 *
	 * @param array $infos_update
	 *
	 * @return mixed
	 *   int   : Identifiant du groupe créé.
	 *   array : Tableau détaillant l'erreur utilisateur.
	 *   FALSE : Si une erreur s'est produite.
	 */
	public static function create(array $infos_update)
	{
		// Vérification des informations fournies.
		$r = self::_checkInfos($infos_update);
		if (isset($r['error']))
		{
			return $r;
		}
		$params = &$r;

		// Paramètres SQL.
		$params['group_perms'] = Utility::jsonEncode(Config::USERS_GROUP_PERMS_DEFAULT);

		// Enregistrement du groupe.
		$sql = sprintf(
			'INSERT INTO {groups} (%s, group_crtdt) VALUES (%s NOW())',
			implode(', ', array_keys($params)),
			str_repeat('?, ', count($params))
		);
		$seq = ['table' => '{groups}', 'column' => 'group_id'];
		if (!DB::execute($sql, array_values($params), $seq))
		{
			return FALSE;
		}

		return DB::lastInsertId();
	}

	/**
	 * Supprime un groupe.
	 *
	 * @param int $group_id
	 *
	 * @return bool
	 *   TRUE si la suppression a réussie, FALSE sinon.
	 */
	public static function delete(int $group_id): bool
	{
		// Impossible de supprimer l'un des trois premiers groupes.
		if ($group_id < 4)
		{
			return FALSE;
		}

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

		// On place tous les utilisateurs du groupe à supprimer dans le groupe 3.
		if (!DB::execute('UPDATE {users} SET group_id = 3 WHERE group_id = ?', $group_id))
		{
			return FALSE;
		}

		// Suppression du groupe.
		if (!DB::execute('DELETE FROM {groups} WHERE group_id = ?', $group_id))
		{
			return FALSE;
		}

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

		return TRUE;
	}

	/**
	 * Édition des informations d'un groupe.
	 *
	 * @param array $infos_update
	 *
	 * @return mixed
	 *   TRUE : Édition réussie.
	 *   FALSE : Une erreur s'est produite.
	 *   NULL  : Aucun changement effectué.
	 *   array : Tableau détaillant l'erreur utilisateur.
	 */
	public static function edit(int $group_id, array $infos_update)
	{
		// Récupération des informations du groupe.
		if (!DB::execute('SELECT * FROM {groups} WHERE group_id = ?', $group_id))
		{
			return FALSE;
		}
		$group_infos = DB::fetchRow();

		// Vérification des informations fournies.
		$r = self::_checkInfos($infos_update, $group_infos);
		if (isset($r['error']))
		{
			return $r;
		}
		$params = &$r;
		if (!$params)
		{
			return;
		}

		// Paramètres.
		$columns = [];
		foreach ($params as $p => &$v)
		{
			$columns[] = $p . ' = ?';
		}
		$columns = implode(', ', $columns);
		$params = array_values($params);
		$params[] = $group_infos['group_id'];

		// Exécution de la requête.
		if (!DB::execute("UPDATE {groups} SET $columns WHERE group_id = ?", $params))
		{
			return FALSE;
		}

		return TRUE;
	}

	/**
	 * Retourne les informations d'un groupe.
	 *
	 * @param int $id
	 *   Identifiant du groupe.
	 *
	 * @return mixed
	 *   Retourne un tableau de toutes les informations,
	 *   NULL si le groupe n'existe pas,
	 *   ou FALSE en cas d'erreur.
	 */
	public static function getInfos(int $group_id)
	{
		$sql = 'SELECT *,
					   CASE WHEN group_id = 2
							THEN 0
							ELSE (SELECT COUNT(*)
									FROM {users} AS u
								   WHERE u.group_id = g.group_id)
							 END AS nb_users
				  FROM {groups} AS g
				 WHERE group_id = ?';
		if (!DB::execute($sql, $group_id))
		{
			return FALSE;
		}
		if (count($i = DB::fetchRow()) < 2)
		{
			return;
		}

		$i['group_perms'] = Utility::jsonDecode($i['group_perms']);
		if (!is_array($i['group_perms']))
		{
			trigger_error('No permissions found.', E_USER_WARNING);
			return FALSE;
		}

		return $i;
	}

	/**
	 * Modifie les permissions de groupes.
	 *
	 * Exemple :
	 *
	 *	Group::permChange([
	 *		'add' => ['permH' => '1'],
	 *		'delete' => ['permY', 'permZ'],
	 *		'rename' => ['permT' => 'permG']
	 *	]);
	 *
	 * @param array $change_perms
	 *
	 * @return bool
	 */
	public static function permChange(array $change_perms): bool
	{
		if (!DB::execute('SELECT group_id, group_perms FROM {groups}'))
		{
			return FALSE;
		}

		$params = [];
		foreach (DB::fetchAll() as &$cols)
		{
			$group_perms = Utility::jsonDecode($cols['group_perms']);
			if (is_array($group_perms))
			{
				if (isset($change_perms['delete']))
				{
					foreach ($change_perms['delete'] as &$perm)
					{
						unset($group_perms[$perm]);
					}
				}
				if (isset($change_perms['add']))
				{
					foreach ($change_perms['add'] as $perm => &$value)
					{
						$group_perms[$perm] = $value;
					}
				}
				if (isset($change_perms['rename']))
				{
					foreach ($change_perms['rename'] as $old => &$new)
					{
						Utility::arrayKeyRename($old, $new, $group_perms);
					}
				}
				ksort($group_perms);
				$params[] = [Utility::jsonEncode($group_perms), $cols['group_id']];
			}
		}

		return DB::execute('UPDATE {groups} SET group_perms = ? WHERE group_id = ?', $params);
	}



	/**
	 * Vérifie les données fournies ($infos_update)
	 * pour modifier les informations d'un groupe ($group_infos).
	 *
	 * @param array $infos_update
	 * @param array $group_infos
	 *
	 * @return array
	 *   Retourne un tableau de paramètres à utiliser
	 *   dans une requête de mise à jour de la base de données,
	 *   ou en cas d'erreur un tableau détaillant l'erreur.
	 */
	private static function _checkInfos(array &$infos_update, array &$group_infos = []): array
	{
		// S'agit-il d'un des trois premiers groupes ?
		$first_groups = isset($group_infos['group_id'])
			&& in_array($group_infos['group_id'], [1, 2, 3]);

		// On traite chaque paramètre.
		$group_params = ['name' => 1, 'title' => 1, 'desc' => 0];
		$sql_params = [];
		foreach ($group_params as $p => &$required)
		{
			$value = (!isset($infos_update[$p]) || Utility::isEmpty((string) $infos_update[$p]))
				? ''
				: Utility::trimAll((string) $infos_update[$p]);

			// L'information est-elle obligatoire (sauf premiers groupes).
			if ($value === '' && $required && !$first_groups)
			{
				return
				[
					'error' => 'required',
					'message' => L10N::getText('required_fields'),
					'param' => $p
				];
			}

			// Pour les trois premiers groupes, si les informations fournies
			// sont les mêmes que celles par défaut, inutiles de les enregistrer.
			if ($first_groups)
			{
				$method = 'getTextGroup' . ucfirst($p);
				if ($value == L10N::$method((int) $group_infos['group_id']))
				{
					$value = '';
				}
			}

			// Limitation de la longueur de la description.
			$value = $p == 'desc' ? substr($value, 0, 500) : $value;

			// Si l'information fournie est différente de celle enregistrée,
			// on l'ajoute au tableau des paramètres SQL.
			$col = "group_$p";
			if (!$group_infos || $value != $group_infos[$col])
			{
				$sql_params[$col] = $value;
			}
		}
		return $sql_params;
	}
}
?>