Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Uploader une image d'un disque local sur un serveur, et éventuellement l'afficher dans le .swf

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Compatible Flash CS3. Cliquer pour en savoir plus sur les compatibilités.Compatible PHP. Cliquer pour en savoir plus sur les compatibilités.Par monz (Stéphane Monnet), le 01 octobre 2009

Offrir à l'internaute la possibilité de charger une image de son disque dur dans un .swf maison peut s'avérer intéressant. Ou qui veut se construire un petit back office en flash devra utiliser la classe FileReference.

Faut bien dire qu'au premier abord, elle est un peu rebutante. Si vous ouvrez une classe d'Upload, en général, ça parait compliqué (beaucoup d'écouteurs, pas mal d'étapes obligées…).

Je vais donc dans ce tuto tenter de décortiquer cela et vous montrer que, finalement, ce n'est pas si complexe.

Il vous faudra quand même :
  • un logiciel pour compiler de l'AS,
  • un éditeur pour du PHP (qui sera notre langage côté serveur),
  • et pour tester en local wamp (pour les Windows) fera l'affaire ou un serveur distant si vous en avez un.






VERSION HYPER BASIQUE : Pour comprendre le principe

Pour ceux qui ont flash, nous allons créer deux fichiers.


L'interface utilisateur

D'abord, un .fla. Dans ce .fla, pour aller vite, je prends un composant Button, je le pose sur ma scène. Je change son “label” en UPLOAD (maj+F7, pour l'inspecteur de composant) et je l'appelle “btnUp”.

tutoup1.jpg

Pourquoi un bouton me direz-vous ?

Et bien tout simplement parce que sans bouton vous ne pourrez rien faire à part afficher l'erreur 2176.

Error: Error #2176

Certaines actions, par exemple celles qui ouvrent une fenêtre contextuelle, ne peuvent être invoquées que par une interaction de l'utilisateur, telle qu'un clic de souris ou un appui sur une touche.

Voilà, réservons notre .fla pour l'instant. Nous aurons un peu de code à lui rajouter, mais trois fois rien (Pas plus de 6 lignes).



La classe de téléchargement

Passons à notre classe. Pas d'affolement, nous allons la faire, pour commencer, la plus simple possible. Donc, je crée un fichier .as, je l'appelle ImageUpload.as, je l'enregistre à côté de mon .fla.

Je commence classiquement par mes imports :

