Стала задача найти скрипт для загрузки нескольких файлов одновременно и интегрировать это решение с фреймворком CodeIgniter 2.
Требования:
- выбор и загрузка нескольких файлов одновременно;
- возможность перетаскивания (drag-and-drop) файлов;
- возможность отменить загрузку в любой момент;
- независимость от внешних фреймворков/библиотек;
- работа без использования flash (!);
- работоспособность под https;
- поддержка клавиатуры;
- прогресс-бар загрузки;
- кроссбраузерность.
Скриптов для загрузки в сети оказалась масса, но большая часть не отвечала перечисленным выше требованиям. Выбор пал на: http://valums.com/ajax-upload/
На этом же сайте можно найти демо-версию а также краткую инструкцию по настроке.
Я же остановлюсь на том как интегрировать этот загрузчить с CodeIgniter 2. Что признаться потребовало небольших усилий.
Этап 1. Скачиваем с официального сайта архив, далее копируем clientfileuploader.js в директорию со скриптами, clientfileuploader.css в директорию с CSS, clientupload_loading.gif в директорию с изображениями соответственно.
Подключаем файл стилей и файл со скриптом в представлении upload_view
...
<link href="/style.css" rel="/css/fileuploader.css" type="text/css" />
...
<div id="file-uploader">
<noscript>
<p>Please enable JavaScript to use file uploader.</p>
<!-- or put a simple form for upload here -->
</noscript>
</div>
<script src="/js/fileuploader.js" type="text/javascript"></script>
<script>
function createUploader() {
var uploader = new qq.FileUploader({
element: document.getElementById('file-uploader'),
action: '/manuals/do_upload',
multiple: true,
debug: true
});
}
// in your app create uploader as soon as the DOM is ready
// don't wait for the window to load
window.onload = createUploader;
</script>
...
Где:
- element — блок в в который будет помещен код загрузчика;
- action — обработчик;
- multiple — выбор нескольких файлов (true/false);
- debug — режим отладки (trur/false).
Этап 2. Правка контроллера:
public function upload()
{
$data = array();
$this->load->view('upload_view', $data);
}
/**
* Обработчик загрузки файлов
*
*/
public function do_upload()
{
// Список поддерживаемых расширений, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array("jpg", "png", "gif");
// Максимально допустимый размер файла, в байтах
$sizeLimit = 3 * 1024 * 1024;
// библиотека в которой будет храниться код обработчика
$this->load->library("qqfileuploader", array($allowedExtensions, $sizeLimit));
// результат работы 'success' => 'true' при успешной загрузке
$result = $this->qqfileuploader->handleUpload('upload/');
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
}
Этап 3. Код самой библиотеки qqfileuploader которую мы подключаем в контроллере:
/**
* Handle file uploads via XMLHttpRequest
*/
class qqUploadedFileXhr {
/**
* Save the file to the specified path
* @return boolean TRUE on success
*/
public function save($path)
{
// Читаем из потока во временный файл,
// проверяем размер файла, если все норм
// записываем в файл $path и возвращаем true
$input = fopen("php://input", "r");
$temp = tmpfile();
$realSize = stream_copy_to_stream($input, $temp);
fclose($input);
if ($realSize != $this->getSize()){
return false;
}
$target = fopen($path, "w");
fseek($temp, 0, SEEK_SET);
stream_copy_to_stream($temp, $target);
fclose($target);
return true;
}
function getName() {
return $_GET['qqfile'];
}
function getSize() {
if (isset($_SERVER["CONTENT_LENGTH"])){
return (int)$_SERVER["CONTENT_LENGTH"];
} else {
throw new Exception('Getting content length is not supported.');
}
}
}
/**
* Handle file uploads via regular form post (uses the $_FILES array)
*/
class qqUploadedFileForm {
/**
* Save the file to the specified path
* @return boolean TRUE on success
*/
function save($path) {
if(!move_uploaded_file($_FILES['qqfile']['tmp_name'], $path)){
return false;
}
return true;
}
function getName() {
return $_FILES['qqfile']['name'];
}
function getSize() {
return $_FILES['qqfile']['size'];
}
}
class Qqfileuploader
{
private $allowedExtensions = array();
private $sizeLimit = 10485760;
private $file;
function __construct(array $params = array())
{
$allowedExtensions = $params[0];
$sizeLimit = $params[1];
$allowedExtensions = array_map("strtolower", $allowedExtensions);
$this->allowedExtensions = $allowedExtensions;
$this->sizeLimit = $sizeLimit;
// проверка post_max_size и upload_max_filesize со значением $sizeLimit
$this->checkServerSettings();
if (isset($_GET['qqfile'])) {
$this->file = new qqUploadedFileXhr();
} elseif (isset($_FILES['qqfile'])) {
$this->file = new qqUploadedFileForm();
} else {
echo "FALSE!";
$this->file = false;
}
}
private function checkServerSettings(){
$postSize = $this->toBytes(ini_get('post_max_size'));
$uploadSize = $this->toBytes(ini_get('upload_max_filesize'));
if ($postSize < $this->sizeLimit || $uploadSize < $this->sizeLimit){
$size = max(1, $this->sizeLimit / 1024 / 1024) . 'M';
die("{'error':'increase post_max_size and upload_max_filesize to $size'}");
}
}
private function toBytes($str){
$val = trim($str);
$last = strtolower($str[strlen($str)-1]);
switch($last) {
case 'g': $val *= 1024;
case 'm': $val *= 1024;
case 'k': $val *= 1024;
}
return $val;
}
/**
* Returns array('success'=>true) or array('error'=>'error message')
*/
function handleUpload($uploadDirectory, $replaceOldFile = FALSE)
{
if (!is_writable($uploadDirectory)){
return array('error' => "Ошибка сервера. Каталог для загрузки не доступен для записи.");
}
if (!$this->file){
return array('error' => 'Ошибка, файл не был загружен. ');
}
$size = $this->file->getSize();
if ($size == 0) {
return array('error' => 'Файл пуст');
}
if ($size > $this->sizeLimit) {
return array('error' => 'Файл слишком велик');
}
$pathinfo = pathinfo($this->file->getName());
//$filename = $pathinfo['filename']; // сохранить имя файла,
$filename = md5(uniqid()); // присвоить уникальное имя
$ext = $pathinfo['extension'];
// проверка на тип файлов по расширению
if($this->allowedExtensions && !in_array(strtolower($ext), $this->allowedExtensions)){
$these = implode(', ', $this->allowedExtensions);
return array('error' => 'Файл имеет неверное расширение, оно должно быть: '. $these . '.');
}
if(!$replaceOldFile){
/// don't overwrite previous files that were uploaded
while (file_exists($uploadDirectory . $filename . '.' . $ext)) {
$filename .= rand(10, 99);
}
}
if ($this->file->save($uploadDirectory . $filename . '.' . $ext)){
return array('success' => true);
} else {
return array('error'=> 'Could not save uploaded file.' .
'The upload was cancelled, or server error encountered');
}
}
}
Если возникнут проблемы с передачей параметров в скрипт может потребоваться правка самого скрипта, вот небольшая инструкция.
