ThemeForest has a nice feature; It allows the authors to upload zip files containing screenshots of their themes. A script then extracts these files and displays the images accordingly. Though I doubt that the developers used PHP to accomplish this task...that's what we're going to use!
What We Must Do
- Create a simple upload form that allows the user to select a zip file.
- Ensure that the user chooses a zip file, and then save it to as a unique file.
- Extract the contents from the zip file and save them to a specific folder.
- Delete the zip file and echo out the contents.
Step 1: Creating the Upload Form
- Create a new file and save it as index.html in the root of your solution.
- Next, we'll build a simple form. Paste the following in.
<form enctype="multipart/form-data" action="" method="post"> <input type="file" name="fupload" /><br /> <input type="submit" value="Upload Zip File" /> </form>
Any time that you'll be allowing your users to upload files, you must set the "enctype" of the form tag equal to "multipart/form-data". We'll next set the action equal to the same page, and the method equal to post.
To keep things as barebones as possible, we only have a file input and a submit button. Note that we've set the name of the file input to "fupload". We'll soon use this value to determine whether or not the page has posted back.
Step 2: Writing the PHP
At the very top of your index.php page, before the doctype has been declared, paste in the following:
<?php
if(isset($_FILES['fupload'])) {
$filename = $_FILES['fupload']['name'];
$source = $_FILES['fupload']['tmp_name'];
$type = $_FILES['fupload']['type'];
Let's take it step by step.
- If the input tag with a name of "fupload" is set, then run the following code, otherwise, do nothing.
- Now we need to create a few variables. We need to capture the name of the file uploaded, the directory where the file is temporarily stored, and the type of file that was chosen.
Let's assume that a user chooses a file called "myPics.zip".
- $_FILES['fupload']['name'] = "myPics.zip"
- $_FILES['fupload']['tmp_name'] = some temporary directory on the server.
- $_FILES['fupload']['type'] = "application/zip". (This value can change depending on which browser the file was uploaded from.
Next, let's explode the file name into two pieces: the name, and the extension.
$name = explode('.', $filename);
$target = 'extracted/' . $name[0] . '-' . time() . '/';
Continuing with our example "myPics.zip" file - $name[0] will equal "myPics". $name[1] will equal "zip".
Next, we create a new variable called "$target". This will be the location that our file is saved to. In order to ensure that a different user withe the same "myPics" name doesn't have his files overwritten, we must make sure that we save the files to a unique location. To accomplish this task, we'll implement the "time()" function. If we append this value to the name of the uploaded file, we can be sure that we'll end up with a unique folder name!
$target = "extracted/myPics-02151985/"
Step 3: Ensure That a Zip File Was Selected
Immediately after your $target variable, paste in the following:
$accepted_types = array('application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/s-compressed');
foreach($accepted_types as $mime_type) {
if($mime_type == $type) {
$okay = true;
break;
}
}
- We start by creating an array called $accepted_types. A quick Google search for "zip mime types" will bring us four values: 'application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/s-compressed'. Each browser has its own way of registering the file type. We must be sure to check each value in such instances.
- To do so, we check to see if any of the items in our array have the same value as "$_FILES['fupload']['type'] ". If they do, we'll set our handy-dandy "$okay" variable equal to 'true' - and can break out of the 'for' statement and rest assured that the user has in fact chosen a zip file.
Unfortunately, Safari and Chrome don't register a type for zip files. This creates a problem for us. After a bit of research, I wasn't able to find an easy solution - without using an extension (PEAR). Instead, we'll make sure that the file name at least ends in "zip". It should be noted that this isn't 100% safe. What if the user uploaded a different file type that ended in 'zip'? Feel free to offer recommendations! :)
$okay = strtolower($name[1]) == 'zip' ? true: false;
- We'll use the ternary operator to keep this statement down to one line. If the extension of the filename ($name[1]) is equal to 'zip', we'll set $okay to true. Otherwise, it will be equal to 'false'.
Next, we'll check to see if $okay is false. If it is, we know that a 'zip' file was not chosen. In such cases, we'll tell PHP to die.
if(!$okay) {
die("Please choose a zip file, dummy!");
}
Step 4: Saving the Zip File
mkdir($target);
$saved_file_location = $target . $filename;
if(move_uploaded_file($source, $saved_file_location)) {
openZip($saved_file_location);
} else {
die("There was a problem. Sorry!");
}
- We first need to create the directory that we referenced with our $target variable. This can easily be done by using the "mkdir" function.
- Next, let's try to move the uploaded file, from the temporary directory, to our $target directory. If that procedure was performed successfully, we'll call the "openZip" function.
Step 5: The openZip() Function
Create a new page and save it as "functions.php". Now add the following code:
<?php
function openZip($file_to_open) {
global $target;
$zip = new ZipArchive();
$x = $zip->open($file_to_open);
if($x === true) {
$zip->extractTo($target);
$zip->close();
unlink($file_to_open);
} else {
die("There was a problem. Please try again!");
}
}
?>
This function will accept one parameter: $file_to_open. This parameter will contain the location of the zip file that we're trying to extract!
- To use the ZipArchive() class, you must first make sure that enable 'php_zip.dll' in your "php.ini" file. Simply search for that string, and then remove the semicolon.
- With any class, we need to create a new instance of the object. We'll next call the "open" method and pass in the location of the file that should be opened. If performed successfully, this method will return 'true'. If that is the case, we'll extract the contents of the zip file to a folder. We already created the path to this folder when we created our $target variable. (Note - In order to access that variable, you need to add "global" in front of the variable. This will tell PHP to go outside of the current function and search for the $target variable.).
- Once the files have been extracted, we'll delete the zip file - as we no longer need it!
Remember - we've created our new 'functions.php' file, but we need to include it! Append the following to the top of your 'index.php' page, just after the opening PHP tag.
require_once 'functions.php';
Echoing the Contents
What we have so far works perfectly! But, just to give you some feedback, let's scan the new directory and echo out the contents. You should delete this next code block from your project. It's only for testing. If you do want to keep it, echo the information out within the body tag.
$scan = scandir($target . $name[0]);
print '<ul>';
for ($i = 0; $i<count($scan); $i++) {
if(strlen($scan[$i]) >= 3) {
$check_for_html_doc = strpos($scan[$i], 'html');
$check_for_php = strpos($scan[$i], 'php');
if($check_for_html_doc === false && $check_for_php === false) {
echo '<li>' . $scan[$i] . '</li>';
} else {
echo '<li><a href="' . $target . $name[0] . '/' . $scan[$i] . '">' . $scan[$i] . '</a></li>';
}
}
}
print '</ul>';
}
I won't go over this last part too much - as it's unnecessary. However, if you would like a full explanation, be sure to watch the associated screencast! To quickly sum it up, this last script scans our new directory and echos out an unordered list containing the contents.
Finished
Not too hard, eh? It's a neat feature that can easily be implemented into your projects. What are your thoughts on security when it comes to uploading zip files? How can we be sure that the contents of the zip file aren't harmful? I'd love to hear everyone's opinion on these security implications. If not handled properly, a hacker could cause serious harm to your server. Let's discuss it! It's possible to search for dangerous file types (.htaccess files) and delete them.
The Final Index.php Page
<?php
include 'functions.php';
if(isset($_FILES['fupload'])) {
$filename = $_FILES['fupload']['name'];
$source = $_FILES['fupload']['tmp_name'];
$type = $_FILES['fupload']['type'];
$name = explode('.', $filename);
$target = 'extracted/' . $name[0] . '-' . time() . '/';
// Ensures that the correct file was chosen
$accepted_types = array('application/zip',
'application/x-zip-compressed',
'multipart/x-zip',
'application/s-compressed');
foreach($accepted_types as $mime_type) {
if($mime_type == $type) {
$okay = true;
break;
}
}
//Safari and Chrome don't register zip mime types. Something better could be used here.
$okay = strtolower($name[1]) == 'zip' ? true: false;
if(!$okay) {
die("Please choose a zip file, dummy!");
}
mkdir($target);
$saved_file_location = $target . $filename;
if(move_uploaded_file($source, $saved_file_location)) {
openZip($saved_file_location);
} else {
die("There was a problem. Sorry!");
}
// This last part is for example only. It can be deleted.
$scan = scandir($target . $name[0]);
print '<ul>';
for ($i = 0; $i<count($scan); $i++) {
if(strlen($scan[$i]) >= 3) {
$check_for_html_doc = strpos($scan[$i], 'html');
$check_for_php = strpos($scan[$i], 'php');
if($check_for_html_doc === false && $check_for_php === false) {
echo '<li>' . $scan[$i] . '</li>';
} else {
echo '<li><a href="' . $target . $name[0] . '/' . $scan[$i] . '">' . $scan[$i] . '</a></li>';
}
}
}
print '</ul>';
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>How to Upload and Open Zip Files With PHP</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div id="container">
<h1>Upload A Zip File</h1>
<form enctype="multipart/form-data" action="" method="post">
<input type="file" name="fupload" /><br />
<input type="submit" value="Upload Zip File" />
</form>
</div><!--end container-->
</body>
</html>
- Subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.
Related Posts
Check out some more great tutorials and articles that you might like
Plus Members
Source Files, Bonus Tutorials and
More for $9 a month for all TUTS+
sites in one subscription.











User Comments
( ADD YOURS )Miles Johnson December 30th
Good tutorial, a few minor things.
This is a better way to get the extension:
$ext = trim(strrchr($_FILES['fupload']['name'], ‘.’), ‘.’);
And then for the accepted types:
if (in_array($type, $accepted_types)) {
( )$okay = true;
}
Adam December 30th
Nice, I can see lots of uses for this! Great Tut!
( )kaqfa December 30th
The One…….
Nice tutorial, but how about other archive like .rar
( )Patrick December 30th
Correct me if I’m wrong, but I’m pretty sure that this snippet:
$okay = strtolower($name[1]) == ‘zip’ ? true: false;
can actually just be:
$okay = strtolower($name[1]) == ‘zip’;
because it already returns a boolean, either true or false, and it’s redundant to assign ‘true’ if it’s true and ‘false’ if it’s false.
( )Jeffrey Way December 30th
@Patrick –
Good point.
( )NetOperator Wibby December 30th
This is pretty interesting. I didn’t know this was possible.
( )Brenley Dueck December 30th
This definitely has some great uses. Have not had to do this often but now I know that it can be done. Thanks!
( )dr.emi December 30th
nice tutorial, but looks likely trouble for limited hosting, caused a user must activated php_zip.dll on php.ini, and then new ZipArchive(); will be work.
But I have solution for it, we can use pclZIP from:
http://www.phpconcept.net/pclzip/index.en.php
I used pclZIP to create and extract a zip file
You may see my poor tutorial on:
http://www.dremi.info/forum/viewtopic.php?f=2&t=359
and download it on:
http://www.4shared.com/file/60692726/38cf1327/extract-create-zip.html
Brother, impressive tutorial
keep rock and roll !! yeah \m/
( )unwiredbrain December 30th
Thorny question: what if I upload an archive that contains a file called “.a”?
Explanation: on UNIX-like systems, the dot on the beginning of the file name marks a file as “hidden”, but it’s still absolutely valid.
Hint: actually, I really don’t know if the very first two
$scanentries will be"."and".."; if not so you could just sort$scan, thus moving"."and".."at the very “top” of the array, then you would be free to use aforloop starting from 2 (two) instead of 0 (zero), avoiding worries from file names.Please, note: I’m NOT an experienced PHP developer, so please don’t take my words as gold — it could have been all wrong; really looking forward for your thought about.
( )Hasanga December 30th
This is really gonna help me with the new application that we are developing.
Many Thanks!
( )Wazdesign December 30th
Hmmmm …… New thing for me
( )Lamin December 31st
Awesome tut but too much code.
( )DKumar M. December 31st
Nice tip…. Thanks for sharing… This is something new to me.
( )Guney Can Gokoglu December 31st
http://www.php.net/manual/en/function.mime-content-type.php
or
http://www.php.net/manual/en/ref.fileinfo.php
for checking file type.
( )Abhijit December 31st
strtolower($name[1]) == ‘zip’ ? true: false;
Here, if the file name has multiple dots then $name[1] will not have the extension. For example, if the filename is my.file.zip. To find the extension you can do strtolower(end($name)).
That’s a minor problem though. Thanks for this great tutorial.
( )Honour Chick December 31st
thxs… awesome tutorial
( )Rogier Strobbe December 31st
I agree with Dr. Emi.. not all hosts will allow you to edit php.ini..
But nice tutorial though!
( )Patrick H. Lauke December 31st
the fallback strtolower should only be run if $okay is still false after the foreach…otherwise, regardless of the result of the foreach, your $okay variable is still determined just by the strtolower
( )Fabryz December 31st
Nice tut, thanks
( )Jeffrey Way December 31st
@Patrick – Really good point. I missed that. I’ll go and fix the code. Thanks!
( )Jeffrey Way December 31st
@Fabryz – Is your avatar Meryl from Metal Gear?
( )sean steezy March 17th
wow. good catch
( )Jeffrey Way December 31st
That still won’t work. Let’s say that user uploads a fake zip file from Firefox. PHP will determine that the mime type is incorrect – $okay = false;.
If $okay still equals false, it’ll check to see if the extension is “zip”. If it is, $okay becomes true.
This one section has given me the most trouble. I believe there is a new function in the latest version of PHP that will handle this issue – but I wanted to keep from using them.
( )Dan December 31st
Really cool
( )Eduardo December 31st
@Abhijit
( )probably it’s better to use regular expressions to filter the file extension.
Timothy December 31st
Nice tutorial. Thanks!
( )Mason Sklut December 31st
Neat tutorial. Many uses for this…..
( )Jimmy Ruska December 31st
You do some strange things.
for ($i = 0; $i= 3) {
( )this is silly to avoid the “.” and “..” it adds to the array for any dir. just use array_slice, array shift or unset to trim the first two items.
Jimmy Ruska December 31st
alright the comments box seems to be prejudice against code.
Pastebin’ed http://pastebin.com/m7d210717
( )Jack December 31st
Great tutorial, Jeffrey! Should be really helpful to anyone who needs to handle ZIP files. Maybe a nice follow-up would be a tutorial on how to zip up collections of files server-side.
( )craig December 31st
No offense, but I would avoid using this as it seems to be relatively insecure. Mime types can be faked, as can file extensions. That’s not to mention that the ZipArchive class is not necessary in order to compress and extract zip files with PHP.
( )tojen December 31st
hi jeff, your flash player is a good work(the player of the video), can you share with me, thank you.
( )Atmoz January 1st
$name = explode(’.', $filename);
$okay = strtolower($name[1]) == ‘zip’ ? true: false;
Better way to do it:
$name = substr(strrchr($filename, ‘.’), 1);
$okay = strtolower($name) == ‘zip’);
A filename can be like “my.filename.something.file”.
( )Ahad January 1st
Great effort here Jeffrey. It does however surprise me that you haven’t used a “MVC PHP FRAMEWORK” => CODIGNITER to do this. It would cut down your code and efforts considerably…
Alas some of us like to get our hands dirtier!! BTW i found this really cool site in case you all are interested. I’m sure Jeff would be…
http://stackoverflow.com/
( )Vyacheslav January 2nd
Thanks, very great tutorial!
( )Daniel Neville January 2nd
I’d have to strongly recommend against using this type of code for portability reasons, the ZipArchive class simply isn’t supported widely enough to rely solely on it.
Dan
( )Jonas January 2nd
I have a number of suggestions for the code:
1. Instead of if(isset($_FILES['fupload'])), use if (is_uploaded_file($_FILES['fupload']['tmp_name'])). It’s safer.
2. Neither $_FILES['fupload']['type'] nor the file extension can be used for anything, since they can be manipulated so easily. E.g., Safari for Windows and Firefox will report an .exe file renamed to .zip as “application/x-zip-compressed”. What you need to do is to read the contents of the .ZIP file or use a library that can do the same – that’s the only way to be sure that what you have your hands on is a .ZIP file and not some other file type in disguise.
3. $name = explode(’.', $filename); won’t work for files that have multiple periods in them (e.g. pics.2009-01-01.zip). Instead, use strrpos() to find the last period:
$lastPeriod = strrpos($filename, ‘.’);
if ($lastPeriod === FALSE) trigger_error(’Can\’t find the file extension.’);
$nameWithoutExtension = substr($filename, 0, $lastPeriod);
$extension = substr($filename, $lastPeriod);
4. Using time() in a file name as a way to make it unique isn’t a good idea if you expect your site to get a lot of traffic: If two people do the same thing within the same second (this is very likely on high-traffic sites), the system won’t work. Use uniqid() instead – it’s based on microseconds instead of seconds.
Another thing: Where’s the pause button on the player?
( )Daniel Neville January 3rd
At Jonas, nice idea. But ineffective:
$extension = array_pop(explode(’.’, $filename));
Is the generally preferred solution, splits it into segments, then takes the last one.
This can still cause errors if you want to accept files such as .gz.tar though, but it’s easy enough to add an exception.
( )Jonas January 3rd
@ Daniel Neville: How is my solution ineffective when it does something your’s don’t? My solution will accept any kind of file names, including ones that have extra periods in them.
The bottom line is, though, that using the extension for anything in this case is superflous; you can’t trust the extension to tell you what’s inside the file.
( )Jonas January 3rd
@ Daniel Neville: I forgot to say that what my solution does which your’s doesn’t is to get the file name (without the extension), not just the extension. Because if all you wanna do is get the extension, you could do it even faster this way: $extension = strrchr($filename, ‘.’).
( )Seb January 5th
I think this would be better …
( )$okay = in_array($type, $accepted_types);
SER January 21st
Good tutorial!
“print” is expensive, use “echo” instead by the way.
( )Caesar January 27th
Hi, very nice tutorial. But I have a little problem:
( )I’m working with Mamp Pro for Mac and I really don’t know how to install/import the zip library. I just notice that is really easy on Windows, but I’m working on a Mac, Does anybody can help me?
Gus Cohen February 12th
Awsome website and Great Tutorials!!! I would like to know if there is a simple way to incorporate a progress bar or a percentage counter so users can monitor the upload process.
( )Tyrel Kelsey February 27th
@Jeffrey Way
( )this would avoid that issue all together since if the variable has already been set once it wont be changed
if(!isset($okay)){
$okay = strtolower($name[1]) == ‘zip’ ? true: false;
}
sean steezy March 17th
this stuff is way beyond my head, but I need to create a file uploader in PHP that is super secure… any ideas where something like that may reside? Thanks!
( )Shivanand Sharma May 8th
So God does exist. I have been stuck trying to do something like this in Drupal on which my site is based. Thanks sooo much for this. And by the way… nicely done!
( )noner May 15th
Hmmm. Interesting tutorial. I was just doing similar thing But for easier manipulation of file paths I’ve made this approach:
—
$fileinfo = pathinfo($_FILES['fupload']['tmp_name']);
$fileinfo['basename'] = basename($fileinfo['basename'],’.’.$fileinfo['extension'])
$zip = new ZipArchive();
$result = $zip->open($fileinfo['dirname'].$fileinfo['basename'].’.’.$fileinfo['extension'],ZIPARCHIVE::CREATE);
if ($result == ZIPARCHIVE::ER_NOZIP) {
die(’not Zip’);
}
—
I’m sorry if I made typos.
( )Paadt May 16th
Please don’t use global, just pass the $target to the function…
( )Krishna Bhattarai July 2nd
Many Many Thank you dear. It works fine. Good job.
( )Keep it up.
Simon July 8th
I am having trouble uploading really big zip files. My browser looses connectivity after 2/3 minutes or so..any ideas how to fix it??
( )folojona October 5th
Thanks. This was very helpful since I didn’t know this class.
( )David Moreen October 25th
This is a nice tutorial, but I can’t test it to see if it works because my webhost does not support the ZipArchive() thingie…
( )