Transfert de fichiers (upload multiple)

En matière de téléchargement la langue française n'est pas assez versatile : seul le mot téléchargement existe, quel que soit le sens. En anglais, upload (vers le serveur) dont il est question ici s'oppose à download (vers le client). Ci-après rien de bien nouveau sous le soleil, si ce n'est la gestion de multiples fichiers qui, à mon grand étonnement, n'a pas beaucoup de succès sur le net.

Formulaire

D'abord le principe de base du formulaire. La balise form reçoit la méthode d'encodage des fichiers enctype='multipart/form-data' qui indique l'utilisation d'un champ de type file. Ce dernier accepte la forme simple name='fichier' mais aussi la forme tableau name='fichier[]' comme l'ensemble des éléments de formulaire, d'ailleurs. Les index du tableau s'incrémentent d'eux même. Un menu ou un champ permet de choisir, au préalable, le nombre de fichiers à télécharger (une fonction javascript pourrait être implémentée afin de rajouter des champs à la volée).

NB : la directive MAX_FILE_SIZE permet d'éviter l'upload de trop gros fichiers, ce qui pourrait mettre le script en danger (timeout). En cas d'erreur, le script l'interprète comme un type incorrect (puisque le fichier a été ignoré par le serveur) sauf si l'on traite l'erreur avec $_FILES['lefichier']['error'].

Traitement

Tout se fait à travers la lecture du tableau multi-dimensionnel $_FILES. En effet, $_FILES['lefichier'] contient l'index de chaque fichier, lui-même doté des informations propres à ce dernier. Le script traite successivement name, error, size, type et tmp_name. Rendu à sa plus simple expression, il permet juste de renommer le nom de fichier à travers $_POST['lenom'], s'il ce dernier est conforme à $nametype (traitement simplifié là aussi).

Le script

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Upload</title>
<meta name="author" content="Pierre Pesty">
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body>
<?
/* variables à modifier */
$taillemax = 512000; // taille max d'un fichier (multiple de 1024)
$filetype = "/jpeg|gif/i"; // types de fichiers acceptés, séparés par |
$nametype = "/\.jpeg|\.jpg|\.gif/i"; // extensions correspondantes
$rep = "upload/"; // répertoire de destination
$maxfichier = 10; // nombre maximal de fichiers
/* fin des modifications */

// fichier courant (URI absolue) : formulaire récursif
$PHP_SELF = basename($_SERVER['PHP_SELF']);

if($_POST) {
	$msg = array(); // message
	$fichier = $_FILES['lefichier']; // simplication du tableau $_FILES
	for($i=0; $i<count($fichier['name']); $i++) {
		// nom du fichier original = nom par défaut
		$nom = $fichier['name'][$i];
		// test existence fichier
		if(!strlen($nom)) {
			$msg[] = "Aucun fichier !";
			continue;
		}
		// si un nouveau nom est renseigné (avec extension correcte)
		if(preg_match($nametype, $_POST['lenom'][$i]))
			$nom = $_POST['lenom'][$i];
		// répertoire de destination
		$destination = $rep.$nom;
		// test erreur (PHP 4.3)
		if($fichier['error'][$i]) {
			switch($fichier['error'][$i]) {
				// dépassement de upload_max_filesize dans php.ini
				case UPLOAD_ERR_INI_SIZE:
				  $msg[] = "Fichier trop volumineux !"; break;
				// dépassement de MAX_FILE_SIZE dans le formulaire
				case UPLOAD_ERR_FORM_SIZE:
				  $msg[] = "Fichier trop volumineux (supérieur à ".(INT)($taillemax/1024)." Mo)"; break;
				// autres erreurs
				default:
				  $msg[] = "Impossible d'uploader le fichier !";
			}
		}
		// test taille fichier
		elseif($fichier['size'][$i] > $taillemax)
			$msg[] = "Fichier $nom trop volumineux : ".$fichier['size'][$i];
		// test type fichier
		elseif(!preg_match($filetype, $fichier['type'][$i]))
			$msg[] = "Fichier $nom de type incorrect : ".$fichier['type'][$i];
		// test upload sur serveur (rep. temporaire)
		elseif(!@is_uploaded_file($fichier['tmp_name'][$i]))
			$msg[] = "Impossible d'uploader $nom";
		// test transfert du serveur au répertoire
		elseif(!@move_uploaded_file($fichier['tmp_name'][$i], $destination))
			$msg[] = "Problème de transfert avec $nom";
		else
			$msg[] = "Fichier <b>$nom</b> téléchargé avec succès !";
	}
	// affichage confirmation
	for($i=0; $i<=count($msg); $i++)
		echo "<p>$msg[$i]</p>";
}

// 1 fichier par défaut (ou supérieur à $maxfichier)
$upload = (isset($_REQUEST['upload']) && $_REQUEST['upload'] <= $maxfichier) ? $_REQUEST['upload'] : 1;

// choix du nombre $upload de fichier(s)
echo "<form action='$PHP_SELF' method='post'>\n";
echo "Quantité <select name='upload' onChange=\"window.open(this.options[this.selectedIndex].value,'_self')\">\n";
for($i=1; $i<=$maxfichier; $i++) {
	echo "<option value='$PHP_SELF?upload=$i'";
	if($i == $upload) echo " selected";
	echo ">$i\n";
}
echo "</select>\n";
echo "<input name='upload' value='$upload' size='3'>\n";
echo "<input type='submit' value='Modifier'></form>\n";

// le formulaire
echo "<form action='$PHP_SELF' enctype='multipart/form-data' method='post'>\n";
// boucle selon nombre de fichiers $upload
for($i=1; $i<=$upload; $i++) {
	echo "<p>Nom $i <input name='lenom[]'>\n";
	echo "<input type='hidden' name='MAX_FILE_SIZE' value='$taillemax'>";
	echo "Fichier <input type='file' name='lefichier[]'></p>\n";
}
?>
<input type='submit' value='Envoyer'>
</form>

</body>
</html>

Base de données

Dans le cas d'un traitement via une base de données, il s'agira d'affecter un nom automatisé aux fichiers (ex. img_1.jpg, img_2.jpg, etc.) en reportant dans la bdd le nom entré via $_POST['lenom'] ; ce qui autorise alors une totale liberté en terme de syntaxe (donc aucune vérification syntaxique de $nom). Le nom correspondra simplement à l'id de la table, lui même associé à l'id de l'image. Afin de gérer cette automatisation, on récupère l'id après un INSERT par la fonction mysql_insert_id(). Le nom sera composé de $prefixe.$id.$extension. Si l'on veut traiter plusieurs types de fichiers, la variable $extension sera récupérée via $fichier['name'] grâce à l'index 1 de getimagesize().

Pas très souple tout ça !

Et oui, en dehors de ne pas avoir le droit à l'erreur pour le nombre de fichiers, de ne rien savoir du traitement de l'upload (aucun message de la part du serveur), le fait de ne pas pouvoir choisir plusieurs fichiers à la fois - en un clic - est des plus pénibles. En HTML point de salut ! Il faut se tourner vers l'Active-X ou, surtout, vers Java. A ce titre, l'applet JUpload est absolument remarquable. Je l'utilise avec bonheur dans plusieurs sites et son implémentation est très simple. Il s'interface avec divers langages dont, bien sûr, notre ami PHP. Le script ci-dessus peut lui être associé.

http://dev.ppan.net [Haut de page]