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

Last change on this file since 111888 was 111888, checked in by maieul@…, 11 months ago

retour sur r11186.
Comme le dit marcimat, les liens en dur sont souvent désactivés chez des
hébergeurs.
r110547 était bien la bonne solution.
Mon diagnostique dans r11186 était mauvais. Le problème n'était pas
d'avoir un fichier vide. Le problème était que le bug résolu en r111885
faisait que le fichier avec extension pouvait disparaître...

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