InternotesSharing Web Development Techniques

Blossoms

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 the enctype="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");
}