index.php
[code]
<?php

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'on');

include 'lib.php';

$progress = new Progress('namespace');
$progress->setOutputContainer('content')
	->setActionUrl('action.php')
	->setActionPostData(array('key'=>'value'))
	->setReadUrl('read.php')
	->setInterval(0.5);

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Title</title>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/jquery-ui.css" />

<?php echo $progress->script(); ?>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript">
$(function() {
	$("#progressbar").progressbar({
		value: 0
	});
});
</script>
</head>
<body>
<div id="progressbar" style="width:400px; height:20px;"></div>
<div id="content"></div>
</body>
</html>[/code]
script.js
[code]
$(function(){$.ajax({
	type: "POST",
	data: actionData,
	url: actionUrl,
	timeout: 120*1000,
	error: function(XMLHttpRequest, textStatus, errorThrown) { console.log(textStatus); }
	});
});

var interval = setInterval(function(){		
	$.ajax({
		url: readUrl,
		success: function(data) {
			if(data.done != undefined) {
		
				/*
				 * Action complete handling
				 */
				$("#content").html("done");
				clearInterval(interval);
				$("#progressbar").progressbar("option", "value", 100);
				/*
				 * End action complete handling
				 */
		
				} else if(data.error != undefined) {
		
					/*
					 * Error handler. data.error.text is the errortext, data.error.code is the optional errorcode
					 */
					$("#content").html(data.error.text);
					clearInterval(interval);
					/*
					 * End error handler
					 */
		
		
				} else {
					if(previousProgress >= data.progress){return;}
		
					/*
					 * Update handler
					 */
					var percentage = Math.round(data.progress/data.max*100);
					$("#progressbar").progressbar("option", "value", percentage);
					$("#content").html(percentage+"%<br />"+data.progress+"/"+data.max);
					/*
					 * End update handler
					 */
		
					var previousProgress = data.progress;
				}
		
			},
			error: function(XMLHttpRequest, textStatus, errorThrown) {console.log(textStatus);},
			dataType: "json",
			type: "GET"
		});
	}, readInterval
);
[/code]


read.php
<?php

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'on');

session_start();

require_once 'lib.php';

$progress = new Progress('namespace');

echo $progress->read();
?>
action.php
<?php

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'on');

session_start();

set_time_limit(120);

require_once 'lib.php';

$progress = new Progress('namespace');
$progress->reset()->setMax(80);

// Dummy actie, slaapt 100x 0.05 sec
try {
	for($i = 1; $i <= 80; $i++) {
		usleep(0.05E6);
		$progress->update($i);
	}
	$progress->done();
	echo 'done';
} catch(Exception $e) {
	$progress->error('An error has occured');
	echo 'error';
}
?>
lib.php
<?php
/*
 * Progress meter
 * @author Pim de Haan
 * @version 1.1
 * 
 * @package Progress
 */
/*
 * The class
 * Three files use it:
 * 	- The main page
 * 	- The action page
 * 	- The read page
 * 
 * The main page first calls action through AJAX, which stores the progress in the session.
 * Then it calls the read page through AJAX on a specified interval.
 * The read page reads the session and echoes it json encoded.
 * The main page then translates and displays the json code.
 * 
 * @package Progress
 */
class Progress {
	
	/*
	 * The action options, including the URL and the optional POST vars
	 * @access private
	 * @var array
	 */
	private $_action = array();
	
	/*
	 * The URL of the read page
	 * @access private
	 * @var string
	 */
	private $_readUrl;
	
	/*
	 * The id of the element containing the output
	 * @access private
	 * @var string
	 */
	private $_outputContainer;
	
	/*
	 * The namespace of the session data, for using multiple progress meters simultaneously
	 * @access private
	 * @var string
	 */
	private $_namespace;
	
	/*
	 * The interval for the AJAX calls to the read page
	 * @access private
	 * @var int
	 */
	private $_interval = 1;
	
	/*
	 * The constructor, sets {@link $_namespace}
	 * @param $namespace string
	 */
	public function __construct($namespace = 'default') {
		if(!session_id()) {
			session_start();
		}
		$this->_namespace = $namespace;
	}
	
	/*
	 * Sets {@link $_outputContainer}
	 * @param $namespace string
	 * @return Progress
	 */
	public function setOutputContainer($container) {
		$this->_outputContainer = $container;
		return $this;
	}
	
