Upload en pseudo-ajax avec Symfony

Le problème du jour se résumera ainsi :

Mettre en place un upload de fichier en AJAX (ou apparenté) afin d’uploader le fichier et de rafraichir un bout de page après l’upload. Cet upload s’incluera dans une page gérée par le framework Symfony, et plus précisément par l’admin generator de Symfony.

A première vue, il n’est pas possible d’uploader des fichiers en AJAX, car pour des raisons de sécurité, l’objet XmlHttpRequest des navigateurs n’autorise que les requêtes GET/POST simples. Et ne permet donc pas de préciser un enctype= »multipart/form-data ».

A noter que cet article présente une solution basique, qui ne marche pas sans JavaScript. Cela fera peut-être l’objet d’une mise à jour plus tard …

Recherche d’un hack

Mais en cherchant un peu sur le net, on trouve des méthodes plus ou moins élégantes comme cette démonstration d’upload AJAX.

Je me permets de dire que c’est plus ou moins élégant, car bien qu’assez poussé niveau javascript et basée sur jQuery, celà crée à l’insue de l’utilisateur une iframe contenant un formulaire et cela submit le formulaire en question. Donc c’est fourbe, et ca utilise une iframe (inventée par IE) qui n’est plus standard en HTML5 … Passons sur ces « détails ».

Insertion dans les actions Symfony

On va donc insérer cet exemple dans nos actions/templates de symfony …
On retrouve dans apps/monapp/module/monmodule/actions/actions.classs.php :

public function executeDownloadFichier(sfWebRequest $request)
{
        $id_object = $request->getParameter('id_object');
        $c_object = CObjectPeer::retrieveByPK($id_object);
        $force_download = new ForceDownload();
        $file = sfConfig::get('app_objecting_dir_fichier_object').'/'.$c_object->getFichier();
        $force_download->download($this->getContext(), $file, $c_object->getFichierName());
}

public function executeShowFichier(sfWebRequest $request)
{
        $id_object = $request->getParameter('id_object');
        $this->object = CObjectPeer::retrieveByPK($id_object);
        $this->form = new CObjectForm();
        $this->form->getObject()->fromArray($this->object->toArray());
        $this->form->configure();
        $this->setTemplate('showFichier');
}

public function executeDeleteFichier(sfWebRequest $request)
{
        $id_object = $request->getParameter('id_object');
        $this->object = CObjectPeer::retrieveByPK($id_object);
        $this->object->setFichier(null);
        $this->object->save();
        $this->executeShowFichier($request);
}
public function executeUploadFichier(sfWebRequest $request)
{
        $id_object = $request->getParameter('id_object');
        $c_object = CObjectPeer::retrieveByPK($id_object);
        $error = "";
        $msg = "";
        $fileElementName = 'fileToUpload';
        if(!empty($_FILES[$fileElementName]['error'])) {
                switch($_FILES[$fileElementName]['error']) {
                        case '1': $error = 'Le fichier téléchargé est trop volumineux.'; break;
                        case '2': $error = 'Le fichier téléchargé dépasse la taille autorisée.'; break;
                        case '3': $error = 'Le fichier n\'a été que partiellement téléchargé.'; break;
                        case '4': $error = 'Aucun fichier n\'a été téléchargé.'; break;
                        case '6': $error = 'Répertoire temporaire manquant.'; break;
                        case '7': $error = 'Impossible d\'enregistrer le fichier.'; break;
                        case '8': $error = 'Type de fichier non autorisé.'; break;
                        case '999':
                        default:
                                $error = 'Erreur inconnue.';
                }
        } elseif(empty($_FILES['fileToUpload']['tmp_name']) || $_FILES['fileToUpload']['tmp_name'] == 'none') {
                        $error = 'Aucun fichier téléchargé.';
        } else {
                $fichier = $_FILES['fileToUpload']['name'];
                $fileName = md5($fichier.time().rand(0, 99999));
                $pos = strrpos($fichier, '.');
                if ($pos === false) { $ext = '.bin'; }
                else { $ext = substr($fichier, $pos); }
                move_uploaded_file($_FILES['fileToUpload']['tmp_name'], sfConfig::get('app_objecting_dir_fichier_object').'/'.$fileName.$ext);

                $c_object->setFichier($fileName.$ext);
                $c_object->setFichierName($fichier);
                $c_object->save();
                @unlink($_FILES['fileToUpload']['tmp_name']);
        }
        $this->executeShowFichier($request);

}

