<?php
declare(strict_types = 1);

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

/**
 * Gestion des logs d'activité des utilisateurs.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class AdminLogs extends Admin
{
	/**
	 * Actions sur la sélection.
	 *
	 * @return void
	 */
	public static function actions(): void
	{
		if (isset($_POST['cancel']) || !isset($_POST['selection']))
		{
			return;
		}
		switch (self::_getSelectedIds($selected_ids))
		{
			case 'ban_ip' :
				$blacklist = $update = preg_split('`[\r\n]+`',
					Config::$params['blacklist_ips'], -1, PREG_SPLIT_NO_EMPTY);

				if ($selected_ids)
				{
					$sql = 'SELECT log_ip
							  FROM {users_logs}
							 WHERE log_id IN (' . DB::inInt($selected_ids) . ')
							   AND user_id = 2';
					if (!DB::execute($sql))
					{
						Report::error();
						break;
					}

					foreach (DB::fetchAll('log_ip', 'log_ip') as &$ip)
					{
						if (!in_array($ip, $update))
						{
							$update[] = $ip;
						}
					}
				}

				if ($blacklist === $update)
				{
					Report::info(__('Aucune IP n\'a été ajoutée à la liste noire.'));
					break;
				}

				$sql = 'UPDATE {config} SET conf_value = ? WHERE conf_name = "blacklist_ips"';
				if (!DB::execute($sql, implode("\n", $update)))
				{
					Report::error();
					break;
				}

				Report::success(($add = count($update) - count($blacklist)) > 1
					? sprintf(__('%s IP ont été ajoutées à la liste noire.'), $add)
					: __('1 IP a été ajoutée à la liste noire.'));
				break;

			case 'delete' :
				$count = 0;
				if ($selected_ids)
				{
					$sql = 'DELETE
							  FROM {users_logs}
							 WHERE log_id IN (' . DB::inInt($selected_ids) . ')';
					if (!DB::execute($sql))
					{
						Report::error();
						break;
					}
					$count = DB::rowCount();
				}
				if ($count < 1)
				{
					Report::info(__('Aucune entrée n\'a été supprimée.'));
				}
				else
				{
					Report::success($count > 1
						? sprintf(__('%s entrées ont été supprimées.'), $count)
						: __('1 entrée a été supprimée.'));
				}
				break;
		}
	}

	/**
	 * Retourne les paramètres correspondant à une action.
	 *
	 * @param string action
	 *
	 * @return array
	 */
	public static function getActionParams(string $action): array
	{
		static $list;
		if (!$list)
		{
			$list = self::getActionsList();
		}

		// Type d'action.
		$arr = explode('_', $action);
		if (in_array($arr[0], ['comment', 'contact', 'forgot',
		'login', 'new', 'password', 'register', 'vote']))
		{
			$action = $arr[0];
			if (in_array($arr[0], ['comment', 'login', 'new', 'vote']) && isset($arr[1]))
			{
				$action .= '_' . $arr[1];
			}
		}

		// Icône.
		switch ($action)
		{
			case 'avatar_change' :
			case 'avatar_delete' :
				$icon = 'avatar';
				break;

			case 'cat_add' :
				$icon = 'category_add';
				break;

			case 'comment_add' :
				$icon = 'comment';
				break;

			case 'contact' :
				$icon = 'mail';
				break;

			case 'favorites_add' :
			case 'favorites_remove' :
				$icon = 'favorite';
				break;

			case 'forgot' :
			case 'login_admin' :
			case 'login_gallery' :
			case 'logout_admin' :
			case 'logout_gallery' :
			case 'new_password' :
			case 'password' :
				$icon = 'password';
				break;

			case 'comment_delete' :
			case 'item_delete' :
				$icon = 'delete';
				break;

			case 'cat_edit' :
			case 'comment_edit' :
			case 'item_edit' :
				$icon = 'edit';
				break;

			case 'profile_change' :
				$icon = 'profile';
				break;

			case 'register' :
				$icon = 'user';
				break;

			case 'upload_files' :
				$icon = 'files_add';
				break;

			case 'vote_add' :
			case 'vote_change' :
			case 'vote_delete' :
				$icon = 'star';
				break;
		}

		return
		[
			'type' => str_replace('_', '-', $action),
			'text' => $list[$action] ?? $action,
			'icon' => $icon ?? ''
		];
	}

	/**
	 * Retourne la liste des différents types d'actions.
	 *
	 * @return array
	 */
	public static function getActionsList(): array
	{
		return
		[
			'avatar_change' => __('Changement de l\'avatar'),
			'avatar_delete' => __('Suppression de l\'avatar'),
			'cat_add' => __('Création d\'une catégorie'),
			'cat_edit' => __('Édition d\'une catégorie'),
			'comment_add' => __('Ajout d\'un commentaire'),
			'comment_delete' => __('Suppression d\'un commentaire'),
			'comment_edit' => __('Édition d\'un commentaire'),
			'contact' => __('Envoi d\'un courriel par la page contact'),
			'favorites_add' => __('Ajout d\'un fichier aux favoris'),
			'favorites_remove' => __('Retrait d\'un fichier des favoris'),
			'forgot' => __('Procédure pour l\'oubli du mot de passe'),
			'item_delete' => __('Suppression d\'un fichier'),
			'item_edit' => __('Édition d\'un fichier'),
			'login_admin' => __('Connexion à l\'administration'),
			'login_gallery' => __('Connexion à la galerie'),
			'logout_admin' => __('Déconnexion depuis l\'administration'),
			'logout_gallery' => __('Déconnexion depuis la galerie'),
			'new_password' => __('Réinitialisation du mot de passe'),
			'password' => __('Accès à une catégorie protégée par mot de passe'),
			'profile_change' => __('Modification du profil'),
			'register' => __('Inscription'),
			'upload_files' => __('Ajout de fichiers'),
			'vote_add' => __('Ajout d\'un vote'),
			'vote_change' => __('Modification d\'un vote'),
			'vote_delete' => __('Suppression d\'un vote')
		];
	}

	/**
	 * Formate les informations.
	 *
	 * @param array $i
	 *   Informations brutes.
	 *
	 * @return array
	 */
	public static function getFormatedInfos(array &$i): array
	{
		// Action.
		$action = self::getActionParams($i['log_action']);

		// Nom d'utilisateur.
		$user_name = __('Invité');
		$user_link = '';
		if ($i['user_id'] != 2)
		{
			$user_name = User::getNickname($i['user_login'], $i['user_nickname']);
			$user_link = App::getURL('user/' . $i['user_id']);
		}

		// Résultat.
		$result = preg_match('`(failure|rejected)`', $i['log_action']) ? 'rejected' : 'accepted';

		// Page.
		$page = '';
		$page_link = '';
		if ($i['log_page'] == 'ajax')
		{
			$page = __('Requête ajax');
		}
		else if (strstr('admin', $i['log_action']))
		{
			$page_link = App::getURLAdmin('login');
		}
		else
		{
			$page = $page_link = GALLERY_HOST . urldecode(App::getURLGallery($i['log_page']));
		}

		// Paramètres.
		$params = $i['log_post'] ? Utility::jsonDecode($i['log_post']) : '';

		// Cause du rejet.
		$rejected_cause = '';
		if (strstr($i['log_action'], 'rejected'))
		{
			switch (preg_replace('`^.+_rejected_(.*)$`', '$1', $i['log_action']))
			{
				case 'antiflood' :
					$rejected_cause = 'Antiflood';
					break;

				case 'blacklist_emails' :
					$rejected_cause = sprintf(__('Liste noire "%s"'), __('Adresses de courriel'));
					break;

				case 'blacklist_names' :
					$rejected_cause = sprintf(__('Liste noire "%s"'), __('Noms d\'utilisateur'));
					break;

				case 'blacklist_ips' :
					$rejected_cause = sprintf(__('Liste noire "%s"'), __('Adresses IP'));
					break;

				case 'blacklist_words' :
					$rejected_cause = sprintf(__('Liste noire "%s"'), __('Mots'));
					break;

				case 'deactivated' :
					$rejected_cause = __('Compte désactivé');
					break;

				case 'maxchars' :
					$rejected_cause = __('Nombre de caractères');
					break;

				case 'maxlines' :
					$rejected_cause = __('Nombre de lignes');
					break;

				case 'maxurls' :
					$rejected_cause = __('Nombre d\'URL');
					break;

				case 'password' :
					$rejected_cause = __('Mot de passe de validation');
					break;

				case 'whitelist_ips' :
					$rejected_cause = __('Liste blanche des adresses IP');
					break;

				default :
					if (substr($action['type'], 0, 5) == 'login')
					{
						$rejected_cause = __('Informations incorrectes');
					}
					if (substr($action['type'], 0, 8) == 'password')
					{
						$rejected_cause = __('Mot de passe incorrect');
					}
					break;
			}
		}

		return
		[
			'action_text' => $action['text'],
			'date' => $i['log_date'],
			'filter_action_link' => App::getURL('logs/action/' . $action['type']),
			'filter_date_link' => App::getURL('logs/date/' . substr($i['log_date'], 0, 10)),
			'filter_ip_link' => App::getURL('logs/ip/' . $i['log_ip']),
			'filter_result_link' => App::getURL('logs/result/' . $result),
			'filter_user_link' => App::getURL('logs/user/' . $i['user_id']),
			'icon' => $action['icon'],
			'id' => $i['log_id'],
			'ip' => $i['log_ip'],
			'page' => $page,
			'page_link' => $page_link,
			'params' => $params,
			'rejected_cause' => $rejected_cause,
			'rejected_match' => $i['log_match'],
			'result' => $result,
			'user_avatar_thumb_src' => Avatar::getURL(
				(int) $i['user_id'], (bool) $i['user_avatar'], TRUE
			),
			'user_name' => $user_name, 
			'user_link' => $user_link
		];
	}

	/**
	 * Récupération des entrées.
	 *
	 * @return void
	 */
	public static function getLogs(): void
	{
		Template::set('objects_count', 0);

		// Nombre d'entrées par page.
		$nb_per_page = Auth::$infos['user_prefs']['logs']['nb_per_page'];

		// Clause WHERE.
		self::_sqlWhere($sql_where, $params);

		// Utilisateur.
		if (isset($_GET['user_id']))
		{
			$sql_where = "($sql_where) AND ul.user_id = " . (int) $_GET['user_id'];
		}

		// Nombre d'entrées.
		if (!DB::execute("SELECT COUNT(*) FROM {users_logs} AS ul WHERE $sql_where", $params))
		{
			return;
		}
		Template::set('objects_count', $nb_logs = DB::fetchVal());

		// Nombre de pages.
		Template::set('nb_pages', ceil($nb_logs / $nb_per_page));
		$sql_limit_start = $nb_per_page * ($_GET['page'] - 1);

		// Critère de tri.
		$sql_order_by = Auth::$infos['user_prefs']['logs']['order_by_column'];
		$sql_order_by_order = Auth::$infos['user_prefs']['logs']['order_by_order'];

		// Récupération des entrées.
		$sql = "SELECT ul.*,
					   user_avatar,
					   user_login,
					   user_nickname
				  FROM {users_logs} AS ul
			 LEFT JOIN {users} AS u USING (user_id)
				 WHERE $sql_where
			  ORDER BY log_$sql_order_by $sql_order_by_order
			     LIMIT $sql_limit_start,$nb_per_page";
		if (!DB::execute($sql, $params))
		{
			return;
		}
		$logs = DB::fetchAll();
		if (!self::_objectsNoResult(count($logs)))
		{
			return;
		}

		// Formatage des données.
		$logs_formated = [];
		foreach ($logs as &$i)
		{
			$logs_formated[] = self::getFormatedInfos($i);
		}
		Template::set('logs', $logs_formated);
	}

	/**
	 * Récupère la liste de tous les utilisateurs ayant des entrées.
	 *
	 * @return void
	 */
	public static function getUsersList(): void
	{
		// Clause WHERE.
		self::_sqlWhere($sql_where, $params);

		// Clause ORDER BY.
		$sql_order_by = 'LOWER(user_login) ASC';
		SQL::nicknameOrderBy($sql_nickname, $sql_order_by);

		$sql = "SELECT user_id AS id,
					   user_login,
					   user_nickname,
					   $sql_nickname,
					   COUNT(ul.log_id) AS count
				  FROM {users_logs} AS ul
			 LEFT JOIN {users} AS u USING (user_id)
				 WHERE $sql_where
				   AND user_status != '-2'
			  GROUP BY user_id,
					   user_login,
					   user_nickname
			  ORDER BY $sql_order_by";
		if (!DB::execute($sql, $params))
		{
			return;
		}
		$users = DB::fetchAll('id');

		$get_link = function(int $user_id = 0)
		{
			$q = preg_replace('`^logs/user/\d+`', 'logs', $_GET['q_pageless']);

			if ($user_id)
			{
				$q = preg_replace('`^logs`', 'logs/user/' . $user_id, $q);
			}

			return App::getURL($q);
		};

		$list =
		[
			[
				'count' => array_sum(array_column($users, 'count')),
				'current' => !isset($_GET['user_id']),
				'id' => 0,
				'link' => $get_link(),
				'name' => '*' . __('Tous')
			]
		];
		if (isset($users[2]))
		{
			$list[] =
			[
				'count' => $users[2]['count'],
				'current' => isset($_GET['user_id']) && $_GET['user_id'] == 2,
				'id' => 2,
				'link' => $get_link(2),
				'name' => '*' . __('Invité')
			];
		}
		foreach ($users as &$i)
		{
			if ($i['id'] != 2)
			{
				$list[] =
				[
					'count' => $i['count'],
					'current' => isset($_GET['user_id']) && $_GET['user_id'] == $i['id'],
					'id' => $i['id'],
					'link' => $get_link((int) $i['id']),
					'name' => User::getNickname($i['user_login'], $i['user_nickname'])
				];
			}
		}
		Template::set('users', $list);

		// Fil d'Ariane.
		$name = __('Tous');
		if (isset($_GET['user_id']))
		{
			foreach (Template::$data['users'] as &$i)
			{
				if ($i['id'] == $_GET['user_id'])
				{
					$name = $i['id'] == 2 ? __('Invité') : $i['name'];
				}
			}
		}
		$breadcrumb =
		[
			[
				'current' => TRUE,
				'name' => $name,
				'url' => App::getURL($_GET['q_pageless'])
			]
		];
		Template::set('breadcrumb', $breadcrumb);
	}

	/**
	 * Informations de template pour le moteur de recherche.
	 *
	 * @param array $functions
	 *
	 * @return void
	 */
	public static function tplSearch(array &$functions): void
	{
		// Colonnes.
		$columns = $functions['columns'](
		[
			'log_ip' => TRUE
		]);

		// Action.
		$actions = [['value' => '*', 'text' => '*' . __('Toutes')]];
		foreach (self::getActionsList() as $value => &$text)
		{
			$actions[] =
			[
				'value' => $value,
				'text' => $text
			];
		}

		// Résultat.
		$result = $functions['select']('result',
		[
			[
				'value' => '*',
				'text' => '*' . __('Tous')
			],
			[
				'value' => 'accepted',
				'text' => __('Accepté')
			],
			[
				'value' => 'rejected',
				'text' => __('Rejeté')
			]
		]);

		Template::set('search', $functions['date']());
		Template::set('search',
		[
			'actions' => $functions['select']('action', $actions),
			'date' => self::$search->options['date'] ?? FALSE,
			'date_column' => self::$search->options['date_column'] ?? 'log_date',
			'columns' => $columns,
			'result' => $result
		]);
	}



	/**
	 * Clause WHERE d'une requête SQL.
	 *
	 * @param mixed $sql
	 * @param mixed $params
	 *
	 * @return void
	 */
	private static function _sqlWhere(&$sql, &$params): void
	{
		$sql = '1=1';
		$params = [];
		switch ($_GET['filter'] ?? '')
		{
			case 'action' :
				$sql = "log_action LIKE :filter";
				$params['filter'] = str_replace('-', '_', $_GET['filter_value']) . '%';
				break;

			case 'date' :
				$sql = 'log_date >= :date_start AND log_date <= :date_end';
				$params = [
					'date_start' => $_GET['filter_value'] . ' 00:00:00',
					'date_end' => $_GET['filter_value'] . ' 23:59:59'
				];
				break;

			case 'ip' :
				$sql = 'log_ip = :filter';
				$params['filter'] = $_GET['filter_value'];
				break;

			case 'result' :
				if ($_GET['filter_value'] == 'accepted')
				{
					$sql = 'log_action NOT LIKE "%rejected%" ';
					$sql .= 'AND log_action NOT LIKE "%failure%"';
				}
				else
				{
					$sql = 'log_action LIKE "%rejected%" OR log_action LIKE "%failure%"';
				}
				break;

			case 'search' :
				if (!$search = self::$search->sql())
				{
					App::redirect($_GET['q_filterless']);
				}
				$sql = $search['sql'];
				$params = $search['params'];
				break;
		}
	}
}
?>