Browse Source

Gros refactoring encore pour y voir plus clair.

- Files gère la relation avec $_FILES, notamment l’extraction des champs / descriptions dans un Files existant, et l’insertion dans $_FILES d’une description de fichier.
- Renommage de Receptionner en Répondre pour être du même noms que ses méthodes internes
- Cache gère le cache des 2 chemins, final / parts, mais délègue ensuite à une classe générique ces 2 répertoires.
- CacheRepertoire gère un répertoire de cache (tmp/bigupload/{nom}/formulaire/arguments formulaire/)
- CacheFichiers gère un répertoire pour les fichiers en fonction du champ du formulaire, de l’identification du fichier et du nom du fichier.
master
Matthieu Marcillaud 5 years ago
parent
commit
482bc07f98
  1. 2
      action/bigup.php
  2. 196
      inc/Bigup.php
  3. 376
      inc/Bigup/Cache.php
  4. 136
      inc/Bigup/CacheFichiers.php
  5. 195
      inc/Bigup/CacheRepertoire.php
  6. 202
      inc/Bigup/Files.php
  7. 32
      inc/Bigup/Flow.php
  8. 48
      inc/Bigup/GestionRepertoires.php
  9. 21
      inc/Bigup/Repondre.php

2
action/bigup.php

@ -10,7 +10,7 @@
function action_bigup_dist() {
include_spip('inc/Bigup');
$bigup = \Spip\Bigup\Receptionner::depuisRequest();
$bigup = \Spip\Bigup\Repondre::depuisRequest();
$bigup->repondre();
exit;

196
inc/Bigup.php

@ -12,12 +12,16 @@ namespace Spip\Bigup;
* @package SPIP\Bigup\Fonctions
*/
include_spip('inc/Bigup/LogTrait');
include_spip('inc/Bigup/Cache');
include_spip('inc/Bigup/CacheFichiers');
include_spip('inc/Bigup/CacheRepertoire');
include_spip('inc/Bigup/Files');
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');
include_spip('inc/Bigup/Repondre');
/**
* Gère la validité des requêtes et appelle Flow
@ -55,7 +59,7 @@ class Bigup {
* @return array
*/
public function retrouver_fichiers() {
$liste = $this->cache->trouver_fichiers_complets();
$liste = $this->cache->final->trouver_fichiers();
$liste = $this->organiser_fichiers_complets($liste);
return $liste;
}
@ -77,11 +81,11 @@ class Bigup {
$uniquement = [$uniquement];
}
$liste = $this->cache->trouver_fichiers_complets();
$liste = $this->cache->final->trouver_fichiers();
foreach ($liste as $champ => $fichiers) {
foreach ($fichiers as $description) {
if (!$uniquement or in_array($description['identifiant'], $uniquement)) {
$this->integrer_fichier($description);
Files::integrer_fichier($description['champ'], $description);
}
}
}
@ -100,9 +104,9 @@ class Bigup {
public function effacer_fichiers($identifiants = []) {
if (!$identifiants) {
$this->debug("Suppression des fichiers");
$this->cache->supprimer_repertoire($this->cache->dir_final());
$this->cache->supprimer_repertoires();
} else {
$this->cache->enlever_fichier_depuis_identifiants($identifiants);
$this->cache->final->supprimer_fichiers($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
@ -146,72 +150,6 @@ class Bigup {
}
/**
* Intégrer le fichier indiqué dans `$FILES`
*
* Tout dépend de l'attribut name qui avait été posté.
*
* Cette info doit se trouver dans le tableau reçu
* dans la clé 'champ'.
*
* Avec `i` le nième fichier posté dans le champ,
* voici un exemple de ce qu'on peut obtenir.
* Noter la position de l'incrément `i` qui se trouve dans le
* premier crochet vide du name.
*
* - name='a' : FILES[a][name] = 'x'
* - name='a[]' : FILES[a][name][i] = 'x'
* - name='a[b]' : FILES[a][name][b] = 'x'
* - name='a[b][]' : FILES[a][name][b][i] = 'x'
* - name='a[][b][]' : FILES[a][i][name][b][0] = 'x'
*
* @param array $description
* Description d'un fichier
* @return array
* Description du fichier
**/
public function integrer_fichier($description) {
// la valeur complete du name.
$champ = $description['champ'];
$arborescence = explode('[', str_replace(']', '', $champ));
$racine = array_shift($arborescence);
if (!count($arborescence)) {
// le plus simple…
$_FILES[$racine] = $description;
} else {
if (!array_key_exists($racine, $_FILES)) {
$_FILES[$racine] = [];
}
$dernier = array_pop($arborescence);
foreach ($description as $cle => $valeur) {
if (!array_key_exists($cle, $_FILES[$racine])) {
$_FILES[$racine][$cle] = [];
}
$me = &$_FILES[$racine][$cle];
foreach ($arborescence as $a) {
if (strlen($a)) {
if (!array_key_exists($a, $me)) {
$me[$a] = [];
}
$me = &$me[$a];
} else {
$i = count($me);
$me[$i] = [];
$me = &$me[$i];
}
}
if (strlen($dernier)) {
$me[$dernier] = $valeur;
} else {
$me[] = $valeur;
}
}
}
return $description;
}
/**
@ -219,121 +157,13 @@ class Bigup {
* est géré par Bigup
*/
public function gerer_fichiers_postes() {
$liste = $this->extraire_fichiers_valides();
$liste = Files::extraire_fichiers_valides();
foreach ($liste as $champ => $fichiers) {
foreach ($fichiers as $description) {
$this->cache->stocker_fichier($champ, $description);
$this->cache->final->stocker_fichier($champ, $description);
}
}
}
/**
* Extrait et enlève de `$_FILES` les fichiers reçus sans erreur
* et crée un tableau avec pour clé le champ d'origine du fichier
*
* @return array Tableau (champ => [description])
*/
public function extraire_fichiers_valides() {
$liste = [];
if (!count($_FILES)) {
return $liste;
}
$infos = []; // name, pathname, error …
foreach ($_FILES as $racine => $descriptions) {
$infos = array_keys($descriptions);
break;
}
foreach ($_FILES as $racine => $descriptions) {
$error = $descriptions['error'];
// cas le plus simple : name="champ", on s'embête pas
if (!is_array($error)) {
if ($error == 0) {
$liste[$racine] = [$descriptions];
unset($_FILES[$racine]);
}
continue;
}
// cas plus compliqués :
// name="champ[tons][][sous][la][pluie][]"
// $_FILES[champ][error][tons][0][sous][la][pluie][0]
else {
$chemins = $this->extraire_sous_chemins_fichiers($error);
foreach ($chemins['phps'] as $k => $chemin) {
$description = [];
foreach ($infos as $info) {
$complet = '$_FILES[\'' . $racine . '\'][\'' . $info . '\']' . $chemin;
eval("\$x = $complet; unset($complet);");
$description[$info] = $x;
}
$complet = $racine . $chemins['names'][$k];
if (empty($liste[$complet])) {
$liste[$complet] = [];
}
$liste[$complet][] = $description;
}
}
}
return $liste;
}
/**
* Retourne l'écriture plate de l'arborescence d'un tableau
*
* - Phps a toutes les arborescences en conservant les index numériques autoincrémentés
* et en mettant les autres index entre guillements
* - Reels a toutes les arborescences en conservant les index numériques autoincrémentés
* - Names a les arborescences sans les index numériques
*
* @param $tableau
* @return array Tableau [ phps => [], reels => [], names => []]
*/
public function extraire_sous_chemins_fichiers($tableau) {
$listes = [
'phps' => [], // ['tons'][0]['sous']['la']['pluie'][0]
'reels' => [], // [tons][0][sous][la][pluie][0]
'names' => [], // [tons][][sous][la][pluie][]
];
// si le name était [], PHP ordonnera les entrées dans l'ordre, forcément.
// si quelqu'un avait mis name="truc[8][]", ça devrait trouver la bonne écriture.
$i = 0;
foreach ($tableau as $cle => $valeur) {
$reel = '[' . $cle . ']';
$php = is_int($cle) ? $reel : '[\'' . $cle . '\']';
if ($cle === $i) {
$name = '[]';
} else {
$name = '[' . $cle . ']';
}
if (is_array($valeur)) {
$ls = $this->extraire_sous_chemins_fichiers($valeur);
foreach ($ls['phps'] as $l) {
$listes['phps'][] = $php . $l;
}
foreach ($ls['reels'] as $l) {
$listes['reels'][] = $reel . $l;
}
foreach ($ls['names'] as $l) {
$listes['names'][] = $name . $l;
}
} else {
$listes['phps'][] = $php;
$listes['reels'][] = $reel;
$listes['names'][] = $name;
}
$i++;
}
return $listes;
}
}

376
inc/Bigup/Cache.php

@ -12,7 +12,6 @@ namespace Spip\Bigup;
* @package SPIP\Bigup\Fonctions
*/
include_spip('inc/Bigup/LogTrait');
/**
* Gère le cache des fichiers dans tmp/bigupload
@ -33,14 +32,14 @@ class Cache {
private $cache_dir = 'bigupload';
/**
* Chemin du répertoire stockant les morceaux de fichiers
* Cache des morceaux de fichiers
* @var string */
private $dir_parts = '';
private $parts = '';
/**
* Chemin du répertoire stockant les fichiers terminés
* Cache des fichiers complets
* @var string */
private $dir_final = '';
private $final = '';
/**
* Constructeur
@ -48,129 +47,20 @@ class Cache {
*/
public function __construct(Identifier $identifier) {
$this->identifier = $identifier;
$this->dir_parts = $this->calculer_chemin_repertoire('parts');
$this->dir_final = $this->calculer_chemin_repertoire('final');
$this->parts = new CacheRepertoire($this, 'parts');
$this->final = new CacheRepertoire($this, 'final');
}
/**
* Calcule un chemin de répertoire de travail d'un type donné
* @param string $type
* Type de répertoire de cache 'parts' ou 'final'.
* @return string
**/
public function calculer_chemin_repertoire($type) {
return
_DIR_TMP . $this->cache_dir
. DIRECTORY_SEPARATOR . $type
. DIRECTORY_SEPARATOR . $this->identifier->auteur
. DIRECTORY_SEPARATOR . $this->identifier->formulaire
. DIRECTORY_SEPARATOR . $this->identifier->formulaire_identifiant
. DIRECTORY_SEPARATOR . $this->identifier->champ;
}
/**
* Retourne le chemin du répertoire stockant les morceaux de fichiers
*
* Si un identifiant décrivant un fichier est fourni, retourne le chemin
* correspondant à cet identifiant de fichier.
*
* Si un fichier est fourni, en plus de l'identifiant, retourne le chemin
* correspondant au fichier
*
* @param string|null $identifiant
* Chaîne identifiant un fichier
* @param string|null $fichier
* Nom du fichier
* @return string|false
*/
public function dir_parts($identifiant = null, $fichier = null) {
return $this->dir($this->dir_parts, $identifiant, $fichier);
}
/**
* Retourne le chemin du répertoire stockant les fichiers complets
*
* Si un identifiant décrivant un fichier est fourni, retourne le chemin
* correspondant à cet identifiant de fichier.
*
* Si un fichier est fourni, en plus de l'identifiant, retourne le chemin
* correspondant au fichier
*
* @param string|null $identifiant
* Chaîne identifiant un fichier
* @param string|null $fichier
* Nom du fichier
* @return string|false
*/
public function dir_final($identifiant = null, $fichier = null) {
return $this->dir($this->dir_final, $identifiant, $fichier);
}
/**
* Retourne un chemin de répertoire cache
* @param string $racine
* @param string $identifiant
* @param string $fichier
* @return string|false
* Pouvoir obtenir les propriétés privées sans les modifier.
* @param string $property
*/
private function dir($racine, $identifiant = null, $fichier = null) {
if (is_null($identifiant) and is_null($fichier)) {
return $racine;
} elseif ($fichier and $identifiant) {
return $racine
. DIRECTORY_SEPARATOR
. $this->hash_identifiant($identifiant)
. DIRECTORY_SEPARATOR
. $this->nommer_fichier($fichier);
} elseif ($identifiant and !$fichier) {
return $racine
. DIRECTORY_SEPARATOR
. $this->hash_identifiant($identifiant);
public function __get($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
return false;
}
/**
* Retourne le nom du répertoire / hash relatif à l'identifiant de fichier indiqué.
* @param string $identifiant
* @return string
*/
public function hash_identifiant($identifiant) {
return substr(md5($identifiant), 0, 6);
}
/**
* Reformater le nom du fichier pour l'écrire sur le serveur
*
* @see copier_document() dans SPIP
* @param string $filename
* @return string Nom du fichier corrigé
*/
function nommer_fichier($filename) {
$extension = pathinfo($filename, PATHINFO_EXTENSION);
include_spip('action/ajouter_documents');
$extension = corriger_extension($extension);
$nom = preg_replace(
"/[^.=\w-]+/", "_",
translitteration(
preg_replace("/\.([^.]+)$/", "",
preg_replace("/<[^>]*>/", '', basename($filename))
)
)
);
return $nom . '.' . $extension;
}
/**
* 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)));
$this->debug("Propriété `$property` demandée mais inexistante.");
return null;
}
/**
@ -182,54 +72,40 @@ class Cache {
* Retourne le chemin final, sinon le chemin partiel
* @return bool
*/
function obtenir_chemin($chemin, $final = true) {
public 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_parts));
if (strpos($chemin, $this->final->dir) === 0) {
$path = substr($chemin, strlen($this->final->dir));
} elseif (strpos($chemin, $this->parts->dir) === 0) {
$path = substr($chemin, strlen($this->parts->dir));
} else {
return false;
}
return ($final ? $this->dir_final : $this->dir_parts) . $path;
return ($final ? $this->final->dir : $this->parts->dir) . $path;
}
/**
* Retourne la liste des fichiers complets, classés par champ
* Supprimer les répertoires caches relatifs à ce formulaire / auteur
*
* @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");
}
* Tous les fichiers partiels ou complets seront effacés,
* et le cache sera nettoyé
*
* @return bool
*/
function supprimer_repertoires() {
$this->final->supprimer_repertoire();
$this->parts->supprimer_repertoire();
return true;
}
return $liste;
/**
* Supprimer le fichier indiqué par son identifiant
* @return bool
*/
function supprimer_fichier($identifiant) {
$this->final->supprimer_fichier($identifiant);
$this->parts->supprimer_fichier($identifiant);
return true;
}
/**
@ -240,10 +116,11 @@ class Cache {
* Chemin du fichier dans le cache de bigup.
* @return array
**/
public function decrire_fichier($chemin) {
public static function decrire_fichier($chemin) {
$filename = basename($chemin);
$extension = pathinfo($chemin, PATHINFO_EXTENSION);
$champ = $this->retrouver_champ_depuis_chemin($chemin);
$champ = self::retrouver_champ_depuis_chemin($chemin);
$identifiant = self::retrouver_identifiant_depuis_chemin($chemin);
include_spip('action/ajouter_documents');
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$desc = [
@ -255,7 +132,7 @@ class Cache {
'error' => 0, // hum
// informations supplémentaires (pas dans $_FILES habituellement)
'pathname' => $chemin,
'identifiant' => md5($chemin),
'identifiant' => $identifiant,
'extension' => corriger_extension(strtolower($extension)),
'champ' => $champ,
];
@ -263,169 +140,26 @@ class Cache {
}
/**
* Pour un champ donné (attribut name) et une description
* de fichier issue des données de `$_FILES`, déplace le fichier
* dans le cache de bigup
*
* @param string $champ
* @param array $description
*/
public function stocker_fichier($champ, $description) {
$nom = $description['name'];
$chemin = $description['tmp_name'];
$nouveau_chemin =
$this->dir_final
. $champ . DIRECTORY_SEPARATOR
. substr(md5($description['size'] . $nom), 10) . DIRECTORY_SEPARATOR
. $nom;
if (GestionRepertoires::creer_sous_repertoire(dirname($nouveau_chemin))) {
if ($this->deplacer_fichier_upload($chemin, $nouveau_chemin)) {
return $this->decrire_fichier($nouveau_chemin);
}
}
return false;
}
/**
* Enlève un fichier complet
*
* @param string $identifiant_ou_repertoire
* Identifiant bigup ou identifiant du répertoire
* @return bool
* True si le fichier est trouvé (et donc enlevé)
**/
public function enlever_fichier($identifiant_ou_repertoire) {
if (!$identifiant_ou_repertoire) {
return false;
}
// si c'est un md5, c'est l'identifiant bigup, sinon celui qui a servi à créer le stockage
if (strlen($identifiant_ou_repertoire) == 32 and ctype_xdigit($identifiant_ou_repertoire)) {
return $this->enlever_fichier_depuis_identifiants($identifiant_ou_repertoire);
}
// sinon c'est un identifant du répertoire
return $this->enlever_fichier_depuis_repertoires($identifiant_ou_repertoire);
}
/**
* 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) {
$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(dirname($description['pathname']));
return true;
}
}
}
return false;
}
/**
* Enlève un fichier (probablement partiel) dont l'identifiant de répertoire est indiqué
*
* @see hash_identifiant() qui a servi à calculer le nom du répertoire de stockage
*
* @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) {
if (!is_array($repertoires)) {
$repertoires = [$repertoires];
}
foreach ($repertoires as $repertoire) {
$this->debug("Demande de suppression du fichier dans : $repertoire");
$this->supprimer_repertoire($this->dir_final($repertoire));
}
return true;
}
/**
* Supprimer le répertoire indiqué et les répertoires parents éventuellement
*
* Si l'on indique une arborescence dans tmp/bigup/final/xxx, le répertoire
* correspondant dans tmp/bigup/parts/xxx sera également supprimé, et inversement.
* Retrouve un nom de champ depuis un chemin de cache de fichier
*
* @param string $chemin
* Chemin du répertoire stockant un fichier bigup
* @return bool
* 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 supprimer_repertoire($chemin) {
// Suppression du contenu du fichier final
if ($dir = $this->obtenir_chemin($chemin, true)) {
GestionRepertoires::supprimer_repertoire($dir);
}
// Suppression du contenu des morceaux du fichier
if ($dir = $this->obtenir_chemin($chemin, false)) {
GestionRepertoires::supprimer_repertoire($dir);
}
return true;
public static function retrouver_champ_depuis_chemin($chemin) {
return basename(dirname(dirname($chemin)));
}
/**
* Déplacer ou copier un fichier
*
* @note
* Proche de inc/documents: deplacer_fichier_upload()
* mais sans l'affichage d'erreur éventuelle.
*
* @uses _DIR_RACINE
* @uses spip_unlink()
* Retrouve un nom de champ depuis un chemin de cache de fichier
*
* @param string $source
* Fichier source à copier
* @param string $dest
* Fichier de destination
* @param bool $move
* - `true` : on déplace le fichier source vers le fichier de destination
* - `false` : valeur par défaut. On ne fait que copier le fichier source vers la destination.
* @return bool|mixed|string
* @param string $chemin
* Chemin de stockage du fichier dans le cache de bigupload
* @return string
* Identifiant du fichier
*/
function deplacer_fichier_upload($source, $dest, $move=false) {
// Securite
if (substr($dest, 0, strlen(_DIR_RACINE)) == _DIR_RACINE) {
$dest = _DIR_RACINE . preg_replace(',\.\.+,', '.', substr($dest, strlen(_DIR_RACINE)));
} else {
$dest = preg_replace(',\.\.+,', '.', $dest);
}
if (!GestionRepertoires::creer_sous_repertoire(dirname($dest))) {
return false;
}
if ($move) {
$ok = @rename($source, $dest);
} else {
$ok = @copy($source, $dest);
}
if (!$ok) {
$ok = @move_uploaded_file($source, $dest);
}
if ($ok) {
@chmod($dest, _SPIP_CHMOD & ~0111);
}
return $ok ? $dest : false;
public static function retrouver_identifiant_depuis_chemin($chemin) {
return basename(dirname($chemin));
}
}

136
inc/Bigup/CacheFichiers.php

@ -0,0 +1,136 @@
<?php
namespace Spip\Bigup;
/**
* Gère le cache des fichiers dans tmp/bigupload
*
* @plugin Bigup
* @copyright 2016
* @author marcimat
* @licence GNU/GPL
* @package SPIP\Bigup\Fonctions
*/
/**
* Gère le cache des fichiers dans tmp/bigupload
*
**/
class CacheFichiers {
use LogTrait;
/**
* Cache racine du stockage
* @var CacheRepertoire */
private $cache = null;
/**
* Nom du champ
* @var string */
private $champ = null;
/**
* Constructeur
*
* @param string $dir_racine
* Chemin de stockage de ces fichiers pour ce formulaire
* @param string $nom_du_champ
* Nom du champ (valeur de l'attribut name) pour ces fichiers
*/
public function __construct(CacheRepertoire $cache, $champ) {
$this->cache = $cache;
$this->champ = $champ;
}
/**
* Retourne le chemin du cache pour ce champ du formulaire
* @return string
*/
public function dir_champ() {
return $this->cache->dir . DIRECTORY_SEPARATOR . $this->champ;
}
/**
* Retourne le chemin du cache pour cet identifiant de fichier du formulaire
* @return string
*/
public function dir_identifiant($identifiant) {
return $this->dir($identifiant);
}
/**
* Retourne le chemin du cache pour cet identifiant de fichier et nom de fichier du formulaire
* @return string
*/
public function dir_fichier($identifiant, $fichier) {
return $this->dir($identifiant, $fichier);
}
/**
* Retourne le chemin du répertoire stockant les morceaux de fichiers
*
* Si un identifiant décrivant un fichier est fourni, retourne le chemin
* correspondant à cet identifiant de fichier.
*
* Si un fichier est fourni, en plus de l'identifiant, retourne le chemin
* correspondant au fichier
*
* @param string|null $identifiant
* Chaîne identifiant un fichier
* @param string|null $fichier
* Nom du fichier
* @return string|false
*/
private function dir($identifiant = null, $fichier = null) {
if (is_null($identifiant) and is_null($fichier)) {
return $this->dir_champ();
} elseif ($fichier and $identifiant) {
return $this->dir_champ()
. DIRECTORY_SEPARATOR
. $this->hash_identifiant($identifiant)
. DIRECTORY_SEPARATOR
. $this->nommer_fichier($fichier);
} elseif ($identifiant and !$fichier) {
return $this->dir_champ()
. DIRECTORY_SEPARATOR
. $this->hash_identifiant($identifiant);
}
return false;
}
/**
* Retourne le nom du répertoire / hash relatif à l'identifiant de fichier indiqué.
* @param string $identifiant
* @return string
*/
public function hash_identifiant($identifiant) {
return substr(md5($identifiant), 0, 8);
}
/**
* Reformater le nom du fichier pour l'écrire sur le serveur
*
* @see copier_document() dans SPIP
* @param string $filename
* @return string Nom du fichier corrigé
*/
function nommer_fichier($filename) {
$extension = pathinfo($filename, PATHINFO_EXTENSION);
include_spip('action/ajouter_documents');
$extension = corriger_extension($extension);
$nom = preg_replace(
"/[^.=\w-]+/", "_",
translitteration(
preg_replace("/\.([^.]+)$/", "",
preg_replace("/<[^>]*>/", '', basename($filename))
)
)
);
return $nom . '.' . $extension;
}
}

195
inc/Bigup/CacheRepertoire.php

@ -0,0 +1,195 @@
<?php
namespace Spip\Bigup;
/**
* Gère le cache des fichiers dans tmp/bigupload
*
* @plugin Bigup
* @copyright 2016
* @author marcimat
* @licence GNU/GPL
* @package SPIP\Bigup\Fonctions
*/
/**
* Gère le cache des fichiers dans tmp/bigupload
**/
class CacheRepertoire {
use LogTrait;
/**
* Gestion générale du cache
* @var Cache
*/
private $cache = null;
/**
* Chemin du répertoire temporaire pour ce formulaire
* @var string */
private $dir = '';
/**
* Chemin du répertoire temporaire pour un champ de formulaire
* @var string */
private $fichiers = null;
/**
* Constructeur
* @param Identifier $identifier
* @param string $nom
* Nom du répertoire de cache
*/
public function __construct(Cache $cache, $nom) {
$this->cache = $cache;
$this->dir =
_DIR_TMP . $this->cache->cache_dir
. DIRECTORY_SEPARATOR . $nom
. DIRECTORY_SEPARATOR . $this->cache->identifier->auteur
. DIRECTORY_SEPARATOR . $this->cache->identifier->formulaire
. DIRECTORY_SEPARATOR . $this->cache->identifier->formulaire_identifiant;
// Si le nom du champ est connu, on crée une facilité pour accéder au chemin des fichiers
if ($this->cache->identifier->champ) {
$this->fichiers = new CacheFichiers($this, $this->cache->identifier->champ);
}
}
/**
* Pouvoir obtenir les propriétés privées sans les modifier.
* @param string $property
*/
public function __get($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
$this->debug("Propriété `$property` demandée mais inexistante.");
return null;
}
/**
* Retourne la liste des fichiers de ce cache,
* classés par champ
*
* @return array Liste [ champ => [ chemin ]]
**/
public function trouver_fichiers() {
// la théorie veut ce rangement :
// $dir/{champ}/{identifiant_fichier}/{nom du fichier.extension}
$directory = $this->dir;
// 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 = Cache::retrouver_champ_depuis_chemin($chemin);
if (empty($liste[$champ])) {
$liste[$champ] = [];
}
$liste[$champ][] = Cache::decrire_fichier($chemin);
$this->debug("Fichier retrouvé : $chemin");
}
return $liste;
}
/**
* Pour un champ donné (attribut name) et une description
* de fichier issue des données de `$_FILES`, déplace le fichier
* dans le cache de bigup
*
* @param string $champ
* @param array $description
*/
public function stocker_fichier($champ, $description) {
$nom = $description['name'];
$chemin = $description['tmp_name'];
$nouveau = new CacheFichiers($this, $champ);
$nouveau_chemin = $nouveau->dir_fichier($description['size'] . $nom, $nom);
if (GestionRepertoires::creer_sous_repertoire(dirname($nouveau_chemin))) {
if (GestionRepertoires::deplacer_fichier_upload($chemin, $nouveau_chemin)) {
return Cache::decrire_fichier($nouveau_chemin);
}
}
return false;
}
/**
* Enlève un fichier
*
* @param string $identifiant_ou_repertoire
* Identifiant du fichier, tel que créé avec CacheFichiers::hash_identifiant()
* @return bool
* True si le fichier est trouvé (et donc enlevé)
**/
public function supprimer_fichier($identifiant)
{
if (!$identifiant) {
return false;
}
return $this->supprimer_fichiers([$identifiant]);
}
/**
* Enlève des fichiers dont les identifiants sont indiqués
*
* @param string|array $identifiants
* Identifiant ou liste d'identifiants de fichier
**/
public function supprimer_fichiers($identifiants) {
$liste = $this->trouver_fichiers();
if (!is_array($identifiants)) {
$identifiants = [$identifiants];
}
$identifiants = array_filter($identifiants);
$this->debug("Demande de suppression de fichiers : " . implode(', ', $identifiants));
foreach ($liste as $champ => $fichiers) {
foreach ($fichiers as $description) {
if (in_array($description['identifiant'], $identifiants)) {
GestionRepertoires::supprimer_repertoire(dirname($description['pathname']));
}
}
}
}
/**
* Supprimer le répertoire indiqué et les répertoires parents éventuellement
*
* Si l'on indique une arborescence dans tmp/bigup/final/xxx, le répertoire
* correspondant dans tmp/bigup/parts/xxx sera également supprimé, et inversement.
*
* @param string $chemin
* Chemin du répertoire stockant un fichier bigup
* @return bool
*/
function supprimer_repertoire() {
GestionRepertoires::supprimer_repertoire($this->dir);
return true;
}
}

202
inc/Bigup/Files.php

@ -0,0 +1,202 @@
<?php
namespace Spip\Bigup;
/**
* Gestion des relations avec `$_FILES`
*
* @plugin Bigup
* @copyright 2016
* @author marcimat
* @licence GNU/GPL
* @package SPIP\Bigup\Fonctions
*/
/**
* Gestion des relations avec `$_FILES`
**/
class Files {
use LogTrait;
/**
* Intégrer le fichier indiqué dans `$FILES`
*
* Tout dépend de l'attribut name qui avait été posté.
*
* Cette info doit se trouver dans le tableau reçu
* dans la clé 'champ'.
*
* Avec `i` le nième fichier posté dans le champ,
* voici un exemple de ce qu'on peut obtenir.
* Noter la position de l'incrément `i` qui se trouve dans le
* premier crochet vide du name.
*
* - name='a' : FILES[a][name] = 'x'
* - name='a[]' : FILES[a][name][i] = 'x'
* - name='a[b]' : FILES[a][name][b] = 'x'
* - name='a[b][]' : FILES[a][name][b][i] = 'x'
* - name='a[][b][]' : FILES[a][i][name][b][0] = 'x'
*
* @param string $champ
* Valeur de l'attribut name du champ.
* @param array $description
* Description d'un fichier
* @return array
* Description du fichier
**/
public static function integrer_fichier($champ, $description) {
$arborescence = explode('[', str_replace(']', '', $champ));
$racine = array_shift($arborescence);
if (!count($arborescence)) {
// le plus simple…
$_FILES[$racine] = $description;
} else {
if (!array_key_exists($racine, $_FILES)) {
$_FILES[$racine] = [];
}
$dernier = array_pop($arborescence);
foreach ($description as $cle => $valeur) {
if (!array_key_exists($cle, $_FILES[$racine])) {
$_FILES[$racine][$cle] = [];
}
$me = &$_FILES[$racine][$cle];
foreach ($arborescence as $a) {
if (strlen($a)) {
if (!array_key_exists($a, $me)) {
$me[$a] = [];
}
$me = &$me[$a];
} else {
$i = count($me);
$me[$i] = [];
$me = &$me[$i];
}
}
if (strlen($dernier)) {
$me[$dernier] = $valeur;
} else {
$me[] = $valeur;
}
}
}
return $description;
}
/**
* Extrait et enlève de `$_FILES` les fichiers reçus sans erreur
* et crée un tableau avec pour clé le champ d'origine du fichier
*
* @return array Tableau (champ => [description])
*/
public static function extraire_fichiers_valides() {
$liste = [];
if (!count($_FILES)) {
return $liste;
}
$infos = []; // name, pathname, error …
foreach ($_FILES as $racine => $descriptions) {
$infos = array_keys($descriptions);
break;
}
foreach ($_FILES as $racine => $descriptions) {
$error = $descriptions['error'];
// cas le plus simple : name="champ", on s'embête pas
if (!is_array($error)) {
if ($error == 0) {
$liste[$racine] = [$descriptions];
unset($_FILES[$racine]);
}
continue;
}
// cas plus compliqués :
// name="champ[tons][][sous][la][pluie][]"
// $_FILES[champ][error][tons][0][sous][la][pluie][0]
else {
$chemins = Files::extraire_sous_chemins_fichiers($error);
foreach ($chemins['phps'] as $k => $chemin) {
$description = [];
foreach ($infos as $info) {
$complet = '$_FILES[\'' . $racine . '\'][\'' . $info . '\']' . $chemin;
eval("\$x = $complet; unset($complet);");
$description[$info] = $x;
}
$complet = $racine . $chemins['names'][$k];
if (empty($liste[$complet])) {
$liste[$complet] = [];
}
$liste[$complet][] = $description;
}
}
}
return $liste;
}
/**
* Retourne l'écriture plate de l'arborescence d'un tableau
*
* - Phps a toutes les arborescences en conservant les index numériques autoincrémentés
* et en mettant les autres index entre guillements
* - Reels a toutes les arborescences en conservant les index numériques autoincrémentés
* - Names a les arborescences sans les index numériques
*
* @param $tableau
* @return array Tableau [ phps => [], reels => [], names => []]
*/
public static function extraire_sous_chemins_fichiers($tableau) {
$listes = [
'phps' => [], // ['tons'][0]['sous']['la']['pluie'][0]
'reels' => [], // [tons][0][sous][la][pluie][0]
'names' => [], // [tons][][sous][la][pluie][]
];
// si le name était [], PHP ordonnera les entrées dans l'ordre, forcément.
// si quelqu'un avait mis name="truc[8][]", ça devrait trouver la bonne écriture.
$i = 0;
foreach ($tableau as $cle => $valeur) {
$reel = '[' . $cle . ']';
$php = is_int($cle) ? $reel : '[\'' . $cle . '\']';
if ($cle === $i) {
$name = '[]';
} else {
$name = '[' . $cle . ']';
}
if (is_array($valeur)) {
$ls = Files::extraire_sous_chemins_fichiers($valeur);
foreach ($ls['phps'] as $l) {
$listes['phps'][] = $php . $l;
}
foreach ($ls['reels'] as $l) {
$listes['reels'][] = $reel . $l;
}
foreach ($ls['names'] as $l) {
$listes['names'][] = $name . $l;
}
} else {
$listes['phps'][] = $php;
$listes['reels'][] = $reel;
$listes['names'][] = $name;
}
$i++;
}
return $listes;
}
}

32
inc/Bigup/Flow.php

@ -30,7 +30,7 @@ class Flow {
/**
* Gestion du cache Bigup
* @var Identifier
* @var Cache
*/
private $cache = null;
@ -150,7 +150,7 @@ class Flow {
$key = key($_FILES);
if (!$this->isChunkUploaded($identifier, $filename, $chunkNumber)) {
if (!$this->cache->deplacer_fichier_upload(
if (!GestionRepertoires::deplacer_fichier_upload(
$file['tmp_name'],
$this->tmpChunkPathFile($identifier, $filename, $chunkNumber))
) {
@ -163,14 +163,18 @@ class Flow {
$this->info("Chunks complets de $identifier");
// recomposer le fichier
$chemin_final = $this->cache->dir_final($identifier, $filename);
$fullFile = $this->createFileFromChunks($this->getChunkFiles($identifier), $chemin_final);
$chemin_parts = $this->cache->parts->fichiers->dir_identifiant($identifier);
$chemin_final = $this->cache->final->fichiers->dir_fichier($identifier, $filename);
$fullFile = $this->createFileFromChunks($this->getChunkFiles($chemin_parts), $chemin_final);
if (!$fullFile) {
// on ne devrait jamais arriver là !
$this->error("! Création du fichier complet en échec (" . $chemin_final . ").");
return $this->send(415);
}
// nettoyer le chemin du répertoire de stockage des morceaux du fichiers
GestionRepertoires::supprimer_repertoire($chemin_parts);
return $fullFile;
} else {
// morceau bien reçu, mais pas encore le dernier…
@ -200,7 +204,7 @@ class Flow {
* @return string Nom de fichier
**/
public function tmpChunkPathFile($identifier, $filename, $chunkNumber) {
return $this->cache->dir_parts($identifier, $filename) . '.part' . $chunkNumber;
return $this->cache->parts->fichiers->dir_fichier($identifier, $filename) . '.part' . $chunkNumber;
}
/**
@ -240,19 +244,20 @@ class Flow {
/**
* Retrouve les morceaux d'un fichier, dans l'ordre !
*
* @param string $identifier
* @return array Liste de chemins de fichiers
* @param string $chemin
* Chemin du répertoire contenant les morceaux de fichiers
* @return array
* Liste de chemins de fichiers
**/
public function getChunkFiles($identifier) {
public function getChunkFiles($chemin) {
// Trouver tous les fichiers du répertoire
$dir_parts = $this->cache->dir_parts($identifier);
$chunkFiles = array_diff(scandir($dir_parts), ['..', '.', '.ok']);
$chunkFiles = array_diff(scandir($chemin), ['..', '.', '.ok']);
// Utiliser un chemin complet, et aucun fichier caché.
$chunkFiles = array_map(
function ($f) use ($dir_parts) {
function ($f) use ($chemin) {
if ($f and $f[0] != '.') {
return $dir_parts . DIRECTORY_SEPARATOR . $f;
return $chemin . DIRECTORY_SEPARATOR . $f;
}
return '';
},
@ -270,7 +275,8 @@ class Flow {
*
* Supprime les morceaux si l'opération réussie.
*
* @param array $crunkFiles Chemin des morceaux de fichiers à concaténer (dans l'ordre)
* @param array $chunkFiles
* Chemin des morceaux de fichiers à concaténer (dans l'ordre)
* @param string $destFile Chemin du fichier à créer avec les morceaux
* @return false|string
* - false : erreur

48
inc/Bigup/GestionRepertoires.php

@ -237,4 +237,52 @@ class GestionRepertoires {
}
return true;
}
/**
* Déplacer ou copier un fichier
*
* @note
* Proche de inc/documents: deplacer_fichier_upload()
* mais sans l'affichage d'erreur éventuelle.
*
* @uses _DIR_RACINE
* @uses spip_unlink()
*
* @param string $source
* Fichier source à copier
* @param string $dest
* Fichier de destination
* @param bool $move
* - `true` : on déplace le fichier source vers le fichier de destination
* - `false` : valeur par défaut. On ne fait que copier le fichier source vers la destination.
* @return bool|mixed|string
*/
public static function deplacer_fichier_upload($source, $dest, $move=false) {
// Securite
if (substr($dest, 0, strlen(_DIR_RACINE)) == _DIR_RACINE) {
$dest = _DIR_RACINE . preg_replace(',\.\.+,', '.', substr($dest, strlen(_DIR_RACINE)));
} else {
$dest = preg_replace(',\.\.+,', '.', $dest);
}
if (!GestionRepertoires::creer_sous_repertoire(dirname($dest))) {
return false;
}
if ($move) {
$ok = @rename($source, $dest);
} else {
$ok = @copy($source, $dest);
}
if (!$ok) {
$ok = @move_uploaded_file($source, $dest);
}
if ($ok) {
@chmod($dest, _SPIP_CHMOD & ~0111);
}
return $ok ? $dest : false;
}
}

21
inc/Bigup/Receptionner.php → inc/Bigup/Repondre.php

@ -12,7 +12,6 @@ namespace Spip\Bigup;
* @package SPIP\Bigup\Fonctions
*/
include_spip('inc/Bigup/LogTrait');
/**
* Gère la réception d'actions ajax
@ -22,7 +21,7 @@ include_spip('inc/Bigup/LogTrait');
* - Supprime les fichiers demandés.
*
**/
class Receptionner {
class Repondre {
use LogTrait;
@ -77,10 +76,10 @@ class Receptionner {
* @return Receptionner
*/
public static function depuisRequest() {
$receptionner = new self(Identifier::depuisRequest());
$receptionner->action = _request('bigup_action');
$receptionner->identifiant = _request('identifiant');
return $receptionner;
$repondre = new self(Identifier::depuisRequest());
$repondre->action = _request('bigup_action');
$repondre->identifiant = _request('identifiant');
return $repondre;
}
/**
@ -121,8 +120,9 @@ class Receptionner {
if (!$this->identifiant) {
return $this->send(404);
}
// si c'est un md5, c'est l'identifiant bigup, sinon celui de flow.
if ($this->cache->enlever_fichier($this->identifiant)) {
// Soit c'est l'identifiant d'origine de Flow,
// Soit c'est l'identifiant du répertoire de ce fichier dans le cache
if ($this->cache->supprimer_fichier($this->identifiant)) {
return $this->send(201);
}
return $this->send(404);
@ -140,16 +140,13 @@ class Receptionner {
// le fichier est complet
if (is_string($res)) {
// remettre le fichier dans $FILES
# $this->integrer_fichier($res);
# Files::integrer_fichier($res);
// envoyer quelques infos sur le fichier reçu
$desc = $this->cache->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->cache->obtenir_chemin(dirname($res), false));
$this->send(200, $desc);
}
Loading…
Cancel
Save