Skip to content

Instantly share code, notes, and snippets.

@LostKobrakai
Created February 16, 2017 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LostKobrakai/ddb11c64dcb0f1bf1c8eed4dd534a868 to your computer and use it in GitHub Desktop.
Save LostKobrakai/ddb11c64dcb0f1bf1c8eed4dd534a868 to your computer and use it in GitHub Desktop.
WireUpload subclass to support chunked uploads by http://www.resumablejs.com/
Dropzone.prototype.uploadFiles = function (files) {
var resumable = new Resumable ({
target: '/',
testChunks: true,
chunkSize: 1 * 1024 * 1024
});
if (resumable.support) {
resumable.on('fileProgress', function (file) {
console.log(file)
var progressValue = Math.floor(resumable.progress() * 100);
Dropzone.prototype.defaultOptions.uploadprogress(file.file, progressValue, null);
});
resumable.on('fileSuccess', (file) => {
return this._finished([file.file], "success", null);
});
resumable.on('error', (message, file) => {
return this._errorProcessing([file.file], message, null);
});
resumable.on('fileAdded', () => resumable.upload());
files.forEach(file => resumable.addFile(file))
} else {
// Otherwise use the old upload function
}
}
<?php namespace ProcessWire;
require __DIR__ . '/src/WireUploadChunked.php';
if($input->get->resumableIdentifier) {
if(\LostKobrakai\WireUploadChunked::testChunkFile()){
http_response_code(200);
}else{
http_response_code(204);
}
return $this->halt();
}
if($_SERVER["REQUEST_METHOD"] == "POST") {
$upload_path = new WireTempDir('file_upload');
$upload = new \LostKobrakai\WireUploadChunked('file');
$upload
->setMaxFiles(1)
->setOverwrite(false)
->setMaxFileSize(50 * 1024 * 1024)
->setDestinationPath($upload_path)
->setValidExtensions(['jpg', 'png', 'jpeg', 'mp4']);
$files = $upload->execute();
$errors = $upload->getErrors();
if (count($errors)) {
http_response_code(500);
foreach ($errors as $error) echo $error;
die();
}
$page->of(false);
foreach ($files as $file) {
$path = $upload_path . $file;
$page->files->add($path);
if (is_file($path)) unlink($path);
}
$page->save('files');
return $this->halt();
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.3.0/min/dropzone.min.css">
</head>
<body>
<form action="." class="dropzone" id="my-awesome-dropzone">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.3.0/min/dropzone.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/resumable.js/1.0.2/resumable.min.js"></script>
<script src="main.js">
</body>
</html>
<?php namespace LostKobrakai;
class WireUploadChunked extends \ProcessWire\WireUpload {
/**
* @var bool $uploadingChunkFile
*/
protected $uploadingChunkFile = false;
/**
* @var array $fileInfo
*/
protected $fileInfo;
/**
* @var array $chunkInfo
*/
protected $chunkInfo;
/**
* @var array $chunkTempDir
*/
protected $chunkTempDir;
public function __construct($name) {
parent::__construct($name);
$this->fileInfo = [
'totalChunks' => $this->input->post->resumableTotalChunks,
'sizeChunk' => $this->input->post->resumableChunkSize,
'relPath' => $this->input->post->resumableRelativePath,
'name' => $this->input->post->resumableFilename,
'identifier' => $this->input->post->resumableIdentifier,
'type' => $this->input->post->resumableType,
'size' => $this->input->post->resumableTotalSize
];
$this->chunkInfo = [
'size' => $this->input->post->resumableCurrentChunkSize,
'number' => $this->input->post->resumableChunkNumber
];
$this->chunkTempDir = $this->config->paths->files . 'Chunks/' . $this->fileInfo['identifier'] . '/';
if(!is_dir($this->chunkTempDir)) \ProcessWire\wireMkdir($this->chunkTempDir, true);
}
/**
* Check if a chunk file is present and valid
*
* @return bool
*
*/
static public function testChunkFile() {
$fileInfo = [
'totalChunks' => \ProcessWire\wire('input')->get->resumableTotalChunks,
'sizeChunk' => \ProcessWire\wire('input')->get->resumableChunkSize,
'relPath' => \ProcessWire\wire('input')->get->resumableRelativePath,
'name' => \ProcessWire\wire('input')->get->resumableFilename,
'identifier' => \ProcessWire\wire('input')->get->resumableIdentifier,
'type' => \ProcessWire\wire('input')->get->resumableType,
'size' => \ProcessWire\wire('input')->get->resumableTotalSize
];
$chunkInfo = [
'size' => \ProcessWire\wire('input')->get->resumableCurrentChunkSize,
'number' => \ProcessWire\wire('input')->get->resumableChunkNumber
];
$dir = \ProcessWire\wire('config')->paths->files . 'Chunks/' . $fileInfo['identifier'] . '/';
if(!is_dir($dir)) return false;
$pathname = $dir . $fileInfo['identifier'] . ".part" . $chunkInfo['number'];
if(!is_file($pathname)) return false;
if(filesize($pathname) != $chunkInfo['size']) return false;
return true;
}
/**
* Returns PHP's $_FILES or one constructed from an ajax upload
*
* @return array|bool
* @throws WireException
*
*/
protected function getPhpFiles() {
if($this->fileInfo['totalChunks'] !== null)
return $this->getChunkedFile();
return parent::getPhpFiles();
}
/**
* Does the given filename have a valid extension?
*
* @param string $name
* @return bool
*
*/
protected function isValidExtension($name) {
if($this->uploadingChunkFile) return true;
return parent::isValidExtension($name);
}
/**
* Move chunks to temp folder (60 mins timeout)
*
* @return array|bool
* @throws WireException
*
*/
protected function getChunkedFile() {
if(!$this->isValidFile($this->fileInfo['name'], $this->fileInfo['size'])) return false;
$f = parent::getPhpFiles();
$this->setupChunkFileUpload(function() use($f) {
if($this->isValidUpload($f['name'], $f['size'], $f['error'])){
$name = $this->fileInfo['identifier'] . ".part" . $this->chunkInfo['number'];
$this->saveUpload($f['tmp_name'], $name, !empty($f['ajax']));
}
});
return $this->checkCompleteFile();
}
/**
* Check if file is complete
*
* @return array|bool
* @throws WireException
*
*/
protected function checkCompleteFile() {
// List all the needed chunk files
$files = array_map(function($i){
$filename = $this->fileInfo['identifier'] . ".part$i";
return new \SplFileInfo($this->chunkTempDir . $filename);
}, range(1, $this->fileInfo['totalChunks']));
// Return if any file is not existing or not readable
foreach ($files as $file)
if(!$file->isFile() || !$file->isReadable())
return false;
// Check the summed file size of chunks
$totalSize = array_sum(array_map(function(\SplFileInfo $file) {
return $file->getSize();
}, $files));
if ($totalSize < $this->fileInfo['size']) return false;
// Write file to temp dir
$pathname = tempnam($this->getTempDir(), 'WireUpload');
$fp = fopen($pathname, 'w');
if ($fp === false) return false;
foreach($files as $file)
fwrite($fp, file_get_contents($file->getPathname()));
fclose($fp);
// Remove chunks
\ProcessWire\wireRmdir($this->chunkTempDir, true);
// Return fileinfo for processwire
$filesize = is_file($pathname) ? filesize($pathname) : 0;
$error = $filesize ? \UPLOAD_ERR_OK : \UPLOAD_ERR_NO_FILE;
return array(
'name' => $this->fileInfo['name'],
'tmp_name' => $pathname,
'size' => $filesize,
'error' => $error,
'ajax' => true,
);
}
protected function getTempDir() {
$dir = $this->wire('config')->uploadTmpDir;
if(!$dir || !is_writable($dir)) $dir = ini_get('upload_tmp_dir');
if(!$dir || !is_writable($dir)) $dir = sys_get_temp_dir();
if(!$dir || !is_writable($dir)) {
throw new WireException("Error writing to $dir. Please define \$config->uploadTmpDir and ensure it is writable.");
}
return $dir;
}
/**
* Setup needed to upload chunks
* - Skip extention checks
* - Use different destination for chunks
* - Do not track uploaded files (no need to return their paths)
* - Do always overwrite chunk files
*
* @param callable $callback
*
*/
protected function setupChunkFileUpload(callable $callback) {
$this->uploadingChunkFile = true;
$originalDestination = $this->destinationPath;
$this->destinationPath = $this->chunkTempDir;
$originalOverwrite = $this->overwrite;
$originalCompletedFilenames = $this->completedFilenames;
$this->overwrite = true;
$callback();
$this->completedFilenames = $originalCompletedFilenames;
$this->overwrite = $originalOverwrite;
$this->destinationPath = $originalDestination;
$this->uploadingChunkFile = false;
}
/**
* Check if the uploaded file will be valid (as soon as it's uploaded)
*
* @param string $name
* @param int $size
* @return bool
*
*/
protected function isValidFile($name, $size) {
$valid = false;
$fname = $this->wire('sanitizer')->name($name);
if(!$size) {
$valid = false; // no data
} else if($name[0] == '.') {
$valid = false;
} else if(!$this->isValidExtension($name)) {
$this->error(
"$fname - " . $this->_('Invalid file extension, please use one of:') . ' ' .
implode(', ', $this->validExtensions)
);
} else if($this->maxFileSize > 0 && $size > $this->maxFileSize) {
$this->error("$fname - " . $this->_('Exceeds max allowed file size'));
} else {
$valid = true;
}
return $valid;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment