Skip to content

Instantly share code, notes, and snippets.

@bojovyletoun
Created February 29, 2012 11:54
Show Gist options
  • Save bojovyletoun/1940333 to your computer and use it in GitHub Desktop.
Save bojovyletoun/1940333 to your computer and use it in GitHub Desktop.
FileUploadMulti
<?php
namespace Nette\Forms\Controls;
use Nette\Http\FileUpload;
/** @author bojovyletoun */
class FileUploadMulti extends UploadControl
{
public $filterInsteadReject; /* if */
public static $garbage = array();
public function __construct($label = NULL, $filterInsteadReject = TRUE)
{
parent::__construct($label);
$this->filterInsteadReject = (bool) $filterInsteadReject;
}
public function getControl()
{
$control = parent::getControl();
$control->multiple(TRUE);
foreach ($this->rules as $r) {
if ($r->operation == Form::RANGE) {
list($control->min, $control->max) = $r->arg;
};
}
return $control;
}
public function setValue($value)
{
if (is_array($value)) {
if (isset($value[0]) && $value[0] instanceof FileUpload) { //array of files
$this->value = $value;
} elseif (is_string($value[0])) {
$this->value = $this->buildForOpera($value); //PHP does't recognize multipart/mixed
} else {
$this->value = array(new FileUpload($value)); // single file - standard PHP
}
} elseif ($value instanceof FileUpload) { //single file was uploaded
$this->value = array($value);
} else {
$this->value = array(); // nothing was uploaded
}
foreach ($this->value as $k => $v) {
if (!$v->isOk()
)
unset($this->value[$k]);
}
return $this;
}
public function getHtmlName()
{
return parent::getHtmlName() . '[]';
}
private function buildForOpera()
{
$input = $_POST[$this->name][0]; //HttpRequestFactory damages binary data
$res = preg_match('/^-+([A-Za-z0-9]+)/', substr($input, 0, 200), $boundary); //analyze header => $boundary
if (!$res)
return array();
$chunks = explode($boundary[1], $input); //split to single files, explode is faster
$list = array();
foreach ($chunks as $chunk) {
$hdr = substr($chunk, 0, 1020); //analyze header => $newline, $disp, $inputname, $name, $type
$res = preg_match(
'#^([\\r\\n]+)Content-Disposition:\s*([^;]+);\s*name=\s*"([^"]+)";\s*filename=\s*"([^"]+)"\s*' .
'Content-Type:\s*([a-z0-9/_-]+)\\1\\1#i', $hdr, $matches);
if ($res) {
list($hdr, $newline, $disp, $inputname, $name, $type) = $matches;
$tmp_name = tempnam(ini_get('upload_tmp_dir'), 'upl');
$file = fopen($tmp_name, "wb");
fwrite($file, substr($chunk, strlen($hdr), -strlen($chunks[0] . $newline)));
self::$garbage[] = $tmp_name;
$fileinfo = array(
"tmp_name" => $tmp_name,
"error" => UPLOAD_ERR_OK, // all ok?
"name" => $name,
// "type" => $type, //can't set
"type" => MimeTypeDetector::fromFile($name), //can't set
"size" => filesize($tmp_name),
);
$list[] = new FileUpload($fileinfo); //FileUpload
foreach ($fileinfo as $k => $v) {
$_FILES[$this->name][$k][] = $v; //$_FILES[]
}
}
}
unset($_POST[$this->name]);
if (self::$garbage)
register_shutdown_function(array(__CLASS__, 'garbage'), self::$garbage);
return $list;
}
public static function garbage($list)
{
foreach ($list as $filename) {
if (file_exists($filename))
unlink($filename);
}
}
/* validator helpers */
public static function map($name, $args)
{
$ctrl = $args[0];
$files = $ctrl->value;
$res = array();
foreach ($files as $key => $httpfile) {
$ctrl->value = $httpfile;
$res[] = $valid = call_user_func_array(array("parent", $name), $args);
if (!$valid) {
foreach (array('error', 'size', 'tmp_name', 'name', 'type') as $prop) {
unset($_FILES[$ctrl->name][$prop][$key]);
}
unset($files[$key]);
}
}
$ctrl->value = $files;
if ($ctrl->filterInsteadReject)
return TRUE;
return in_array(FALSE, $res) ? FALSE : TRUE;
}
/* validators */
public static function validateFileSize(UploadControl $control, $limit)
{
return self::map(__FUNCTION__, func_get_args());
}
public static function validateMimeType(UploadControl $control, $mimeType)
{
return self::map(__FUNCTION__, func_get_args());
}
public static function validateImage(UploadControl $control)
{
return self::map(__FUNCTION__, func_get_args());
}
public static function validateRange(UploadControl $ctrl, $range)
{
$files = &$ctrl->value;
$cnt = count($files);
$min = (int) $range[0];
$max = $range[1] ? $range[1] : 4000;
if ($cnt > $max) {
if ($ctrl->filterInsteadReject) {
$files = array_slice($files, 0, $max);
foreach (array('error', 'size', 'tmp_name', 'name', 'type') as $prop) {
$_FILES[$ctrl->name][$prop] = array_slice($_FILES[$ctrl->name][$prop], 0, $max, TRUE);
}return TRUE;
} return false;
} if ($cnt < $min) {
return FALSE;
}return TRUE;
}
public function isFilled()
{
return count($this->value) && $this->value[0]->isOk();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment