<?php
declare(strict_types = 1);

/**
 * Gestion des fichiers envoyés par le navigateur.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class Upload
{
	/**
	 * On vérifie que les données postées ne dépassent pas les limites
	 * serveur. Si c'est le cas, on génère un message d'avertissement.
	 *
	 * @return bool
	 */
	public static function contentLength(): bool
	{
		if (isset($_SERVER['CONTENT_LENGTH'])
		&& ((int) $_SERVER['CONTENT_LENGTH']) > Utility::getUploadMaxFilesize('post'))
		{
			Report::warning(__('Fichier trop lourd.'));
			return FALSE;
		}

		return TRUE;
	}

	/**
	 * Retourne un tableau informant d'une erreur lors de l'upload
	 * d'un ficher via $_FILES, ou un tableau vide si aucune erreur.
	 *
	 * @param array $file_infos
	 *   Informations du fichier dans la variable $_FILES.
	 *   Exemple : $_FILES['userfile']
	 *
	 * @return array
	 */
	public static function error(array &$file_infos): array
	{
		switch ($error = (int) $file_infos['error'])
		{
			// Aucune erreur.
			case 0 :
				return [];

			// Fichier trop lourd.
			case 1 :
			case 2 :
				return
				[
					'error' => $error,
					'text' => __('Poids du fichier trop grand.')
				];

			// Aucun fichier.
			case 4 :
				return
				[
					'error' => $error,
					'text' => __('Aucun fichier envoyé.')
				];

			// Erreur inconnue.
			default :
				return
				[
					'error' => $error,
					'text' => __('Erreur inconnue.')
				];
		}
	}

	/**
	 * Retourne le chemin du fichier $file_infos chargé par POST
	 * déplacé dans le répertoire $dir.
	 *
	 * @param array $file_infos
	 *   Informations du fichier dans la variable $_FILES.
	 *   Exemple : $_FILES['userfile']
	 * @param string $dir
	 *   Chemin absolu du répertoire où transférer le fichier.
	 *
	 * @return mixed
	 *   Retourne le chemin du fichier (string),
	 *   ou bien un tableau d'erreur (array).
	 */
	public static function filepath(array &$file_infos, string $dir)
	{
		// Gestion des erreurs d'envoi utilisateur.
		if ($error = self::error($file_infos))
		{
			return $error;
		}

		// On déplace l'image vers le répertoire destination.
		$filename = App::getValidFilename($file_infos['name']);
		$filepath = $dir . '/' . $filename;
		if (!move_uploaded_file($file_infos['tmp_name'], $filepath))
		{
			return
			[
				'error' => 0,
				'text' => __('Impossible de déplacer le fichier.')
			];
		}

		return $filepath;
	}

	/**
	 * Déplace des fichiers d'un répertoire temporaire
	 * vers le répertoire des albums.
	 *
	 * @param array $files
	 *   Fichiers à déplacer.
	 * @param string $temp_path
	 *   Chemin du répertoire temporaire.
	 * @param string $dest_path
	 *   Chemin du répertoire destination.
	 *
	 * @return array
	 */
	public static function itemMove(array $files, string $temp_path, string $dest_path): array
	{
		$temp_files = scandir($temp_path);
		$temp_path_original = $temp_path . '/original';
		foreach ($files as &$filename)
		{
			if (!in_array($filename, $temp_files))
			{
				continue;
			}

			$old_filepath = $temp_path . '/' . $filename;
			$old_filepath_original = $temp_path_original . '/' . $filename;

			// On modifie les noms de fichiers si nécessaire.
			while (TRUE)
			{
				$filename_temp = $filename;
				$filename = basename(File::getSecureFilename($filename, $dest_path));

				// Si changement du nom de fichier,
				// on modifie aussi le nom de fichier de l'original.
				if ($filename_temp !== $filename && is_dir($temp_path_original))
				{
					$filename_original = basename(File::getSecureFilename(
						$temp_path_original . '/' . $filename,
						$temp_path_original
					));
					if ($filename_original !== $filename)
					{
						$filename = $filename_original;
					}
					else
					{
						break;
					}
				}
				else
				{
					break;
				}
			}

			$new_filepath = $dest_path . '/' . $filename;
			$new_filepath_original = $temp_path_original . '/' . $filename;

			// On déplace le fichier.
			if (file_exists($old_filepath) && !file_exists($new_filepath))
			{
				File::rename($old_filepath, $new_filepath);
			}

			// On renomme l'original.
			if ($old_filepath_original != $new_filepath_original
			&& file_exists($old_filepath_original) && !file_exists($new_filepath_original))
			{
				File::rename($old_filepath_original, $new_filepath_original);
				if (file_exists($old_filepath_original . '.rotate'))
				{
					File::rename($old_filepath_original . '.rotate',
						$new_filepath_original . '.rotate');
				}
			}
		}

		return $files;
	}

	/**
	 * Redimensionnement d'une image.
	 *
	 * @param string $temp_file_path
	 *   Chemin du fichier qui se situe dans un répertoire temporaire.
	 *
	 * @return string
	 *   Retourne une chaine vide en cas de succès,
	 *   ou un message en cas d'erreur.
	 */
	public static function itemResize(string $temp_file_path): string
	{
		if (!Config::$params['upload_resize'])
		{
			return '';
		}

		// Informations du fichier.
		$i = Image::getTypeSize($temp_file_path);

		// Orientation.
		$metadata = new Metadata($temp_file_path);
		$orientation = (int) $metadata->getExifValue('orientation');
		$rotate = in_array($orientation, [5, 6, 7, 8]);
		$width = $rotate ? $i['height'] : $i['width'];
		$height = $rotate ? $i['width'] : $i['height'];

		// L'image dépasse-t-elle les limites ?
		$max_width = (int) Config::$params['upload_resize_maxwidth'];
		$max_height = (int) Config::$params['upload_resize_maxheight'];
		if ($width <= $max_width && $height <= $max_height)
		{
			return '';
		}

		// On copie le fichier original dans un nouveau répertoire
		// pour pouvoir récupérer les métadonnées lors du scan.
		$temp_dir = dirname($temp_file_path);
		$file_name = basename($temp_file_path);
		$temp_file_path_original = $temp_dir . '/original/' . $file_name;
		if (!is_dir($temp_dir . '/original'))
		{
			File::mkdir($temp_dir . '/original');
		}
		if (!File::copy($temp_file_path, $temp_file_path_original))
		{
			return __('Impossible de copier le fichier.');
		}

		// Création d'une image GD.
		$gd_image = GD::createFrom($temp_file_path);

		// On effectue une rotation si nécessaire.
		if (GD::orientation($gd_image, $orientation) && $rotate)
		{
			// On crée un fichier indiquant qu'une rotation impliquant
			// un inversement "largeur" - "hauteur" a été effectuée.
			File::putContents($temp_file_path_original . '.rotate');
		}

		// On redimensionne.
		$dst_size = Image::getResizedSize($width, $height, $max_width, $max_height);
		GD::resize($gd_image, 0, 0, $width, $height, 0, 0, $dst_size['w'], $dst_size['h']);

		// Enregistrement du fichier.
		if (!GD::file($gd_image, $temp_file_path, $i['filetype'],
		(int) Config::$params['upload_resize_quality']))
		{
			return __('Impossible de créer le fichier.');
		}

		return '';
	}

	/**
	 * Vérifie si un fichier envoyé par le navigateur respecte les limites
	 * de poids et de dimensions spécifiées dans les options de la galerie.
	 *
	 * @param string $filepath
	 *   Chemin du fichier.
	 *
	 * @return array
	 */
	public static function itemVerify(string $filepath): array
	{
		// Récupération des informations du fichier.
		$i = self::_infos($filepath);

		// Fichier vide ou aucun fichier.
		if (!$i['size'])
		{
			return
			[
				'error' => 'empty',
				'infos' => $i,
				'message' => __('Le fichier est vide.')
			];
		}

		$types_supported = Item::getTypesSupported();

		// Vérification de l'extension de fichier.
		if (!preg_match('`.\.(' . implode('|', $types_supported['ext']) . ')$`i', $filepath))
		{
			return
			[
				'error' => 'ext',
				'infos' => $i,
				'message' => __('Extension de fichier non accepté.')
			];
		}

		// Vérification du type de fichier selon
		// le type MIME ou l'extension de fichier.
		if (!$i['type'])
		{
			return
			[
				'error' => 'type',
				'infos' => $i,
				'message' => sprintf(__('Type de fichier non accepté (%s).'), $i['mime'])
			];
		}

		// Vérification du type MIME.
		if (function_exists('mime_content_type')
		&& !in_array($i['mime'], $types_supported['mime']))
		{
			return
			[
				'error' => 'mime',
				'infos' => $i,
				'message' => $i['type'] == 'video'
					? sprintf(__('Format de vidéo non valide (%s).'), $i['mime'])
					: sprintf(__('Format d\'image non valide (%s).'), $i['mime'])
			];
		}

		// Vérification du poids du fichier.
		$max_filesize = ((int) Config::$params['upload_maxfilesize']) * 1024 * 1024;
		if ($i['size'] > $max_filesize)
		{
			return
			[
				'error' => 'filesize',
				'infos' => $i,
				'message' => sprintf(
					__('Le poids du fichier ne doit pas dépasser %s.'),
					L10N::formatFilesize($max_filesize)
				)
			];
		}

		// Vérification des dimensions de l'image.
		if ($i['type'] == 'image')
		{
			if (!$i['width'] || !$i['height'])
			{
				return
				[
					'error' => 'novalid',
					'infos' => $i,
					'message' => __('Image non valide.')
				];
			}

			$max_width = (int) Config::$params['upload_maxwidth'];
			$max_height = (int) Config::$params['upload_maxheight'];
			if (($w = $i['width'] > $max_width)
			 || ($h = $i['height'] > $max_height))
			{
				return
				[
					'error' => $w ? 'width' : 'height',
					'infos' => $i,
					'message' => sprintf(
						__('L\'image doit avoir des dimensions de %s x %s pixels maximum.'),
						$max_width,
						$max_height
					)
				];
			}
		}

		return ['error' => NULL, 'infos' => $i, 'message' => NULL];
	}



	/**
	 * Retourne les informations d'un fichier.
	 *
	 * @param string $filepath
	 *   Chemin du fichier.
	 *
	 * @return array
	 */
	private static function _infos(string $filepath): array
	{
		$i = ['height' => 0, 'mime' => '', 'size' => 0, 'type' => '', 'width' => 0];

		if (!file_exists($filepath))
		{
			return $i;
		}

		// Type de fichier.
		$type_code = Item::getTypeCode($filepath, $i['mime']);
		if ($type_code == Item::TYPE_AVIF && !function_exists('imageavif'))
		{
			$i['type'] = '';
		}
		else if (in_array($type_code, Item::IMAGE_TYPES))
		{
			$i['type'] = 'image';
		}
		else if (in_array($type_code, Item::VIDEO_TYPES))
		{
			$i['type'] = 'video';
		}

		// Poids du fichier.
		$i['size'] = filesize($filepath);

		// Dimensions des images.
		if ($i['type'] == 'image' && $img = Image::getTypeSize($filepath))
		{
			$i['width'] = $img['width'];
			$i['height'] = $img['height'];
		}

		return $i;
	}
}
?>