Valider 5213a4ac rédigé par marcimat's avatar marcimat
Parcourir les fichiers

Merge branch 'refactoring'

* refactoring:
  La valeur du champ, qui est présente dans le token, est calculé dès la création de la classe Identifier, afin que le Cache puisse instancier sa valeur lors de sa création lui aussi.
  Différentes corrections de bugs suite aux déplacements. Notamment une bonne blague sur un constructeur appelé ‘construct' au lieu de ‘__construct’
  On déplace les fonctions de Bigup dans des classes spécifiques pour que l’ensemble soit plus lisible. - Identifier : gère la récupération des paramètres d’identification du formulaire et éventuellement du champ si un token est là. - Cache : gère l’interaction de nos éléments dans le cache tmp/bigupload - Receptionner : gère les actions ajax (relatives à l’action/bigup.php) - Bigup : gère les fonctions utilisées depuis les pipelines
parents 34e4e23f 8913492d
Chargement en cours
Chargement en cours
Chargement en cours
Chargement en cours
+2 −3
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -10,9 +10,8 @@
function action_bigup_dist() {

	include_spip('inc/Bigup');
	$Bigup = new \SPIP\Bigup\Bigup();
	$Bigup->recuperer_parametres();
	$Bigup->repondre();
	$bigup = \Spip\Bigup\Receptionner::depuisRequest();
	$bigup->repondre();
	exit;

}
+5 −11
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -64,18 +64,11 @@ function bigup_header_prive($flux) {
**/
function bigup_get_bigup($flux) {

	// il nous faut le nom du formulaire et son hash
	// et pas de bol, le hash est pas envoyé dans le pipeline.
	// (il est calculé après charger). Alors on se recrée un hash pour nous.
	$form = $flux['args']['form'];
	$args = $flux['args']['args'];
	#$post = $flux['args']['je_suis_poste'];

	array_unshift($args, $GLOBALS['spip_lang']);
	$formulaire_args = encoder_contexte_ajax($args, $form);

	include_spip('inc/Bigup');
	$bigup = new \Spip\Bigup\Bigup($form, $formulaire_args);

	$bigup = new \Spip\Bigup\Bigup(
		\Spip\Bigup\Identifier::depuisArgumentsPipeline($flux['args'])
	);

	return $bigup;
}
@@ -137,6 +130,7 @@ function bigup_formulaire_charger($flux) {
function bigup_formulaire_receptionner($flux) {
	if (_request('bigup_retrouver_fichiers')) {
		$bigup = bigup_get_bigup($flux);
		$bigup->gerer_fichiers_postes(); // les fichiers postés sans JS
		$bigup->reinserer_fichiers(_request('bigup_reinjecter_uniquement'));
	}
	return $flux;
+34 −508
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -12,9 +12,12 @@ namespace Spip\Bigup;
 * @package    SPIP\Bigup\Fonctions
 */

include_spip('inc/Bigup/LogTrait');
include_spip('inc/Bigup/Cache');
include_spip('inc/Bigup/Flow');
include_spip('inc/Bigup/GestionRepertoires');
include_spip('inc/Bigup/Identifier');
include_spip('inc/Bigup/LogTrait');
include_spip('inc/Bigup/Receptionner');

/**
 * Gère la validité des requêtes et appelle Flow
@@ -24,198 +27,25 @@ class Bigup {
	use LogTrait;

	/**
	 * Login ou identifiant de l'auteur qui intéragit
	 * @var string */
	private $auteur = '';

	/**
	 * Nom du formulaire qui utilise flow
	 * @var string */
	private $formulaire = '';

	/**
	 * Hash des arguments du formulaire
	 * @var string */
	private $formulaire_args = '';

	/**
	 * Identifie un formulaire par rapport à un autre identique sur la même page ayant un appel différent.
	 * @var string */
	private $formulaire_identifiant = '';

	/**
	 * Nom du champ dans le formulaire qui utilise flow
	 * @var string */
	private $champ = '';

	/**
	 * Token de la forme `champ:time:cle`
	 * @var string
	**/
	private $token = '';
	
	/**
	 * Expiration du token (en secondes)
	 *
	 * @todo À définir en configuration
	 * @var int
	**/
	private $token_expiration = 3600 * 24;

	/**
	 * Nom d'une action demandée
	 *
	 * Si pas de précision => gestion par Flow
	 * 
	 * @var string
	**/
	private $action = '';

	/**
	 * Identifiant d'un fichier (en cas de suppression demandée)
	 *
	 * Cet identifiant est soit un md5 du chemin du fichier sur le serveur
	 * (envoyé dans la clé 'identifiant' des fichiers déjà présents pour ce formulaire),
	 *
	 * Soit un identifiant (uniqueIdentifier) qui sert au rangement du fichier, calculé
	 * par Flow.js ou Resumable.js à partir du nom et de la taille du fichier.
	 * Cet identifiant là est envoyé si on annule un fichier en cours de téléversement.
	 *
	 * @var string
	**/
	private $identifiant = '';

	/**
	 * Nom du répertoire, dans _DIR_TMP, qui va stocker les fichiers et morceaux de fichiers
	 * @var string */
	private $cache_dir = 'bigupload';

	/**
	 * Chemin du répertoire stockant les morceaux de fichiers
	 * @var string */
	 private $dir_parts = '';
	 * Identification du formulaire, auteur, champ, tokem
	 * @var Identifier
	 */
	private $identifier = null;

	/**
	 * Chemin du répertoire stockant les fichiers terminés
	 * @var string */
	 private $dir_final = '';

	 * Gestion du cache Bigup
	 * @var Identifier
	 */
	private $cache = null;

	/**
	 * Constructeur
	 *
	 * @param string $formulaire Nom du formulaire
	 * @param string $formulaire_args Hash du formulaire
	 * @param string $token Jeton d'autorisation
	**/
	public function __construct($formulaire = '', $formulaire_args = '', $token = '') {
		$this->token = $token;
		$this->formulaire = $formulaire;
		$this->formulaire_args = $formulaire_args;
		$this->identifier_auteur();
		$this->identifier_formulaire();
	}

	/**
	 * Retrouve les paramètres pertinents pour gérer le test ou la réception de fichiers.
	 * @param Identifier $identifier
	 **/
	public function recuperer_parametres() {
		// obligatoires
		$this->token           = _request('bigup_token');
		$this->formulaire      = _request('formulaire_action');
		$this->formulaire_args = _request('formulaire_action_args');
		// optionnels
		$this->action          = _request('bigup_action');
		$this->identifiant     = _request('identifiant');
		$this->identifier_formulaire();
	}

	/**
	 * Répondre
	 *
	 * Envoie un statut HTTP de réponse et quitte, en fonction de ce qui était demandé,
	 *
	 * - soit tester un morceau de fichier,
	 * - soit réceptionner un morceau de fichier,
	 * - soit effacer un fichier
	 *
	 * Si les hash ne correspondaient pas, le programme quitte évidemment.
	**/
	public function repondre() {
		if (!$this->verifier_token()) {
			return $this->send(403);
		}

		$this->calculer_chemin_repertoires();

		if ($this->action) {
			$repondre_action = 'repondre_' . $this->action;
			if (method_exists($this, $repondre_action)) {
				return $this->$repondre_action();
			}
			$action_externe = charger_fonction('bigup_' . $repondre_action, 'action', true);
			if ($action_externe = charger_fonction('bigup_' . $repondre_action, 'action', true)) {
				return $action_externe($this);
			}
			// Action inconnue.
			return $this->send(403);
		}

		return $this->repondre_flow();
	}

	/**
	 * Répondre le cas de suppression d'un fichier
	 *
	 * L'identifiant de fichier est le md5 du chemin de stockage.
	**/
	public function repondre_effacer() {
		if (!$this->identifiant) {
			return $this->send(404);
		}
		// si c'est un md5, c'est l'identifiant
		if (strlen($this->identifiant) == 32 and ctype_xdigit($this->identifiant)) {
			if ($this->enlever_fichier_depuis_identifiants($this->identifiant)) {
				return $this->send(201);
			}
		} elseif ($this->enlever_fichier_depuis_repertoires($this->identifiant)) {
			return $this->send(201);
		}
		return $this->send(404);
	}


	/**
	 * Répondre le cas de réception ou test de morceau de fichier
	**/
	public function repondre_flow() {
		include_spip('inc/Bigup/Flow');
		$flow = new Flow();
		$flow->definir_repertoire('parts', $this->dir_parts);
		$flow->definir_repertoire('final', $this->dir_final);
		$res = $flow->run();

		// le fichier est complet
		if (is_string($res)) {
			// remettre le fichier dans $FILES
			# $this->integrer_fichier($res);

			// envoyer quelques infos sur le fichier reçu
			$desc = $this->decrire_fichier($res);
			// ne pas permettre de connaître le chemin complet
			unset($desc['pathname'], $desc['tmp_name']);

			// nettoyer le chemin du répertoire de stockage des morceaux du fichiers
			GestionRepertoires::supprimer_repertoire($this->obtenir_chemin(dirname($res), false));

			$this->send(200, $desc);
		}

		if (is_int($res)) {
			$this->send($res);
		}

		$this->send(415);
	public function __construct(Identifier $identifier) {
		$this->identifier = $identifier;
		$this->cache = new Cache($identifier);
	}

	/**
@@ -225,8 +55,7 @@ class Bigup {
	 * @return array
	 */
	public function retrouver_fichiers() {
		$this->calculer_chemin_repertoires();
		$liste = $this->trouver_fichiers_complets();
		$liste = $this->cache->trouver_fichiers_complets();
		$liste = $this->organiser_fichiers_complets($liste);
		return $liste;
	}
@@ -248,8 +77,7 @@ class Bigup {
			$uniquement = [$uniquement];
		}

		$this->calculer_chemin_repertoires();
		$liste = $this->trouver_fichiers_complets();
		$liste = $this->cache->trouver_fichiers_complets();
		foreach ($liste as $champ => $fichiers) {
			foreach ($fichiers as $description) {
				if (!$uniquement or in_array($description['identifiant'], $uniquement)) {
@@ -262,117 +90,27 @@ class Bigup {


	/**
	 * Enlève un fichier complet dont l'identifiant est indiqué
	 *
	 * @param string|array $identifiants
	 *     Identifiant ou liste d'identifiants de fichier
	 * @return bool True si le fichier est trouvé (et donc enlevé)
	**/
	public function enlever_fichier_depuis_identifiants($identifiants) {
		$this->calculer_chemin_repertoires();
		$liste = $this->trouver_fichiers_complets();
		if (!is_array($identifiants)) {
			$identifiants = [$identifiants];
		}
		$this->debug("Demande de suppression de fichier : " . implode(', ', $identifiants));

		foreach ($liste as $champ => $fichiers) {
			foreach ($fichiers as $description) {
				if (in_array($description['identifiant'], $identifiants)) {
					// en théorie, le chemin 'parts' a déjà été nettoyé
					$this->supprimer_repertoire_fichier(dirname($description['pathname']));
					return true;
				}
			}
		}

		return false;
	}


	/**
	 * Enlève un fichier (probablement partiel) dont le nom est indiqué
	 *
	 * @param string|array $repertoires
	 *     Un repertoire ou liste de répertoires de stockage du fichier.
	 *     Il correspond au `uniqueIdentifier` transmis par le JS
	 * @return bool True si le fichier est trouvé (et donc enlevé)
	 **/
	public function enlever_fichier_depuis_repertoires($repertoires) {
		$this->calculer_chemin_repertoires();
		if (!is_array($repertoires)) {
			$repertoires = [$repertoires];
		}
		foreach ($repertoires as $repertoire) {
			$this->debug("Demande de suppression du fichier dans : $repertoire");
			$this->supprimer_repertoire_fichier($this->dir_final . DIRECTORY_SEPARATOR . $repertoire);
		}
		return true;
	}

	/**
	 * Efface tous ou des fichiers envoyés pour ce formulaire par un auteur.
	 * Efface tous ou certains fichiers envoyés pour ce formulaire par un auteur.
	 *
	 * @param array|string $identifiants
	 *     Identifiant de fichier ou liste des identifiants concernés, le cas échéant.
	 *     Efface tous les fichiers sinon.
	 * @return true
	 */
	public function effacer_fichiers($identifiants = []) {
		$this->calculer_chemin_repertoires();
		$this->debug("Suppression des fichiers restants");
		if (!$identifiants) {
			$this->supprimer_repertoire_fichier($this->dir_final);
			$this->cache->supprimer_repertoire($this->cache->dir_final());
		} else {
			$this->enlever_fichier_depuis_identifiants($identifiants);
			$this->cache->enlever_fichier_depuis_identifiants($identifiants);
			// les fichiers avec ces identifiants n'étant possiblement plus là
			// ie: ils ont été déplacés lors du traitement du formulaire
			// on nettoie les répertoires vides complètement
			GestionRepertoires::supprimer_repertoires_vides($this->dir_final);
			GestionRepertoires::supprimer_repertoires_vides($this->cache->dir_final());
		}
		return true;
	}

	/**
	 * À partir d'un chemin de stockage final ou partiel d'un fichier dans bigup,
	 * retrouver le chemin final ou partiel correspondant
	 *
	 * @param string $chemin
	 * @param bool $final
	 *     Retourne le chemin final, sinon le chemin partiel
	 * @return bool
	 */
	function obtenir_chemin($chemin, $final = true) {
		// on vérifie que ce chemin concerne bigup uniquement
		if (strpos($chemin, $this->dir_final) === 0) {
			$path = substr($chemin, strlen($this->dir_final));
		} elseif (strpos($chemin, $this->dir_parts) === 0) {
			$path = substr($chemin, strlen($this->dir_final));
		} else {
			return false;
		}
		return ($final ? $this->dir_final : $this->dir_parts) . $path;
	}

	/**
	 * Supprimer le répertoire indiqué et les répertoires parents éventuellement.
	 *
	 * @param string $chemin
	 *     Chemin du répertoire stockant un fichier bigup
	 * @param string $quoi
	 *     Quelle partie supprimer : 'final', 'parts' ou 'tout' (les 2)
	 * @return bool
	 */
	function supprimer_repertoire_fichier($chemin, $quoi = 'tout') {
		// Suppression du contenu du fichier final
		if (in_array($quoi, ['tout', 'final']) and $dir = $this->obtenir_chemin($chemin, true)) {
			GestionRepertoires::supprimer_repertoire($dir);
		}
		// Suppression du contenu des morceaux du fichier
		if (in_array($quoi, ['tout', 'parts']) and $dir = $this->obtenir_chemin($chemin, false)) {
			GestionRepertoires::supprimer_repertoire($dir);
		}
		return true;
	}

	/**
	 * Groupe en tableau les fichiers trouvés
@@ -408,182 +146,12 @@ class Bigup {
	}


	/**
	 * Retourne la liste des fichiers complets, classés par champ
	 *
	 * @return array Liste [ champ => [ chemin ]]
	**/
	public function trouver_fichiers_complets() {
		// la théorie veut ce rangement :
		// $dir/{champ}/{identifiant_fichier}/{nom du fichier.extension}
		$directory = $this->dir_final;

		// pas de répertoire… pas de fichier… simple comme bonjour :)
		if (!is_dir($directory)) {
			return [];
		}

		$liste = [];

		$files = new \RecursiveIteratorIterator(
			new \RecursiveDirectoryIterator($directory)
		);

		foreach ($files as $filename) {
			if ($filename->isDir()) continue; // . ..
			if ($filename->getFilename()[0] == '.') continue; // .ok

			$chemin = $filename->getPathname();
			$champ = $this->retrouver_champ_depuis_chemin($chemin);

			if (empty($liste[$champ])) {
				$liste[$champ] = [];
			}
			$liste[$champ][] = $this->decrire_fichier($chemin);
			$this->debug("Fichier retrouvé : $chemin");
		}

		return $liste;
	}

	/**
	 * Retrouve un nom de champ depuis un chemin de cache de fichier
	 *
	 * @param string $chemin
	 *     Chemin de stockage du fichier dans le cache de bigupload
	 * @return string
	 *     Nom du champ (valeur de l'attribut name de l'input d'origine)
	 */
	function retrouver_champ_depuis_chemin($chemin) {
		return basename(dirname(dirname($chemin)));
	}

	/**
	 * Vérifier le token utilisé
	 *
	 * Le token doit arriver, de la forme `champ:time:clé`
	 * De même que formulaire_action et formulaire_action_args
	 *
	 * Le temps ne doit pas être trop vieux d'une part,
	 * et la clé de sécurité doit évidemment être valide.
	 * 
	 * @return bool
	**/
	public function verifier_token() {
		if (!$this->token) {
			$this->debug("Aucun token");
			return false;
		}

		$_token = explode(':', $this->token);

		if (count($_token) != 3) {
			$this->debug("Token mal formé");
			return false;
		}

		list($champ, $time, $cle) = $_token;
		$time = intval($time);
		$now = time();


		if (($now - $time) > $this->token_expiration) {
			$this->log("Token expiré");
			return false;
		}

		if (!$this->formulaire) {
			$this->log("Vérifier token : nom du formulaire absent");
			return false;
		}

		if (!$this->formulaire_args) {
			$this->log("Vérifier token : hash du formulaire absent");
			return false;
		}

		include_spip('inc/securiser_action');
		if (!verifier_action_auteur("bigup/$this->formulaire/$this->formulaire_args/$champ/$time", $cle)) {
			$this->error("Token invalide");
			return false;
		}

		$this->champ = $champ;

		$this->debug("Token OK : formulaire $this->formulaire, champ $champ, identifiant $this->formulaire_identifiant");

		return true;
	}


	/**
	 * Calcule les chemins des répertoires de travail
	 * qui stockent les morceaux de fichiers et les fichiers complétés
	**/
	public function calculer_chemin_repertoires() {
		$this->dir_parts = $this->calculer_chemin_repertoire('parts');
		$this->dir_final = $this->calculer_chemin_repertoire('final');
	}

	/**
	 * Calcule un chemin de répertoire de travail d'un type donné
	 * @return string
	**/
	public function calculer_chemin_repertoire($type) {
		return
			_DIR_TMP . $this->cache_dir
			. DIRECTORY_SEPARATOR . $type
			. DIRECTORY_SEPARATOR . $this->auteur
			. DIRECTORY_SEPARATOR . $this->formulaire
			. DIRECTORY_SEPARATOR . $this->formulaire_identifiant
			. DIRECTORY_SEPARATOR . $this->champ;
	}

	/**
	 * Identifier l'auteur qui accède
	 *
	 * Retrouve un identifiant unique, même pour les auteurs anonymes.
	 * Si on connait l'auteur, on essaie de mettre un nom humain
	 * pour une meilleure visibilité du répertoire.
	 * 
	 * Retourne un identifiant d'auteur :
	 * - {id_auteur}.{login} sinon
	 * - {id_auteur} sinon
	 * - 0.{session_id}
	 *
	 * @return string
	**/
	public function identifier_auteur() {
		// un nom d'identifiant humain si possible
		include_spip('inc/session');
		$identifiant = session_get('id_auteur');
		// visiteur anonyme ? on prend un identifiant de session PHP.
		if (!$identifiant) {
			if (session_status() == PHP_SESSION_NONE) {
				session_start();
			}
			$identifiant .= "." . session_id();
		} elseif ($login = session_get('login')) {
			$identifiant .= "." . $login;
		}
		return $this->auteur = $identifiant;
	}

	/**
	 * Calcule un identifiant de formulaire en fonction de ses arguments
	 *
	 * @return string l'identifiant
	**/
	public function identifier_formulaire() {
		return $this->formulaire_identifiant = substr(md5($this->formulaire_args), 0, 6);
	}

	/**
	 * Intégrer le fichier indiqué dans `$FILES`
	 *
	 * Tout dépend de l'attribut name qui a été posté.
	 * Tout dépend de l'attribut name qui avait été posté.
	 *
	 * Cette info doit se trouver dans le tableau reçu
	 * (ou calculé si on envoie le chemin du fichier en cache)
	 * dans la clé 'champ'.
	 *
	 * - name='a' : FILES[a][name] = 'x'
@@ -591,16 +159,12 @@ class Bigup {
	 * - name='a[b]' : FILES[a][name][b] = 'x'
	 * - name='a[b][]' : FILES[a][name][b][0] = 'x'
	 *
	 * @param string|array $description
	 *     array : Description déjà calculée
	 *     string : chemin du fichier
	 * @param array $description
	 *     Description d'un fichier
	 * @return array
	 *     Description du fichier
	**/
	public function integrer_fichier($description) {
		if (!is_array($description)) {
			$description = $this->decrire_fichier($description); 
		}
		// la valeur complete du name.
		$champ = $description['champ'];
		$arborescence = explode('[', str_replace(']', '', $champ));
@@ -636,53 +200,15 @@ class Bigup {
		return $description;
	}

	/**
	 * Décrire un fichier (comme dans `$_FILES`)
	 *
	 * @uses retrouver_champ_depuis_chemin()
	 * @param string $chemin
	 *     Chemin du fichier dans le cache de bigup.
	 * @return array
	**/
	public function decrire_fichier($chemin) {
		$filename = basename($chemin);
		$extension = pathinfo($chemin, PATHINFO_EXTENSION);
		$champ = $this->retrouver_champ_depuis_chemin($chemin);
		include_spip('action/ajouter_documents');
		$finfo = finfo_open(FILEINFO_MIME_TYPE);
		$desc = [
			// présent dans $_FILES
			'name' => $filename,
			'tmp_name' => $chemin,
			'size' => filesize($chemin),
			'type' => finfo_file($finfo, $chemin),
			'error' => 0, // hum
			// informations supplémentaires (pas dans $_FILES habituellement)
			'pathname' => $chemin,
			'identifiant' => md5($chemin),
			'extension' => corriger_extension(strtolower($extension)),
			'champ' => $champ,
		];
		return $desc;
	}


	/**
	 * Envoie le code header indiqué… et arrête tout.
	 * Chaque fichier présent dans `$_FILES` n'étant pas en erreur
	 * est géré par Bigup
	 *
	 * @param int $code
	 * @param array|null $data Données à faire envoyer en json
	 * @return void
	**/
	public function send($code, $data = null) {
		$this->debug("> send $code");
		http_response_code($code);
		if ($data) {
			header("Content-Type: application/json; charset=" . $GLOBALS['meta']['charset']);
			echo json_encode($data);
		}
		exit;
	}
	 * @todo
	 */
	public function gerer_fichiers_postes() {

	}

}

inc/Bigup/Cache.php

0 → 100644
+277 −0

Fichier ajouté.

La taille limite d'aperçu a été dépassée, l'affichage des modifications a donc été réduit.

+1 −4
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -25,9 +25,6 @@ class GestionRepertoires {

	use LogTrait;

	private $dir_final = '';
	private $dir_parts = '';

	/**
	 * Vérifier et préparer l'arborescence jusqu'au répertoire parent
	 *
@@ -105,7 +102,7 @@ class GestionRepertoires {
		foreach ($fichiers as $fichier) {
			$chemin = $repertoire . DIRECTORY_SEPARATOR . $fichier;
			if (is_dir($chemin)) {
				bigup_nettoyer_repertoire_recursif($chemin, $age_max);
				self::nettoyer_repertoire_recursif($chemin, $age_max);
			}
			elseif (is_file($chemin) and !jeune_fichier($chemin, $age_max)) {
				supprimer_fichier($chemin);
Chargement en cours