package  {
 
	import flash.events.Event; // pour récupérer la sélection de l'utilisateur
	import flash.net.FileReference; //notre classe FileReference
	import flash.net.URLRequest;//pour indiquer l'adresse de notre script php

On continue dans le classico classique :

	public class ImageUpload {
 
	private var scriptURL:URLRequest;//l'adresse de notre script
	private var file:FileReference;//notre objet FileReference
 
		public function ImageUpload(dir:String)
		{
			//notre constructeur, vide pour commencer.      
		}

A partir de là, nous allons utiliser les méthodes essentielles pour l'upload de la classe FileReference :

browse(typeFilter:Array = null):Boolean
upload(request:URLRequest, uploadDataFieldName:String = "Filedata", testUpload:Boolean = false):void 

Commençons par browse. Comme vous le voyez, cette méthode peut se passer d'arguments. Donc, dans une fonction init(), écrivons tout simplement :

		public function init():void
		{
			//je crée mon objet FileReference
			file = new FileReference();          
 
			file.browse();
		}

La méthode browse sur mon objet file va déclencher l'ouverture d'une fenêtre dans laquelle l'utilisateur va pouvoir parcourir ses fichiers locaux et en choisir un. En l'état, il me manque l'écouteur qui va récupérer ce choix. J'ajoute donc un écouteur à mon objet file et j'écoute l'événement adapté : SELECT.

		public function init():void
		{
			file = new FileReference();
			file.addEventListener(Event.SELECT, onSelectImage);
			file.browse();
		}

Avant d'écrire ma function onSelectImage, j'ajoute à mon init() l'adresse de mon script PHP.

		public function init():void
		{
			scriptURL = new URLRequest();
			scriptURL.url = "http://localhost/upload.php";// si je teste en local
			file = new FileReference();
			file.addEventListener(Event.SELECT, onSelectImage);
			file.browse();
		}

Donc, j'ai une fenêtre qui s'ouvre, l'utilisateur peut choisir un fichier. Quand il en a choisi un (en double-cliquant sur un fichier ou en sélectionnant un fichier puis le classique bouton “ouvrir”), l'événement SELECT est envoyé. Je le récupère dans ma fonction onSelectImage. Elle comprend deux lignes :

  • la première pour remplir mon objet file (avec les infos du fichier choisi)
  • la seconde pour appeler sur mon objet file la méthode upload. Je ne lui passe ici que l'adresse du script PHP (sous la forme de mon URLRequest).
		private function onSelectImage(event:Event):void {
			file = FileReference(event.target);
			file.upload(scriptURL);
		}

Voilà. Ma classe est terminée. Je vous avais annoncé 6 lignes de codes à ajouter dans notre .fla. Allons-y.


Lancer l'opération au clic sur le bouton

Elles consistent en des manoeuvres basiques : instancier notre classe et écouter notre bouton “UPLOAD” qui, vous l'avez deviné, appellera notre fonction init();

var monUp:ImageUpload = new ImageUpload();
 
btnUP.addEventListener(MouseEvent.CLICK, lancerUpload);
 
function lancerUpload(e:MouseEvent):void
{
    monUp.init();
}

Une classe de moins de 30 lignes de code, un .fla élémentaire, nous voilà parti pour notre premier upload depuis un .swf. Enfin presque. Il nous faut encore écrire un petit script PHP. Et le placer sur notre serveur (local ou distant).



Le script PHP

Je vous propose le minimum pour commencer :

<?php
$extension_fichier = strtolower( array_pop( explode( ".", $_FILES['Filedata']['name'] ) ) ) ;
 
move_uploaded_file( $_FILES['Filedata']['tmp_name'], 'imageUp.'.$extension_fichier)
 
?>

$_FILES me permet de récupérer l'élément uploadé. Il prend plusieurs paramètres qui me permettent par exemple de connaître le nom du fichier : $_FILES['Filedata']['name']

La première ligne nous permet de récupérer l'extension du fichier choisi. Elle se décompose ainsi : on sépare en deux éléments d'un tableau le nom du fichier uploadé ( $_FILES['Filedata']['name'] ), en prenant comme point de séparation le point (qui précède toutes extensions).

explode( ".", $_FILES['Filedata']['name'] ) // -En AS3, ce serait split()-
Exemple : image1.jpg
Après mon explode, j'ai [0] = image1 et [1]=jpg

Ne voulant conserver que l'extension, je récupère la dernière partie de ce tableau avec la méthode array_pop, qui fait sauter le dernier élément d'un tableau et le retourne. -comme en AS3, pop()-

array_pop( explode( ".", $_FILES['Filedata']['name'] ) )
Exemple : mon array_pop retourne donc [1]= jpg.

La seconde ligne déplace le fichier temporairement chargé ( $_FILES['Filedata']['tmp_name'] ) vers son emplacement définitif. Ici, je renomme le fichier : imageUp+extension.

Ces 4 lignes de php suffisent pour le moment. Nous pouvons tester notre .swf. Je clique sur le bouton UPLOAD, je choisis une image, je fais “ouvrir” et si je vais voir dans mon dossier serveur “www” (chez wamp), j'ai bien mon image.

Mon upload fonctionne. Mais comme vous l'avez noté, je ne gère pas grand chose, ni les erreurs qui peuvent survenir, ni le type de fichiers que l'utilisateur peut uploader, ni l'emplacement où j'enregistre mon image. Essayons d'améliorer notre classe ImageUpload.




PREMIERE AMELIORATION : Ecouter la fin de l'upload

La première chose que nous allons faire est d'écouter certains des événements distribués par la classe FileReference.

Pour un aperçu complet des événements distribués : voir le tutoriel de t-servi.com

Ici, j'ai retenu Event.OPEN, Event.COMPLETE, Event.CANCEL et le plus important que nous traiterons à part : DataEvent.UPLOAD_COMPLETE_DATA.

Comme nous avons un .fla et un .as, le plus sage pour les faire s'entendre, c'est de dispatcher depuis la classe ImageUpload des événements persos qui vont reprendre les trois étapes qui nous intéressent : début de l'upload, fin de l'upload, annulation de l'utilisateur.



Ecoute et diffusion des évènements du téléchargement par la classe

Je reprends donc ma classe et je crée trois var static qui me serviront à gérer ces événements.

package  {
 
	//j'ai toujours mes trois imports
	import flash.events.Event;
	import flash.net.FileReference;
	import flash.net.URLRequest;
 
	//je rajoute le dispatcher
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
 
 
	//J'étends ma classe afin qu'elle puisse diffuser des événements    
	public class ImageUpload extends EventDispatcher {
 
 
		//mes trois variables
		static public var START_UP:String = "Upload démarre";
		static public var END_UP:String = "Fin de l'upload";
		static public var ANNUL_UP:String = "Cancel de l'utilisateur";

Dans ma fonction init(), j'enregistre mes nouveaux écouteurs sur mon objet file.

		public function init():void
		{
			scriptURL = new URLRequest();
			scriptURL.url = "http://localhost/upload.php";
			file = new FileReference();
			file.addEventListener(Event.SELECT, onSelectImage);
 
			//nouveaux écouteurs           
			file.addEventListener(Event.OPEN, onDebutUpload);
			file.addEventListener(Event.COMPLETE, onUploadComplet);
			file.addEventListener(Event..CANCEL, onAnnul);
 
			file.browse();
		}

Mes écouteurs étant enregistrés, il me faut créer les fonctions associées. Elles sont toutes les trois sur le même modèle. Elles relaient les événements diffusés par mon objet file.

		private function onAnnul(event:Event):void {
			dispatchEvent(new Event(ImageUpload.ANNUL_UP));
		}
 
		private function onUploadComplet(event:Event):void {
			dispatchEvent(new Event(ImageUpload.END_UP));
		}
 
		private function onDebutUpload(event:Event):void {
			dispatchEvent(new Event(ImageUpload.START_UP));
		}



Ecouter les évènements dans l'interface

Il ne reste plus qu'à écouter ces événements persos dans notre .fla et à les exploiter. Nous allons commencer simplement en créant un petit champs texte dans lequel apparaîtront les différentes étapes de notre Upload.

tutoup2.jpg

Pour alimenter ce TextField appelé info, nous enregistrons sur notre instance de classe ImageUpload les écouteurs correspondants :

monUp.addEventListener(ImageUpload.START_UP, onStartUp);
monUp.addEventListener(ImageUpload.END_UP, onEndUp);
monUp.addEventListener(ImageUpload.ANNUL_UP, onAnnul);
 
function onStartUp(e:Event):void
{
    info.text ="Démarrage de l'upload";
}
 
function onEndUp(e:Event):void
{
    info.text ="Image chargée sur le serveur";
}
 
function onAnnul(e:Event):void
{
    info.text ="Upload annulé par l'utilisateur.";
}

Voilà, cela vous donne le principe. Après, il serait bien évidemment opportun d'écouter l'événement IOErrorEvent.IO_ERROR et pour les upload un peu lourds de créer une petite barre de chargement calqué sur l'événement ProgressEvent.PROGRESS.



TRES IMPORTANT

L'événement COMPLETE signifie seulement que les données sont bien TOUTES arrivées à l'adresse du script PHP.

Si je change mon script PHP de la sorte :

<?php
/* $extension_fichier = strtolower( array_pop( explode( ".", $_FILES['Filedata']['name'] ) ) ) ;
move_uploaded_file( $_FILES['Filedata']['tmp_name'], 'imageUp.'.$extension_fichier))
echo true;
*/
?>

C'est à dire, que mon script ne fait rien, tout étant commenté, flash va envoyer les données et si elles atteignent leur but (le serveur), l'événement COMPLETE sera diffusé. Pour autant, mon image ne sera pas copié sur le serveur.

Si vous voulez avoir un retour sur la bonne exécution de votre script PHP, vous devez :


Dans le swf

Ecouter l'événement : DataEvent.UPLOAD_COMPLETE_DATA.

Je rajoute donc une var static à mon ImageUpload.as

		static public var SERVEUR_RETOUR:String="Le serveur a répondu";

et une autre pour stocker le message du serveur et pouvoir ainsi le passer à mon .fla.

		private var _messServeur:String;

Je place un écouteur sur file ;

			file.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA,rapportChargement);

Ma fonction qui récupère le message du serveur sera :

		private function rapportChargement(event:DataEvent):void {
			_messServeur=String(event.data);
			dispatchEvent(new Event(ImageUpload.SERVEUR_RETOUR));
		}
 
		//J'en profite pour créer mon getter, qui permettra à mon .fla de récupérer le message
		public function get messServeur():String
		{
		    return _messServeur;
		}

Et dans mon .fla, maintenant nous avons l'habitude, on ajoute un écouteur sur notre instance de classe ImageUpload

monUp.addEventListener(ImageUpload.SERVEUR_RETOUR, onReponseServeur);

Et la fonction correspondante, qui nous permet de passer à notre TextField info le message reçu du serveur :

function onReponseServeur(e:Event):void
{
    info.text = monUp.messServeur;
}


Dans le script PHP

Jusqu'ici plus que minimaliste, j'ajoute une petite condition pour avoir un retour de la bonne exécution de mon script.

Exemple :

<?php
$extension_fichier = strtolower( array_pop( explode( ".", $_FILES['Filedata']['name'] ) ) ) ;
 
$result  =  move_uploaded_file( $_FILES['Filedata']['tmp_name'], 'imageUp.'.$extension_fichier);
if ($result)
{
    $message =  "image uploadée";
}
else
{
    $message = "pb d'écriture";
}
 
echo $message;
?>

Avec cet écouteur (DataEvent.UPLOAD_COMPLETE_DATA), je suis assuré que mon image a bien été copiée sur le serveur et que je peux donc l'utiliser.




DEUXIEME AMELIORATION : Autoriser le chargement d'un certain types de fichiers

Cette amélioration se fait très simplement avec la classe FileFilter.

Pas besoin de long discours, il suffit d'ajouter deux lignes à notre classe :

	import flash.net.FileFilter;
		// je crée une var const de type FileFilter que je remplis derechef
		// avec les types d'images que j'autorise dans le browser.
		private const imagesFilter:FileFilter = new FileFilter("Images", "*.jpg;*.gif;*.png");

Et, dernière modification, lorsque j'appelle la méthode browse, je lui passe cette fois-ci un argument (sous forme de tableau), mon FileFilter.

			file.browse([imagesFilter]);

C'est tout.

Connaissant la vulnérabilité des .swf, il est toujours judicieux de doubler cette mesure dans le php. Afin qu'il ne soit pas uploadé n'importe quel fichier sur votre serveur.

<?php
$type = array ( 'jpg', 'gif', 'png') ; // extensions de fichiers images autorisées
 
//si nous recevons bien quelque chose
if ($_FILES['Filedata'])
    {
        $extension_fichier = strtolower( array_pop( explode( ".", $_FILES['Filedata']['name'] ) ) ) ;
 
        // si cette extension ne se trouve pas dans notre tableau $type on arrête tout.
        if( !in_array( $extension_fichier, $type ) ) exit;
 
 
$result  =  move_uploaded_file( $_FILES['Filedata']['tmp_name'], 'imageUp.'.$extension_fichier);
if ($result)
{
    $message =  "image uploadée";
}
else
{
    $message = "pb d'écriture";
}
 
echo $message;
}
 
?>




TROISIEME AMELIORATION : Je choisis le dossier dans lequel j'upload la photo

L'une des possibilités offertes par la classe FileReference, est de passer non seulement notre upload, mais dans le même temps des variables que notre script PHP pourra récupérer en POST. Nous allons utiliser cette possibilité pour passer à PHP le nom d'un dossier dans lequel nous voudrions voir stocker notre photo.



Le swf

Pour cela, je vais changer un peu le constructeur de ma classe et lui fournir un argument :

		private var dest:String; //dossier de destination
 
		public function ImageUpload(dir:String) {
 
			dest = dir;
 
		}

Ce dossier de destination, nous allons donc le passer via des variables stockés dans notre URLRequest.

Vous vous souvenez de notre adresse de destination, de la forme :

			scriptURL = new URLRequest();
			scriptURL.url = "http://localhost/upload.php";

Je lui ajoute une méthode de transmissions des variables:

			scriptURL.method = URLRequestMethod.POST;

Et je lui passe ma variable :

			var variables:URLVariables = new URLVariables();
			variables.doss = dest;
			scriptURL.data = variables;

Il n'y a rien de plus à modifier pour l'.as (à part peut-être un ou deux import que je n'aurais pas mentionnés), la ligne:

			file.upload(scriptURL);

étant toujours valide.

Dans mon .fla, je passe un nom de dossier dans mon constructeur. Je remplace:

var monUp:ImageUpload = new ImageUpload();

par:

var monUp:ImageUpload = new ImageUpload("images");



Le script PHP

Il nous reste quelques modifications dans notre PHP, afin que cette nouvelle donnée soit exploitée. J'ai commenté les principales modifications. Il s'agit juste de récupérer notre variables.doss ( $_POST[“doss”] ) et de tester si le dossier existe et s'il n'existe pas de le créer. Enfin, cette fois nous conservons le nom original de l'image ( $_FILES['Filedata']['name'] ).

<?php
$type = array ( 'jpg', 'gif', 'png') ; // extensions de fichiers images autorisées
 
//si nous recevons bien quelque chose
if ($_FILES['Filedata'])
{
	$extension_fichier = strtolower( array_pop( explode( ".", $_FILES['Filedata']['name'] ) ) ) ;
 
        // si cette extension ne se trouve pas dans notre tableau $type
        if( !in_array( $extension_fichier, $type ) ) exit; // on stoppe le traitement
 
 
	// vérification de l'existence du dossier
	$fp = is_dir($_POST["doss"]);
        if ($fp)
        {
		//on copie dans le dossier
		traceResult($extension_fichier);
		//si le dossier n'existe pas
        }
        else
        {
		$cp=mkdir($_POST["doss"]);//on crée le dossier
		traceResult($extension_fichier);
	}
 
}
 
function traceResult($ext)
{
	$result  =  move_uploaded_file( $_FILES['Filedata']['tmp_name'], $_POST["doss"]."/".$_FILES['Filedata']['name'] );
	if ($result)
	{
		$message =  "image uploadée";
	}
	else
	{
		$message = "pb d'écriture";
	}
	echo $message;
}
 
?>




EN GUISE DE CONCLUSION : Récupérer le nom de l'image

Uploader une image sur un serveur via flash, c'est bien. Pouvoir la récupérer et l'utiliser dans notre .swf, c'est mieux. Pour cela, nous allons simplement utiliser un Loader. Le seul souci est de lui passer les bonnes informations. Notre adresse serveur, notre nom de dossier, nous le fournissons, donc, pas de problèmes. Par contre, le nom de l'image que choisira l'utilisateur, nous ne le connaissons évidemment pas ! Mais il est assez facile de le récupérer.


Récupérer le nom de l'image dans la classe

Retournons une dernière fois dans notre ImageUpload.as.

		private function onSelectImage(event:Event):void {
			file = FileReference(event.target);
			file.upload(scriptURL);
		}

Vous vous souvenez que cette fonction est déclenchée par l'événement Select. Et bien, à ce moment de notre script, nous avons accès au nom de l'image choisir par l'utilisateur. Nous n'avons plus qu'à le récupérer.

		private function onSelectImage(event:Event):void {
			file = FileReference(event.target);
			_nameImage= file.name;
			file.upload(scriptURL);
		}

J'ai évidemment créé une var private _nameImage et je lui adjoins un getter.

		public function get nameImage():String
		{
			return _nameImage;
		}



Charger l'image dans le swf

Dans mon .fla, j'attends évidemment d'avoir confirmation que l'image est bien sur le serveur avant de récupérer son nom et de la charger dans un Loader. Pour le chargement dans le loader, je fais le minimum, ceci n'étant pas le propos de ce tuto.

function onReponseServeur(e:Event):void
{
	info.text = monUp.messServeur;
	var image:Loader = new Loader();
	image.contentLoaderInfo.addEventListener(Event.COMPLETE, afficheImage);
	var tempURL : String = "http://localhost/images/"+monUp.nameImage;
	image.load(new URLRequest(tempURL));
}
 
function afficheImage(e:Event):void
{
	addChild(e.target.loader);
}




BONUS

Pour ceux qui ont le flash player 10, je joins aux sources de ce tuto une petite classe (je n'en suis pas l'auteur) qui permet avec FileReference de récupérer une image locale directement dans le player sans passer par l'étape serveur.



Sources