Загрузка сразу нескольких файлов
Если нужно дать пользователю возможность загрузки нескольких файлов, традиционное решение на данный момент — использовать для этой цели Flash (реже — Java applet или ActiveX). В случае, если соответствующий плагин недоступен, пользователю, как правило, показывают стандартный HTML-элемент для загрузки файла.
Последнюю ситуацию можно улучшить, если использовать встроенную в браузеры возможность множественной загрузки файлов. Из всех браузеров сейчас данную возможность не поддерживает только Internet Explorer (впрочем, мы ещё не видели девятую версию, может там что-то изменится), остальные браузеры — Opera 9 и выше (а так же версии 3.5—6.05), Firefox 3.6+, Chrome 3.0.191.0+ и Safari 4.0.1+ такую возможность предоставляют.
Достаточно написать что-то вроде
PHP оказался готов к такой конструкции (именно для него в параметре «name» стоят квадратные скобки), он просто разложит загружаемые файлы по элементам массива $_FILES, если только мы не используем «Оперу».
К сожалению, «Опера» (ещё с версии 3.5) отправляет, при использовании мультизагрузки, файлы в контейнере «multipart/mixed», который PHP не понимает.
В качестве парсера я использовал PECL-модуль mailparse (есть бинарник для Windows).
У меня в примере ожидается параметр «file», но это значение легко вынести в настройку. Код мне кажется достаточно простым, чтобы его не комментировать, но, если что-то не понятно, спросите, я добавлю комментарии.
- if ( isset ( $_POST [ 'file' ], $_POST [ 'file' ][ 0 ]))
- if ( $idx = strpos( $_POST [ 'file' ][ 0 ], "\n" ))
- $bound = substr( $_POST [ 'file' ][ 0 ], 2 , $idx - 2 );
- $body = "MIME-Version: 1.0\nContent-type: multipart/form-data; boundary=\n\n" .
- $_POST [ 'file' ][ 0 ];
- unset ( $_POST [ 'file' ][ 0 ]);
- $f = & $_FILES [ 'file' ];
- $f [ 'name' ] = $f [ 'type' ] = $f [ 'tmp_name' ] = $f [ 'error' ] = $f [ 'size' ];
- $msg = mailparse_msg_create();
- if (mailparse_msg_parse( $msg , $body ))
- $i = 0 ;
- foreach (mailparse_msg_get_structure( $msg ) as $st )
- $section = mailparse_msg_get_part( $msg , $st );
- $data = mailparse_msg_get_part_data( $section );
- if ( $data [ 'content-type' ] == 'multipart/form-data' )
- continue ;
- >
- ob_start();
- if (mailparse_msg_extract_part( $section , $body ))
- $tmp = tempnam(sys_get_temp_dir(), 'php' );
- file_put_contents( $tmp , ob_get_clean());
- $f [ 'name' ][ $i ] = $data [ 'disposition-filename' ];
- $f [ 'type' ][ $i ] = $data [ 'content-type' ];
- $f [ 'tmp_name' ][ $i ] = $tmp ;
- $f [ 'error' ][ $i ] = 0 ;
- $f [ 'size' ][ $i ] = filesize( $tmp );
- $i ++;
- > else
- ob_end_clean();
- >
- >
- >
- unset ( $f );
- mailparse_msg_free( $msg );
- >
- >
Я не совсем уверен насчёт публикации этой статьи в блог «PHP», возможно «HTML» подошёл бы больше, с другой стороны, здесь рассматривается способ использования множественной загрузки вместе в PHP.
P.S. перенёс в «Веб-разработка», как предложили в комментариях, действительно блог к теме намного ближе.
When uploading multiple files, the $_FILES variable is created in the form:
[type] => Array
(
[0] => text/plain
[1] => text/plain
)
[tmp_name] => Array
(
[0] => /tmp/phpYzdqkD
[1] => /tmp/phpeEwEWG
)
I found it made for a little cleaner code if I had the uploaded files array in the form
Array
(
[0] => Array
(
[name] => foo.txt
[type] => text/plain
[tmp_name] => /tmp/phpYzdqkD
[error] => 0
[size] => 123
)
[1] => Array
(
[name] => bar.txt
[type] => text/plain
[tmp_name] => /tmp/phpeEwEWG
[error] => 0
[size] => 456
)
)
I wrote a quick function that would convert the $_FILES array to the cleaner (IMHO) array.
function reArrayFiles (& $file_post )
$file_ary = array();
$file_count = count ( $file_post [ 'name' ]);
$file_keys = array_keys ( $file_post );
for ( $i = 0 ; $i < $file_count ; $i ++) foreach ( $file_keys as $key ) $file_ary [ $i ][ $key ] = $file_post [ $key ][ $i ];
>
>
?>
Now I can do the following:
if ( $_FILES [ 'upload' ]) $file_ary = reArrayFiles ( $_FILES [ 'ufile' ]);
foreach ( $file_ary as $file ) print 'File Name: ' . $file [ 'name' ];
print 'File Type: ' . $file [ 'type' ];
print 'File Size: ' . $file [ 'size' ];
>
>
A bit update to 14 year ago note from "phpuser at gmail dot com".
That update converts to a really more friendly array form incoming _POST info for uploaded files.
And that variants works identical for non-multiple uploads and multiple uploads:
//Функция переформатирует массив поданных POST'ом файлов
function reArrayFiles (& $file_post ) $isMulti = is_array ( $file_post [ 'name' ]);
$file_count = $isMulti ? count ( $file_post [ 'name' ]): 1 ;
$file_keys = array_keys ( $file_post );
$file_ary = []; //Итоговый массив
for( $i = 0 ; $i < $file_count ; $i ++)
foreach( $file_keys as $key )
if( $isMulti )
$file_ary [ $i ][ $key ] = $file_post [ $key ][ $i ];
else
$file_ary [ $i ][ $key ] = $file_post [ $key ];
So, if you have an input element like this:
This should be written as
else you'll only be able to get one of the files.
function reArrayImages($file_post) <
$file_ary = [];
$file_keys = array_keys($file_post);
foreach ($file_post as $key => $value) <
foreach ($value as $key2 => $value2) <
$file_ary[$key2][$key] = $value2;
>
>
return $file_ary;
>
Here is a function to fix the indices of a multi-dimensional for easier parsing when dealing with file uploads. It takes a single $_FILES field array as a parameter and separates each individual uploaded file by numeric key. This allows for iterating like:
fixFilesArray ( $_FILES [ 'array_of_files' ]);
foreach ( $_FILES [ 'array_of_files' ] as $position => $file ) // should output array with indices name, type, tmp_name, error, size
var_dump ( $file );
>
?>
Here's the code:
foreach ( $files as $key => $part ) // only deal with valid keys and multiple files
$key = (string) $key ;
if (isset( $names [ $key ]) && is_array ( $part )) foreach ( $part as $position => $value ) $files [ $position ][ $key ] = $value ;
>
// remove old key reference
unset( $files [ $key ]);
>
>
>
?>
The cleanest way to rearrange the $_FILES
function rearrange ( $arr ) foreach( $arr as $key => $all ) foreach( $all as $i => $val ) $new [ $i ][ $key ] = $val ;
>
>
return $new ;
>
?>
This is a very simple example:
if(!empty( $img ))
$img_desc = reArrayFiles ( $img );
print_r ( $img_desc );
foreach( $img_desc as $val )
$newname = date ( 'YmdHis' , time ()). mt_rand (). '.jpg' ;
move_uploaded_file ( $val [ 'tmp_name' ], './uploads/' . $newname );
>
>
function reArrayFiles ( $file )
$file_ary = array();
$file_count = count ( $file [ 'name' ]);
$file_key = array_keys ( $file );
for( $i = 0 ; $i < $file_count ; $i ++)
foreach( $file_key as $val )
$file_ary [ $i ][ $val ] = $file [ $val ][ $i ];
>
>
return $file_ary ;
>
If you try and upload files with multi-dimensional names like this:
You will get an unexpected format like this:
array(
'submission' => array
(
'name' => array( 'screenshot' => 'monster_wallpaper.jpg' ),
'type' => array( 'screenshot' => 'image/jpeg' ),
'tmp_name' => array( 'screenshot' => '/tmp/php48lX2Y' ),
'error' => array( 'screenshot' => 0 ),
'size' => array( 'screenshot' => 223262 ),
),
.
?>
You can use the following function to re-format the array recursively in the usual format:
foreach( $files as $key => & $part )
$key = ( string ) $key ;
if( in_array ( $key , $names ) )
$name = $key ;
if( ! in_array ( $key , $names ) )
$path [] = $key ;
if( is_array ( $part ) )
$part = format_files_array ( $part , $name , $new , $path );
elseif( ! is_array ( $part ) )
$current =& $new ;
foreach( $path as $p )
$current =& $current [ $p ];
$current [ $name ] = $part ;
unset( $path );
$name = null ;
>
>
This is just a modification of the code which is the top note by "phpuser" here. His/her version requires that the $file_post array passed in to the function was created by a form submitted with the multiple attribute set. With multiple set in the html input tag, $_FILES["fileInputName"]["name"] is an array no matter if only one file is sent or multiple. But when is used without the multiple attribute then $_FILES["fileInputName"]["name"] is not an array, it contains the the string with the filename. To use this neat function with or without multiple set and to get back an array which you can "foreach" over in either case, use this modification:
$file_count = $multiple ? count($file_post['name']) : 1;
$file_keys = array_keys($file_post);
for ($i=0; $i foreach ($file_keys as $key)
$file_ary[$i][$key] = $multiple ? $file_post[$key][$i] : $file_post[$key];
>
>
Recursive solution for complex situations (supports any nested arrays including indexed arrays)
function getFixedFilesArray() $walker = function ($arr, $fileInfokey, callable $walker) $ret = array();
foreach ($arr as $k => $v) if (is_array($v)) $ret[$k] = $walker($v, $fileInfokey, $walker);
> else $ret[$k][$fileInfokey] = $v;
>
>
return $ret;
>;
$files = array();
foreach ($_FILES as $name => $values) // init for array_merge
if (!isset($files[$name])) $files[$name] = array();
>
if (!is_array($values['error'])) // normal syntax
$files[$name] = $values;
> else // html array feature
foreach ($values as $fileInfoKey => $subArray) $files[$name] = array_replace_recursive($files[$name], $walker($subArray, $fileInfoKey, $walker));
>
>
>
With multiple file uploads
post_max_size: the total amount of data posted by the client (all files, and all other form field)
upload_max_filesize: the maximum size of 1 single file. (just like )
so, with the directives:
post_max_size 25M
upload_max_filesize 2M
you can send 12 files of up to 2 MB and use up to 1 MB for your additional form-values.
As long as you read only a single copy of 1 file into memory, the memory_limit directive can be held reasonable small as well.
I prefer something like this!
public function arrayImages ( & $file_post )
if( empty( $file_post ) ) return $file_post ;
>
if( 'array' !== gettype ( $file_post [ 'name' ]) ) return $file_post ;
>
$keys = array_keys ( $file_post [ 'name' ]);
$file_array = array();
foreach ( $keys as $key ) foreach ( $file_post as $res => $item ) $file_array [ $key ][ $res ] = $item [ $key ];
>
>
return $file_array ;
>
?>
Once I had to do a maintenance in a huge ERP that had several multiple upload inputs inside an array. Just like this:
The $_FILES array is created like this:
Array
(
[upload] => Array
(
[name] => Array
(
[avatar] => teste.c
[attachment] => teste
)
[type] => Array
(
[avatar] => text/x-csrc
[attachment] => application/octet-stream
)
[tmp_name] => Array
(
[avatar] => /opt/lampp/temp/phpuf3KNj
[attachment] => /opt/lampp/temp/php0yPZap
)
[error] => Array
(
[avatar] => 0
[attachment] => 0
)
[size] => Array
(
[avatar] => 1960
[attachment] => 8661
)
[upload2] => Array
(
[name] => Array
(
[avatar] => jefrey.html
[attachment] => notas.txt
)
[type] => Array
(
[avatar] => text/html
[attachment] => text/plain
)
[tmp_name] => Array
(
[avatar] => /opt/lampp/temp/php87nfyu
[attachment] => /opt/lampp/temp/phpUBlvVz
)
[error] => Array
(
[avatar] => 0
[attachment] => 0
)
[size] => Array
(
[avatar] => 583
[attachment] => 191
)
I've managed to re-arrange this array like this:
Array
(
[upload] => Array
(
[avatar] => Array
(
[name] => teste.c
[type] => text/x-csrc
[tmp_name] => /opt/lampp/temp/phpuf3KNj
[error] => 0
[size] => 1960
)
[attachment] => Array
(
[name] => teste
[type] => application/octet-stream
[tmp_name] => /opt/lampp/temp/php0yPZap
[error] => 0
[size] => 8661
)
[upload2] => Array
(
[avatar] => Array
(
[name] => jefrey.html
[type] => text/html
[tmp_name] => /opt/lampp/temp/php87nfyu
[error] => 0
[size] => 583
)
[attachment] => Array
(
[name] => notas.txt
[type] => text/plain
[tmp_name] => /opt/lampp/temp/phpUBlvVz
[error] => 0
[size] => 191
)
Here's my snippet:
function reArrayFilesMultiple (& $files ) $uploads = array();
foreach( $_FILES as $key0 => $FILES ) foreach( $FILES as $key => $value ) foreach( $value as $key2 => $value2 ) $uploads [ $key0 ][ $key2 ][ $key ] = $value2 ;
>
>
>
$files = $uploads ;
return $uploads ; // prevent misuse issue
>
?>
$countarray = count($_FILES['uploadfile']['name']);
$newarray = array();
for($i=0;$i <$countarray;$i++)<
$newarray[$i]['name']=$_FILES['uploadfile']['name'][$i];
$newarray[$i]['type']=$_FILES['uploadfile']['type'][$i];
$newarray[$i]['tmp_name']=$_FILES['uploadfile']['tmp_name'][$i];
$newarray[$i]['error']=$_FILES['uploadfile']['error'][$i];
$newarray[$i]['size']=$_FILES['uploadfile']['size'][$i];
>
by simply naming differently each file input you'll get easily accesible arrays from $_FILES, in the form $_FILES['input_name']['file_attribute']. For example:
$_FILES['input_name1']['name']. ['input_name1']['size']
$_FILES['input_name2']['name']. ['input_name2']['size']
$_FILES['input_nameX']['name']. ['input_nameX']['size']
If you want to upload multiple file at once, remember "multiple" attribute:
Когда передо мной в очередной раз встала задача об одновременной загрузке нескольких файлов на сервер (без перезагрузки страницы, само собой), я стал блуждать по интернетам в поисках довольно корявого jQuery-плагина, который позволяет имитировать ajax-загрузку файла (того самого плагина, который со скрытым фрэймом: от java- и flash- плагинов сразу было решено отказаться). В процессе поиска я вспомнил, что в грядущем стандарте html 5 возможности по работе с файлами должны быть существенно расширены, и часть этих возможностей доступна уже сейчас. В итоге было решено опробовать их в действии.
Рассматривать возможности File API будем на примере одновременной загрузки нескольких картинок на сервер. В конце статьи приводится готовое решение, оформленное в виде jQuery-плагина.
- Независимость от внешних плагинов
- Возможность контролировать процесс загрузки и отображать информацию о нем (прогрессбар всегда добавляет терпения пользователю)
- Возможность прочитать файл и узнать его размер до начала загрузки (в нашем примере это дает нам возможность отсеять файлы, не содержащие изображений и показывать миниатюры картинок)
- Возможность выбрать сразу несколько файлов через стандартное поле выбора файла
- Возможность использовать интерфейс drag and drop для выбора файлов. Да-да, мы сможем перетаскивать файлы для загрузки прямо с рабочего стола или, например, из проводника!
Для начала разберемся с html-кодом. Нам понадобится дефолтный элемент input, контейнер для перетаскивания файлов и список ul, куда мы будем помещать миниатюрки изображений:
Ничего особенного, кроме того, что для элемента input указан атрибут multiple="true" . Это необходимо для того, чтобы в стандартном диалоге выбора файлов можно было выделять их сразу несколько. Кстати, начиная с Firefox 4, разработчики браузера обещают, что ненавистные многим верстальщикам стандартные поля выбора файла можно будет скрывать, а диалог показывать, вызвав событие click для скрытого элемента.
Теперь перейдем к JavaScript (обратите внимание, что я использовал jQuery для упрощения манипуляций с DOM. Тот, кто по каким-либо причинам захочет отказаться от jQuery, сможет без труда переделать скрипты таким образом, чтобы обойтись без него). Сначала сохраним в переменных ссылки на html-элементы, снявшиеся в главных ролях. Далее определим обработчики событий для стандартного поля выбора файлов и для области, куда можно будет перетаскивать файлы.
И в том и в другом случае в обработчике мы получаем доступ к объекту FileList, который по сути представляет собой массив объектов File. Этот массив передается функции displayFiles(), текст которой приведен ниже.
Объект File содержит метаданные о файле, такие как его имя, размер и тип (в формате MIME, например, image/gif) соответственно в свойствах name, size и type. Для доступа же к содержимому файла существует специальный объект FileReader.
Внутри функции displayFiles() мы проходимся по переданному массиву файлов и сначала отсеиваем те, которые не являются изображениями. Далее для каждого изображения создается элемент списка li, куда помещается пустой пока элемент img (обратите внимание, что в кажом элементе li также создается свойство file, содержащее соответствующий объект). После чего создается экземпляр FileReader и для него определяется обработчик onload, в котором данные передаются прямо в атрибут src созданного ранее элемента img. Метод readAsDataURL() объекта FileReader принимает параметром объект File и запускает чтение данных из него. В результате для всех выбранных через стандартное поле или перетащенных прямо в браузер картинок, мы видим их миниатюры (искусственно уменьшенные до 150 пикселей).
Вот, собственно, и все. С нетерпением ждем утверждения и распространения html 5!
Кое-какие ссылки
-
— работающий пример того, что описывалось выше (плюс еще кое-что) — весь код целиком в архиве — проверка браузеров на соответсвие html 5 (очень наглядно) — площадка для экпериментов с кодом от Google (ее интерфейс будет знаком тем, кто использовал многочисленные API Google)
UPD
Для упрощения использования всего вышеизложенного, был создан JQuery-плагин. При помощи него можно загружать файлы через File API там, где это возможно, и реализовать замену (например, обычную отправку формы) там, где нет. По просьбам трудящихся и соотносясь с замечаниями комментаторов, была добавлена загрузка через объект FormData в браузерах, которые его поддерживают (Chrome, Safari 5+, FF 4+). В самом верху файла с плагином есть описание параметров, методов, а также краткие примеры использования. Более полный пример использования можно увидеть здесь (это изначальный пример из этой статьи, только переделанный на использование плагина, его полный код, включая серверную часть, можно скачать здесь [see UPD2]).
Использованные источники
UPD2
По просьбе пользователя glebovgin плагин был доработан таким образом, чтобы можно было отправлять не только непосредственно объект File, но также Blob-данные (объект Blob). Это может быть полезно, если есть необходимость отправлять на сервер, например, содержимое canvas, ну или просто вручную сгенерированные данные.
В демке (которая переехала немного на другой адрес) был добавлен пример отправки картинки из canvas. На данный момент эта возможность работает в FF, Chrome, IE10.
Исходный код ныне доступен на GitHub. Замечания, предложения, улучшения приветствуются!
Возьмём по умолчанию Chrome v.88. Задача звучит так:
Само собой, можно решить такую задачу, просто сжав все нужные файлы в один ZIP-архив, а потом уже скачать его. Выходит, пользователь скачает единый файл, который потом самостоятельно разархивирует. Например, можно использовать библиотеку jszip, которая позволяет скачивать набор файлов, сжав их.
Вот небольшой пример скачивания с предварительным сжатием из документации:
«А где тут нетривиальность?» — спросите вы. И будете правы. А если речь идёт об одновременном скачивании с сайта двух, трёх или десяти файлов? Например: есть лист в селекте, в котором можно выбрать определённое количество файлов для скачивания. Введём дополнительное условие: у пользователя нет установленного архиватора, поэтому вариант со сжатием в архив отбрасываем. Как решить такую задачу?
Сначала подготовим браузер. Chrome по умолчанию запрещает скачивать мультифайлы. Это сделано в целях безопасности. Поэтому эту функцию надо сначала разблокировать в настройках браузера:
- Заходим в настройки сайтов: chrome://settings/content.
- Переходим в доступ дополнительных прав (Additional permissions).
- Выбираем Automatic downloads.
- Добавляем необходимый сайт в категорию Allow.
Отлично, теперь ваш браузер стал менее безопасным позволяет скачивать на конкретном сайте сразу несколько файлов.
Первый подход рассмотрим на примере генерации файлов с помощью FileReader и API для чтения base64. Отмечу сразу, что у FileReader довольно обширное API, поэтому выбирайте то, что больше нравится: text, arrayBuffer или binaryString.
А ещё можно использовать createObjectURL — он позволяет хранить File-объекты или Blob-объекты.
Два варианта выше генерируют файлы на стороне клиента. Конечно, так будет не всегда, время от времени мы получаем файлы со стороны бэкенда по прямым ссылкам. Это можно реализовать с помощью скачивания по URL. Chrome требует, чтобы была задержка, поэтому особенностью этого метода и станет реализация искусственной задержки.
Итого
Вот три довольно простых способа, с помощью которых можно одновременно скачать несколько файлов с сайта и сделать это быстро. Главное — включите нужные разрешения для конкретного сайта, а затем выбирайте способ по душе.
Вы пытаетесь загрузить несколько файлов одновременно? Вот как реализовать загрузку нескольких файлов с использованием HTML и PHP.
В этой статье я собираюсь показать, как использовать один HTML-файл для загрузки нескольких файлов. В дополнение к этому я продемонстрирую использование нескольких файловых входов с дополнительными полями ввода.
Реализация загрузки нескольких файлов
Во-первых, вам нужно создать HTML- форму с атрибутом enctype = ‘multiple / form-data’ . Фактически, атрибут enctype указывает, как данные формы должны быть закодированы при отправке их на сервер. Когда вы используете формы, которые имеют элемент управления загрузкой файлов, вам нужно указать enctype как множественные / form-data .
Если вы используете ввод одного файла, вам нужно включить элемент file для выбора нескольких файлов. Чтобы сделать это, вам нужно назвать входной файл в виде массива, например. имя = “файлы []” . Кроме того, элемент «Файл ввода» должен иметь несколько = «несколько» или просто несколько.
Форма HTML будет выглядеть следующим образом:
Когда пользователь отправляет форму после выбора файлов, мы можем обработать форму с помощью простых фрагментов PHP следующим образом:
Загрузка нескольких файлов с дополнительной информацией
Иногда требуется загрузить несколько файлов с дополнительной информацией, такой как заголовок, описание и т.д. В таких случаях вам необходимо использовать несколько элементов управления вводом файлов.
На приведенном выше снимке экрана каждый входной файл имеет соответствующий заголовок. Вот пример HTML.
Как видите, есть несколько элементов управления вводом текста и файлов. Добавляя ‘[]’ к вашим именам входных элементов, входные элементы будут передаваться как массивы.
Когда вы отправляете вышеуказанную форму, $ _POST [‘title’] будет массивом. Сопоставляя индекс массива $ _POST [‘title’] с $ _FILES [‘fileUpload’] [‘name’], вы можете получить соответствующий заголовок, пару имен файлов. Например, $ _POST [‘title’] [0] – это заголовок файла $ _FILES [‘fileUpload’] [‘name’] [0] и так далее.
Таким образом, вы можете реализовать несколько загрузок файлов с использованием HTML и PHP.
Проверка типа файла и размера файла
Вы можете ограничить тип файла, проверив расширение загруженного файла с помощью набора разрешенных расширений. Следующий код проверяет правильность файла изображения.
Чтобы проверить размер файла, вы можете использовать $ _FILES [‘image’] [‘size’] следующим образом:
Кроме того, вы также можете переименовать имя файла перед загрузкой. Вот как заменить пробелы в имени файла на подчеркивание и добавить метку времени к имени файла.
Читайте также: