a9ed6e581189d6e20f8318b32abdcd160bb6289e
[lhc/web/www.git] / www / plugins-dist / medias / action / ajouter_documents.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Gestion de l'action ajouter_documents
15 *
16 * @package SPIP\Medias\Action
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 include_spip('inc/getdocument');
24 include_spip('inc/documents');
25 include_spip('inc/choisir_mode_document'); // compat core
26 include_spip('inc/renseigner_document');
27
28 /**
29 * Ajouter des documents
30 *
31 * @param int $id_document
32 * Document à remplacer, ou pour une vignette, l'id_document de maman
33 * 0 ou 'new' pour une insertion
34 * @param array $files
35 * Tableau de tableaux de propriétés pour chaque document à insérer
36 * @param string $objet
37 * Objet auquel associer le document
38 * @param int $id_objet
39 * id_objet
40 * @param string $mode
41 * Mode par défaut si pas precisé pour le document
42 * @return array
43 * Liste des id_documents inserés
44 */
45 function action_ajouter_documents_dist($id_document, $files, $objet, $id_objet, $mode) {
46 $ajouter_un_document = charger_fonction('ajouter_un_document', 'action');
47 $ajoutes = array();
48
49 // on ne peut mettre qu'un seul document a la place d'un autre ou en vignette d'un autre
50 if (intval($id_document)) {
51 $ajoutes[] = $ajouter_un_document($id_document, reset($files), $objet, $id_objet, $mode);
52 } else {
53 foreach ($files as $file) {
54 $ajoutes[] = $ajouter_un_document('new', $file, $objet, $id_objet, $mode);
55 }
56 }
57
58 return $ajoutes;
59 }
60
61 /**
62 * Ajouter un document (au format $_FILES)
63 *
64 * @param int $id_document
65 * Document à remplacer, ou pour une vignette, l'id_document de maman
66 * 0 ou 'new' pour une insertion
67 * @param array $file
68 * Propriétes au format $_FILE étendu :
69 *
70 * - string tmp_name : source sur le serveur
71 * - string name : nom du fichier envoye
72 * - bool titrer : donner ou non un titre a partir du nom du fichier
73 * - bool distant : pour utiliser une source distante sur internet
74 * - string mode : vignette|image|documents|choix
75 * @param string $objet
76 * Objet auquel associer le document
77 * @param int $id_objet
78 * id_objet
79 * @param string $mode
80 * Mode par défaut si pas precisé pour le document
81 * @return array|bool|int|mixed|string|unknown
82 *
83 * - int : l'id_document ajouté (opération réussie)
84 * - string : une erreur s'est produit, la chaine est le message d'erreur
85 *
86 */
87 function action_ajouter_un_document_dist($id_document, $file, $objet, $id_objet, $mode) {
88
89 $source = $file['tmp_name'];
90 $nom_envoye = $file['name'];
91
92 // passer en minuscules le nom du fichier, pour eviter les collisions
93 // si le file system fait la difference entre les deux il ne detectera
94 // pas que Toto.pdf et toto.pdf
95 // et on aura une collision en cas de changement de file system
96 $file['name'] = strtolower(translitteration($file['name']));
97
98 // Pouvoir definir dans mes_options.php que l'on veut titrer tous les documents par d?faut
99 if (!defined('_TITRER_DOCUMENTS')) {
100 define('_TITRER_DOCUMENTS', false);
101 }
102
103 $titrer = isset($file['titrer']) ? $file['titrer'] : _TITRER_DOCUMENTS;
104 $mode = ((isset($file['mode']) and $file['mode']) ? $file['mode'] : $mode);
105
106 include_spip('inc/modifier');
107 if (isset($file['distant']) and $file['distant']
108 and !in_array($mode, array('choix', 'auto', 'image', 'document'))) {
109 spip_log("document distant $source accepte sans verification, mode=$mode", 'medias'._LOG_INFO_IMPORTANTE);
110 include_spip('inc/distant');
111 $file['tmp_name'] = _DIR_RACINE . copie_locale($source);
112 $source = $file['tmp_name'];
113 unset($file['distant']);
114 }
115
116 // Documents distants : pas trop de verifications bloquantes, mais un test
117 // via une requete HEAD pour savoir si la ressource existe (non 404), si le
118 // content-type est connu, et si possible recuperer la taille, voire plus.
119 if (isset($file['distant']) and $file['distant']) {
120 if (!tester_url_absolue($source)) {
121 return _T('medias:erreur_chemin_distant', array('nom' => $source));
122 }
123 include_spip('inc/distant');
124 $source = str_replace(array("'",'"','<'),array("%27",'%22','%3C'), $source);
125 if (is_array($a = renseigner_source_distante($source))) {
126 $champs = $a;
127 # NB: dans les bonnes conditions (fichier autorise et pas trop gros)
128 # $a['fichier'] est une copie locale du fichier
129
130 $infos = renseigner_taille_dimension_image($champs['fichier'], $champs['extension'], true);
131 // on ignore erreur eventuelle sur $infos car on est distant, ca ne marche pas forcement
132 if (is_array($infos)) {
133 $champs = array_merge($champs, $infos);
134 }
135
136 unset($champs['type_image']);
137 } // on ne doit plus arriver ici, car l'url distante a ete verifiee a la saisie !
138 else {
139 spip_log("Echec du lien vers le document $source, abandon");
140
141 return $a; // message d'erreur
142 }
143 } else { // pas distant
144
145 $champs = array(
146 'distant' => 'non'
147 );
148
149 $champs['titre'] = '';
150 if ($titrer) {
151 if ($titrer_document = charger_fonction('titrer_document', 'inc', true)) {
152 $champs['titre'] = $titrer_document($nom_envoye);
153 }
154 else {
155 $titre = substr($nom_envoye, 0, strrpos($nom_envoye, '.')); // Enlever l'extension du nom du fichier
156 $titre = preg_replace(',[[:punct:][:space:]]+,u', ' ', $titre);
157 $champs['titre'] = preg_replace(',\.([^.]+)$,', '', $titre);
158 }
159 }
160
161 if (!is_array($fichier = fixer_fichier_upload($file, $mode))) {
162 return is_string($fichier) ?
163 $fichier : _T('medias:erreur_upload_type_interdit', array('nom' => $file['name']));
164 }
165
166 $champs['inclus'] = $fichier['inclus'];
167 $champs['extension'] = $fichier['extension'];
168 $champs['fichier'] = $fichier['fichier'];
169
170 /**
171 * Récupère les informations du fichier
172 * -* largeur
173 * -* hauteur
174 * -* type_image
175 * -* taille
176 * -* ses metadonnées si une fonction de metadatas/ est présente
177 */
178 $infos = renseigner_taille_dimension_image($champs['fichier'], $champs['extension']);
179 if (is_string($infos)) {
180 // c'est un message d'erreur !
181 return $infos;
182 }
183
184 // lorsqu’une image arrive avec une mauvaise extension par rapport au mime type, adapter.
185 // Exemple : si extension .jpg mais le contenu est un png
186 if (!empty($infos['type_image']) and $infos['type_image'] !== $champs['extension']) {
187 spip_log('Image `' . $file['name'] . '` mauvaise extension. Correcte : ' . $infos['type_image'], 'medias' . _LOG_INFO);
188 $new = copier_document($infos['type_image'], $file['name'] . '.' . $infos['type_image'], $champs['fichier']);
189 if ($new) {
190 supprimer_fichier($champs['fichier']);
191 $champs['fichier'] = $new;
192 $champs['extension'] = $infos['type_image'];
193 $infos = renseigner_taille_dimension_image($champs['fichier'], $champs['extension']);
194 if (is_string($infos)) {
195 // c'est un message d'erreur !
196 return $infos;
197 }
198 spip_log('> Image `' . $file['name'] . '` renommée en : ' . basename($champs['fichier']), 'medias' . _LOG_INFO);
199 } else {
200 spip_log('! Image `' . $file['name'] . '` non renommée en extension : ' . $champs['extension'], 'medias' . _LOG_INFO_IMPORTANTE);
201 }
202 }
203
204 $champs = array_merge($champs, $infos);
205
206 // Si mode == 'choix', fixer le mode image/document
207 if (in_array($mode, array('choix', 'auto'))) {
208 $choisir_mode_document = charger_fonction('choisir_mode_document', 'inc');
209 $mode = $choisir_mode_document($champs, $champs['inclus'] == 'image', $objet);
210 }
211 $champs['mode'] = $mode;
212
213 if (($test = verifier_taille_document_acceptable($champs)) !== true) {
214 spip_unlink($champs['fichier']);
215
216 return $test; // erreur sur les dimensions du fichier
217 }
218
219 unset($champs['type_image']);
220 unset($champs['inclus']);
221 $champs['fichier'] = set_spip_doc($champs['fichier']);
222 }
223
224 // si le media est pas renseigne, le faire, en fonction de l'extension
225 if (!isset($champs['media'])) {
226 $champs['media'] = sql_getfetsel(
227 'media_defaut',
228 'spip_types_documents',
229 'extension=' . sql_quote($champs['extension'])
230 );
231 }
232
233 // lier le parent si necessaire
234 if ($id_objet = intval($id_objet) and $objet) {
235 $champs['parents'][] = "$objet|$id_objet";
236 }
237
238 // "mettre a jour un document" si on lui
239 // passe un id_document
240 if ($id_document = intval($id_document)) {
241 unset($champs['titre']); // garder le titre d'origine
242 unset($champs['date']); // garder la date d'origine
243 unset($champs['descriptif']); // garder la desc d'origine
244 // unset($a['distant']); # on peut remplacer un doc statique par un doc distant
245 // unset($a['mode']); # on peut remplacer une image par un document ?
246 }
247
248 include_spip('action/editer_document');
249 // Installer le document dans la base
250 if (!$id_document) {
251 if ($id_document = document_inserer()) {
252 spip_log(
253 'ajout du document ' . $file['tmp_name'] . ' ' . $file['name'] . " (M '$mode' T '$objet' L '$id_objet' D '$id_document')",
254 'medias'
255 );
256 } else {
257 spip_log(
258 'Echec insert_document() du document ' . $file['tmp_name'] . ' ' . $file['name'] . " (M '$mode' T '$objet' L '$id_objet' D '$id_document')",
259 'medias' . _LOG_ERREUR
260 );
261 }
262 }
263 if (!$id_document) {
264 return _T('medias:erreur_insertion_document_base', array('fichier' => '<em>' . $file['name'] . '</em>'));
265 }
266
267 document_modifier($id_document, $champs);
268
269 // permettre aux plugins de faire des modifs a l'ajout initial
270 // ex EXIF qui tourne les images si necessaire
271 // Ce plugin ferait quand même mieux de se placer dans metadata/jpg.php
272 pipeline(
273 'post_edition',
274 array(
275 'args' => array(
276 'table' => 'spip_documents', // compatibilite
277 'table_objet' => 'documents',
278 'spip_table_objet' => 'spip_documents',
279 'type' => 'document',
280 'id_objet' => $id_document,
281 'champs' => array_keys($champs),
282 'serveur' => '', // serveur par defaut, on ne sait pas faire mieux pour le moment
283 'action' => 'ajouter_document',
284 'operation' => 'ajouter_document', // compat <= v2.0
285 ),
286 'data' => $champs
287 )
288 );
289
290 return $id_document;
291 }
292
293
294 /**
295 * Corrige l'extension du fichier dans quelques cas particuliers
296 *
297 * @note
298 * Une extension 'pdf ' passe dans la requête de contrôle
299 * mysql> SELECT * FROM spip_types_documents WHERE extension="pdf ";
300 *
301 * @todo
302 * À passer dans base/typedoc
303 *
304 * @param string $ext
305 * @return string
306 */
307 function corriger_extension($ext) {
308 $ext = preg_replace(',[^a-z0-9],i', '', $ext);
309 switch ($ext) {
310 case 'htm':
311 $ext = 'html';
312 break;
313 case 'jpeg':
314 $ext = 'jpg';
315 break;
316 case 'tiff':
317 $ext = 'tif';
318 break;
319 case 'aif':
320 $ext = 'aiff';
321 break;
322 case 'mpeg':
323 $ext = 'mpg';
324 break;
325 }
326
327 return $ext;
328 }
329
330 /**
331 * Vérifie la possibilité d'uploader une extension
332 *
333 * Vérifie aussi si l'extension est autorisée pour le mode demandé
334 * si on connait le mode à ce moment là
335 *
336 * @param string $source
337 * Nom du fichier
338 * @param string $mode
339 * Mode d'inclusion du fichier, si connu
340 * @return array|bool|string
341 *
342 * - array : extension acceptée (tableau descriptif).
343 * Avec un index 'autozip' si il faut zipper
344 * - false ou message d'erreur si l'extension est refusée
345 */
346 function verifier_upload_autorise($source, $mode = '') {
347 $infos = array('fichier' => $source);
348 $res = false;
349 if (preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $source, $match)
350 and $ext = $match[1]
351 ) {
352 $ext = corriger_extension(strtolower($ext));
353 if ($res = sql_fetsel(
354 'extension,inclus,media_defaut as media',
355 'spip_types_documents',
356 'extension=' . sql_quote($ext) . " AND upload='oui'"
357 )) {
358 $infos = array_merge($infos, $res);
359 }
360 }
361 if (!$res) {
362 if ($res = sql_fetsel(
363 'extension,inclus,media_defaut as media',
364 'spip_types_documents',
365 "extension='zip' AND upload='oui'"
366 )) {
367 $infos = array_merge($infos, $res);
368 $res['autozip'] = true;
369 }
370 }
371 if ($mode and $res) {
372 // verifier en fonction du mode si une fonction est proposee
373 if ($verifier_document_mode = charger_fonction('verifier_document_mode_' . $mode, 'inc', true)) {
374 $check = $verifier_document_mode($infos); // true ou message d'erreur sous forme de chaine
375 if ($check !== true) {
376 $res = $check;
377 }
378 }
379 }
380
381 if (!$res or is_string($res)) {
382 spip_log("Upload $source interdit ($res)", _LOG_INFO_IMPORTANTE);
383 }
384
385 return $res;
386 }
387
388
389 /**
390 * Tester le type de document
391 *
392 * - le document existe et n'est pas de taille 0 ?
393 * - interdit a l'upload ?
394 * - quelle extension dans spip_types_documents ?
395 * - est-ce "inclus" comme une image ?
396 *
397 * Le zipper si necessaire
398 *
399 * @param array $file
400 * Au format $_FILES
401 * @param string $mode
402 * Mode d'inclusion du fichier, si connu
403 * @return array
404 */
405 function fixer_fichier_upload($file, $mode = '') {
406 /**
407 * On vérifie que le fichier existe et qu'il contient quelque chose
408 */
409 if (is_array($row = verifier_upload_autorise($file['name'], $mode))) {
410 if (!isset($row['autozip'])) {
411 $row['fichier'] = copier_document($row['extension'], $file['name'], $file['tmp_name']);
412 /**
413 * On vérifie que le fichier a une taille
414 * si non, on le supprime et on affiche une erreur
415 */
416 if ($row['fichier'] && (!$taille = @intval(filesize(get_spip_doc($row['fichier']))))) {
417 spip_log('Echec copie du fichier ' . $file['tmp_name'] . ' (taille de fichier indéfinie)');
418 spip_unlink(get_spip_doc($row['fichier']));
419 return _T('medias:erreur_copie_fichier', array('nom' => $file['tmp_name']));
420 } else {
421 return $row;
422 }
423 } else {
424 // creer un zip comme demande
425 // pour encapsuler un fichier dont l'extension n'est pas supportee
426 unset($row['autozip']);
427
428 $ext = 'zip';
429 if (!$tmp_dir = tempnam(_DIR_TMP, 'tmp_upload')) {
430 return false;
431 }
432
433 spip_unlink($tmp_dir);
434 @mkdir($tmp_dir);
435
436 include_spip('inc/charsets');
437 $tmp = $tmp_dir . '/' . translitteration($file['name']);
438
439 // conserver l'extension dans le nom de fichier, par exemple toto.js => toto.js.zip
440 $file['name'] .= '.' . $ext;
441
442 // deplacer le fichier tmp_name dans le dossier tmp
443 deplacer_fichier_upload($file['tmp_name'], $tmp, true);
444
445 include_spip('inc/pclzip');
446 $source = _DIR_TMP . basename($tmp_dir) . '.' . $ext;
447 $archive = new PclZip($source);
448
449 $v_list = $archive->create(
450 $tmp,
451 PCLZIP_OPT_REMOVE_PATH,
452 $tmp_dir,
453 PCLZIP_OPT_ADD_PATH,
454 ''
455 );
456
457 effacer_repertoire_temporaire($tmp_dir);
458 if (!$v_list) {
459 spip_log('Echec creation du zip');
460 return false;
461 }
462
463 $row['fichier'] = copier_document($row['extension'], $file['name'], $source);
464 spip_unlink($source);
465 /**
466 * On vérifie que le fichier a une taille
467 * si non, on le supprime et on affiche une erreur
468 */
469 if ($row['fichier'] && (!$taille = @intval(filesize(get_spip_doc($row['fichier']))))) {
470 spip_log('Echec copie du fichier ' . $file['tmp_name'] . ' (taille de fichier indéfinie)');
471 spip_unlink(get_spip_doc($row['fichier']));
472
473 return _T('medias:erreur_copie_fichier', array('nom' => $file['tmp_name']));
474 } else {
475 return $row;
476 }
477 }
478 } else {
479 return $row;
480 } // retourner le message d'erreur
481 }
482
483 /**
484 * Verifier si le fichier respecte les contraintes de tailles
485 *
486 * @param array $infos
487 * @return bool|mixed|string
488 */
489 function verifier_taille_document_acceptable(&$infos) {
490
491 // si ce n'est pas une image
492 if (!$infos['type_image']) {
493 if (defined('_DOC_MAX_SIZE') and _DOC_MAX_SIZE > 0 and $infos['taille'] > _DOC_MAX_SIZE * 1024) {
494 return _T(
495 'medias:info_doc_max_poids',
496 array(
497 'maxi' => taille_en_octets(_DOC_MAX_SIZE * 1024),
498 'actuel' => taille_en_octets($infos['taille'])
499 )
500 );
501 }
502 } // si c'est une image
503 else {
504 if ((defined('_IMG_MAX_WIDTH') and _IMG_MAX_WIDTH and $infos['largeur'] > _IMG_MAX_WIDTH)
505 or (defined('_IMG_MAX_HEIGHT') and _IMG_MAX_HEIGHT and $infos['hauteur'] > _IMG_MAX_HEIGHT)
506 ) {
507 $max_width = (defined('_IMG_MAX_WIDTH') and _IMG_MAX_WIDTH) ? _IMG_MAX_WIDTH : '*';
508 $max_height = (defined('_IMG_MAX_HEIGHT') and _IMG_MAX_HEIGHT) ? _IMG_MAX_HEIGHT : '*';
509
510 // pas la peine d'embeter le redacteur avec ca si on a active le calcul des miniatures
511 // on met directement a la taille maxi a la volee
512 if (isset($GLOBALS['meta']['creer_preview']) and $GLOBALS['meta']['creer_preview'] == 'oui') {
513 include_spip('inc/filtres');
514 $img = filtrer('image_reduire', $infos['fichier'], $max_width, $max_height);
515 $img = extraire_attribut($img, 'src');
516 $img = supprimer_timestamp($img);
517 if (@file_exists($img) and $img !== $infos['fichier']) {
518 spip_unlink($infos['fichier']);
519 @rename($img, $infos['fichier']);
520 $size = @getimagesize($infos['fichier']);
521 $infos['largeur'] = $size[0];
522 $infos['hauteur'] = $size[1];
523 $infos['taille'] = @filesize($infos['fichier']);
524 }
525 }
526
527 if ((defined('_IMG_MAX_WIDTH') and _IMG_MAX_WIDTH and $infos['largeur'] > _IMG_MAX_WIDTH)
528 or (defined('_IMG_MAX_HEIGHT') and _IMG_MAX_HEIGHT and $infos['hauteur'] > _IMG_MAX_HEIGHT)
529 ) {
530 return _T(
531 'medias:info_image_max_taille',
532 array(
533 'maxi' =>
534 _T(
535 'info_largeur_vignette',
536 array(
537 'largeur_vignette' => $max_width,
538 'hauteur_vignette' => $max_height
539 )
540 ),
541 'actuel' =>
542 _T(
543 'info_largeur_vignette',
544 array(
545 'largeur_vignette' => $infos['largeur'],
546 'hauteur_vignette' => $infos['hauteur']
547 )
548 )
549 )
550 );
551 }
552 }
553
554 if (defined('_IMG_MAX_SIZE') and _IMG_MAX_SIZE > 0 and $infos['taille'] > _IMG_MAX_SIZE * 1024) {
555 return _T(
556 'medias:info_image_max_poids',
557 array(
558 'maxi' => taille_en_octets(_IMG_MAX_SIZE * 1024),
559 'actuel' => taille_en_octets($infos['taille'])
560 )
561 );
562 }
563 }
564
565 // verifier en fonction du mode si une fonction est proposee
566 if ($verifier_document_mode = charger_fonction('verifier_document_mode_' . $infos['mode'], 'inc', true)) {
567 return $verifier_document_mode($infos);
568 }
569
570 return true;
571 }