moxiecode / plupload-handler-php Goto Github PK
View Code? Open in Web Editor NEWConvenience Class to handle Plupload originated file uploads.
Convenience Class to handle Plupload originated file uploads.
Is this class receive any development attention? It would be nice to implement max file size check.
(Using IE and Chrome as "two users" simultaneously on my WAMP server)
If two users upload files with the same name at the same time, the last file to be completed is not written. The temp directory for the last file remains on the server.
Hi,
I see that this page talks about other environments:
http://www.plupload.com/docs/Chunking
for a convenient server-side handler, but only see a PHP variety. Is this example mature enough now to implement in other languages? I'm interested in a Node.js implementation and could create or help with implementing a Node.js version.
Thanks,
Mark
Not an issue, just a question (had no idea where to post otherwise - sorry if this is the wrong place).
Does this script handle pausing/resuming of uploads and network interrupts? With the basic plupload PHP template the upload gets corrupted, does this script fix that?
in some of our project we need to send files above php max-limit with chunking and do not want to chmod upload directories.
so i write FTP add-on to PluploadHandler.php.
first for chunk file name issue you must add these lines to javascript:
BeforeUpload: function (up, file) {
// Called right before the upload for a given file starts, can be used to cancel it if required
up.settings.multipart_params = {
filename: file.name
};
}
so we must add some extra lines to PluploadHandler.php
FIND define('PLUPLOAD_SECURITY_ERR', 105); ADD AFTER
define('PLUPLOAD_FTP_ERR', 106);
define('PLUPLOAD_FTPUP_ERR', 107);
define('PLUPLOAD_FTPCH_ERR', 108);
define('PLUPLOAD_CHDIR_ERR', 109);
define('PLUPLOAD_CHFILE_ERR', 110);
find PLUPLOAD_SECURITY_ERR => "File didn't pass security check." ADD AFTER
,
PLUPLOAD_FTP_ERR => "Failed to connect FTP server.",
PLUPLOAD_FTPUP_ERR => "Failed to send file with FTP.",
PLUPLOAD_FTPCH_ERR => "Failed to combine file with FTP.",
PLUPLOAD_CHDIR_ERR => "Failed to open part file.",
PLUPLOAD_CHFILE_ERR => "Cannot open parted file."
then change appropriate changes in upload.php (mine is this)
`require_once("PluploadHandler.php");
require_once("PluploadFTP.php");
$settings = array(
'target_dir' => 'path/to/upload', //absolute ftp directory
'rel_dir' => 'path/to/upload', //relative directory from script
'allow_extensions' => 'jpg,png',
'ftphost' => 'ftp.domain.tld', // ftp host address
'ftpuser' => 'ftpuser', // ftp user
'ftppass' => 'ftppass' // ftp pass
);
PluploadFTP::no_cache_headers();
PluploadFTP::cors_headers();
if (!PluploadFTP::handleFTP($settings)) {
die(json_encode(array(
'OK' => 0,
'error' => array(
'code' => PluploadHandler::get_error_code(),
'message' => PluploadHandler::get_error_message()
)
)));
} else {
die(json_encode(array('OK' => 1)));
}`
and the new PluploadFTP.php file is this:
`<?php
class PluploadFTP extends PluploadHandler
{
/**
* Handle FTP Upload
*
* @return nothing
* @param array Configuration Files
*/
static function handleFTP($conf = array())
{
// 5 minutes execution time
@set_time_limit(5 * 60);
parent::$_error = null; // start fresh
$conf = self::$conf = array_merge(array(
'file_data_name' => 'file',
'tmp_dir' => ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload",
'target_dir' => false,
'rel_dir' => false,
'cleanup' => true,
'max_file_age' => 5 * 3600,
'chunk' => isset($_REQUEST['chunk']) ? intval($_REQUEST['chunk']) : 0,
'chunks' => isset($_REQUEST['chunks']) ? intval($_REQUEST['chunks']) : 0,
'file_name' => isset($_REQUEST['name']) ? $_REQUEST['name'] : false,
'filename' => isset($_REQUEST['filename']) ? $_REQUEST['filename'] : false,
'allow_extensions' => false,
'delay' => 0,
'cb_sanitize_file_name' => array(__CLASS__, 'sanitize_file_name'),
'cb_check_file' => false,
'ftphost' => false,
'ftpuser' => false,
'ftppass' => false,
), $conf);
try {
if (!$conf['file_name']) {
if (!empty($_FILES)) {
$conf['file_name'] = $_FILES[$conf['file_data_name']]['name'];
} else {
throw new Exception('', PLUPLOAD_INPUT_ERR);
}
}
// Cleanup outdated temp files and folders
if ($conf['cleanup']) {
self::cleanupFTP();
}
// Fake network congestion
if ($conf['delay']) {
usleep($conf['delay']);
}
if (is_callable($conf['cb_sanitize_file_name'])) {
$file_name = call_user_func($conf['cb_sanitize_file_name'], $conf['file_name']);
} else {
$file_name = $conf['file_name'];
}
if($conf['filename']) $file_name = $conf['filename'];
// Check if file type is allowed
if ($conf['allow_extensions']) {
if (is_string($conf['allow_extensions'])) {
$conf['allow_extensions'] = preg_split('{\s*,\s*}', $conf['allow_extensions']);
}
if (!in_array(strtolower(pathinfo($file_name, PATHINFO_EXTENSION)), $conf['allow_extensions'])) {
throw new Exception('', PLUPLOAD_TYPE_ERR);
}
}
$file_path = rtrim($conf['target_dir'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file_name;
$tmp_path = $file_path . ".part";
// Write file or chunk to appropriate temp location
if ($conf['chunks']) {
self::write_file_toFTP("$file_path.dir.part" . DIRECTORY_SEPARATOR . $conf['chunk']);
// Check if all chunks already uploaded
if ($conf['chunk'] == $conf['chunks'] - 1) {
self::write_chunks_to_fileFTP("$file_path.dir.part", $tmp_path);
}
} else {
self::write_file_toFTP($tmp_path);
}
// Upload complete write a temp file to the final destination
if (!$conf['chunks'] || $conf['chunk'] == $conf['chunks'] - 1) {
if (is_callable($conf['cb_check_file']) && !call_user_func($conf['cb_check_file'], $tmp_path)) {
//@self::rrmdirFTP($tmp_path);
throw new Exception('', PLUPLOAD_SECURITY_ERR);
}
$conn_id = self::ftpConnect();
ftp_rename($conn_id, $tmp_path, $file_path);
ftp_close($conn_id);
return array(
'name' => $file_name,
'path' => $file_path,
'size' => filesize($file_path)
);
}
// ok so far
return true;
} catch (Exception $ex) {
parent::$_error = $ex->getCode();
return false;
}
}
/**
* Writes either a multipart/form-data message or a binary stream
* to the specified file with FTP.
*
* @throws Exception In case of error generates exception with the corresponding code
*
* @param string $file_path The path to write the file to
* @param bool $file_data_name The name of the multipart field
*/
static function write_file_toFTP($file_path, $file_data_name = false)
{
if (!$file_data_name) $file_data_name = self::$conf['file_data_name'];
if (!empty($_FILES) && isset($_FILES[$file_data_name])) {
if ($_FILES[$file_data_name]["error"] || !is_uploaded_file($_FILES[$file_data_name]["tmp_name"])) {
throw new Exception('', PLUPLOAD_MOVE_ERR);
}
$file_path = str_replace("\\","/", substr($file_path, 1, strlen($file_path)));
$destination = "ftp://".self::$conf['ftpuser'].":".self::$conf['ftppass']."@".self::$conf['ftphost']."/".$file_path;
$ch = curl_init();
$localfile = $_FILES[$file_data_name]['tmp_name'];
$fp = fopen($localfile, 'r');
curl_setopt($ch, CURLOPT_URL, $destination);
curl_setopt($ch, CURLOPT_UPLOAD, 1);
curl_setopt($ch, CURLOPT_INFILE, $fp);
curl_setopt($ch, CURLOPT_INFILESIZE, filesize($localfile));
curl_setopt($ch, CURLOPT_FTP_CREATE_MISSING_DIRS, 1);
curl_exec ($ch);
$error_no = curl_errno($ch);
curl_close ($ch);
if ($error_no != 0) throw new Exception('', PLUPLOAD_FTPUP_ERR);
} else {
// Handle binary streams
if (!$in = @fopen("php://input", "rb")) throw new Exception('', PLUPLOAD_INPUT_ERR);
$myresult = self::ftpupload($in, $file_path);
if($myresult != 0) throw new Exception('', PLUPLOAD_FTPCH_ERR);
@fclose($in);
}
}
/**
* Combine chunks from the specified folder into the single file with FTP.
*
* @throws Exception In case of error generates exception with the corresponding code
*
* @param string $file_path The file to write the chunks to
*/
static function write_chunks_to_fileFTP($chunk_dir, $file_path)
{
$base_dir = dirname($file_path);
$newFile = str_replace($base_dir.DIRECTORY_SEPARATOR, "", $chunk_dir);
$chunk_file = self::$conf['rel_dir'].DIRECTORY_SEPARATOR.$newFile;
for ($i = 0; $i < self::$conf['chunks']; $i++) {
$chunk_path = $chunk_file . DIRECTORY_SEPARATOR . $i;
if (!file_exists($chunk_path)) throw new Exception('', PLUPLOAD_CHDIR_ERR);
if (!$in = @fopen($chunk_path, "rb")) throw new Exception('', PLUPLOAD_CHFILE_ERR);
$myresult = self::ftpupload($chunk_path, $file_path);
if($myresult != 0) throw new Exception('', PLUPLOAD_FTPCH_ERR);
fclose($in);
}
// Cleanup
self::rrmdirFTP($newFile);
}
/**
* Concise way to recursively remove a directory with FTP
*
* @throws Exception In case of error generates exception with the corresponding code
*
* @param string $dir Directory to remove
*/
private static function rrmdirFTP($dir)
{
$dir = self::$conf['target_dir'].DIRECTORY_SEPARATOR.$dir;
$conn_id = self::ftpConnect();
ftp_chdir($conn_id, $dir);
$contents = ftp_nlist($conn_id, ".");
foreach($contents as $file){
if(is_dir(self::$conf['rel_dir'].DIRECTORY_SEPARATOR.$file))
self::rrmdirFTP($file);
else
@ftp_delete($conn_id, $file);
}
ftp_rmdir($conn_id, $dir);
ftp_close($conn_id);
}
/**
* Cleanup outdated temp files and folders
*
*/
private static function cleanupFTP()
{
/*
$uploadDir = self::$conf['target_dir'];
$conn_id = self::ftpConnect();
ftp_chdir($conn_id, $uploadDir);
$contents = ftp_nlist($conn_id, ".");
foreach($contents as $file){
if(is_dir(self::$conf['rel_dir'].DIRECTORY_SEPARATOR.$file)){
$is_part = substr($file, strlen($file) -5);
if($is_part == ".part"){
if(time() - ftp_mdtm($conn_id, $file) < self::$conf['max_file_age']){
continue;
}
self::rrmdirFTP($file);
}
}
}
ftp_close($conn_id);
*/
}
/**
* Connect to FTP server
*
* @return resource ftp resource
*
* @throws Exception In case of error generates exception with the corresponding code
*
* */
private static function ftpConnect(){
$conn_id = ftp_connect(self::$conf['ftphost']);
if (!$conn_id) throw new Exception('', PLUPLOAD_FTP_ERR);
ftp_login($conn_id, self::$conf['ftpuser'], self::$conf['ftppass']);
ftp_pasv($conn_id, true);
return $conn_id;
}
/**
* FTP upload with append mode
*
* @return bool true/error code
*
* @throws Exception In case of error generates exception with the corresponding code
*
* @param string $chunk_file chunk file
* @param string $file_path last file
*
*/
private static function ftpupload( $chunk_file , $file_path )
{
$file_path = str_replace(DIRECTORY_SEPARATOR,"/", substr($file_path, 1, strlen($file_path)));
$destination = "ftp://".self::$conf['ftpuser'].":".self::$conf['ftppass']."@".self::$conf['ftphost']."/".$file_path;
$ch = curl_init();
if (!$fp = @fopen($chunk_file, "r")) throw new Exception('', PLUPLOAD_INPUT_ERR);
curl_setopt($ch, CURLOPT_UPLOAD, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
curl_setopt($ch, CURLE_OPERATION_TIMEOUTED, 300);
curl_setopt($ch, CURLOPT_URL, $destination);
curl_setopt($ch, CURLOPT_FTPAPPEND, TRUE ); // APPEND FLAG
curl_setopt($ch, CURLOPT_INFILE, $fp);
curl_setopt($ch, CURLOPT_INFILESIZE, filesize($chunk_file));
curl_exec($ch);
fclose ($fp);
$errorMsg = '';
$errorMsg = curl_error($ch);
$errorNumber = curl_errno($ch);
curl_close($ch);
return $errorNumber;
}
}`
so i have got issues about;
i can not check last modified time of chunk directory, so i can not cleanupFTP directory. because the capabilities of ftp_mdtm is not working correctly.
i use this script and it works with chunks correctly.
thanks for your help.
cheers.
"filesize" function used to get the "size" is not working for big files.
return array(
'name' => $file_name,
'path' => $file_path,
'size' => filesize($file_path)
);
replace the function with other function or loose the info.
how to handle additional information in form like title or section number ?
I've included up.setOption('multipart_params'
for additional params but how to handle them in php file ?
Hello,
When using chunks, the final file is recomposed from chunk files.
This procedure takes a lot of time: takes as much as you were copying the file intro another file (on se same disk). Consider a copy of 10GB file, thats too much.
I've changed the class, using the example from here
https://github.com/moxiecode/plupload/blob/master/examples/upload.php
loose "write_file_to" and "write_chunks_to_file" functions
and leave only
$file_path = rtrim($conf['target_dir'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $file_name;
// Open temp file
if (!$out = @fopen("{$file_path}.part", $conf['chunks'] ? "ab" : "wb")) {
throw new Exception('', PLUPLOAD_OUTPUT_ERR);
}
if (!empty($_FILES)) {
if ($_FILES["file"]["error"] || !is_uploaded_file($_FILES["file"]["tmp_name"])) {
throw new Exception('', PLUPLOAD_MOVE_ERR);
}
// Read binary input stream and append it to temp file
if (!$in = @fopen($_FILES["file"]["tmp_name"], "rb")) {
throw new Exception('', PLUPLOAD_INPUT_ERR);
}
}
while ($buff = fread($in, 4096)) {
fwrite($out, $buff);
}
@fclose($out);
@fclose($in);
// Check if file has been uploaded
if (!$conf['chunks'] || $conf['chunk'] == $conf['chunks'] - 1) {
// Strip the temp .part suffix off
rename("{$file_path}.part", $file_path);
}
return array(
'name' => $file_name,
'path' => $file_path,
//'size' => filesize($file_path)
);
thank you.
It's in the config params, but it's not used anywhere.
This code is so well written, thank you!
If John Doe uploads a file named "gangster.jpg" and Jane Doe uploads a completely different image named "gangster.jpg", the previous file is overwritten by this handler. Could you add an option to disable overwrites? Also another option to keep both files? For example, rewriting to "gangster(2).jpg".
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.