Skip to content
Extraits de code Groupes Projets
ajouter_documents.php 17,1 ko
Newer Older
<?php

/***************************************************************************\
 *  SPIP, Système de publication pour l'internet                           *
 *  Copyright © avec tendresse depuis 2001                                 *
 *  Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James  *
 *  Ce programme est un logiciel libre distribué sous licence GNU/GPL.     *
 *  Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne.   *
\***************************************************************************/

/**
 * Gestion de l'action ajouter_documents
 *
 * @package SPIP\Medias\Action
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
if (!defined('_ECRIRE_INC_VERSION')) {

include_spip('inc/getdocument');
include_spip('inc/documents');
include_spip('inc/choisir_mode_document'); // compat core
include_spip('inc/renseigner_document');
cedric@yterium.com's avatar
cedric@yterium.com a validé
/**
 * Ajouter des documents
 *
 * @param int $id_document
 *   Document à remplacer, ou pour une vignette, l'id_document de maman
cedric@yterium.com's avatar
cedric@yterium.com a validé
 *   0 ou 'new' pour une insertion
 * @param array $files
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
 *   Tableau de tableaux de propriétés pour chaque document à insérer
 * @param string $objet
 *   Objet auquel associer le document
 * @param int $id_objet
cedric@yterium.com's avatar
cedric@yterium.com a validé
 *   id_objet
 * @param string $mode
 *   Mode par défaut si pas precisé pour le document
cedric@yterium.com's avatar
cedric@yterium.com a validé
 * @return array
 *   Liste des id_documents inserés
cedric@yterium.com's avatar
cedric@yterium.com a validé
 */
function action_ajouter_documents_dist($id_document, $files, $objet, $id_objet, $mode) {
	$ajouter_un_document = charger_fonction('ajouter_un_document', 'action');
	// on ne peut mettre qu'un seul document a la place d'un autre ou en vignette d'un autre
		$ajoutes[] = $ajouter_un_document($id_document, reset($files), $objet, $id_objet, $mode);
			$ajoutes[] = $ajouter_un_document('new', $file, $objet, $id_objet, $mode);
		}
 * Ajouter un document (au format $_FILES)
cedric@yterium.com's avatar
cedric@yterium.com a validé
 * @param int $id_document
 *   Document à remplacer, ou pour une vignette, l'id_document de maman
cedric@yterium.com's avatar
cedric@yterium.com a validé
 *   0 ou 'new' pour une insertion
 * @param array $file
 *   Propriétes au format $_FILE étendu :
 *   - string tmp_name : source sur le serveur
 *   - string name : nom du fichier envoye
 *   - bool titrer : donner ou non un titre a partir du nom du fichier
 *   - bool distant : pour utiliser une source distante sur internet
 *   - string mode : vignette|image|documents|choix
cedric@yterium.com's avatar
cedric@yterium.com a validé
 * @param string $objet
 *   Objet auquel associer le document
cedric@yterium.com's avatar
cedric@yterium.com a validé
 * @param int $id_objet
 *   id_objet
 * @param string $mode
 *   Mode par défaut si pas precisé pour le document
cedric@yterium.com's avatar
cedric@yterium.com a validé
 * @return array|bool|int|mixed|string|unknown
 *
 *   - int : l'id_document ajouté (opération réussie)
 *   - string : une erreur s'est produit, la chaine est le message d'erreur
function action_ajouter_un_document_dist($id_document, $file, $objet, $id_objet, $mode) {
	$source = $file['tmp_name'];
	$nom_envoye = $file['name'];

	// passer en minuscules le nom du fichier, pour eviter les collisions
	// si le file system fait la difference entre les deux il ne detectera
	// pas que Toto.pdf et toto.pdf
	// et on aura une collision en cas de changement de file system
	$file['name'] = strtolower(translitteration($file['name']));
	// Pouvoir definir dans mes_options.php que l'on veut titrer tous les documents par d?faut
	if (!defined('_TITRER_DOCUMENTS')) {
		define('_TITRER_DOCUMENTS', false);
	}
	$titrer = isset($file['titrer']) ? $file['titrer'] : _TITRER_DOCUMENTS;
	$mode = ((isset($file['mode']) and $file['mode']) ? $file['mode'] : $mode);
	if (
		isset($file['distant']) and $file['distant']
		and !in_array($mode, ['choix', 'auto', 'image', 'document'])
	) {
		spip_log("document distant $source accepte sans verification, mode=$mode", 'medias' . _LOG_INFO_IMPORTANTE);
		include_spip('inc/distant');
		$file['tmp_name'] = _DIR_RACINE . copie_locale($source);
		$source = $file['tmp_name'];
		unset($file['distant']);
	}
	// Documents distants : pas trop de verifications bloquantes, mais un test
	// via une requete HEAD pour savoir si la ressource existe (non 404), si le
	// content-type est connu, et si possible recuperer la taille, voire plus.
	if (isset($file['distant']) and $file['distant']) {
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
		if (!tester_url_absolue($source)) {
			return _T('medias:erreur_chemin_distant', ['nom' => $source]);
		$source = str_replace(["'",'"','<'], ['%27','%22','%3C'], $source);
		if (is_array($a = renseigner_source_distante($source))) {
			$champs = $a;
			# NB: dans les bonnes conditions (fichier autorise et pas trop gros)
			# $a['fichier'] est une copie locale du fichier

			$infos = renseigner_taille_dimension_image($champs['fichier'], $champs['extension'], true);
			// on ignore erreur eventuelle sur $infos car on est distant, ca ne marche pas forcement
				foreach ($infos as $k => $v) {
					if (!empty($v) or empty($champs[$k])) {
						$champs[$k] = $v;
					}
				}
		} // on ne doit plus arriver ici, car l'url distante a ete verifiee a la saisie !
		else {
			spip_log("Echec du lien vers le document $source, abandon");
			if ($titrer_document = charger_fonction('titrer_document', 'inc', true)) {
				$champs['titre'] = $titrer_document($nom_envoye);
			}
			else {
				$titre = substr($nom_envoye, 0, strrpos($nom_envoye, '.')); // Enlever l'extension du nom du fichier
				$titre = preg_replace(',[[:punct:][:space:]]+,u', ' ', $titre);
				$champs['titre'] = preg_replace(',\.([^.]+)$,', '', $titre);
			}
		if (!is_array($fichier = fixer_fichier_upload($file, $mode))) {
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
			return is_string($fichier) ?
				$fichier : _T('medias:erreur_upload_type_interdit', ['nom' => $file['name']]);
		$champs['inclus'] = $fichier['inclus'];
		$champs['extension'] = $fichier['extension'];
		$champs['fichier'] = $fichier['fichier'];
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
		/**
		 * Récupère les informations du fichier
		 * -* largeur
		 * -* hauteur
		 * -* type_image
		 * -* taille
		 * -* ses metadonnées si une fonction de metadatas/ est présente
		 */
		$infos = renseigner_taille_dimension_image($champs['fichier'], $champs['extension']);
		if (is_string($infos)) {
		}

		// lorsqu’une image arrive avec une mauvaise extension par rapport au mime type, adapter.
		// Exemple : si extension .jpg mais le contenu est un png
		if (!empty($infos['type_image']) and $infos['type_image'] !== $champs['extension']) {
			spip_log('Image `' . $file['name'] . '` mauvaise extension. Correcte : ' . $infos['type_image'], 'medias' . _LOG_INFO);
			$subdir = determiner_sous_dossier_document($infos['type_image'], $file['name'] . '.' . $infos['type_image'], $mode);
			$new = copier_document($infos['type_image'], $file['name'] . '.' . $infos['type_image'], $champs['fichier'], $subdir);
			if ($new) {
				supprimer_fichier($champs['fichier']);
				$champs['fichier'] = $new;
				$champs['extension'] = $infos['type_image'];
				$infos = renseigner_taille_dimension_image($champs['fichier'], $champs['extension']);
				if (is_string($infos)) {
					// c'est un message d'erreur !
					return $infos;
				}
				spip_log('> Image `' . $file['name'] . '` renommée en : ' . basename($champs['fichier']), 'medias' . _LOG_INFO);
			} else {
				spip_log('! Image  `' . $file['name'] . '` non renommée en extension : ' . $champs['extension'], 'medias' . _LOG_INFO_IMPORTANTE);
			}
		}
		// voir si le document a besoin d'un nettoyage et le cas echeant relire ses infos apres
		if (sanitizer_document($champs['fichier'], $champs['extension'])) {
			$infos = renseigner_taille_dimension_image($champs['fichier'], $champs['extension']);
		}

		// Si mode == 'choix', fixer le mode image/document
		if (in_array($mode, ['choix', 'auto'])) {
			$choisir_mode_document = charger_fonction('choisir_mode_document', 'inc');
			$mode = $choisir_mode_document($champs, $champs['inclus'] == 'image', $objet);
		// ne pas tester les tailles max de logos et documents définies dans les defines _MAX_SIZE, _MAX_WIDTH & _MAX_HEIGHT
		// lors de la migration des logos en base cf logo_migrer_en_base()
		if (!isset($GLOBALS['logo_migrer_en_base'])) {
			if (($test = verifier_taille_document_acceptable($champs)) !== true) {
				spip_unlink($champs['fichier']);
				return $test; // erreur sur les dimensions du fichier
			}
		unset($champs['type_image']);
		unset($champs['inclus']);
		$champs['fichier'] = set_spip_doc($champs['fichier']);
	}

	// si le media est pas renseigne, le faire, en fonction de l'extension
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
		$champs['media'] = sql_getfetsel(
			'media_defaut',
			'spip_types_documents',
			'extension=' . sql_quote($champs['extension'])
		);
	// attention au cas particulier du site 0 utilisé pour le logo du site
	if ($objet and (($id_objet = intval($id_objet)) or in_array($objet, ['site', 'rubrique']))) {
		$champs['parents'][] = "$objet|$id_objet";

	// "mettre a jour un document" si on lui
	// passe un id_document
		unset($champs['titre']); // garder le titre d'origine
		unset($champs['date']); // garder la date d'origine
		unset($champs['descriptif']); // garder la desc d'origine
		// unset($a['distant']); # on peut remplacer un doc statique par un doc distant
		// unset($a['mode']); # on peut remplacer une image par un document ?
	include_spip('action/editer_document');
	if (!$id_document) {
		if ($id_document = document_inserer()) {
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
			spip_log(
				'ajout du document ' . $file['tmp_name'] . ' ' . $file['name'] . "  (M '$mode' T '$objet' L '$id_objet' D '$id_document')",
				'medias'
			);
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
			spip_log(
				'Echec document_inserer() du document ' . $file['tmp_name'] . ' ' . $file['name'] . "  (M '$mode' T '$objet' L '$id_objet' D '$id_document')",
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
				'medias' . _LOG_ERREUR
			);
		return _T('medias:erreur_insertion_document_base', ['fichier' => '<em>' . $file['name'] . '</em>']);

	// permettre aux plugins de faire des modifs a l'ajout initial
	// ex EXIF qui tourne les images si necessaire
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
	// Ce plugin ferait quand même mieux de se placer dans metadata/jpg.php
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
	pipeline(
		'post_edition',
				'table' => 'spip_documents', // compatibilite
				'table_objet' => 'documents',
				'spip_table_objet' => 'spip_documents',
				'id_objet' => $id_document,
				'champs' => array_keys($champs),
				'serveur' => '', // serveur par defaut, on ne sait pas faire mieux pour le moment
				'action' => 'ajouter_document',
				'operation' => 'ajouter_document', // compat <= v2.0
/**
 * Sous-repertoire dans lequel on stocke le document
 * en regle general $ext/ sauf pour les logo
 * @param $ext
 * @param $fichier
 * @param $mode
 * @return mixed
 */
function determiner_sous_dossier_document($ext, $fichier, $mode) {

	// si mode un logoxx on met dans logo/
	if (strncmp($mode, 'logo', 4) === 0) {

/**
 * Corrige l'extension du fichier dans quelques cas particuliers
 * @note
 *     Une extension 'pdf ' passe dans la requête de contrôle
 *     mysql> SELECT * FROM spip_types_documents WHERE extension="pdf ";
 * @todo
 *     À passer dans base/typedoc
 * @param string $ext
 * @return string
 */
function corriger_extension($ext) {
	$ext = preg_replace(',[^a-z0-9],i', '', $ext);
	switch ($ext) {
		case 'htm':
 * Vérifie la possibilité d'uploader une extension
 * Vérifie aussi si l'extension est autorisée pour le mode demandé
 * si on connait le mode à ce moment là
 *
 * @param string $source
 *     Nom du fichier
 * @param string $mode
 *     Mode d'inclusion du fichier, si connu
 * @return array|bool|string
 *
 *     - array : extension acceptée (tableau descriptif).
 *       Avec un index 'autozip' si il faut zipper
 *     - false ou message d'erreur si l'extension est refusée
function verifier_upload_autorise($source, $mode = '') {
	$infos = ['fichier' => $source];
	$res = false;
	if (
		preg_match(',\.([a-z0-9]+)(\?.*)?$,i', $source, $match)
		if (
			$res = sql_fetsel(
				'extension,inclus,media_defaut as media',
				'spip_types_documents',
				'extension=' . sql_quote($ext) . " AND upload='oui'"
			)
		) {
		if (
			$res = sql_fetsel(
				'extension,inclus,media_defaut as media',
				'spip_types_documents',
				"extension='zip' AND upload='oui'"
			)
		) {
			$res['autozip'] = true;
		}
	}
		// verifier en fonction du mode si une fonction est proposee
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
		if ($verifier_document_mode = charger_fonction('verifier_document_mode_' . $mode, 'inc', true)) {
			$check = $verifier_document_mode($infos); // true ou message d'erreur sous forme de chaine
				$res = $check;
		spip_log("Upload $source interdit ($res)", _LOG_INFO_IMPORTANTE);
	return $res;
 * Tester le type de document
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
 * - le document existe et n'est pas de taille 0 ?
 * - interdit a l'upload ?
 * - quelle extension dans spip_types_documents ?
 * - est-ce "inclus" comme une image ?
 * Le zipper si necessaire
 * @param array $file
 *     Au format $_FILES
 * @param string $mode
 *     Mode d'inclusion du fichier, si connu
function fixer_fichier_upload($file, $mode = '') {
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
	/**
	 * On vérifie que le fichier existe et qu'il contient quelque chose
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
	 */
	if (is_array($row = verifier_upload_autorise($file['name'], $mode))) {
		$subdir = determiner_sous_dossier_document($row['extension'], $file['name'], $mode);
			$row['fichier'] = copier_document($row['extension'], $file['name'], $file['tmp_name'], $subdir);
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
			/**
			 * On vérifie que le fichier a une taille
			 * si non, on le supprime et on affiche une erreur
			 */
			if ($row['fichier'] && (!$taille = @intval(filesize(get_spip_doc($row['fichier']))))) {
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
				spip_log('Echec copie du fichier ' . $file['tmp_name'] . ' (taille de fichier indéfinie)');
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
				spip_unlink(get_spip_doc($row['fichier']));
				return _T('medias:erreur_copie_fichier', ['nom' => $file['tmp_name']]);
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
				return $row;
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
		} else {
			// creer un zip comme demande
			// pour encapsuler un fichier dont l'extension n'est pas supportee
			unset($row['autozip']);
			$ext = 'zip';
			if (!$tmp_dir = tempnam(_DIR_TMP, 'tmp_upload')) {
				return false;
			spip_unlink($tmp_dir);
			@mkdir($tmp_dir);

			include_spip('inc/charsets');
			$tmp = $tmp_dir . '/' . translitteration($file['name']);
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
			// conserver l'extension dans le nom de fichier, par exemple toto.js => toto.js.zip
			$file['name'] .= '.' . $ext;

			// deplacer le fichier tmp_name dans le dossier tmp
			deplacer_fichier_upload($file['tmp_name'], $tmp, true);

			$source = _DIR_TMP . basename($tmp_dir) . '.' . $ext;

			include_spip('inc/archives');
			$archive = new Spip\Archives\SpipArchives($source);
			$res = $archive->emballer([$tmp]);

			effacer_repertoire_temporaire($tmp_dir);
			if (!$res) {
				spip_log("Echec creation du zip $source", 'medias' . _LOG_ERREUR);
				return _T('medias:erreur_ecriture_fichier');
			$row['fichier'] = copier_document($row['extension'], $file['name'], $source, $subdir);
			spip_unlink($source);
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
			/**
			 * On vérifie que le fichier a une taille
			 * si non, on le supprime et on affiche une erreur
			 */
			if ($row['fichier'] && (!$taille = @intval(filesize(get_spip_doc($row['fichier']))))) {
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
				spip_log('Echec copie du fichier ' . $file['tmp_name'] . ' (taille de fichier indéfinie)');
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
				spip_unlink(get_spip_doc($row['fichier']));
				return _T('medias:erreur_copie_fichier', ['nom' => $file['tmp_name']]);
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
				return $row;
cedric@yterium.com's avatar
cedric@yterium.com a validé
/**
 * Verifier si le fichier respecte les contraintes de tailles
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
 * @param  array $infos
cedric@yterium.com's avatar
cedric@yterium.com a validé
 * @return bool|mixed|string
 */
function verifier_taille_document_acceptable(&$infos) {

	$is_logo = in_array($infos['mode'], ['logoon', 'logooff']);
	$verifier_taille_document_acceptable = charger_fonction('verifier_taille_document_acceptable', 'inc');
	$res = $verifier_taille_document_acceptable($infos, $is_logo);
	// si erreur, on arrete la
	if ($res !== true) {
		return $res;
	// verifier en fonction du mode si une fonction est proposee
kent1@arscenic.info's avatar
kent1@arscenic.info a validé
	if ($verifier_document_mode = charger_fonction('verifier_document_mode_' . $infos['mode'], 'inc', true)) {
cedric@yterium.com's avatar
cedric@yterium.com a validé
		return $verifier_document_mode($infos);