<?php
declare(strict_types = 1);

/**
 * Gestionnaire de fichiers.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class File
{
	/**
	 * Copie un fichier.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 * @param string $f_new
	 *   Nouveau chemin du fichier.
	 *
	 * @return bool
	 */
	public static function copy(string $f, string $f_new): bool
	{
		self::makeWritable(dirname($f_new));

		return copy($f, $f_new);
	}

	/**
	 * Retourne le contenu d'un fichier.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 *
	 * @return mixed
	 *   FALSE en cas d'erreur.
	 */
	public static function getContents(string $f)
	{
		return file_get_contents($f);
	}

	/**
	 * Retourne le type MIME d'un fichier.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 *
	 * @return string
	 */
	public static function getMimeType(string $f): string
	{
		if (function_exists('mime_content_type'))
		{
			return (string) mime_content_type($f);
		}

		if (function_exists('getimagesize') && is_array($i = getimagesize($f)))
		{
			return (string) ($i['mime'] ?? '');
		}

		return '';
	}

	/**
	 * Retourne le nom de fichier ou de répertoire $f avec un nombre
	 * à la fin incrémenté autant de fois que nécessaire si un fichier
	 * de même nom existe déjà dans le répertoire $dir ou le tableau $files.
	 *
	 * Exemple : si $f='file.jpg' et que $dir.'/'.$f existe déjà, alors
	 * la méthode retournera 'file_2.jpg'. Si 'file_2.jpg' existe aussi,
	 * alors on retourne 'file_3.jpg', etc.
	 *
	 * @param string $f
	 *   Chemin du fichier ou du répertoire.
	 * @param string $dir
	 *   Répertoire de fichiers.
	 * @param array $files
	 *   Tableau de fichiers.
	 *
	 * @return string
	 */
	public static function getSecureFilename(string $f, string $dir, array $files = []): string
	{
		$new_f = $f;
		$n = 2;
		while (file_exists($dir . '/' . $new_f) || in_array($new_f, $files))
		{
			$new_f = (is_dir($dir . '/' . $new_f))
				? $f . '_' . $n
				: preg_replace('`^(.+)\.([^\.]+)$`', '\1_' . $n . '.\2', $f);
			$n++;
		}
		return $new_f;
	}

	/**
	 * Détermine si un fichier est un GIF animé.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 *
	 * @return bool
	 */
	public static function isAnimatedGIF(string $f): bool
	{
		return is_file($f)
			&& ($content = self::getContents($f))
			&& preg_match('`(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}`s', $content);
	}

	/**
	 * Détermine si un fichier est un PNG animé.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 *
	 * @return bool
	 */
	public static function isAnimatedPNG(string $f): bool
	{
		if (!is_file($f) || !$handle = fopen($f, 'r'))
		{
			return FALSE;
		}
		$previous_data = '';
		while (!feof($handle))
		{
			if (!$data = fread($handle, 1024))
			{
				return FALSE;
			}
			if (strpos($data, 'acTL') !== FALSE
			 || strpos($previous_data . $data, 'acTL') !== FALSE)
			{
				return TRUE;
			}
			if (strpos($data, 'IDAT') !== FALSE
			 || strpos($previous_data . $data, 'IDAT') !== FALSE)
			{
				return FALSE;
			}
			$previous_data = $data;
		}
		fclose($handle);

		return FALSE;
	}

	/**
	 * Détermine si un fichier est un WEBP animé.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 *
	 * @return bool
	 */
	public static function isAnimatedWEBP(string $f): bool
	{
		return is_file($f)
			&& ($content = self::getContents($f))
			&& strpos($content, 'ANMF') !== FALSE;
	}

	/**
	 * Rend un fichier ou un répertoire accessible en écriture.
	 *
	 * @param string $f
	 *   Chemin du fichier ou du répertoire.
	 *
	 * @return bool
	 */
	public static function makeWritable(string $f): bool
	{
		if (is_writable($f))
		{
			return TRUE;
		}

		chmod($f, is_dir($f) ? 0775 : 0664);
		return is_writable($f);
	}

	/**
	 * Crée un répertoire.
	 *
	 * @param string $f
	 *   Chemin du répertoire.
	 *
	 * @return bool
	 */
	public static function mkdir(string $f): bool
	{
		self::makeWritable(dirname($f));

		return mkdir($f, 0775);
	}

	/**
	 * Écrit un contenu dans un fichier.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 * @param mixed $data
	 *   Contenu à écrire dans le fichier.
	 * @param int $flags
	 *
	 * @return mixed
	 *   FALSE en cas d'erreur.
	 */
	public static function putContents(string $f, $data = '', int $flags = 0)
	{
		self::makeWritable(dirname($f));

		return file_put_contents($f, $data, $flags);
	}

	/**
	 * Renomme un fichier ou un répertoire.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 * @param string $f_new
	 *   Nouveau chemin du fichier.
	 *
	 * @return bool
	 */
	public static function rename(string $f, string $f_new): bool
	{
		self::makeWritable(dirname($f_new));
		self::makeWritable($f);

		return rename($f, $f_new);
	}

	/**
	 * Supprime un répertoire et/ou le contenu d'un répertoire.
	 *
	 * @param string $f
	 *   Chemin du répertoire.
	 * @param bool $f_delete
	 *   Doit-on supprimer le répertoire $f ?
	 *
	 * @return bool
	 */
	public static function rmdir(string $f, bool $f_delete = TRUE): bool
	{
		if (!self::makeWritable($f))
		{
			return FALSE;
		}

		if ($res = opendir($f))
		{
			while ($ent = readdir($res))
			{
				if ($ent == '.' || $ent == '..')
				{
					continue;
				}

				$f_sub = $f . '/' . $ent;

				if (is_dir($f_sub))
				{
					if (!self::rmdir($f_sub))
					{
						return FALSE;
					}
				}
				else if (is_file($f_sub))
				{
					if (!self::unlink($f_sub))
					{
						return FALSE;
					}
				}
			}
			closedir($res);
		}

		return $f_delete ? rmdir($f) : TRUE;
	}

	/**
	 * Change la date de dernière modification d'un fichier.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 * @param int $time
	 *   Date de modification souhaitée.
	 *
	 * @return bool
	 */
	public static function touch(string $f, int $time = -1): bool
	{
		self::makeWritable(dirname($f));
		self::makeWritable($f);

		return touch($f, $time === -1 ? time() : $time);
	}

	/**
	 * Supprime un fichier.
	 *
	 * @param string $f
	 *   Chemin du fichier.
	 *
	 * @return bool
	 */
	public static function unlink(string $f): bool
	{
		if (!self::makeWritable(dirname($f))
		 || !self::makeWritable($f))
		{
			return FALSE;
		}

		return unlink($f);
	}
}
?>