plupload-handler-php
plupload-handler-php copied to clipboard
PluploadFTP.php new feature
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.
this code is of interest but it has been damaged by github because it's in a comment. can you put it up somewhere so it can be obtained as intended?