	/*
	 * Sets {@link $_action}
	 * @param $url string
	 * @return Progress
	 */
	public function setActionUrl($url) {
		$this->_action['url'] = $url;
		return $this;
	}
	
	/*
	 * Sets {@link $_action} ['postdata'], if it is a associative array, it is converted to a string
	 * @param $data string|array
	 * @return Progress
	 */
	public function setActionPostData($data) {
		if(is_string($data)) {
			$query = $data;
		} else {
			$query = '';
			foreach($data as $key => $value) {
				$query .= '&'.urlencode($key).'='.urlencode($value);
			}
			$query = substr($query, 1);
		}
		$this->_action['postData'] = $query;
		return $this;
	}
	
	/*
	 * Sets {@link $_readUrl}
	 * @param $url string
	 * @return Progress
	 */
	public function setReadUrl($url) {
		$this->_readUrl = $url;
		return $this;
	}
	
	/*
	 * Sets {@link $_max}
	 * And resets the session
	 * @param $max int
	 * @return Progress
	 */
	public function setMax($max) {
		$this->_write('max', $max);
		return $this;
	}
	
	/*
	 * Sets {@link $_interval}
	 * @param $time int
	 * @return Progress
	 */
	public function setInterval($time) {
		$this->_interval = $time;
		return $this;
	}
	
	/*
	 * Sets current progress from the session using {@link Progress::_write()}
	 * @param $progress int
	 * @return Progress
	 */
	public function update($progress) {
		$this->_write('progress', $progress);
		return $this;
	}
	
	/*
	 * Sets a error
	 * @param $text string
	 * @param $code int
	 * @return Progress
	 */
	public function error($text, $code = null) {
		$array['text'] = $text;
		if(!is_null($code)) {
			$array['code'] = $code;
		}
		$this->_write('error', $array);
		return $this;
	}
	
	/*
	 * Sets the action is done
	 * @return Progress
	 */
	public function done() {
		$this->_write('done', true);
		return $this;
	}
	
	/*
	 * Reads current progress and max from the session using {@link Progress::_read()} and encodes it with JSON
	 * @return string
	 */
	public function read() {
		$obj = new StdClass;
		foreach($this->_readAll() as $key => $val) {
			$obj->$key = $val;
		}
		
		$sessionData = $_SESSION;		
		@session_start();		
		$_SESSION = $sessionData;
		
		$data = $_SESSION['progress'][$this->_namespace];
		
		session_write_close();
		
		return json_encode($data);
	}
	
	/*
	 * Writes a value to the session
	 * Keeps the data while reopening the session
	 * session_write_close() is used to prevent session locking
	 * 
	 * @param $key string
	 * @param $value string|int|array
	 * @return Progress
	 */
	private function _write($key, $value) {
		$sessionData = $_SESSION;		
		@session_start();		
		$_SESSION = $sessionData;
		
		if(!is_null($key)) {
			$_SESSION['progress'][$this->_namespace][$key] = $value;
		} else {
			$_SESSION['progress'][$this->_namespace] = $value;
		}
		session_write_close();
		return $this;
	}
	
	/*
	 * Reads a value from the session
	 * @param $key string
	 * @return string|int
	 */
	private function _read($key) {
		return $_SESSION['progress'][$this->_namespace][$key];
	}
	
	/*
	 * Resets the session
	 * @return Progress
	 */
	public function reset() {
		$this->_write(null, null);
		return $this;
	}
	
	/*
	 * Reads all session values
	 * @return array
	 */
	private function _readAll() {
		return $_SESSION['progress'][$this->_namespace];
	}
	
	/*
	 * Returns the javascript
	 * 
	 * @return string
	 */
	public function script() {
		$return = '<script type="text/javascript">';
		$return .= 'var actionData = "'.(isset($this->_action['postData'])?$this->_action['postData']:'').'";';
		$return .= 'var actionUrl = "'.$this->_action['url'].'?'.session_name().'='.session_id().'";';
		$return .= 'var readUrl = "'.$this->_readUrl.'?'.session_name().'='.session_id().'";';
		$return .= 'var readInterval = '.$this->_interval*1000 .';';
		$return .= '</script>';
		return $return;
	}
}
?>