Uploading a ZIP File
It is easy enough to upload and process a single file in PHP, but uploading a folder full of data will need a different approach.
The simplest way to upload a folder is to Zip it first and upload the file. At the server end, we can unzip it and put the contents into a nominated folder.
Here will look at how to process a zip file full of individual files. In this case, we won’t have any nested folders, which will simplify the process.
The Upload Button
To begin with, we will need an upload button.
<form method="post" enctype="multipart/form-data" action="">
<label for="import-folder">Import Folder</label>
<input type="file" name="import-folder" accept="application/zip">
</form>
- Like all upload forms, you need to include
method="post"
and theenctype="multipart/form-data"
. The action can be anywhere, of course. - For convenience we can include
accept="application/zip"
which will cause the browser to check for a zip file; however, we will check the uploaded file at the PHP end as well.
Processing the File
When we upload the file, we will first need to check the Mime Type. Here, we can write a MimeType()
function which is a wrapper around something more complex, and it would be a good idea to include it in whatever library you normally use.
function MimeType($filename) {
$finfo = new finfo(FILEINFO_MIME_TYPE);
return $finfo->file($filename);
}
This is more reliable than depending on the Mime Type from the $_FILES
array, since that can be faked, or, at least, inaccurate.
Like all uploaded files, the data will appear in the $_FILES
array. In our example, it will be in $_FILES['import-folder']
.
For convenience we can assign this to a simple variable, to make the rest of the code easier to manage.
// Import Images Folder
$import = $_FILES['import-folder'];
if(!$import['error'] && MimeType($import['tmp_name']) == 'application/zip') {
}
$import['tmp_name']
has the current location of the uploaded file, which we can leave where it is. If we decide we don’t want to go ahead, for example if the Mime Type is wrong, the file will be automatically deleted when the script is complete.
$import['error']
will be non-zero if there is an error, or 0 if it has been successfully uploaded. Here we foregoing proper error handling.
Apart from the Mime Type, the most obvious error will be Error 2
, which is that the uploaded file is too big. The maximum file size is preset in PHP, but can be overridden using the .htaccess
file:
# Uploads
php_value post_max_size 40M
php_value upload_max_filesize 20M
The post_max_size
is the maximum for the whole of the form, while the upload_max_filesize
is for a single file.
Extracting the Files
We will copy all of the files into a designated upload folder. PHP has a built-in class, called ZipArchive
, to do this, but the process will be complicated by the fact that the ZIP file probably contains a folder with the files; we want the contents, but not the folder.
To use the ZipArchive
class, we create a new object, and then open()
the archive, process it, and close()
it.
$zip = new ZipArchive;
$zip->open($import['tmp_name']);
$zip->close();
Although there is a a built-in extractTo()
method, it includes the folders, so we will have to iterate through the files ourselves, and read off the individual file names using the getNameIndex()
method.
for($i=0; $i<$zip->numFiles; $i++) {
$source=$zip->getNameIndex($i);
if(substr($source,-1) == '/') continue;
}
substr($source,-1)
is the last character of a string. If it is /
, then the string must be a folder, so we skip it.
After that, we will use basename()
to read only the file name without preceding folders, and copy from the ZIP file to our upload folder using the special zip://
protocol:
$name=basename($source);
copy("zip://{$import['tmp_name']}#$source","upload/$name");
The Complete Code
The whole process is as follows:
$import = $_FILES['import-folder'];
if(!$import['error'] && MimeType($import['tmp_name']) == 'application/zip') {
$zip = new ZipArchive;
$zip->open($import['tmp_name']);
for($i=0; $i<$zip->numFiles; $i++) {
$source=$zip->getNameIndex($i);
if(substr($source,-1) == '/') continue;
$name=basename($source);
copy("zip://{$import['tmp_name']}#$source","upload/$name");
}
$zip->close();
}
An Unzip Function
If you’re going to make a habit of uploading zip files, you might want to add the following function to your library:
function unzip(string $zipFile, string $destination) {
$zip = new ZipArchive();
$zip->open($zipFile);
for($i=0; $i<$zip->numFiles; $i++) {
$file=$zip->getNameIndex($i);
if(substr($file,-1) == '/') continue;
$name=basename($file);
copy("zip://$zipFile#$file","$destination/$name");
}
$zip->close();
}
The preceding example would then look like this:
$import = $_FILES['import-folder'];
if(!$import['error'] && MimeType($import['tmp_name']) == 'application/zip') {
unzip($import['tmp_name'],"upload/$name");
}