Skip to content

Commit

Permalink
Add files
Browse files Browse the repository at this point in the history
  • Loading branch information
mikejpeters committed May 8, 2014
0 parents commit 7e1e1a9
Show file tree
Hide file tree
Showing 5 changed files with 465 additions and 0 deletions.
126 changes: 126 additions & 0 deletions Plupload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

namespace boundstate\plupload;

use Yii;
use yii\base\Widget;
use yii\base\Exception;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\helpers\Url;

/**
* Wrapper for Plupload
* A multiple file upload utility using Flash, Silverlight, Google Gears, HTML5 or BrowserPlus.
* @url http://www.plupload.com/
* @version 1.0
* @author Bound State Software
*/
class Plupload extends Widget
{
/**
* Page URL or action to where the files will be uploaded to.
* @var mixed
*/
public $url;

public $htmlOptions = [];

/**
* The label to display on the browse link.
* @var string
*/
public $browseLabel = 'Select Files';

/**
* HTML options for the browse link.
* @var array
*/
public $browseOptions = [];

/**
* ID of the error container.
* @var string
*/
public $errorContainer;

/**
* Options to pass directly to the JavaScript plugin.
* Please refer to the Plupload documentation:
* @link http://www.plupload.com/documentation.php
* @var array
*/
public $options = [];

/**
* The JavaScript event callbacks to attach to Plupload object.
* @link http://www.plupload.com/example_events.php
* In addition to the standard events, this widget adds a "FileSuccess"
* event that is fired when a file is uploaded without error.
* NOTE: events signatures should all have a first argument for event, in
* addition to the arguments documented on the Plupload website.
* @var array
*/
public $events = [];

/**
* @return int the max upload size in MB
*/
public static function getPHPMaxUploadSize()
{
$max_upload = (int)(ini_get('upload_max_filesize'));
$max_post = (int)(ini_get('post_max_size'));
$memory_limit = (int)(ini_get('memory_limit'));
return min($max_upload, $max_post, $memory_limit);
}

/**
* @inheritdoc
*/
public function init()
{
// Make sure URL is provided
if (empty($this->url))
throw new Exception(Yii::t('yii','{class} must specify "url" property value.',array('{class}'=>get_class($this))));

if (!isset($this->htmlOptions['id']))
$this->htmlOptions['id'] = $this->getId();

$id = $this->htmlOptions['id'];

if (!isset($this->browseOptions['id']))
$this->browseOptions['id'] = "plupload_{$id}_browse";

if (!isset($this->errorContainer))
$this->errorContainer = "plupload_{$id}_em";

$bundle = PluploadAsset::register($this->view);

$defaultOptions = [
'browse_button' => $this->browseOptions['id'],
'url' => Url::to($this->url),
'container' => $id,
'runtimes' => 'gears,html5,flash,silverlight,browserplus',
'flash_swf_url' => "{$bundle->baseUrl}/Moxie.swf",
'silverlight_xap_url' => "{$bundle->baseUrl}/Moxie.xap",
'max_file_size' => self::getPHPMaxUploadSize() . 'mb',
'error_container' => "#{$this->errorContainer}",
];

$options = ArrayHelper::merge($defaultOptions, $this->options);
$options = Json::encode($options);

// Output
echo Html::beginTag('div', $this->htmlOptions);
echo Html::a($this->browseLabel, '#', $this->browseOptions);
echo Html::endTag('div');

// Generate event JavaScript
$events = '';
foreach ($this->events as $event => $callback)
$events .= "uploader.bind('$event', $callback);\n";

$this->view->registerJs("var uploader = new plupload.Uploader($options);\nuploader.init();\n$events");
}
}
268 changes: 268 additions & 0 deletions PluploadAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
<?php

namespace boundstate\plupload;

use yii\base\Action;

