source: spip-zone/_plugins_/cvt-upload/trunk/inc/cvtupload.php @ 103830

Last change on this file since 103830 was 103830, checked in by maieul@…, 3 years ago

divers problème de gestion des index lorsqu'on corrige l'envoi d'un champ contenant plusieurs fichiers (https://contrib.spip.net/Envoyer-des-fichiers-avec-un-formulaire-Formidable#forum491833)

File size: 12.0 KB
Line 
1<?php
2
3// Sécurité
4if (!defined("_ECRIRE_INC_VERSION")) return;
5
6include_spip('base/abstract_sql');
7/**
8 * Chercher si des champs fichiers ont été déclarés dans le fichier formulaires/xxx.php
9 * Sert de condition preliminaire pour les pipelines formulaire_charger, formulaire_verifier et formulaire_fond du plugin
10 *
11 * @param string $form
12 *     le nom du formulaire
13 * @param array $args
14 *     - l'id de l'objet
15 *
16 * @return array
17 *     valeur(s) de l'attribut 'name' du ou des input de type file dans formulaires/xxx.html
18 */
19function cvtupload_chercher_fichiers($form, $args) {
20        $fichiers = array();
21       
22        // S'il existe une fonction de fichiers dédiée à ce formulaire
23        if ($fonction_fichiers = charger_fonction('fichiers', 'formulaires/'.$form, true)) {
24                $fichiers = call_user_func_array($fonction_fichiers, $args);
25        }
26       
27        // Dans tous les cas on applique le pipeline, si un plugin veut ajouter des choses
28        $fichiers = pipeline(
29                'formulaire_fichiers',
30                array('args'=>array('form'=>$form, 'args'=>$args), 'data'=>$fichiers)
31        );
32       
33        return $fichiers;
34}
35
36/**
37 * Génére le HTML de chaque fichier déjà uploadé
38 *
39 * @param array $infos_fichiers
40 *              Tableau contenant les informations pour chaque champ de fichier
41 * @return array
42 *              Retourne un tableau avec pour chaque champ une clé contenant le HTML
43 **/
44function cvtupload_generer_html($infos_fichiers = null) {
45        static $html_fichiers = array();       
46        // Si on a des infos de fichiers, on va re-générer du HTML
47        if ($infos_fichiers and is_array($infos_fichiers)) {
48                foreach ($infos_fichiers as $champ=>$fichier) {
49                        // Si c'est un champ unique
50                        if (isset($fichier['name'])) {
51                                $html_fichiers[$champ] = recuperer_fond(
52                                        'formulaires/inc-cvtupload-fichier',
53                                        array_merge($fichier, array(
54                                                'crochets' => "[$champ]",
55                                                'champ'    => "$champ",
56                                        ))
57                                );
58                        }
59                        // Sinon c'est un champ multiple
60                        else {
61                                foreach ($fichier as $cle=>$infos) {
62                                        $html_fichiers[$champ][$cle] = recuperer_fond(
63                                                'formulaires/inc-cvtupload-fichier',
64                                                array_merge($infos, array(
65                                                        'crochets' => "[$champ][$cle]",
66                                                        'champ'    => $champ . "[$cle]",
67                                                ))
68                                        );
69                                }
70                        }
71                }
72        }
73       
74        return $html_fichiers;
75}
76
77/**
78 * Déplace un fichier uploadé dans un endroit temporaire et retourne des informations dessus.
79 * @param array $fichier
80 *              Le morceau de $_FILES concernant le ou les fichiers
81 * @param string $repertoire
82 *              Chemin de destination des fichiers
83 * @param string $form
84 *              Formulaire d'où ça vient
85 * @param bool $deplacer
86 *              Mettre a False pour se contenter de copier
87 * @return array
88 *              Retourne un tableau d'informations sur le fichier ou un tableau de tableaux si plusieurs fichiers. Ce tableau est compatible avec l'action "ajouter_un_fichier" de SPIP.
89 **/
90function cvtupload_deplacer_fichier($fichier, $repertoire, $form, $deplacer=true) {
91        $vignette_par_defaut = charger_fonction('vignette', 'inc/');
92        $infos = array();
93       
94        // On commence par nettoyer le dossier
95        cvtupload_nettoyer_repertoire($repertoire);
96        if (is_array($fichier['name'])) {
97                foreach ($fichier['name'] as $cle=>$nom) {
98                        // On commence par transformer le nom du fichier pour éviter les conflits, on supprime notamment les accents
99                        $nom = preg_replace(',\.\.+,', '.', $nom); // pas de .. dans le nom du doc
100                        $nom = preg_replace("/[^.=\w-]+/", "_",
101                                translitteration(preg_replace("/<[^>]*>/", '', $nom)));
102                        $nom = strtolower($nom);
103                        if (
104                                // Si le fichier a bien un nom et qu'il n'y a pas d'erreur associé à ce fichier
105                                ($nom != null)
106                                and ($fichier['error'][$cle] == 0)
107                                // Et qu'on génère bien un nom de fichier aléatoire pour déplacer le fichier
108                                and $chemin_aleatoire = tempnam($repertoire, $form.'_')
109                        ) {
110                                // Déplacement du fichier vers le dossier de réception temporaire + récupération d'infos
111                                if (deplacer_fichier_upload($fichier['tmp_name'][$cle], $chemin_aleatoire, $deplacer)) {
112                                        $infos[$cle]['tmp_name'] = $chemin_aleatoire;
113                                        $infos[$cle]['name'] = $nom;
114                                        // On en déduit l'extension et du coup la vignette
115                                        $infos[$cle]['extension'] = pathinfo($fichier['name'][$cle], PATHINFO_EXTENSION);
116                                        $infos[$cle]['vignette'] = $vignette_par_defaut($infos[$cle]['extension'], false, true);
117                                        //On récupère le type MIME du fichier aussi
118                                        $infos[$cle]['mime'] = cvt_upload_determiner_mime($fichier['type'][$cle],$infos[$cle]['extension']);
119                                        $infos[$cle]['taille'] = $fichier['size'][$cle];
120                                        // On stocke des infos sur le formulaire
121                                        $infos[$cle]['form'] = $form;
122                                        $infos[$cle]['infos_encodees'] = encoder_contexte_ajax($infos[$cle], $form);
123                                }
124                        }
125                }
126        }
127        else {
128                // On commence par transformer le nom du fichier pour éviter les conflits
129                $nom = trim(preg_replace('/[\s]+/', '_', strtolower(translitteration($fichier['name']))));
130                if (
131                        // Si le fichier a bien un nom et qu'il n'y a pas d'erreur associé à ce fichier
132                        ($nom != null)
133                        and ($fichier['error'] == 0)
134                        // Et qu'on génère bien un nom de fichier aléatoire pour déplacer le fichier
135                        and $chemin_aleatoire = tempnam($repertoire, $form.'_')
136                ) {
137                        // Déplacement du fichier vers le dossier de réception temporaire + récupération d'infos
138                        if (deplacer_fichier_upload($fichier['tmp_name'], $chemin_aleatoire, $deplacer)) {
139                                $infos['tmp_name'] = $chemin_aleatoire;
140                                $infos['name'] = $nom;
141                                // On en déduit l'extension et du coup la vignette
142                                $infos['extension'] = pathinfo($fichier['name'], PATHINFO_EXTENSION);;
143                                $infos['vignette'] = $vignette_par_defaut($infos['extension'], false, true);
144                                //On récupère le type MIME du fichier aussi, ainsi que la taille
145                                $infos['mime'] = cvt_upload_determiner_mime($fichier['type'], $infos['extension']);
146                                $infos['taille'] = $fichier['size'];
147                                // On stocke des infos sur le formulaire
148                                $infos['form'] = $form;
149                                $infos['infos_encodees'] = encoder_contexte_ajax($infos, $form);
150                        }
151                }
152        }
153       
154        return $infos;
155}
156
157/**
158 * Modifier $_FILES pour que le nom et le chemin du fichier temporaire
159 * correspondent à ceux qu'on a défini dans cvtupload_deplacer_fichier().
160 * Cela permet aux traitements ultérieurs
161 * de ne pas avoir à se préoccuper de l'emploi ou non de cvtupload.
162 *
163 * @param $infos_fichiers
164 *  Information sur les fichiers tels que déplacés par cvtupload_deplacer_fichier()
165 * @return void
166**/
167function cvtupload_modifier_files($infos_fichiers) {
168        foreach ($infos_fichiers as $champ => $description) {
169                if (isset($description['tmp_name'])){//Upload unique
170                         $_FILES[$champ] = array();//On surcharge tout la description $_FILES pour ce champ.  Dans tous les cas les infos ont été stockées dans $description
171                         $_FILES[$champ]['name'] = $description['name'];
172                         $_FILES[$champ]['tmp_name'] = $description['tmp_name'];
173                         $_FILES[$champ]['type'] = $description['mime'];
174                         $_FILES[$champ]['error'] = 0; //on fait comme s'il n'y avait pas d'erreur, ce qui n'est pas forcément vrai…
175                         $_FILES[$champ]['size'] = $description['taille']; 
176                }
177                else {//Upload multiple
178                        //On surcharge tout la description $_FILES pour ce champ. Dans tous les cas les infos ont été stockées dans $description
179                        if (isset($_FILES[$champ])) {
180                                $old_FILES_champ = $_FILES[$champ];
181                        } else {
182                                $old_FILES_champ = array();
183                        }
184                        $_FILES[$champ]['name'] = array();
185                        $_FILES[$champ]['tmp_name'] = array();
186                        $_FILES[$champ]['type'] = array();
187                        $_FILES[$champ]['error'] = array();
188                        $_FILES[$champ]['size'] = array();
189                        // Et on re-rempli à partir de $description
190                        foreach ($description as $fichier_individuel => $description_fichier_individuel) {
191                                $_FILES[$champ]['name'][$fichier_individuel] = $description_fichier_individuel['name'];
192                                $_FILES[$champ]['tmp_name'][$fichier_individuel] = $description_fichier_individuel['tmp_name'];
193                                $_FILES[$champ]['type'][$fichier_individuel] = $description_fichier_individuel['mime'];
194                                $_FILES[$champ]['error'][$fichier_individuel] = 0; //on fait comme s'il n'y avait pas d'erreur, ce qui n'est pas forcément vrai…
195                                $_FILES[$champ]['size'][$fichier_individuel] = $description_fichier_individuel['taille'];               
196                        }
197                        // Si on vient d'envoyer un ou plusieur $champ[] vide, on les rajoute dans notre nouveau $FILES
198                        if (isset($old_FILES_champ['error'])) {
199                                foreach ($old_FILES_champ['error'] as $id_fichier_individuel => $error_fichier_individuel){
200                                        if ($error_fichier_individuel!=0 and !isset($infos_fichiers[$champ][$id_fichier_individuel])){//Uniquement les erreurs
201                                                $_FILES[$champ]['name'][$id_fichier_individuel] = $old_FILES_champ['name'][$id_fichier_individuel];
202                                                $_FILES[$champ]['tmp_name'][$id_fichier_individuel] = $old_FILES_champ['tmp_name'][$id_fichier_individuel];
203                                                $_FILES[$champ]['type'][$id_fichier_individuel] = $old_FILES_champ['type'][$id_fichier_individuel];
204                                                $_FILES[$champ]['error'][$id_fichier_individuel] = $old_FILES_champ['error'][$id_fichier_individuel];
205                                                $_FILES[$champ]['size'][$id_fichier_individuel] = $old_FILES_champ['size'][$id_fichier_individuel];
206                                        }
207                                }
208                        }
209                        // On remet de l'ordre dans champ dans chaque tableau correspondant à une propriété de $_FILES, histoire d'avoir 0,1,2,3 et pas 3,1,0,2
210                        foreach ($_FILES[$champ] as $propriete => $valeurs_propriete) {
211                                ksort($valeurs_propriete);
212                                $_FILES[$champ][$propriete] = $valeurs_propriete;
213                        }
214                }
215        }
216}
217
218/**
219 * Nettoyer $_FILES pour effacer les entrées dont on a vérifié qu'elle ne répondaient pas à certains critères
220 *
221 * @param string $champ
222 *      Le nom du champ concerné dans $_FILES
223 * @param string[]|string $erreurs
224 *      Si un upload multiple, un tableau des $erreurs avec comme clés les numéros des fichiers à supprimer dans $_FILES[$champ]
225 *      Si un upload unique, une chaîne, qui si non vide, indique qu'il faut effacer le $_FILE[$champ]
226 * @return void
227**/
228function cvtupload_nettoyer_files_selon_erreurs($champ,$erreurs){
229        if (is_array($erreurs)) { // cas d'upload multiple
230                foreach ($erreurs as $cle=>$erreur) {
231                        foreach ($_FILES[$champ] as $propriete=>$valeur) {
232                                unset($_FILES[$champ][$propriete][$cle]);       
233                         }
234                }
235        }
236        elseif ($erreurs!='') { // cas d'upload unique avec erreur
237                unset($_FILES[$champ]);
238        }
239}
240
241/**
242 * Détermine un MIME lorsque les informations de PHP sont imprécises.
243 * Par exemple PHP considère qu'un fichier .tex est de MIME application/octet-stream
244 * Ce qui n'est absolument pas utilse
245 * @param string $mime_suppose
246 * @param string $extension
247 * return string $mime_trouve
248**/
249function cvt_upload_determiner_mime($mime_suppose, $extension) {
250        if (!in_array($mime_suppose, array('text/plain', '', 'application/octet-stream'))) { // si on a un mime précis, on le renvoie, tout simplement
251                return $mime_suppose;
252        }
253        $mime_spip = sql_getfetsel('mime_type','spip_types_documents','extension='.sql_quote($extension));
254        if ($mime_spip) {
255                return $mime_spip;
256        } else {
257                return $mime_suppose;
258        }
259}
260/**
261 * Nettoyer un répertoire suivant l'age et le nombre de ses fichiers
262 *
263 * @param string $repertoire
264 *              Répertoire à nettoyer
265 * @param int $age_max
266 *              Age maxium des fichiers en seconde
267 * @param int $max_files
268 *              Nombre maximum de fichiers dans le dossier
269 * @return void
270 **/
271function cvtupload_nettoyer_repertoire($repertoire, $age_max = _CVTUPLOAD_AGE_MAX, $max_files = _CVTUPLOAD_MAX_FILES) {
272        include_spip('inc/flock');
273       
274        // Si on entre bien dans le répertoire
275        if ($ressource_repertoire = opendir($repertoire)) {
276                $fichiers = array();
277               
278                // On commence par supprimer les plus vieux
279                while ($fichier = readdir($ressource_repertoire)) {
280                        if (!in_array($fichier, array('.', '..', '.ok'))) {
281                                $chemin_fichier = $repertoire.$fichier;
282                               
283                                if (is_file($chemin_fichier) and !jeune_fichier($chemin_fichier, $age_max)) {
284                                        supprimer_fichier($chemin_fichier);
285                                }
286                                else {
287                                        $fichiers[@filemtime($chemin_fichier).'_'.rand()] = $chemin_fichier;
288                                }
289                        }
290                }
291               
292                // On trie les fichiers par ordre de leur date
293                ksort($fichiers);
294               
295                // Puis s'il reste trop de fichiers, on supprime le surplus
296                $nb_fichiers = count($fichiers);
297                if ($nb_fichiers > $max_files) {
298                        $nb_a_supprimer = $nb_fichiers - $max_files - 1;
299                       
300                        while ($nb_a_supprimer) {
301                                $fichier = array_shift($fichiers);
302                                supprimer_fichier($fichier);
303                                $nb_a_supprimer--;
304                        }
305                }
306        }
307}
Note: See TracBrowser for help on using the repository browser.