NB : la classe ForceDownload peut-être récupérée sur le Blog de Menial

Créations des templates et des templates partial

L’action principale étant gérée par l’admin generator, l’include du fichier dans la conf generator.yml est : _currentFichier qui contient :

<div id="block-fichier">
<?php
        $this->setAttribute('module', $this->getModuleName());
        include_partial('global/currentFichier', $this->getAttributeHolder()->getAll());
?>
</div>

NB : j’ai placé le script dans global pour le réutiliser plusieurs fois dans l’application, mais c’est selon l’architecture de votre projet.

On retrouve dans apps/monapp/module/monmodule/templtes/showFichierSuccess.php qqch de proche, sans le <div id= »block-fichier »>

<?php
        $this->setAttribute('module', $this->getModuleName());
        include_partial('global/currentFichier', $this->getAttributeHolder()->getAll());
?>

Le template principal est lui composé de :

<?php echo use_helper('I18N'); ?>
<?php echo use_helper('Fichier'); ?>
<!-- Ca c'est la bibliothèque jQuery standard -->
<script type="text/javascript" src="<?php echo public_path('/js/ajaxfileupload/jquery.js')?>"></script>
<!-- Ca c'est la bibliothèque d'upload ajax basée sur jQuery détaillée plus bas -->
<script type="text/javascript" src="<?php echo public_path('/js/ajaxfileupload/ajaxfileupload.js')?>"></script>
<?
        # Permet de récupérer l'id de l'objet que la page soit gérée par l'admin generator ou par nos propres actions.
        $id_object = $form && $form->getObject() && $form->getObject()->getPrimaryKey() ? $form->getObject()->getPrimaryKey() : $object->getPrimaryKey();
?>
<div>
        <label><?php echo __('libelle_fichier'); ?></label>
        <!-- Cas ou le fichier a déjà été téléchargé. On affiche un lien (ici en window.open) pour télécharger le fichier. -->
        <?php if($form && $form->getObject() && $form->getObject()->getFichier()): ?>
                <a href="#" onclick="window.open('<?php echo url_for($module.'/downloadFichier?id_object=' .$form->getObject()->getPrimaryKey()); ?>', '_blank');">
                        <?php echo __('libelle_fichier_telecharge'); ?>
                </a>
                <!-- Lien de suppression du fichier avec rafraîchissement de l'include_partial -->
                <a href="#" onclick="<?=helper_delete_fichier($module,$id_object)?>"><img src="/helper/sf/sf_admin/images/cancel.png" title="<?php echo __('libelle_fichier_supprimer'); ?>" alt="Icône supprimer" /></a>
        <?php else: ?>
                <!-- File upload avec une action onchange qui permet d'uploader le fichier dès la sélection du fichier et de mettre à jour l'include_partial une fois l'upload terminé. -->
                <!-- A noter aussi que l'objet indicator n'est pas présent dans cette page, car il était ailleurs pour moi, mais qu'il est géré. -->
                <div style="float:left">
                        <input id="fileToUpload" type="file" size="20" name="fileToUpload" onchange="ajaxFileUpload('indicator','<?=url_for($module.'/uploadFichier?id_object='.$id_object)?>','<?=url_for($module.'/showFichier?id_object='.$id_object)?>');" /><br />
                </div>
                <div style="clear: both;"></div>
                <a><?php echo __('libelle_fichier_non_telecharge'); ?></a>
        <?php endif; ?>
        <div style="clear: both;"></div>
</div>

Ajout du javascript modifié

Résolution des conflits

Print Friendly

Laisser un commentaire