/**
* PluploadAction class file.
*
* Copyright 2009, Moxiecode Systems AB
* Released under GPL License.
*
* License: http://www.plupload.com/license
* Contributing: http://www.plupload.com/contributing
*/
class PluploadAction extends Action {

/**
* The directory to upload files to.
* @var string
*/
public $targetDir;

/**
* Maximum execution time in seconds.
* Default is five seconds.
* @var integer
*/
public $maxExecutionTime = 300;

/**
* Whether to remove old files.
* @var boolean
*/
public $cleanup = true;

/**
* Success callback with signature: success($filepath, $params)
* @var callable
*/
public $success;

/**
* Maximum file age in seconds (if cleanup is enabled).
* Default is 5 minutes.
* @var integer
*/
public $maxFileAge = 18000;

/**
* The filename
* @var string
*/
private $_filename;

/**
* The current chunk number.
* @var integer
*/
private $_chunk;

/**
* The total number of chunks.
* @var integer
*/
private $_chunks;

private $_params = array();

/**
* Runs the action.
* This method displays the view requested by the user.
* @throws CHttpException if the view is invalid
*/
public function run() {

if (!$this->targetDir)
$this->targetDir = ini_get("upload_tmp_dir") . DIRECTORY_SEPARATOR . "plupload";

$this->_filename = $this->cleanFilename(isset($_REQUEST["name"]) ? $_REQUEST["name"] : '');
$this->_chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
$this->_chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
$this->_params = $_REQUEST;

@set_time_limit($this->maxExecutionTime);
$this->handleUpload();
}

/**
* Cleans the filename and renames it if necessary.
* @param string $value a filename
* @return string
*/
public function cleanFilename($value)
{
// Clean the fileName for security reasons
$value = preg_replace('/[^\w\._]+/', '_', $value);

// Make sure the fileName is unique but only if chunking is disabled
if ($this->_chunks < 2 && file_exists($this->targetDir . DIRECTORY_SEPARATOR . $value)) {
$ext = strrpos($value, '.');
$fileName_a = substr($value, 0, $ext);
$fileName_b = substr($value, $ext);

$count = 1;
while (file_exists($this->targetDir . DIRECTORY_SEPARATOR . $fileName_a . '_' . $count . $fileName_b))
$count++;

return $fileName_a . '_' . $count . $fileName_b;
} else
return $value;
}

/**
* Returns the full path of the file.
* @return string
*/
public function getFilePath()
{
return $this->targetDir . DIRECTORY_SEPARATOR . $this->_filename;
}

/**
* Returns the header content type.
* @return string
*/
public function getContentType()
{
if (isset($_SERVER["CONTENT_TYPE"]))
return $_SERVER["CONTENT_TYPE"];
if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
return $_SERVER["HTTP_CONTENT_TYPE"];
return null;
}

/**
* Handles the file upload.
*/
protected function handleUpload() {

$this->outputHeaders();

// Create target dir
if (!file_exists($this->targetDir))
@mkdir($this->targetDir);

// Remove old temp files
if ($this->cleanup)
$this->removeOldFiles();

// Handle non multipart uploads older WebKit versions didn't support multipart in HTML5
if (strpos($this->contentType, "multipart") !== false) {
if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
// Open temp file
$out = fopen("{$this->filePath}.part", $this->_chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen($_FILES['file']['tmp_name'], "rb");

if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
$this->outputError(101, 'Failed to open input stream.');
fclose($in);
fclose($out);
@unlink($_FILES['file']['tmp_name']);
} else
$this->outputError(102, 'Failed to open output stream.');
} else
$this->outputError(103, 'Failed to move uploaded file.');
} else {
// Open temp file
$out = fopen("{$this->filePath}.part", $this->_chunk == 0 ? "wb" : "ab");
if ($out) {
// Read binary input stream and append it to temp file
$in = fopen("php://input", "rb");

if ($in) {
while ($buff = fread($in, 4096))
fwrite($out, $buff);
} else
$this->outputError(101, 'Failed to open input stream.');

fclose($in);
fclose($out);
} else
$this->outputError(102, 'Failed to open output stream.');
}

// Check if file has been uploaded
if (!$this->_chunks || $this->_chunk == $this->_chunks - 1) {
// Strip the temp .part suffix off
rename("{$this->filePath}.part", $this->filePath);
}

// Run success callback if specified
if ($this->success) {
try {
$response = call_user_func($this->success, $this->filePath, $this->_params);
echo CJSON::encode($response);
Yii::app()->end();
} catch (Exception $e) {
echo CJSON::encode(array(
'success'=>FALSE,
'errors'=>array('Exception: ' . $e->getMessage()),
));
Yii::app()->end();
}
}

// Return JSON response
echo CJSON::encode(array(
'filepath'=>$this->filePath,
'params'=>$this->_params,
));
Yii::app()->end();
}

/**
* Removes old files from the destination directory.
*/
protected function removeOldFiles()
{
if (is_dir($this->targetDir) && ($dir = @opendir($this->targetDir))) {
while (($file = readdir($dir)) !== false) {
$tmpfilePath = $this->targetDir . DIRECTORY_SEPARATOR . $file;

// Remove temp file if it is older than the max age and is not the current file
if (preg_match('/\.part$/', $file) && (filemtime($tmpfilePath) < time() - $this->maxFileAge) && ($tmpfilePath != "{$this->filePath}.part")) {
@unlink($tmpfilePath);
}
}

closedir($dir);
} else
$this->outputError(100, 'Failed to open temp directory "'.$this->targetDir.'".');
}

/**
* Outputs HTML headers.
*/
protected function outputHeaders()
{
// HTTP headers for no cache etc
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
}

/**
* Outputs an error in JSON.
* @param integer $code error code
* @param string $message error message
*/
protected function outputError($code, $message)
{
echo CJSON::encode(array(
'error' => array(
'code'=>$code,
'message'=>$message,
)
));
Yii::app()->end();
}
}
Loading

0 comments on commit 7e1e1a9

Please sign in to comment.