WP File Manager
Current Path:
/
home
/
argothem
/
www
/
organecyberpresse
/
plugins-dist
/
bigup
/
inc
/
Bigup
/
Name
Action
..
Cache.php
Edit
CacheFichiers.php
Edit
CacheRepertoire.php
Edit
Files.php
Edit
Flow.php
Edit
Formulaire.php
Edit
GestionRepertoires.php
Edit
Identifier.php
Edit
LogTrait.php
Edit
Repondre.php
Edit
Editing: Flow.php
<?php namespace Spip\Bigup; use Spip\Bigup\Cache; /** * Intégration de flow.js (ou resumable.js) côté PHP * * @note * Le fonctionnement est sensiblement le même entre resumable.js et le fork flow.js * Seul le nom du préfixe des variables change * * @link https://github.com/dilab/resumable.php Inspiration * @link https://github.com/flowjs/flow-php-server Autre implémentation pour Flow. * * @plugin Bigup * @copyright 2015 * @author marcimat * @licence GNU/GPL * @package SPIP\Bigup\Fonctions */ include_spip('inc/Bigup/LogTrait'); /** * Retours de la classe Flow * Indique le code de réponse http, et d’éventuelles données. */ class FlowResponse { public $code = 415; public $data = null; public function __construct($code, $data = null) { $this->code = $code; $this->data = $data; } } /** * Réceptionne des morceaux de fichiers envoyés par flow.js **/ class Flow { use LogTrait; /** * Gestion du cache Bigup */ private ?Cache $cache = null; /** * Préfixe utilisé par la librairie JS lors d'une requête */ private string $prefixe = 'flow'; /** * Taille de fichier maximum */ private int $maxSizeFile = 0; /** * Constructeur * @param Cache $cache **/ public function __construct(Cache $cache) { $this->cache = $cache; } /** * Définir la taille maximale des fichiers * @param int $size En Mo */ public function setMaxSizeFile($size) { $this->maxSizeFile = intval($size); } /** * Trouve le prefixe utilisé pour envoyer les données * * La présence d'une des variables signale un envoi effectué par une des librairies js utilisée. * * - 'flow' si flow.js * - 'resumable' si resumable.js * * @return bool True si préfixe présent et trouvé, false sinon. **/ public function trouverPrefixe() { if (_request('flowIdentifier')) { $this->prefixe = 'flow'; return true; } if (_request('resumableIdentifier')) { $this->prefixe = 'resumable'; return true; } return false; } /** * Tester l'arrivée du javascript et agir en conséquence * * 2 possibilités : * * - Le JS demande si un morceau de fichier est déjà présent (par la méthode GET) * - Le JS poste une partie d'un fichier (par la méthode POST) * * Le script retourne * - le chemin du fichier complet si c’est le dernier morceau envoyé, * - sinon un [code http, data] à envoyer * * @return FlowResponse|string * - string : chemin du fichier terminé d’uploadé **/ public function run() { if (!$this->trouverPrefixe()) { return $this->response(415); } if (!empty($_POST) and !empty($_FILES)) { return $this->handleChunk(); } elseif (!empty($_GET)) { return $this->handleTestChunk(); } return $this->response(415); } /** * Retrouve un paramètre de flow * * @param string $nom * @return mixed **/ public function _request($nom) { return _request($this->prefixe . ucfirst($nom)); } /** * Retours à faire partir au navigateur * * @param int $code * @param array|null $data * @return FlowResponse **/ public function response($code, $data = null) { return new FlowResponse($code, $data); } /** * Retours avec texte d’erreur à faire au navigateur * * @param string $message * @param int $code * @return FlowResponse **/ public function responseError($message, $code = 415) { return $this->response($code, [ 'error' => $message ]); } /** * Teste si le morceau de fichier indiqué est déjà sur le serveur * * @return FlowResponse **/ public function handleTestChunk() { $identifier = $this->_request('identifier'); $filename = $this->_request('filename'); $chunkNumber = (int) $this->_request('chunkNumber'); static::info("Test chunk $identifier n°$chunkNumber"); if (!$this->isChunkUploaded($identifier, $filename, $chunkNumber)) { return $this->response(204); } else { return $this->response(200); } } /** * Enregistre un morceau de fichier * * @return FlowResponse|string * - string : Si fichier terminé d'uploader (réception du dernier morceau), retourne le chemin du fichier **/ public function handleChunk() { $identifier = $this->_request('identifier'); $filename = $this->_request('filename'); $chunkNumber = (int) $this->_request('chunkNumber'); $chunkSize = (int) $this->_request('chunkSize'); $totalChunks = (int) $this->_request('totalChunks'); $totalSize = (int) $this->_request('totalSize'); $maxSize = $this->maxSizeFile * 1024 * 1024; static::info("Réception chunk $identifier n°$chunkNumber"); if ($maxSize and $totalSize > $maxSize) { static::info('Fichier reçu supérieur à taille autorisée'); return $this->responseError(_T('bigup:erreur_taille_max', ['taille' => taille_en_octets($maxSize)])); } $file = reset($_FILES); if (!$this->isChunkUploaded($identifier, $filename, $chunkNumber)) { if ( !GestionRepertoires::deplacer_fichier_upload( $file['tmp_name'], $this->tmpChunkPathFile($identifier, $filename, $chunkNumber) ) ) { return $this->response(415); } } // tous les morceaux recus ? if ($this->isFileUploadComplete($filename, $identifier, $totalSize, $totalChunks)) { static::info("Chunks complets de $identifier"); $chemin_parts = $this->cache->parts->fichiers->dir_fichier($identifier, $filename); $chemin_final = $this->cache->final->fichiers->path_fichier($identifier, $filename); $eviter_concurrence = $chemin_parts . DIRECTORY_SEPARATOR . '.done'; if (file_exists($eviter_concurrence)) { static::debug("Chunks de $identifier déjà en traitement"); return $this->response(200); } touch($eviter_concurrence); // recomposer le fichier $fullFile = $this->createFileFromChunks($this->getChunkFiles($chemin_parts), $chemin_final); if (!$fullFile) { // on ne devrait jamais arriver là ! static::error('! Création du fichier complet en échec (' . $chemin_final . ').'); return $this->response(415); } // créer les infos du fichiers $this->cache->final->fichiers->decrire_fichier($identifier, [ 'name' => $filename, 'tmp_name' => $fullFile, 'size' => $totalSize, 'type' => $file['type'], 'error' => 0, // hum ]); // nettoyer le chemin du répertoire de stockage des morceaux du fichiers GestionRepertoires::supprimer_repertoire($chemin_parts); return $fullFile; } // morceau bien reçu, mais pas encore le dernier… return $this->response(200); } /** * Retourne le nom du fichier qui enregistre un des morceaux * * @param string $identifier * @param string $filename * @param int $chunkNumber * @return string Nom de fichier **/ public function tmpChunkPathFile($identifier, $filename, $chunkNumber) { return $this->cache->parts->fichiers->path_fichier($identifier, $filename) . '.part' . $chunkNumber; } /** * Indique si un morceau de fichier a déjà été sauvegardé * * @param string $identifier * @param string $filename * @param int $chunkNumber * @return bool True si présent **/ public function isChunkUploaded($identifier, $filename, $chunkNumber) { return file_exists($this->tmpChunkPathFile($identifier, $filename, $chunkNumber)); } /** * Indique si tous les morceaux d'un fichier ont été reçus * * @param string $filename * @param string $identifier * @param int $totalSize * @param int $totalChunks Nombre de chunks déclarés par flow * @return bool **/ public function isFileUploadComplete($filename, $identifier, $totalSize, $totalChunks) { for ($i = 1; $i <= $totalChunks; $i++) { if (!$this->isChunkUploaded($identifier, $filename, $i)) { return false; } } $chunkTotalSize = $this->getChunkTotalSize($filename, $identifier); if ($totalSize < $chunkTotalSize) { static::error("Taille incorrecte des morceaux pour $identifier : $totalSize attendu, $chunkTotalSize present"); return false; } return true; } /** * Retourne la taille de l’ensemble des morceaux récupérés pour ce fichier * * @param string $identifier * @param string $filename * @return int Taille en octets, total des différentes parties */ public function getChunkTotalSize($filename, $identifier) { $chunksDir = $this->cache->parts->fichiers->dir_fichier($identifier, $filename); $chunks = $this->getChunkFiles($chunksDir); $size = 0; foreach ($chunks as $chunk) { $size += filesize($chunk); } return $size; } /** * Retrouve les morceaux d'un fichier, dans l'ordre ! * * @param string $chemin * Chemin du répertoire contenant les morceaux de fichiers * @return array * Liste de chemins de fichiers **/ public function getChunkFiles($chemin) { // Trouver tous les fichiers du répertoire $chunkFiles = array_diff(scandir($chemin), ['..', '.', '.ok']); // Utiliser un chemin complet, et aucun fichier caché. $chunkFiles = array_map( function ($f) use ($chemin) { if ($f and $f[0] != '.') { return $chemin . DIRECTORY_SEPARATOR . $f; } return ''; }, $chunkFiles ); $chunkFiles = array_filter($chunkFiles); natsort($chunkFiles); return $chunkFiles; } /** * Recrée le fichier complet à partir des morceaux de fichiers * * Supprime les morceaux si l'opération réussie. * * @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 * - string : chemin du fichier complet sinon. **/ public function createFileFromChunks($chunkFiles, $destFile) { // au cas où le fichier complet serait déjà là… if (file_exists($destFile)) { @unlink($destFile); } if (!GestionRepertoires::creer_sous_repertoire(dirname($destFile))) { return false; } // Si un seul morceau c'est qu'il est complet. // on le déplace simplement au bon endroit if (count($chunkFiles) == 1) { if (@rename($chunkFiles[0], $destFile)) { static::info('Fichier complet déplacé : ' . $destFile); return $destFile; } } $fp = fopen($destFile, 'w'); foreach ($chunkFiles as $chunkFile) { fwrite($fp, file_get_contents($chunkFile)); } fclose($fp); if (!file_exists($destFile)) { return false; } static::info('Fichier complet recréé : ' . $destFile); static::debug('Suppression des morceaux.'); foreach ($chunkFiles as $f) { @unlink($f); } return $destFile; } }