In this tutorial I will explain how to create a progress bar for PHP file uploads. There is no method built into PHP for returning the status of an upload in progress, so this requires a module called the Alternative PHP Cache (APC). This allows you to store data across sessions and includes built-in functionality for storing/returning file upload stats.
This tutorial assumes you have some basic understanding of XHTML, PHP, and JavaScript. If you don't, it may be hard to understand.
Installing APC is beyond the scope of this tutorial, so visit the APC Installation page for help or talk to your host. To find out if APC is installed already, create a PHP file on your site containing "<?php phpinfo(); ?>", visit it in your browser, and look for a section titled 'APC.' If it is present, then it is installed.
Creating the HTML Form
The first step is to create the form that will accept the file upload. This page will also contain the progress bar, hidden when the page loads. Start with creating the <form> element:
Code: Select all
<html>
<head>
<title>Upload Progress Bar</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data" name="upload" id="upload" target="upload_frame">
<input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?php echo $uid; ?>" />
<input type="file" name="file" id="file" />
<input type="submit" name="submit" id="submit" value="Upload!" />
</form>
</body>
</html>
Now to break down the code:
Code: Select all
<form action="upload.php" method="post" enctype="multipart/form-data" name="upload" id="upload" target="upload_frame">
Code: Select all
<input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?php echo $uid; ?>" />
The next two lines are the file selector and submit button and are fairly self-explanatory.
Next, we need to add the progress bar. The bar will consist of two CSS-styled divs, one nested within the other. The inner one will expand based on the upload progress. Here is what it looks like:
Code: Select all
<div id="pb_outer">
<div id="pb_inner"></div>
</div>
Code: Select all
<style type="text/css">
#pb_outer {
height: 20px;
border: 1px inset #000000;
width: 80%;
margin: 20px auto;
display: none;
}
#pb_inner {
font-weight: bold;
color: #FFFFFF;
background-color: #003399;
height: 20px;
width: 1px;
text-align: center;
}
</style>
Now is also a good time to add that hidden iframe that will upload the file in the background:
Code: Select all
<iframe style="display: none" id="upload_frame" name="upload_frame"></iframe>
Code: Select all
<html>
<head>
<title>Upload Progess Bar</title>
<style type="text/css">
#pb_outer {
height: 20px;
border: 1px inset #000000;
width: 80%;
margin: 20px auto;
display: none;
}
#pb_inner {
font-weight: bold;
color: #FFFFFF;
background-color: #003399;
height: 16px;
width: 1px;
text-align: center;
padding: 2px 0px;
}
</style>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data" name="upload" id="upload" target="upload_frame">
<input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?php echo $uid; ?>" />
<input type="file" name="file" id="file" />
<input type="submit" name="submit" id="submit" value="Upload!" />
</form>
<div id="pb_outer">
<div id="pb_inner"></div>
</div>
<iframe style="display: none" id="upload_frame" name="upload_frame"></iframe>
</body>
</html>
Now that we have a form that submits a file to a non-existent PHP script via a frame, it's time to make that script (and some helper scripts).
First, we need to generate that unique key with just one simple line in upload_form.php:
Code: Select all
<?php $uid = md5(uniqid(rand())); ?>
Now it's time for an actual file upload script, which generally looks like this:
Code: Select all
<?php
if($_FILES['file']['error'] == UPLOAD_ERR_OK){
$path = '/var/www/uploads/';
$path .= basename($_FILES['file']['name']);
if(move_uploaded_file($_FILES['file']['tmp_name'], $path)){
// upload successful
}
}
?>
Place this code in a file called upload.php. This will make sure there was no upload error, and then move the file to the specified path. Note that if you do not use move_uploaded_file(), the uploaded file will be deleted from the temp directory.
The last bit of PHP we need is for fetching the current upload progress:
Code: Select all
<?php
if(isset($_GET['uid'])){
$status = apc_fetch('upload_' . $_GET['uid']);
echo round($status['current']/$status['total']*100);
}
?>
There is one slight problem with this script, especially since we will be calling this with AJAX, and that is caching. Some browsers will cache the output of the script and cause the bar to freeze after the initial request. To solve this, we can use headers to instruct the browser not to cache:
Code: Select all
header('Expires: Tue, 08 Oct 1991 00:00:00 GMT');
header('Cache-Control: no-cache, must-revalidate');
Here is what getprogress.php should look like:
Code: Select all
<?php
header('Expires: Tue, 08 Oct 1991 00:00:00 GMT');
header('Cache-Control: no-cache, must-revalidate');
if(isset($_GET['uid'])){
$status = apc_fetch('upload_' . $_GET['uid']);
echo round($status['current']/$status['total']*100);
}
?>
Now this is where it gets interesting. We need to uses JavaScript to animate the progress bar based on the output of getprogress.php, and this requires the use of AJAX. AJAX is a method that allows you to load content on demand without reloading the entire web page.
To set up the AJAX functionality, we need to create a cross-browser request object. There are two main types of request objects, one that works on most browsers, and one that works in Internet Explorer. Below is the code that I use to handle this. Place this within the head tags in upload_form.php:
Code: Select all
<script type="text/javascript">
var HttpRequestObject = false;
if(window.XMLHttpRequest) {
HttpRequestObject = new XMLHttpRequest();
}
else if(window.ActiveXObject) {
HttpRequestObject = new ActiveXObject("Microsoft.XMLHTTP");
}
</script>
Now that we have that set up, we need the actual functions that do the work. We will create two functions, one that initializes the progress bar and one that moves the progress bar. We will start with the one that moves the bar and makes the AJAX calls. Let's start with the backbones of using AJAX:
Code: Select all
function getProgress(uid) {
if(HttpRequestObject) {
HttpRequestObject.open('GET', 'getprogress.php?uid=' + uid, true);
HttpRequestObject.onreadystatechange = function() {
if(HttpRequestObject.readyState == 4 && HttpRequestObject.status == 200) {
}
}
HttpRequestObject.send(null);
}
}
Since we are using GET, we pass variables by appending it to the URL in the format ?key=value, in this case we pass the unique ID this way. The next part of the function uses the onreadystatechange event handler to set up the actions that will happen as soon as the data is returned. If the readystate is 4 (finished), and HTTP response code is 200, then it is ready to do stuff. The last line of the function is what actually sends the request to the server.
Now that we have out function set up to query the server for upload status, it is now time to use the info to move the progress bar. Here's the code we'll use:
Code: Select all
var progress = HttpRequestObject.responseText;
document.getElementById('pb_inner').style.width = progress + '%';
document.getElementById('pb_inner').innerHTML = progress + '%';
At this point, the function will execute just once. We need to cause it to loop until the upload is complete. This can be easily accomplished using the setTimeout function, which calls a function after a delay set in milliseconds. In this case we'll call the getProgress() function again.
Code: Select all
if(progress < 100) {
setTimeout('getProgress("' + uid + '")', 100);
}
else {
document.getElementById('pb_inner').innerHTML = 'Upload Complete!';
}
Here is the function in it's entirety:
Code: Select all
function getProgress(uid) {
if(HttpRequestObject) {
HttpRequestObject.open('GET', 'getprogress.php?uid=' + uid, true);
HttpRequestObject.onreadystatechange = function() {
if(HttpRequestObject.readyState == 4 && HttpRequestObject.status == 200) {
var progress = HttpRequestObject.responseText;
document.getElementById('pb_inner').style.width = progress + '%';
document.getElementById('pb_inner').innerHTML = progress + '%';
if(progress < 100) {
setTimeout('getProgress("' + uid + '")', 100);
}
else {
document.getElementById('pb_inner').innerHTML = 'Upload Complete!';
}
}
}
HttpRequestObject.send(null);
}
}
Code: Select all
function startProgress(uid) {
document.getElementById('upload').style.display = 'none';
document.getElementById('pb_outer').style.display = 'block';
setTimeout('getProgress("' + uid + '")', 1000);
}
Now all that is left is to tie the form the the JavaScript by adding this to the form HTML element:
Code: Select all
onSubmit="startProgress('<?php echo $uid; ?>');"
The Finished Product
I hope this was helpful! Any questions, post a comment. For your convenience, here is all the code used in this tutorial.
To see it in action, see our example.
upload_form.php
Code: Select all
<?php $uid = md5(uniqid(rand())); ?>
<html>
<head>
<title>Upload Progess Bar</title>
<style type="text/css">
#pb_outer {
height: 20px;
border: 1px inset #000000;
width: 80%;
margin: 20px auto;
display: none;
}
#pb_inner {
font-weight: bold;
color: #FFFFFF;
background-color: #003399;
height: 20px;
width: 1px;
text-align: center;
}
</style>
<script type="text/javascript">
var HttpRequestObject = false;
if(window.XMLHttpRequest) {
HttpRequestObject = new XMLHttpRequest();
}
else if(window.ActiveXObject) {
HttpRequestObject = new ActiveXObject("Microsoft.XMLHTTP");
}
function startProgress(uid) {
document.getElementById('upload').style.display = 'none';
document.getElementById('pb_outer').style.display = 'block';
setTimeout('getProgress("' + uid + '")', 1000);
}
function getProgress(uid) {
if(HttpRequestObject) {
HttpRequestObject.open('GET', 'getprogress.php?uid=' + uid, true);
HttpRequestObject.onreadystatechange = function() {
if(HttpRequestObject.readyState == 4 && HttpRequestObject.status == 200) {
var progress = HttpRequestObject.responseText;
document.getElementById('pb_inner').style.width = progress + '%';
document.getElementById('pb_inner').innerHTML = progress + '%';
if(progress < 100) {
setTimeout('getProgress("' + uid + '")', 100);
}
else {
document.getElementById('pb_inner').innerHTML = 'Upload Complete!';
}
}
}
HttpRequestObject.send(null);
}
}
</script>
</head>
<body>
<form onSubmit="startProgress('<?php echo $uid; ?>');" action="upload.php" method="post" enctype="multipart/form-data" name="upload" id="upload" target="upload_frame">
<input type="hidden" name="APC_UPLOAD_PROGRESS" id="progress_key" value="<?php echo $uid; ?>" />
<input type="file" name="file" id="file" />
<input type="submit" name="submit" id="submit" value="Upload!" />
</form>
<div id="pb_outer">
<div id="pb_inner"></div>
</div>
<iframe style="display: none" id="upload_frame" name="upload_frame"></iframe>
</body>
</html>
Code: Select all
<?php
if($_FILES['file']['error'] == UPLOAD_ERR_OK){
$path = '/var/www/uploads/';
$path .= basename($_FILES['file']['name']);
if(move_uploaded_file($_FILES['file']['tmp_name'], $path)){
// upload successful
}
}
?>
Code: Select all
<?php
header('Expires: Tue, 08 Oct 1991 00:00:00 GMT');
header('Cache-Control: no-cache, must-revalidate');
if(isset($_GET['uid'])){
$status = apc_fetch('upload_' . $_GET['uid']);
echo round($status['current']/$status['total']*100);
}
?>